Bootcamp/Implementation

Spring Security 전체 과정 흐름 추적

개발자 오리 2026. 7. 16. 21:50

💡  오늘 학습 키워드

  • Spring Security 전체 과정 흐름 추적

 

 

🎯  학습 내용 정리

Spring Security를 처음 학습하면 SecurityFilterChain, JwtAuthenticationFilter, Authentication, SecurityContextHolder, AuthorizationFilter 등 다양한 구성 요소를 개별적으로 배우게 된다. 하지만 실제 요청이 들어왔을 때 이들이 어떤 순서로 동작하는지 한 번에 이해하기는 쉽지 않다.

 

예를 들어 다음과 같은 의문이 생길 수 있다.

  • JWT는 언제 검증될까?
  • Authentication은 언제 생성될까?
  • SecurityContextHolder에는 누가 저장할까?
  • @PreAuthorize는 언제 실행될까?
  • Controller에서는 어떻게 로그인한 사용자를 조회할 수 있을까?

따라서 이번에는 하나의 HTTP 요청이 들어온 순간부터 Controller가 실행될 때까지 Spring Security 내부에서 발생하는 모든 과정을 시간 순서대로 추적해보자.

 

 

JWT 인증 기반 요청은 다음과 같은 순서로 처리된다.

Client
  ↓
HTTP Request
  ↓
DelegatingFilterProxy
  ↓
FilterChainProxy
  ↓
SecurityFilterChain
  ↓
JwtAuthenticationFilter
  ↓
AuthorizationFilter
  ↓
DispatcherServlet
  ↓
HandlerMapping
  ↓
Controller
  ↓
Service
  ↓
Response

Spring Security는 Controller보다 먼저 모든 인증과 권한 검사를 수행한다.

 

 

1단계. HTTP 요청이 서버에 도착

예를 들어 사용자가 자신의 정보를 조회하는 API를 호출한다고 가정한다.

GET /users/me

Authorization: Bearer eyJhbGc...

아직 Controller는 실행되지 않았다.

Tomcat은 먼저 등록된 Filter들을 실행하기 시작한다.

 

 

2단계. DelegatingFilterProxy가 요청을 전달

Spring Security는 Servlet Filter를 직접 등록하지 않는다.

Tomcat에는 다음 Filter 하나만 등록된다.

DelegatingFilterProxy

 

이 Filter가 Spring Bean인 FilterChainProxy를 찾아 요청을 위임한다.

Tomcat
  ↓
DelegatingFilterProxy
  ↓
FilterChainProxy

 

 

 

3단계. FilterChainProxy가 SecurityFilterChain을 선택

애플리케이션에는 여러 개의 SecurityFilterChain이 존재할 수 있다.

예를 들어

@Bean
SecurityFilterChain apiSecurity(HttpSecurity http) {
    ...
}

@Bean
SecurityFilterChain adminSecurity(HttpSecurity http) {
    ...
}

 

FilterChainProxy는 현재 요청 URL과 가장 일치하는 SecurityFilterChain을 선택한다.

Request
  ↓
FilterChainProxy
  ↓
Matching SecurityFilterChain

 

 

 

4단계. JwtAuthenticationFilter가 JWT를 검증

이제 Security Filter들이 순서대로 실행된다.

먼저 JWT Filter가 Authorization Header를 확인한다.

String bearerToken =
        request.getHeader("Authorization");

 

Bearer Token이라면 JWT를 추출한다.

String token =
        bearerToken.substring(7);

 

JWT를 검증한다.

jwtProvider.validateToken(token);

 

 

 

5단계. Authentication을 생성

JWT가 정상이라면 Claims에서 사용자 정보를 읽는다.

Authentication authentication =
        jwtProvider.getAuthentication(token);

Authentication 안에는

  • Principal
  • Authorities
  • authenticated=true

가 저장된다.

 

 

6단계. SecurityContextHolder에 저장

Authentication만 생성해서는 인증이 완료되지 않는다.

반드시 현재 요청의 SecurityContext에 저장해야 한다.

SecurityContextHolder
        .getContext()
        .setAuthentication(authentication);

 

이후부터 Spring Security는 현재 사용자를 인증된 사용자로 인식한다.

 

 

7단계. AuthorizationFilter가 권한을 검사

인증이 끝났다면 권한을 확인한다.

예를 들어

@PreAuthorize("hasRole('ADMIN')")

 

AuthorizationFilter는 Authentication의 권한을 확인한다.

ROLE_USER
  ↓
hasRole("ADMIN")
  ↓
false
  ↓
403

권한이 충분하면 다음 단계로 이동한다.

 

 

8단계. DispatcherServlet이 Controller를 찾음

Security Filter Chain을 모두 통과하면 DispatcherServlet이 실행된다.

DispatcherServlet
  ↓
HandlerMapping
  ↓
Controller

이제 비즈니스 로직이 실행될 준비가 완료된다.

 

 

9단계. @AuthenticationPrincipal이 동작

Controller에서는 다음과 같이 현재 로그인한 사용자를 받을 수 있다.

@GetMapping("/me")
public UserResponse me(
        @AuthenticationPrincipal
        CustomUserDetails user
) {

    return userService.findById(
            user.getUserId()
    );
}

Spring Security는 SecurityContextHolder에서 Authentication을 조회한 뒤 Principal을 전달한다.

 

 

10단계. Service 실행

Controller는 일반적인 비즈니스 로직을 수행한다.

@Service
public class UserService {

    public UserResponse findById(Long id) {

        ...

    }
}

이 시점에서는 인증이 이미 완료되었기 때문에 Service는 인증 여부를 다시 검사할 필요가 없다.

 

 

11단계. 응답을 반환

Controller가 응답을 반환하면

Controller
  ↓
DispatcherServlet
  ↓
Filter Chain
  ↓
Client

순서로 응답이 전달된다.

요청이 종료되면 SecurityContext도 함께 정리된다.

 

 

요청이 끝나면 SecurityContext는 어떻게 될까?

Spring Security는 요청이 종료되면 SecurityContext를 제거한다.

SecurityContextHolder.clearContext();

다음 요청에서 이전 사용자의 Authentication이 남아 있으면 심각한 보안 문제가 발생할 수 있기 때문이다.

 

 

전체 요청 흐름을 한 번 더 정리해보면 다음과 같다.

Client
  ↓
DelegatingFilterProxy
  ↓
FilterChainProxy
  ↓
SecurityFilterChain
  ↓
JwtAuthenticationFilter
  ↓
Authorization Header
  ↓
JWT 검증
  ↓
Authentication 생성
  ↓
SecurityContextHolder 저장
  ↓
AuthorizationFilter
  ↓
DispatcherServlet
  ↓
HandlerMapping
  ↓
Controller
  ↓
Service
  ↓
Response
  ↓
SecurityContext 제거

 

 

 

실무에서 자주 하는 실수는 무엇이 있을까?

 

1. Controller에서 JWT를 직접 파싱하는 경우

다음과 같은 구현은 권장되지 않는다.

@GetMapping("/me")
public UserResponse me(
        @RequestHeader("Authorization") String token
) {

    Long userId = jwtProvider.getUserId(token);

    ...
}

JWT 검증은 JwtAuthenticationFilter에서 이미 완료되므로, Controller에서는 @AuthenticationPrincipal 또는 Authentication을 사용하는 것이 적절하다.

 

2. Authentication을 생성하고 SecurityContextHolder에 저장하지 않는 경우

다음 코드만으로는 인증이 완료되지 않는다.

Authentication authentication =
        jwtProvider.getAuthentication(token);

 

반드시 SecurityContextHolder에 저장해야 한다.

SecurityContextHolder.getContext()
        .setAuthentication(authentication);

 

 

3. JwtAuthenticationFilter에서 filterChain.doFilter()를 호출하지 않는 경우

다음 Filter와 DispatcherServlet, Controller가 실행되지 않는다.

filterChain.doFilter(request, response);

 

4. 요청 간에 Authentication이 유지된다고 생각하는 경우

SecurityContextHolder는 요청이 끝나면 정리된다.

SecurityContextHolder.clearContext();

따라서 다음 요청에서는 JWT를 다시 검증하여 새로운 Authentication을 생성해야 한다.

 

 

Spring Security는 HTTP 요청이 들어오면 DelegatingFilterProxy를 시작으로 FilterChainProxy와 SecurityFilterChain을 거쳐 인증과 권한 검사를 수행한다. JwtAuthenticationFilter는 JWT를 검증하여 Authentication을 생성하고 SecurityContextHolder에 저장하며, AuthorizationFilter는 이를 이용해 권한을 확인한다.

 

이후 요청은 DispatcherServlet을 거쳐 Controller와 Service로 전달되고, @AuthenticationPrincipal은 SecurityContextHolder에 저장된 인증 정보를 이용해 현재 로그인한 사용자를 주입한다. 요청이 종료되면 SecurityContext는 정리되며 다음 요청에서 다시 동일한 인증 과정을 수행한다.

 

 

📚  한줄 정리

Spring Security의 모든 인증과 권한 검사는 Controller 이전의 Security Filter Chain에서 완료되며, Authentication을 SecurityContextHolder에 저장하는 순간부터 애플리케이션은 해당 요청을 인증된 사용자로 처리한다.