이 글에서 얻는 것

  • Servlet FilterSpring Security Filter Chain의 관계를 이해합니다.
  • DelegatingFilterProxyFilterChainProxy의 역할을 명확히 구분합니다.
  • Authentication(인증)과 Authorization(인가) 처리 흐름을 도식화할 수 있습니다.

0) Spring Security 거대한 숲

Spring Security는 수많은 필터들이 체인(Chain) 처럼 연결되어 요청을 검사하는 거대한 방화벽입니다. 가장 먼저 이해해야 할 것은 “어떻게 Servlet Container(Tomcat)와 Spring Container가 연결되는가?“입니다.

1) DelegatingFilterProxy & FilterChainProxy

서블릿 컨테이너(Tomcat)는 스프링 빈(Bean)을 모릅니다. 단지 서블릿 필터(Filter)만 알 뿐입니다. 스프링 시큐리티는 이 틈을 DelegatingFilterProxy라는 다리(Bridge) 로 연결합니다.

flowchart TB
    Client[Client Request] --> Tomcat[Servlet Container]
    
    subgraph TomcatEnv [Tomcat Filters]
        Filter1[EncodingFilter]
        Filter2[MyLogFilter]
        Delegating[DelegatingFilterProxy<br/>(Standard Servlet Filter)]
    end
    
    Tomcat --> Filter1 --> Filter2 --> Delegating
    
    subgraph SpringContext [Spring ApplicationContext]
        FilterChainProxy[FilterChainProxy<br/>(Spring Bean)]
        
        subgraph SecurityChains [SecurityFilterChain]
            SecFilter1[SecurityContextPersistenceFilter]
            SecFilter2[UsernamePasswordAuthFilter]
            SecFilter3[AuthorizationFilter]
        end
        
        Delegating -->|Delegate| FilterChainProxy
        FilterChainProxy --> SecFilter1 --> SecFilter2 --> SecFilter3
    end

    style Delegating fill:#ffe0b2,stroke:#f57c00
    style FilterChainProxy fill:#e1f5fe,stroke:#0277bd
    style SecurityChains fill:#f3e5f5,stroke:#7b1fa2
  1. DelegatingFilterProxy: 서블릿 필터 표준을 따르며, 실제 처리는 스프링 빈(FilterChainProxy)에게 위임합니다.
  2. FilterChainProxy: 보안 필터 체인의 사령관입니다. 요청 URL에 따라 적절한 SecurityFilterChain을 선택합니다.
  3. SecurityFilterChain: 실제 보안 로직을 수행하는 필터들의 묶음입니다. (예: /api/**용 체인, /login용 체인 등)

2) 핵심 필터 순서와 역할

기본적으로 15개 이상의 필터가 동작하지만, 핵심 필터 5가지를 기억하면 됩니다.

순서필터 이름역할
1SecurityContextPersistenceFilter이전 요청의 인증 정보(Context)를 복구합니다. (JSESSIONID 등)
2LogoutFilter로그아웃 요청을 가로채서 세션을 무효화합니다.
3UsernamePasswordAuthenticationFilter아이디/암호 기반 로그인 요청을 처리합니다.
4ExceptionTranslationFilter인증/인가 실패 예외(401, 403)를 핸들링합니다.
5FilterSecurityInterceptor (AuthorizationFilter)인가(권한) 판정을 수행합니다. (가장 마지막)

3) Authentication (인증) 아키텍처

“로그인” 버튼을 눌렀을 때, 내부에서는 어떤 일이 벌어질까요?

sequenceDiagram
    participant Requester as Client
    participant AuthFilter as UsernamePasswordAuthFilter
    participant AuthManager as AuthenticationManager
    participant Provider as AuthProvider (Dao)
    participant UserDetail as UserDetailsService
    
    Requester->>AuthFilter: POST /login (username, password)
    AuthFilter->>AuthManager: authenticate(Token)
    AuthManager->>Provider: authenticate(Token)
    Provider->>UserDetail: loadUserByUsername(username)
    UserDetail-->>Provider: UserDetails (DB User)
    Provider->>Provider: Password Match Verify
    
    alt Match Success
        Provider-->>AuthManager: Authentication (Success)
        AuthManager-->>AuthFilter: Authentication (Success)
        AuthFilter->>AuthFilter: SecurityContextHolder.setAuth()
        AuthFilter-->>Requester: Session/Token Response
    else Match Fail
        Provider-->>AuthManager: BadCredentialsException
        AuthManager-->>AuthFilter: Throw Exception
        AuthFilter-->>Requester: 401 Unauthorized
    end
  • AuthenticationManager: 인증을 총괄하는 인터페이스 (구현체: ProviderManager).
  • AuthenticationProvider: 실제 인증 로직을 수행 (예: DB 조회, LDAP, Oauth 등).
  • UserDetails: 사용자의 정보를 담는 VO.
  • UserDetailsService: 유저 정보를 DB에서 가져오는 서비스.

4) 실전 커스텀 필터 예제

JWT 토큰 인증을 추가하려면, UsernamePasswordAuthenticationFilter 앞에 커스텀 필터를 넣어야 합니다.

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain chain)
            throws ServletException, IOException {
        
        String authHeader = request.getHeader("Authorization");
        
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String jwt = authHeader.substring(7);
            // ... JWT 검증 로직 ...
            // SecurityContextHolder에 인증 정보 저장
        }
        
        chain.doFilter(request, response);
    }
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

요약

  • Spring Security는 서블릿 필터 체인 위에서 동작하는 스프링 빈들의 집합입니다.
  • DelegatingFilterProxy가 서블릿 컨테이너와 스프링 컨텍스트를 연결합니다.
  • **인증(Authentication)**은 AuthenticationManager -> Provider -> UserDetailsService 흐름을 따릅니다.
  • 커스텀 필터의 순서 배치가 핵심입니다.

다음 단계

  • OAuth 2.0 심화: /learning/deep-dive/deep-dive-oauth2-oidc/
  • JWT vs Session: /learning/deep-dive/deep-dive-jwt-session/