반복영역 건너뛰기
주메뉴 바로가기
본문 바로가기
제품/서비스
EMS Solution
Features
클라우드 관리
서버관리
데이터베이스 관리
네트워크 관리
트래픽 관리
설비 IoT 관리
무선 AP 관리
교환기 관리
운영자동화
실시간 관리
백업 관리
스토리지 관리
예방 점검
APM Solution
애플리케이션 관리
URL 관리
브라우저 관리
ITSM Solution
서비스데스크
IT 서비스 관리
Big Data Solution
SIEM
AI 인공지능
Dashboard
대시보드
Consulting Service
컨설팅 서비스
고객
레퍼런스
고객FAQ
문의하기
가격
자료실
카탈로그
회사소개
비전·미션
연혁
2016~현재
2000~2015
인증서·수상
투자정보
재무정보
전자공고
IR자료
새소식
공고
보도자료
오시는 길
채용
피플
컬처
공고
FAQ
블로그
열기
메인 페이지로 이동
블로그
기술이야기
블로그
최신이야기
사람이야기
회사이야기
기술이야기
다양한이야기
SMS를 통한 서버관리는 꼭 이렇게 해야만 한다?!
네트워크 정보 수집 프로토콜의 모든 것 (SNMP, RMON, ICMP, Syslog)
임형섭
2024.03.04
페이스북 공유하기
트위터 공유하기
링크드인 공유하기
블로그 공유하기
무선 AP를 WNMS를 통해 올바르게 관리하는 방법
지난 포스팅을 통해
NMS의 기본 개념
과
NMS의 구성요소와 역할
에 대해서 살펴보았는데요. 오늘은
네트워크 정보 수집을 위한 다양한 프로토콜
에 대해서 자세히 알아보겠습니다.
네트워크 프로토콜(Network Protocol)은 네트워크에 연결된 장비 간의 메시지 흐름을 통제하고 관리하는 기본적인 절차와 규칙을 정한 규약입니다.
웹 브라우저, 파일 전송, 이메일 송수신, 미디어 스트리밍 등과 같은 모든 온라인 활동을 가능하게 하기 때문에 네트워크 정보 전달의 핵심요소라고 할 수 있죠.
이번 시간에는 주요
네트워크 프로토콜인 ICMP, SNMP
를 중점적으로 알아보겠습니다.
ㅣICMP는 무엇이고 어떻게 동작하는가?
ICMP(Internet Control Message Protocol)는 주로 네트워크의 경로상의 문제나, 호스트(단말)의 문제 등을 파악할 때 사용하는 프로토콜인데요. 대표적인 서비스가 ping입니다. 구체적인 동작원리를 살펴보면 다음과 같습니다.
오류 보고
◾ 네트워크에서 데이터를 보낼 때 오류가 발생하면, 오류를 발생시킨 장비(예: 라우터, 스위치)는 오류 정보를 담아 ICMP 메시지를 처음 보낸 사람에게 전송합니다. 이를 통해 무엇이 잘못됐는지 정확히 파악하고 문제를 해결할 수 있습니다.
◾ 예를 들어 한 컴퓨터에서 인터넷을 통해 데이터를 보내는데, 그 데이터가 목적지에 도달하지 못하면 ICMP가 '이 주소로는 데이터를 배달할 수 없어!'라고 알려주는 역할을 하죠. 이렇게 사용자나 네트워크 관리자가 문제를 알리고 대응할 수 있게 도와주는 게 ICMP의 주요 역할입니다.
[그림] ICMP 동작 방식
진단 및 테스트
◾ 네트워크의 연결 상태나 성능을 테스트하기 위해 ICMP 에코 요청과 에코 응답 메시지를 사용합니다. 이를 통해 네트워크의 지연시간(latency)이나 패킷 손실(packet loss) 등을 측정할 수 있습니다. '핑(ping, Packet INternet Groper)'을 대표적인 예로 들 수 있습니다.
◾ 쉽게 표현하면 '너 지금 연결 잘 되어 있니?'라고 물었을 경우 대상 장비가 '응, 잘 되어 있어!'라고 대답하면 연결이 잘 되어 있는 것이고, 대답이 없거나 늦는 것과 같은 문제를 식별하는 것이죠.
ICMP도 좋은 도구이지만, 네트워크의 복잡성이 빠르게 증가하고 호스트 수가 증가하면서 ICMP만으로는 네트워크 관리가 어려워지는 문제가 발생했는데요. 이를 개선하기 위해서 탄생한 것이 바로 SNMP입니다.
우선 SNMP의 히스토리부터 살펴보겠습니다.
ㅣSNMP 히스토리: 각 버전별 개념과 차이점은?
SNMP(Simple Network Management Protocol)는 1988년에 아래의 세 가지 니즈에 부합하기 위해 등장했습니다.
◾ ICMP보다 많은 기능의 탑재
◾ 네트워크 문제를 직관적이고 쉽게 해결할 수 있어야 함
◾ 표준화된 프로토콜의 사용
이후 몇 가지 버전을 거쳐서 현재는 네트워크 장비를 모니터링하기 위한 프로토콜로 자리를 잡아서 대부분의 NMS 상에서 이용되고 있습니다.
잠깐 SNMP의 처리단계를 살펴보면, SNMP는 Get/Set/Trap의 단순 명령 구조로 구성되는데요, 메시지 타입별 역할은 아래와 같이 정리할 수 있습니다.
위와 같은 처리단계를 가지고 있는 SNMP는 보안 기능 강화 및 기능 개선을 위해서 초기 v1 버전에서 v3 버전까지 업그레이드됐습니다.
각 버전은 보안, 성능, 유연성 등의 측면에서 발전되었으며 현재는 SNMPv2가 가장 많이 사용되고 있죠. SNMP 버전 별 특징에 대해서 자세히 알아보겠습니다.
SNMP v1
가장 초기에 만들어진 프로토콜로 기본적인 정보만을 주고받아서 네트워크 장비들의 상태를 확인하고, 간단한 명령 정도만 내릴 수 있습니다. 보안에 많이 약한 편이고, 정보를 주고받을 때 특별한 암호화나 보호 방법을 사용하지 않기에 정보가 노출될 위험이 있습니다.
SNMP v2
SNMPv1의 단점을 해결하기 위해 개발된 버전입니다. 보안 기능과 네트워크 과부하, 관리 효율성 등에 대한 기능이 향상되었습니다.
MIB(Management Information Base) 구조를 개선하여, 새로운 데이터 타입과 객체 식별자(프로그래밍에서 특정 객체를 식별하는 데 사용되는 값이나 이름)을 도입했습니다. 이로써 더 많은 종류의 데이터를 효과적으로 다룰 수 있게 되었지만, v1과 호환이 안되는 문제가 있어 상용화에는 실패했습니다.
SNMP v2c (Community-Based Security)
SNMPv2c는 '커뮤니티 기반' 방식을 사용하며 'Community String' (공동체 문자열)을 이용합니다. Community String은 정보를 주고받기 위해 인증 과정에서 비밀번호를 사용하는 것으로, 학교에서 특정 비밀번호를 알고 있는 사람들만 특정 정보를 볼 수 있게 하는 것과 비슷합니다.
하지만 비밀번호가 복잡하지 않은 편이라, 조금 더 높은 보안을 필요로 하는 경우에는 적합하지 않을 수 있습니다. 현재 가장 많이 사용되고 있는 버전입니다.
SNMP v3
보안과 관리 기능을 대폭 강화한 버전입니다. SNMPv3는 정보를 주고받을 때 강력한 인증과 암호화를 사용하여, 네트워크 상의 중요한 정보를 안전하게 지킬 수 있습니다.
또한 복잡한 네트워크 환경에서 사용자가 많을 경우에도, 각 사용자의 접근 권한을 관리할 수 있는 기능이 있습니다. 하지만 이전 버전들보다 더 복잡한 보안 모델과 설정 등의 이유로 널리 사용되고 있지는 않습니다.
[그림] SNMP 버전과 수를 한눈에 볼 수 있는 제니우스 EMS 화면
참고로 SNMP에는 위와 같이 다양한 버전이 있기 때문에 모든 NMS는 제니우스처럼 어떤 버전으로 수집했는지와 수를 파악할 수 있어야 합니다.
이제 SNMP에 대해서 조금 더 자세하게 살펴보겠습니다.
ㅣSNMP 자세히 보기: MIB의 개념과 구조
MIB(Management Information Base)는 관리 정보 기반이라고 불립니다. SNMP를 통해 관리되어야 할 정보나 자원들을 모아둔 것으로, Manager와 Agent 간 정보를 주고받는 정보의 집합체입니다.
MIB에는 SNMP를 통해 주고받는 정보가 어떤 의미를 가지고 어떻게 사용될 수 있는지에 대한 정의가 포함되어 있습니다. 또한 각각의 정보는 '객체'라고 불리며, 이 객체들은 계층적으로 구성되어 있기에 관리하고자 하는 정보를 쉽게 찾을 수 있게 도와주죠.
대표적으로 CPU 사용량, 메모리 사용량, 포트의 up/down 같은 상태 정보 등이 MIB에 포함됩니다. 마치 항해사가 바다를 항해하기 위해 지도를 사용하는 것처럼, MIB를 통해 네트워크의 상태를 정확히 파악하고 필요한 조치를 취할 수 있습니다.
MIB의 구조를 자세히 살펴보면 우선 큰 나무를 뒤집어 놓았다고 생각한다면 이해하기 쉽습니다. 큰 나무의 밑동(Root) → 각각의 가지(Branches) → 잎사귀(Leavers)로 나누어져 내려오는 형태인데요, 부분별로 자세히 살펴보겠습니다.
◾
밑동(Root):
모든 MIB 트리의 시작점으로, 'iso(1)', 'org(3)', 'dod(6)', 'internet(1)' 등으로 구성되어 있습니다. 여기서 'internet'은 네트워크 장비와 관련된 표준 MIB를 나타냅니다.
◾
가지(Branches):
밑동에서 나온 큰 가지들은 네트워크 장비의 다양한 부분을 나타냅니다. 예를 들어 'mgmt(2)' 가지는 일반적인 관리 정보, 'private(4)' 가지는 각 제조업체의 고유 정보 등을 의미합니다.
◾
잎사귀(Leaves):
가장 작은 단위의 정보를 나타내는 부분으로 특정 장비의 상태, 성능 지표, 설정값 등 구체적인 데이터가 저장됩니다.
MIB에서는 네트워크 장비의 정보가 여러 '분류'로 나누어져 있는데, '네트워크 인터페이스'라는 분류 아래에는 네트워크 카드의 상태, 속도, 전송된 데이터의 양과 같은 정보들이 담겨 있습니다.
MIB는 복잡해 보일 수 있지만, 네트워크 장비와 관련된 정보를 체계적으로 관리하고 접근할 수 있도록 설계되어 있습니다. 이 구조 덕분에 네트워크 관리자는 네트워크의 건강 상태를 쉽게 체크하고 필요한 조정을 할 수 있습니다.
다음으로는 MIB 내의 각 객체를 고유하게 식별하는 OID에 대해서 알아보겠습니다.
ㅣSNMP 자세히 보기: OID 확인 방법과 수집항목
OID(Object Identifier)는 MIB 내에 포함되어 있는 각 개별 정도에 대한 ID 값입니다. 아래 그림에서 볼 수 있듯이, 트리의 하단 값이 OID인데 MIB의 각 개별 정보에 대한 ID를 의미합니다.
[그림] OID Tree 구조
대형 도서관에서 원하는 책을 찾을 때 책의 번호를 확인하여 빠르고 정확하게 찾는 것처럼, 특정 오브젝트의 ID(Num)을 부여한 게 OID입니다. OID는 포함하고 있는 각 정보를 숫자로 표현합니다.
◾
Enterprise OID:
네트워크 업계에서 공통으로 사용하는 OID
◾
Private OID:
각 네트워크 벤더사에서 사용하는 독자적인 OID
예를 들어 Juniper Networks라는 네트워크 스위치 벤더에서 사용하고 있는 OID 값을 [1.3.5.6.1.9 ]라는 전용 OID 값을 사용한다고 가정하면, Juniper Networks 라우터의 경우 뒤에 라우터 제품별 OID '11'이 더 붙은 [1.3.5.6.1.9.11 ] 형태의 OID로 구성됩니다.
[그림] 제니우스 예시 화면
지금까지 네트워크 모니터링에 필요한 ICMP, SNMP 그리고 MIB, OID에 대해 살펴봤습니다. 참고로 제니우스(Zenius)-NMS에서는 OID 사전을 제공하고 있으며, 이를 통하여 관리하고 싶은 항목의 MIB 항목 및 OID 정보를 쉽게 찾을 수 있습니다.
이제 SNMP의 주요 개념 중 하나인 SNMP Trap에 대해서 알아보겠습니다.
ㅣSNMP Trap의 개념 그리고 특징은?
Manager(관리자)는 Server(Agent)로 메시지 요청(Polling)을 하게 되고, Server(Agent)는 응답(Notifying)을 하는 방식으로 진행됩니다.
그런데 Server가 비정상적인 이벤트를 감지하면 Manager의 Polling을 기다리지 않고 바로 Manager에게 메시지를 보내는데요, 이 긴급 메시지를 Trap(트랩)이라고 합니다. 우리가 날씨에 대해서 찾아보지 않아도 폭설이 예상될 때 폭설을 경고하는 자동 알림 시스템과 비슷한 개념입니다.
[그림] SNMP 프로토콜 동작 방식
SNMP Trap은 일반적으로 높은 CPU 사용량이나 디스크 공간 부족과 같이 해결해야 할 문제를 나타냅니다. 중앙 모니터링 시스템으로 전송되어 분석 및 조치를 취할 수 있죠. 이를 통해 Manager는 큰 문제가 발생하기 전에 잠재적인 문제를 신속하게 식별하고 해결할 수 있습니다.
SNMP Trap의 방식과 기능을 네 가지로 나누어 살펴보겠습니다.
(1) 비동기적 알림
SNMP Trap는 주기적인 폴링이 아닌, 이벤트 기반의 알림을 통해 즉각적으로 대응할 수 있도록 비동기적인 방법을 제공합니다.
(2) 실시간 알림
SNMP Trap은 이벤트가 발생하는 즉시 알림을 제공하여, 실시간으로 네트워크 상태 및 장치 상태를 모니터링해서 문제 발생 시 즉각적인 대응과 조치를 가능하게 합니다.
(3) 이벤트 기반 모니터링
SNMP Trap은 장치나 응용 프로그램에서 특정 이벤트가 발생했을 때만 알림을 보내기 때문에, 불필요한 트래픽을 발생시키지 않습니다. 따라서 자원을 효율적으로 사용하면서 중요한 상태 변경을 식별합니다.
(4) 자동화된 대응
SNMP Trap을 사용하면 이벤트 발생 시, 자동으로 대응 조치를 취할 수 있는 자동화 시스템을 구축할 수 있습니다. 이를 통해 관리자의 개입 없이 특정 이벤트에 대한 대응을 효과적으로 수행할 수 있습니다.
[그림] Zenius Syslog 감시 설정 등록 페이지(위), Zenius Syslog 이벤트 페이지(아래)
이와 같은 SNMP Trap을 통해 빠르게 이상을 탐지하는 것이 중요한데요. 제니우스(Zenius)-Syslog와 Trap에서는 Syslog, Trap에 각각 특정 이벤트 조건을 설정하여 이벤트를 감지하고, 장애를 통보할 수 있는 기능을 제공하고 있습니다.
이제 마지막으로 SNMP 못지않게 네트워크 관리에 중요한 역할을 하는 Syslog, RMON에 대해서 알아보겠습니다.
ㅣ Syslog, RMON의 개념과 동작원리는?
Syslog
Syslog는 컴퓨터 시스템, 네트워크 장비, 보안 장비 등에서 일어나는 모든 상황과 변화를 서버에 기록하는 프로토콜입니다. 관리 대상인 장비에서 일어나는 모든 상황을 메모리에 기록하죠. 로그/오류 관리가 주 목적이고 Unix와 Linux에서 많이 사용됩니다.
대부분의 라우터와 스위치들은 Syslog 프로토콜을 이용하여 Log들을 Syslog 서버로 보내고, 수백수천 대의 장비에 일일이 접속하여 로그를 볼 수 없기 때문에 '중앙 집중식'으로 관리합니다.
작업 방식은 주로 Client-Push 모델로 이러우지고 있고, 장비에서 일어나는 모든 상황 변화를 Layer4 프로토콜이 메모리에 기록하며, Syslog 서버는 UDP 포트 514에서 메세지를 수신합니다.
Syslog 수집항목은 시스템 운영/네트워크/보안/애플리케이션 등과 관련된 로그를 수집 및 분석하고, 각 항목별로 오류와 트랜잭션 등에 대한 내용을 확인합니다.
출처ⓒ viettelco.net
RMON
RMON(Remote Network Monitoring)은 네트워크 장비나 서버에서 발생하는 트래픽과 문제들을 원격에서 감시하기 위해 만들어진 프로토콜로, SNMP보다 확장된 개념이라고 할 수 있습니다.
네트워크 관리자는 RMON을 통해, 네트워크의 성능을 측정하고 문제가 발생했을 때 신속하게 해결할 수 있습니다. 회사에서 인터넷이 느려지거나 연결이 되지 않을 때 RMON을 사용하면 원인을 빠르게 찾아내어 문제를 해결할 수 있죠.
RMON과 SNMP의 연관성을 우선 아래 이미지를 통해 살펴보겠습니다.
출처ⓒ dpstele.com/blog/what-is-rmon.php
좀 더 자세히 살펴보면
◾ RMON은 SNMP 위에서 작동하며, SNMP 보다 더 광범위한 데이터를 수집/분석할 수 있는 기능을 제공합니다.
◾ SNMP가 네트워크의 '기본적인 통신'을 담당한다면, RMON은 그 위에서 보다 '세밀한 관찰과 분석'을 가능하게 합니다.
◾ RMON은 SNMP의 특정 데이터를 사용하여 네트워크 트래픽 패턴이나, 성능 문제, 네트워크 내의 비정상적인 활동 등을 실시간으로 감시하고 기록할 수 있게 해줍니다.
◾ RMON에서 Probe라는 수행 장비를 사용하며, 네트워크 트래픽 및 통계 수집 그리고 성능 모니터링을 위해 활용합니다.
결과적으로 RMON의 기능을 통해 네트워크의 문제를 더 빨리 발견하고, 효율적으로 대응할 수 있죠.
마지막으로 SNMP, RMON, ICMP, Syslog의 주요 내용들을 아래 표를 통해 한눈에 살펴보겠습니다.
。。。。。。。。。。。。
지금까지 네트워크 정보 수집을 위한 다양한 프로토콜의 종류와 특징에 대해서 알아보았습니다. 효과적인 네트워크 관리를 위해서 혁신적인 기술들이 많이 개발되고 있는데요, 이를 활용해서 성공적으로 네트워크를 운영하시기를 바라겠습니다!
#네트워크 프로토콜
#SNMP
#RMON
#ICMP
#Syslog
임형섭
프리세일즈팀
안정적이고 효과적인 비즈니스 운영을 위한 고객 맞춤형 IT 인프라 모니터링 시스템을 제안합니다.
필진 글 더보기
목록으로
추천 콘텐츠
이전 슬라이드 보기
Spring Security로 로그인부터 사용자 전환까지 구현하기
Spring Security로 로그인부터 사용자 전환까지 구현하기
ID/PW 로그인으로 시작한 인증 기능도 제품의 접점이 늘어나면 다양한 요구사항을 만나게 됩니다. 외부 시스템에는 API Key를 이용한 호출이 필요하고, 개발 과정에서는 사용자 ID만으로 권한별 화면을 확인해야 할 때가 있습니다. 운영 지원을 위해 관리자가 하위 사용자의 실행 환경으로 전환하는 기능도 필요합니다. 이러한 요구사항을 구현하려면 인증 정보를 검증하는 방식뿐 아니라 요청을 구분하고 인증 상태를 유지하는 구조도 함께 이해해야 합니다. 이 글에서는 먼저 Spring Security의 공식 구조를 바탕으로 로그인 처리 흐름을 살펴보겠습니다. 이어서 프로젝트에서 세 가지 인증 방식을 분리한 방법을 자세히 알아보고, 마지막으로 원래 로그인한 사용자를 보존하면서 실행 사용자를 전환하는 구현을 살펴보겠습니다. 1장: Servlet 필터 체인부터 인증 관리자·제공자·토큰·세션까지 2장: ID/PW, API Key, 개발용 ID 인증의 커스텀 구현 3장: 상위 사용자의 하위 사용자 전환과 원래 계정 복귀 ※ 프로젝트 코드는 설명에 필요한 부분만 발췌했으며, 암호화·복호화·비밀번호 검증의 세부 구현과 실제 인증값은 주석으로 생략했습니다. 1. 공식 문서로 이해하는 로그인 처리 구조 로그인을 구현할 때는 요청을 받는 코드와 사용자를 검증하는 코드의 역할을 먼저 구분해야 합니다. Spring Security는 이를 필터, 인증 관리자, 인증 제공자, 인증 결과 객체로 나누어 처리합니다. 요청이 이 구성 요소들을 거쳐 인증되고 저장되는 흐름을 이해하면, 이후에 살펴볼 여러 로그인 방식과 사용자 전환도 같은 구조 안에서 파악할 수 있습니다. ※설명 기준: 프로젝트는 Spring Boot 3.0.4를 사용하며, Spring Security 버전은 6.0.2입니다. 이 장은 Spring Security 6.x의 Servlet 구조를 설명하며, 그림과 설명은 공식 6.5 레퍼런스를 사용했습니다. 아래 설정은 학습용 예시로, 다음 장의 프로젝트 설정과는 구분됩니다. 1.1. Servlet 컨테이너의 FilterChain에 들어가는 보안 필터 먼저 요청이 들어오는 지점부터 살펴보겠습니다. Spring Security의 보안 필터가 연결되는 대상은 Apache Tomcat 같은 Servlet 컨테이너가 실행하는 Servlet Filter 체계입니다. 간혹 이를 “Apache 필터”라고 표현하기도 하지만, Apache HTTP Server의 모듈이나 필터 체계와는 구분해야 합니다. 그림 1. 컨테이너의 필터들과 Servlet. 요청은 여러 필터를 거쳐 Spring MVC의 DispatcherServlet에 도착합니다. 각 필터는 요청을 다음 단계로 넘기거나 직접 응답하고 처리를 종료할 수 있습니다. Spring Security도 이 구조를 이용해 인증이 필요한 요청을 컨트롤러에 도착하기 전에 차단할 수 있습니다. 이 과정에서 컨테이너의 필터와 Spring Bean을 연결하는 클래스가 DelegatingFilterProxy입니다. 이 프록시는 Spring에서 관리하는 보안 필터 Bean으로 처리를 위임합니다. 일반적으로 위임 대상인 springSecurityFilterChain Bean의 실제 구현은 FilterChainProxy이며, 이름에 Chain이 포함되어 있지만 Servlet 관점에서는 하나의 Filter로 동작합니다. 그림 2. Servlet 컨테이너와 Spring ApplicationContext를 연결하는 프록시. 이 구조를 요청의 이동 순서에 따라 살펴보겠습니다. 컨테이너의 필터 체인에서 보안 처리 지점에 도착한 요청은 DelegatingFilterProxy를 통해 FilterChainProxy로 전달됩니다. FilterChainProxy는 요청에 맞는 SecurityFilterChain을 선택하고, 그 안의 보안 필터들을 순서대로 실행합니다. 보안 처리를 통과한 요청은 컨테이너의 필터 체인으로 돌아가 Servlet까지 진행합니다. 공식 문서의 Architecture에서도 이러한 계층을 각각의 그림으로 설명합니다. 1.2. 필터 매니저와 패턴 매처: 어떤 보안 체인을 적용할까 그렇다면 여러 보안 체인이 등록되어 있을 때는 어떤 체인을 선택할까요? 앞서 살펴본 FilterChainProxy가 이 선택을 담당하며, 이 글에서 말하는 “필터 매니저” 역할에 해당합니다. 우선순위가 높은 체인부터 RequestMatcher로 요청을 검사하고, 처음 일치한 체인 하나만 실행합니다. 일치하는 체인을 모두 이어서 실행하는 방식은 아닙니다. 그림 3. 요청에 처음 일치한 SecurityFilterChain 선택. 이 선택 구조를 설정하려면 요청 매처의 역할을 구분해야 합니다. 먼저 securityMatcher와 requestMatchers가 어떤 단계에서 사용되는지 알아보겠습니다. 두 설정 모두 요청을 비교하지만, 체인을 선택하는 단계와 선택된 체인 안에서 접근 권한을 확인하는 단계로 역할이 나뉩니다. 로그인 필터에도 별도의 요청 매처가 있으며, 각 역할은 다음과 같습니다. 예를 들어 /api/** 전용 체인이 먼저 선택되면 뒤쪽 웹 체인의 formLogin 설정은 해당 요청에 적용되지 않습니다. 같은 이유로 /secured/**만 담당하는 체인에 기본 /login 처리를 설정하면 로그인 요청이 그 체인에 도달하지 못합니다. 또한 어느 체인에도 일치하지 않는 요청은 Spring Security의 보호를 받지 않습니다. 마지막에 전체 요청을 담당하는 체인을 두는 이유도 이러한 선택 구조 때문입니다. 1.3. 인증 관리자, 인증 제공자, 토큰의 역할 요청에 적용할 체인이 정해지면 그 안에서 실제 인증이 진행됩니다. 이때 흔히 “authenticator”라고 부르는 역할은 하나의 클래스에 모여 있지 않습니다. 인증 필터가 입력을 해석하고, AuthenticationManager가 인증 요청을 받으며, AuthenticationProvider가 해당 인증 방식을 검증합니다. AuthenticationManager의 대표 구현인 ProviderManager는 등록된 인증 제공자에게 검증을 위임합니다. 각 제공자는 supports를 통해 자신이 처리할 수 있는 Authentication 타입을 알립니다. 이는 해당 토큰의 처리 가능 여부를 나타내며, 로그인 성공을 의미하지는 않습니다. 이처럼 역할을 분리하면 요청에서 자격 증명을 추출하는 방식과 사용자 검증 정책을 각각 확장할 수 있습니다. 필터와 인증 제공자 사이에서 전달되는 입력과 결과는 Authentication 객체로 표현합니다. ID/PW 인증에서는 UsernamePasswordAuthenticationToken을 사용할 수 있습니다. 검증 전에는 ID와 비밀번호를 담고, 검증 후에는 사용자 객체와 부여된 권한을 가진 인증 결과를 받습니다. 여기서 Token은 Spring Security 내부의 인증 객체를 뜻하며, 이 객체를 사용한다고 해서 JWT가 발급되거나 브라우저에 문자열 토큰이 반환되는 것은 아닙니다. 이제 인증 요청과 결과가 코드에서 어떻게 연결되는지 살펴보겠습니다. 공식 인증 아키텍처 예제에서 인증 요청과 결과를 받는 부분만 축약했으며, HTTP 응답과 세션 저장 처리는 생략했습니다. 위 코드의 request는 아직 검증되지 않은 인증 요청입니다. authenticate 호출을 통해 제공자의 검증을 마친 뒤에야 인증에 성공한 결과를 사용할 수 있습니다. 저장된 사용자 정보로 인증하는 기본 구현인 DaoAuthenticationProvider도 이 흐름을 따릅니다. UserDetailsService로 사용자를 조회하고, PasswordEncoder로 입력한 비밀번호와 저장된 값을 검증합니다. 사용자 조회와 비밀번호 검증은 별개의 책임이므로, 사용자 정보를 조회했다는 사실만으로 로그인이 성공하지는 않습니다. 검증을 마치면 사용자와 권한이 담긴 Authentication을 반환합니다. 비밀번호 보관·검증에 사용하는 구체적인 알고리즘은 이 글의 범위에서 생략합니다. 1.4. 표준 ID/PW 로그인 한 번 따라가기 이제 앞에서 살펴본 구성 요소들이 표준 로그인에서 어떻게 연결되는지 예제를 통해 알아보겠습니다. 공식 Form Login 예제의 핵심 설정은 formLogin입니다. 아래 코드는 해당 예제에 접근 규칙과 반환문을 추가한 학습용 예시이며, 사용자 저장소와 인증 제공자는 별도로 준비되어 있다고 가정합니다. 이 설정의 기본 폼은 POST /login으로 username, password 파라미터를 보냅니다. CSRF 보호가 켜져 있다면 폼에 CSRF 토큰도 포함해야 합니다. JSON 요청 본문으로 로그인 정보를 전달하는 API는 기본 폼 파라미터 처리와 별도로 입력 처리를 구성해야 합니다. 로그인 요청이 들어온 이후에는 앞서 설명한 인증 구성 요소들이 다음 순서로 동작합니다. 1. UsernamePasswordAuthenticationFilter가 로그인 요청에서 ID/PW를 추출합니다. 2. 미인증 UsernamePasswordAuthenticationToken을 만들어 AuthenticationManager에 전달합니다. 3. 인증 제공자가 사용자와 자격 증명을 검증하고 인증 결과를 반환합니다. 4. 인증에 성공하면 세션 인증 전략을 실행하고 인증 결과를 SecurityContext에 설정합니다. 5. 성공 핸들러가 후속 응답을 생성합니다. 인증에 실패하면 실패 핸들러가 응답합니다. 이처럼 표준 로그인에서는 자격 증명 검증을 마친 뒤에도 세션 처리와 응답 생성이 이어집니다. 프로젝트의 커스텀 로그인 역시 이 후속 흐름까지 연결해야 합니다. 특히 로그인 상태를 다음 요청에서도 사용하려면 인증 결과를 어디에 저장하는지 확인해야 합니다. 1.5. 이번 요청의 인증을 다음 요청까지 유지하기 현재 사용자 정보는 SecurityContextHolder가 보관하는 SecurityContext의 Authentication으로 표현됩니다. 기본적인 Servlet 환경에서는 이 Holder가 요청 처리 스레드와 연결됩니다. 따라서 현재 요청에서 사용할 인증 정보를 설정하는 작업과, 다음 요청에서 다시 읽을 수 있도록 저장하는 작업은 구분해야 합니다. 요청 간 인증 상태를 연결하는 역할은 SecurityContextRepository가 담당합니다. 세션 기반 로그인에서는 브라우저가 다음 요청에 세션 쿠키를 보내면 서버가 저장된 컨텍스트를 복원합니다. Spring Security 6의 기본 SecurityContextHolderFilter는 컨텍스트를 읽는 역할을 담당하므로, 커스텀 인증 코드에서는 저장소에 명시적으로 저장해야 합니다. 기본 폼 로그인 필터가 처리하던 인증 흐름을 직접 구현한다면 이 저장 과정도 함께 연결해야 합니다. 인증 정보를 저장하는 것과 별개로, 새 인증에 어떤 세션 정책을 적용할지도 결정해야 합니다. 이를 담당하는 구성 요소가 SessionAuthenticationStrategy입니다. 동시 로그인 제한을 예로 들어 세션 정책을 어떻게 설정하는지 살펴보겠습니다. 아래는 공식 예제의 핵심 부분이며, 프로젝트가 같은 정책을 적용했다는 의미는 아닙니다. 이 설정은 기존 로그인을 유지하면서 두 번째 로그인을 거절합니다. maxSessionsPreventsLogin(true)를 생략하면 기본적으로 기존 세션을 만료시키는 방향으로 동작합니다. 세션 종료 이벤트를 추적하려면 HttpSessionEventPublisher도 연결해야 합니다. 또한 직접 만든 인증 필터에는 동시 세션 제어를 명시적으로 구성해야 합니다. 여기까지의 흐름은 요청에 맞는 체인 선택, 인증 객체 생성, 제공자의 검증, 인증 결과 저장으로 정리할 수 있습니다. 이 순서에 맞춰 코드를 읽으면 여러 인증 방식이 공통으로 사용하는 부분과 서로 다르게 구현한 부분을 구분하기 쉽습니다. 다음 장에서는 이 구조를 ID/PW, API Key, 개발용 ID 인증에 각각 적용한 방식을 살펴보겠습니다. 2. 세 가지 인증 요구사항을 Spring Security 확장 지점에 연결하기 프로젝트에는 인증이 필요한 세 가지 접점이 있습니다. 사용자는 화면에서 ID와 비밀번호로 로그인하고, 외부 연동 프로그램은 API Key로 API를 호출합니다. 개발자는 화면을 구현하면서 사용자 ID만으로 특정 사용자의 권한을 확인해야 합니다. 이러한 요구사항을 처리하기 위해 요청을 선택하는 SecurityFilterChain, 자격 증명을 해석하는 필터, 실제 검증을 수행하는 AuthenticationProvider의 역할을 나누었습니다. 여기서 ID는 DB의 숫자형 사용자 식별자가 아니라 사용자가 입력하는 loginName을 뜻합니다. ID/PW 인증과 개발용 인증에는 UsernamePasswordAuthenticationToken을 사용하고, API Key 인증에는 별도의 ZngApiKeyAuthenticationToken을 사용합니다. 인증 요청에 사용하는 Token은 다르지만, 인증 결과의 principal은 MyUserDetails로 통일했습니다. 따라서 이후 서비스는 동일한 사용자 모델과 메서드 권한 검사 체계를 사용할 수 있습니다. MyUserDetails는 Spring Security의 UserDetails 인터페이스를 구현한 프로젝트의 사용자 클래스입니다. Kotlin에서는 클래스 선언에 : UserDetails를 붙여 구현합니다. UserDetails는 사용자명, 권한 목록, 계정 잠금·활성 상태 등 인증에 필요한 정보를 제공하는 인터페이스입니다. 프로젝트에서는 여기에 사용자 ID와 계층, 소속 관리자, 접근 가능한 대상 그룹, 원래 로그인한 사용자 정보 등의 업무 속성을 추가했습니다. 인증 성공 후 MyUserDetails를 Authentication.principal에 넣으면 Spring Security의 권한 검사와 업무 코드의 현재 사용자 조회가 같은 객체를 기준으로 동작합니다. 다만 사용자 객체를 제공하는 것과 자격 증명을 검증하는 것은 별개의 역할입니다. UserDetails 구현만으로 비밀번호나 계정 상태가 자동 검증되지는 않으며, 이 프로젝트에서는 커스텀 Provider가 필요한 검사를 직접 호출합니다. getPassword()도 일반적인 저장 비밀번호 제공 경로로 사용하지 않습니다. 뒤에서 살펴볼 ID/PW Provider와 개발용 Provider 역시 목적에 따라 검증 범위가 다릅니다. 이러한 공통 사용자 모델을 바탕으로 각 인증 방식의 요청 구분과 결과 저장 범위를 다음과 같이 구성했습니다. 이제 요청 분류부터 시작해 각 인증 방식의 구현을 차례로 살펴보겠습니다. 이 장의 코드는 현재 저장소 구현에서 발췌했으며, 패키지와 import는 생략했습니다. 자격 증명 처리 부분은 주석으로 대체했고, 암호화·복호화·해시 알고리즘과 파라미터, 실제 인증값은 포함하지 않았습니다. 2.1 먼저 인증 방식이 충돌하지 않도록 요청을 분류합니다 세 가지 인증 방식을 함께 사용하려면 먼저 각 요청이 어느 체인으로 들어갈지 정해야 합니다. 앞 장에서 설명한 것처럼 여러 체인이 선언되어 있어도 처음 일치한 체인 하나만 요청을 처리합니다. 따라서 @Order와 securityMatcher의 조합은 단순한 실행 순서 설정을 넘어 인증 정책의 일부가 됩니다. 프로젝트에서는 세 가지 인증 진입점보다 앞에 인증 방식의 개수를 검사하는 체인을 두었습니다. 여기서 확인하는 항목은 로그인 요청 여부, 세션에 저장된 SecurityContext, Authorization 헤더입니다. 세션 쿠키의 존재만으로 판단하지 않고, HttpSessionSecurityContextRepository.containsContext(request)로 실제 저장된 컨텍스트를 확인합니다. 위 함수의 결과는 Order 40 체인의 matcher에서 사용합니다. 값이 1이 아닌 요청은 이 체인에서 선택해 401로 응답합니다. 예를 들어 세션으로 로그인한 요청에 API Key 헤더를 추가하거나, POST /api/login에 Authorization 헤더를 함께 보내면 두 방식이 겹칩니다. 이 경우 뒤쪽의 API Key 체인이나 세션 인증 체인으로 넘어가지 않습니다. 다만 이 단계에서는 Authorization 헤더의 형식이 아니라 존재 여부만 확인합니다. 이 검사의 적용 범위는 앞서 선택되는 체인까지 함께 보아야 이해할 수 있습니다. OPTIONS(Order 10), 정적 리소스(Order 20), 공개키 조회 GET /api/user/pk(Order 30), 비정상 로그아웃 처리(Order 35), 특정 보고서 생성 요청(Order 37)은 Order 40보다 먼저 선택될 수 있습니다. 이후에는 별도의 보고서용 인증(Order 50), 개발용 Basic(Order 60), API Key(Order 70), 세션 인증(Order 80)이 이어집니다. 따라서 모든 URL이 동일한 검사를 거치는 것은 아니며, @Order와 matcher를 기준으로 흐름을 확인해야 합니다. 각 인증 체인으로 분기할 때 사용하는 헤더 matcher는 대소문자를 무시하는 startsWith(authScheme, true)로 구현되어 있습니다. 이는 scheme에 따라 요청을 구분하는 역할이며, 엄격한 헤더 문법 검증까지 수행하는 구현은 아닙니다. 이렇게 요청 경로를 나눈 뒤, 각 필터와 Provider에서 인증 방식에 맞는 처리를 진행합니다. 2.2 ID/PW 로그인: 입력 형식과 사용자 정책을 분리합니다 먼저 일반 로그인은 POST /api/login 요청의 JSON body를 읽습니다. 이를 담당하는 ZngUsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속하며, 생성자에서 ProviderManager(ZngUsernamePasswordAuthenticationProvider(userService))를 전달합니다. 필터는 입력과 HTTP 요청을 처리하고 Provider는 사용자를 검증하므로, 역할 분담은 표준 로그인과 같습니다. 표준 폼 로그인과 달라지는 부분은 입력 형식입니다. 폼 파라미터 대신 JSON 본문을 읽어야 하므로 커스텀 필터를 사용했습니다. 필터는 입력을 준비한 뒤 다음과 같이 미인증 Token을 생성합니다. 이 Token은 검증할 요청을 전달하는 객체이며, 아직 인증 성공 상태는 아닙니다. 이때 details에 추가한 HTTP 요청 정보는 이후 IP 정책 검사에 사용합니다. Token을 전달받은 Provider는 서비스에 ID/PW 검증을 위임하고, 필요한 비밀번호 저장 형식 이전 처리를 수행한 뒤 사용자 상태를 추가로 확인합니다. 서비스에서는 ID 오류와 비밀번호 오류를 구별해 알리지 않고 동일한 인증 예외로 처리합니다. 자격 증명 검증 이후에는 어떤 항목을 추가로 확인하는지 코드로 살펴보겠습니다. 검증에 성공하면 principal은 문자열 ID에서 MyUserDetails로 바뀌고, authorities에는 사용자 권한이 포함되며, credentials는 null로 반환됩니다. 이 Provider의 supports는 전달된 Token이 UsernamePasswordAuthenticationToken 계열인지 확인합니다. ProviderManager는 이 지원 여부를 기준으로 검증을 담당할 Provider를 선택합니다. 인증 결과를 받은 다음에는 세션 정책을 적용하고 로그인 상태를 저장해야 합니다. 이를 위해 로그인 필터에 다음과 같은 세션 전략과 성공 핸들러를 지정했습니다. 먼저 세션 전략의 첫 번째 구성 요소는 설정값으로 최대 동시 세션 수를 확인합니다. 한도를 넘으면 신규 로그인을 예외로 거절하거나 기존 세션을 expireNow()로 만료 표시합니다. 두 번째 전략은 새 세션을 SessionRegistry에 등록합니다. 만료 표시된 세션으로 후속 요청이 들어오면 세션 체인에 배치한 ConcurrentSessionFilter가 처리합니다. 즉, 세션에 만료 여부를 기록하는 시점과 해당 세션의 다음 요청을 차단하는 시점은 구분됩니다. 다만 현재 동시 세션 전략에는 sortedBy의 반환값을 사용하지 않는 코드가 있어, 주석의 의도만으로 가장 오래 사용하지 않은 세션이 반드시 선택된다고 설명하기는 어렵습니다. 또한 위 Composite에 직접 포함된 전략은 동시 세션 검사와 등록 두 가지이므로, 이 설정만으로 세션 ID 변경 전략까지 적용된다고 단정할 수는 없습니다. 세션 전략을 통과하면 성공 핸들러가 SecurityContext를 생성하고 HttpSessionSecurityContextRepository.saveContext로 명시적으로 저장합니다. 다음 요청에서 세션에 저장된 인증 정보를 사용할 수 있는 이유입니다. 성공 응답은 리다이렉트 대신 MyResult JSON으로 반환하며, 로그인 실패 핸들러는 401을 반환합니다. 로그아웃 요청인 POST /api/logout에는 Spring Security의 로그아웃 처리와 감사 기록을 연결하고, 성공 JSON을 응답하도록 구성했습니다. 인증 결과를 통계와 감사 처리에 반영하는 흐름은 이벤트로 연결했습니다. ProviderManager에 AuthenticationEventPublisher를 설정하고, SecurityConfig의 이벤트 리스너에서 인증 성공 시 사용자 로그인 시도 횟수와 마지막 로그인 시각을 갱신합니다. 사용자 관련 실패와 API Key 자체의 실패는 서로 다른 이벤트로 매핑해 실패 횟수를 갱신할 대상도 구분합니다. 다만 이 이벤트는 Provider의 인증 결과를 알리는 것이므로, 이후 동시 세션 전략까지 성공했다는 의미는 아닙니다. 2.3 API key 인증: 사용자 인증에 호출 범위를 더합니다 ID/PW 로그인이 브라우저의 세션을 유지하기 위한 흐름이라면, 외부 연동에서는 Authorization: ZNG <API_KEY> 형식의 헤더로 인증 정보를 전달합니다. 실제 키는 예시에 포함하지 않았습니다. Order 70 체인이 이 헤더를 가진 요청을 선택하면, BasicAuthenticationFilter 위치에 배치한 ZngApiKeyAuthenticationFilter가 처리를 담당합니다. 이 필터는 OncePerRequestFilter를 상속합니다. 헤더에서 키를 읽고 형식을 확인한 뒤, 식별자와 검증용 비밀값을 미인증 ZngApiKeyAuthenticationToken에 담습니다. 여기에 요청 details를 추가해 API Key 전용 ProviderManager로 전달합니다. 인증 결과를 받은 뒤에는 컨텍스트를 설정하고 저장합니다. 이 과정이 ID/PW 로그인과 어떻게 다른지 다음 코드와 함께 살펴보겠습니다. 앞서 본 로그인 처리와 코드 형태는 비슷하지만, 여기서 사용하는 저장소는 RequestAttributeSecurityContextRepository입니다. API Key 필터는 HttpSession에 인증 결과를 직접 저장하지 않고 요청 속성 저장소를 사용하므로, 의도한 인증 유지 범위는 현재 요청입니다. 다만 설정에 SessionCreationPolicy.STATELESS가 명시된 것은 아니어서, 이 코드만으로 애플리케이션 전체에서 어떤 이유로도 세션이 생성되지 않는다고 볼 수는 없습니다. 이 인증 결과를 만드는 ZngApiKeyAuthenticationProvider는 먼저 식별자로 키를 조회하고, 비밀값 일치 여부와 활성·만료·잠금 상태를 확인합니다. 이어서 키에 연결된 사용자도 조회합니다. 키가 유효하더라도 사용자가 휴면·잠금·자격 증명 만료·비활성 상태이거나 IP 조건을 만족하지 못하면 인증하지 않습니다. 검증에 성공하면 ZngApiKeyAuthenticationToken.authenticated(user, null, user.authorities, apiKey)를 반환합니다. 전용 Token에 apiKey를 남기는 이유는 인증 이후에도 키별 호출 범위를 확인해야 하기 때문입니다. 다음 인가 코드에서 이 정보를 사용합니다. 위 코드는 인증과 인가의 역할을 구분해 보여줍니다. Provider가 “어떤 사용자와 키인가”를 검증했다면, ZngApiKeyAuthorizationManager는 “이 키로 이 요청을 호출해도 되는가”를 판단합니다. 요청은 키에 허용된 API 경로와 HTTP 메서드 matcher에 일치해야 하며, 사용자 전환·사용자 관리·API Key 관리 등 별도로 선언한 금지 matcher에도 해당하지 않아야 합니다. 이 제한은 선언된 matcher 범위에 적용되며, 모든 관리 기능을 자동으로 차단한다는 의미는 아닙니다. 컨트롤러와 서비스의 메서드 권한 검사도 별도로 작동합니다. 실패 처리도 API Key 인증 흐름에 맞추어 구성했습니다. 필터에서 AuthenticationException이 발생하면 감사 기록을 남기고 컨텍스트를 비운 뒤 403 진입점으로 응답합니다. 키 형식이 맞지 않으면 필터 자체에서 예외를 던지는 대신 다음 필터로 넘기지만, 인증된 API Key Token이 없으면 이후 인가를 통과할 수 없습니다. API Key로 들어오는 로그아웃 요청은 세션 로그아웃과 구분되므로, 앞쪽의 비정상 로그아웃 처리 체인에서 별도로 처리합니다. 2.4 개발용 ID 인증: 기본 HTTP Basic 필터에 Provider만 교체합니다 마지막으로 개발 중 사용자별 권한과 화면 동작을 확인하기 위한 인증 방식을 살펴보겠습니다. 이때는 지정한 사용자의 컨텍스트로 API를 호출할 수 있도록, 기본 HTTP Basic 처리에 ID만 조회하는 Provider를 연결했습니다. 요청을 해석하는 기본 필터는 사용하면서 사용자 검증 부분을 별도로 구성한 것입니다. 여기서 “ID만으로 로그인”한다는 것은 HTTP body에 숫자 ID 하나를 보내는 별도 로그인 API를 뜻하지 않습니다. 요청은 Basic 헤더 형식을 따르며, 기본 필터가 이를 UsernamePasswordAuthenticationToken으로 변환합니다. 이후 커스텀 Provider가 authentication.name, 즉 loginName으로 사용자를 조회합니다. 프런트엔드의 개발 로그인 설정도 입력받은 사용자명을 Basic 형식으로 준비해 후속 요청의 Authorization 헤더에 넣습니다. 실제 인증값을 만드는 표현식은 본문에서 생략했습니다. 위 Provider는 비밀번호를 비교하지 않으며, 일반 로그인 Provider에서 수행하는 사용자 상태·IP 검사도 호출하지 않습니다. 검증을 단순화한 개발 편의 기능이므로 활성화 범위를 함께 확인해야 합니다. 현재 조건은 **`prod` 프로필이 활성화되지 않은 환경(@Profile("!prod"))**입니다. 반면 API Key 체인에는 이 프로필 조건이 없습니다. 개발용 Basic 체인이 활성화된 환경에서도 ZNG 헤더 요청은 API Key 체인으로 들어가며, 두 인증 방식은 헤더에 따라 구분됩니다. 앞서 설명한 Order 40의 인증 방식 개수 검사도 개발용 요청에 적용됩니다. 따라서 세션 컨텍스트를 유지한 채 Basic 헤더를 추가하면 인증 방식이 충돌한 요청으로 처리됩니다. 지금까지 세 가지 인증 요구사항을 필터·Provider·Token·Context·세션 전략의 역할에 맞추어 연결한 방식을 살펴봤습니다. 다음 장의 사용자 전환은 이렇게 인증된 사용자가 다른 사용자의 컨텍스트로 이동하는 기능입니다. 다음 장에서는 새 자격 증명을 검증하는 로그인과 비교하면서, 전환 권한을 판단하고 세션 상태를 유지하는 방법을 알아보겠습니다. 3. 상위 사용자에서 하위 사용자로: 로그인 상태를 유지하며 실행 주체 바꾸기 운영 지원 과정에서는 관리자가 하위 사용자의 화면과 데이터 접근 범위를 확인해야 할 때가 있습니다. 매번 상대 사용자의 비밀번호를 받아 다시 로그인하는 방식으로는 이러한 요구를 처리하기 어렵습니다. 프로젝트에서는 이미 인증된 상위 사용자의 권한으로 전환 가능 여부를 판단하고, 이후 요청에서 사용할 Authentication을 대상 사용자의 것으로 교체했습니다. 이때는 두 사용자를 구분해야 합니다. 하나는 현재 기능을 실행하는 사용자이고, 다른 하나는 처음 로그인하여 전환 권한의 근거가 된 사용자입니다. 현재 사용자는 화면과 업무 권한의 기준이며, 최초 사용자는 다른 사용자로 이동하거나 원래 계정으로 돌아갈 수 있는 권한의 근거입니다. 이 구분을 유지하기 위해 먼저 원래 로그인한 사용자 정보를 보관합니다. 3.1. 원래 로그인한 사용자는 한 번만 보관합니다 MyUserDetails에는 원래 로그인한 사용자를 보관하는 orgLoginUser 필드가 있습니다. 값이 null이면 현재 객체가 원래 로그인한 사용자를 나타냅니다. 필드 주석은 ID/PW 로그인을 기준으로 작성되어 있습니다. API Key 인증으로 들어오는 전환 요청은 별도의 인가 매니저에서 차단하므로, 여기서는 일반적인 세션 로그인 이후의 전환 흐름을 중심으로 설명하겠습니다. 예를 들어 관리자 A가 사용자 B로 전환하면 B의 orgLoginUser가 A를 가리킵니다. B 상태에서 사용자 C로 다시 이동하더라도 최초 사용자는 A로 유지됩니다. 전환할 때마다 이전 사용자를 연결하는 이력 구조를 만드는 대신, 언제나 하나의 원본 계정을 기준으로 전환 권한을 판단합니다. Spring Security에도 사용자 전환용 SwitchUserFilter가 있으며, 공식 구현은 SwitchUserGrantedAuthority에 원래 Authentication을 보관해 복귀에 활용합니다. 프로젝트에서는 이 필터를 등록하지 않고, 컨트롤러에서 업무 계층을 검증한 뒤 MyUserDetails.orgLoginUser로 원본 사용자를 보관했습니다. 따라서 다음 코드는 SwitchUserFilter를 확장한 구현이 아니라, 프로젝트의 사용자 모델을 이용한 별도 구현입니다. 3.2. 전환 권한은 현재 사용자보다 최초 사용자를 기준으로 판단합니다 원본 사용자를 보관했다면 전환 요청에서 이 정보를 권한 판단에 사용합니다. 실제 진입점은 POST /api/user/change/{userId}입니다. 컨트롤러는 현재 사용자와 원본 사용자를 구분한 뒤, 원본 사용자의 계층을 기준으로 대상에게 전환할 수 있는지 확인합니다. 코드의 계층 검사는 MyUserDetails의 level을 기준으로 합니다. 1은 SUPER, 2는 ADMIN, 3은 일반 사용자이며, isAdmin()과 isSuper()도 이 값을 검사합니다. ADMIN은 대상의 adminId가 자신의 ID와 같은 경우에만 전환할 수 있습니다. adminId는 일반 사용자에게는 소속 관리자의 ID이고, ADMIN과 SUPER에게는 본인의 ID입니다. 따라서 관리자 A는 자신의 하위 사용자로 이동할 수 있으며, 본인으로 복귀할 때도 같은 조건을 통과합니다. 이 값은 조회 SQL의 case when u.z_mylevel = 3 then u.z_parid else u.z_myid end에서 생성됩니다. SUPER의 전환 요청에는 ADMIN과 같은 대상별 소속 검사 분기가 없습니다. 사용자 계층에 맞는 선택 목록은 제공하지만, 실제 전환 API가 SUPER의 대상을 반드시 그 목록 안으로 제한한다고 설명할 수는 없습니다. 이후에 살펴볼 화면의 전환 후보 목록과 서버의 전환 허용 조건은 구분해야 합니다. 최초 사용자를 기준으로 검사하기 때문에, 이미 일반 사용자로 전환한 상태에서도 org가 관리자를 가리키면 다른 하위 사용자로 이동하거나 관리자 본인으로 돌아갈 수 있습니다. 반면 처음부터 일반 사용자로 로그인했다면 org도 일반 사용자이므로 요청이 거절됩니다. 이 권한 검사는 메서드의 @PreAuthorize가 아니라 메서드 본문에서 수행합니다. 3.3. 인증된 토큰을 새로 만들고 세션에 저장합니다 전환 권한 검사를 통과하고 대상이 확정되면, 해당 사용자를 principal로 가진 새 토큰을 만듭니다. 비밀번호를 다시 검증하는 요청이 아니므로 AuthenticationManager나 ID/PW용 AuthenticationProvider는 호출하지 않습니다. 앞에서 수행한 전환 권한 검사가 이 토큰 생성의 근거가 됩니다. 이 코드에서 권한 적용의 기준은 toBe.authorities입니다. 원래 관리자의 권한을 합치지 않고 대상 사용자의 권한으로 새 Authentication을 구성하므로, 일반 업무 API는 전환된 사용자의 권한을 기준으로 동작합니다. 원래 관리자 정보는 전환과 추적을 위한 참조로 보관되며, 새 토큰에 관리자 권한이 자동으로 승계되는 것은 아닙니다. 권한을 구성한 다음에는 바뀐 인증 상태를 현재 요청과 후속 요청에 반영합니다. 새 SecurityContext를 만들어 SecurityContextHolder에 설정하는 작업은 현재 요청의 실행 주체를 바꿉니다. 이어지는 saveContext는 다음 요청에서도 전환 결과를 사용할 수 있도록 HTTP 세션에 저장합니다. 1장에서 설명한 컨텍스트 설정과 저장의 구분이 여기에도 적용됩니다. 공식 문서에서도 SecurityContextRepository를 후속 요청까지 인증 상태를 연결하는 지점으로 설명하며, HttpSessionSecurityContextRepository는 이를 HTTP 세션에 연결합니다. 이렇게 인증 객체를 교체하면 업무 코드에서도 같은 사용자 조회 방식을 유지할 수 있습니다. 현재 사용자를 가져오는 공통 함수를 통해 이 동작을 확인해 보겠습니다. 전환 후 getCurrent()는 새 principal을 반환합니다. 따라서 이 공통 조회 방식을 사용하는 개별 업무 서비스에 전환 대상의 ID를 별도로 전달할 필요가 없습니다. 현재 사용자로부터 그룹이나 권한을 조회하는 기존 코드도 전환된 사용자 정보를 기준으로 동작합니다. 3.4. 후보 목록과 복귀도 같은 원본 계정을 활용합니다 사용자 전환을 화면에서 제공하려면 이동할 수 있는 후보를 조회하고 원래 계정으로 돌아가는 흐름도 필요합니다. 후보 목록은 GET /api/user/change-list로 조회합니다. 여기에서도 원본 계정을 먼저 구한 다음, 현재 실행 중인 사용자만 목록에서 제외합니다. ADMIN에게는 본인과 직접 하위 사용자를, SUPER에게는 본인과 하위 관리자 및 그 사용자들을 제공합니다. getChildren의 SQL은 z_parid로 직접 하위를 조회하므로, SUPER의 목록은 하위 관리자와 그 사용자에 대한 두 번의 계층 조회로 구성됩니다. 임의 깊이의 트리를 재귀적으로 조회하는 구현은 아닙니다. 목록에 포함된 원본 계정으로 복귀할 때도 같은 전환 API를 사용하며, 별도의 복귀 전용 API는 두지 않았습니다. 원본 사용자 ID를 전달하면 getUserById가 DB에서 새 MyUserDetails를 조회합니다. 대상이 원본과 같을 때는 orgLoginUser를 별도로 설정하지 않으며, 이때 equals는 사용자 ID를 기준으로 비교합니다. 따라서 원본 계정으로 돌아오면 다시 orgLoginUser == null인 상태가 됩니다. 3.5. 실행 사용자와 실제 행위자를 함께 남길 수 있게 합니다 전환과 복귀가 가능해지면, 이후 작업을 누가 수행했는지도 구분할 필요가 있습니다. 사용자 B의 권한으로 실행했다는 사실만 남기면 B가 직접 수행했는지, 관리자 A가 B로 전환해 수행했는지 파악하기 어렵습니다. 이를 위해 감사 로그 모델은 현재 사용자와 원본 사용자를 함께 저장할 수 있도록 구성했습니다. 다음은 감사 로그 생성자 인자 중 관련 부분을 발췌한 코드입니다. 감사 로그 대상 메서드의 AOP는 UserService.getCurrent()로 기록할 사용자를 설정합니다. 따라서 전환 상태에서 기록되는 작업에는 현재 사용자와 원본 사용자를 구분해 담을 수 있습니다. 감사 기록과 함께 확인할 부분은 전환 시 다시 수행하는 인증·세션 처리의 범위입니다. 현재 구현은 컨트롤러에서 인증 객체를 직접 만들기 때문에, 최초 로그인에서 수행하는 계정 상태·허용 IP 검증이나 세션 인증 전략을 다시 호출하지 않습니다. SessionRegistry 재등록, 세션 ID 변경, 로그인 성공 이벤트 발행도 전환 메서드에는 없습니다. 따라서 기존 세션의 인증 주체를 교체하는 처리와 새로운 로그인에 적용하는 동시 세션 관리 정책을 같은 동작으로 보아서는 안 됩니다. 마지막으로 어떤 인증 경로에서 사용자 전환 기능에 접근할 수 있는지 확인해 보겠습니다. ZngApiKeyAuthorizationManager는 "/api/user/change/*" to POST를 금지 목록에 넣고, API Key 자체의 접근 범위와 이 목록을 함께 검사합니다. 따라서 API Key로는 사용자 전환을 할 수 없으며, 컨트롤러뿐 아니라 앞단의 필터 체인에서 수행하는 인가까지 확인해야 전체 제한을 파악할 수 있습니다. 이 글의 사례는 기존 세션 로그인 상태에서 제공하는 사용자 전환 흐름을 중심으로 설명했습니다. 이 구현을 통해서는 원본 계정으로 전환 권한을 판단하고, 대상 계정으로 업무 권한을 구성한 뒤, 바뀐 컨텍스트를 세션에 저장합니다. 이 세 역할을 구분함으로써 일반 업무 API의 현재 사용자 조회 방식을 유지하면서 사용자 전환과 원본 계정 복귀를 구현할 수 있었습니다.
2026.11.04
다음 슬라이드 보기