JWT(1)
TIL: Spring Security 동작 원리와 JWT 기반 인증 구현
1. Spring Security 개요 및 아키텍처
Spring Security는 애플리케이션의 인증(Authentication)과 인가(Authorization)를 담당하는 프레임워크입니다. 가장 중요한 특징은 Servlet Filter 기반으로 동작한다는 점입니다.
1.1 동작 구조: FilterChain
Spring Security는 일반적인 DispatcherServlet 로직이 실행되기 전, 서블릿 필터 단계에서 요청을 가로채어 보안 로직을 수행합니다.
- DelegatingFilterProxy: 서블릿 컨테이너(Tomcat 등)와 스프링 컨테이너(IoC) 사이의 브릿지 역할을 합니다. 서블릿 필터에서 스프링 빈으로 등록된 필터들을 실행할 수 있게 해줍니다.
- FilterChainProxy: 실제 보안 필터들을 관리하며, 요청 URL에 따라 적절한 SecurityFilterChain을 선택하여 실행합니다.
- SecurityFilterChain: 여러 개의 보안 필터(
CsrfFilter,LogoutFilter,UsernamePasswordAuthenticationFilter등)가 체인 형태로 연결되어 순차적으로 실행됩니다.
2. 기존 세션 기반의 인증(Authentication) 흐름
기본적인 Spring Security(Form Login)의 인증 절차는 다음과 같습니다.
2.1 인증 절차 (Authentication Flow)
- 요청 수신: 사용자가 로그인 요청(POST /login)을 보냅니다.
- 토큰 생성:
UsernamePasswordAuthenticationFilter가 요청을 가로채어 ID/PW를 담은UsernamePasswordAuthenticationToken(인증 전 객체)을 생성합니다. - 인증 위임:
AuthenticationManager(구현체:ProviderManager)에게 인증 처리를 위임합니다. - 검증 수행:
AuthenticationProvider(구현체:DaoAuthenticationProvider)가 실제 검증을 수행합니다.- UserDetailsService: DB에서 사용자 정보를 조회하여
UserDetails객체를 반환합니다. - PasswordEncoder: 입력된 비밀번호와 DB의 비밀번호를 비교합니다.
- UserDetailsService: DB에서 사용자 정보를 조회하여
- 인증 성공: 인증이 성공하면
Authentication(인증 후 객체)을 생성하여SecurityContextHolder에 저장합니다 (이 정보는 세션에 저장됨).
2.2 인가 절차 (Authorization Flow)
- 인증된 사용자가 특정 리소스에 접근할 때
AuthorizationFilter가 동작합니다. SecurityContext에 있는 권한 정보(GrantedAuthority)를 확인하여 접근 허용 여부를 결정합니다.
3. JWT(JSON Web Token) 도입 배경
세션 기반 인증은 서버가 상태(State)를 저장해야 하므로 다음과 같은 한계가 있습니다.
- 확장성 문제: 서버를 Scale-out 할 때 세션 동기화 문제(Sticky Session, Session Clustering 필요)가 발생합니다.
- 모바일/SPA 환경: 쿠키/세션 관리가 번거롭고, CORS(Cross-Origin Resource Sharing) 정책으로 인한 복잡성이 증가합니다.
JWT는 Stateless(무상태) 방식이므로 서버 확장에 유리하며, 토큰 자체에 서명(Signature)이 포함되어 있어 검증이 용이합니다.
4. JWT 기반 Spring Security 구현
JWT를 적용하면 기존의 세션 저장 방식을 버리고, 토큰 검증 방식으로 흐름을 변경해야 합니다. 이를 위해 표준 필터를 커스텀 필터로 교체하거나 추가해야 합니다.
4.1 설정 변경 (SecurityConfig)
- 세션 미사용:
SessionCreationPolicy.STATELESS로 설정하여 세션을 생성하지 않도록 합니다. - CSRF 비활성화: 세션을 사용하지 않으므로 CSRF 보호가 불필요하여 비활성화(
csrf.disable()) 합니다. - FormLogin 비활성화: 기본 폼 로그인 대신 JSON 기반 로그인을 처리할 것이므로 설정하지 않습니다.
4.2 커스텀 필터 구현 및 배치
JWT 처리를 위해 크게 두 가지 필터를 구현하여 SecurityFilterChain에 끼워 넣습니다.
A. JwtAuthenticationFilter (로그인 및 토큰 발급)
- 역할:
UsernamePasswordAuthenticationFilter를 확장하여 구현하며, 로그인 요청을 처리합니다. - 동작:
attemptAuthentication: Request에서 JSON 데이터를 읽어 로그인 시도.successfulAuthentication: 인증 성공 시 JWT(Access Token, Refresh Token)를 생성하여 응답 헤더나 바디로 클라이언트에게 전달합니다.
- 위치: 기존
UsernamePasswordAuthenticationFilter자리에 배치(addFilterAt) 합니다.
B. JwtVerificationFilter (토큰 검증)
- 역할:
OncePerRequestFilter를 확장하여 모든 요청마다 토큰의 유효성을 검사합니다. - 동작:
- Request Header(
Authorization)에서 “Bearer “로 시작하는 토큰을 추출합니다. - JWT 서명을 검증하고 Claims(사용자 정보)를 추출합니다.
- 유효한 토큰이라면
SecurityContextHolder에Authentication객체를 강제로 저장하여, 이후 필터들이 인증된 사용자로 인식하게 만듭니다.
- Request Header(
- 위치:
JwtAuthenticationFilter앞에 배치(addFilterBefore)하여, 로그인 외의 요청이 들어올 때 먼저 토큰을 검사하도록 합니다.
4.3 예외 처리 (SecurityExceptionHandlingFilter)
- JWT 검증 과정에서 발생하는 예외(
ExpiredJwtException,SignatureException등)를 처리하기 위해, 필터 체인의 가장 앞단(VerificationFilter 앞)에 예외 처리 필터를 배치하는 것이 좋습니다.
요약: 필터 순서
SecurityExceptionHandlingFilter(예외 처리)JwtVerificationFilter(토큰 유효성 검사)JwtAuthenticationFilter(로그인 및 토큰 발급)
5. 결론 (Session vs JWT 차이점)
| 항목 | Session 방식 | JWT 방식 |
|---|---|---|
| 인증 정보 저장 | 서버의 세션 스토어 (메모리/DB) | 클라이언트 (토큰 보유) |
| 확장성 | 서버 확장 시 세션 동기화 필요 | Stateless하므로 확장 용이 |
| 인증 확인 | Session ID로 서버 조회 | 토큰의 서명(Signature) 검증 |
| 로그아웃 | 서버 세션 삭제로 즉시 가능 | 토큰 만료 전까지 제어 어려움 (Blacklist 등 필요) |
Next Step:
작성하신 APISecurityConfig 설정 코드에서 addFilterBefore 등의 순서가 올바르게 적용되었는지 다시 한번 확인해 보시겠습니까?