[스프링/보안] 서블릿 필터와 시큐리티 필터
2026. 10. 7.
스프링 프레임워크에서 필터(filter)란 클라이언트로부터 유입되는 요청이 스프링의 핵심 진입점인 DispatcherServlet에 도달하기 전과 클라이언트로 응답이 나가기 직전에 두 시점에 요청과 응답을 가로채어 공통 작업을 처리하는 웹 컴포넌트이다. 요청은 스프링의 컨트롤러, HandlerInterceptor), 디스패처 서블릿(DispatcherServlet) 보다도 가장 앞단에 위치한 필터를 먼저 거친다.
필터는 스프링 프레임워크에 도입된 기술은 아니며 J2EE 서블릿 에 정의된 표준 기술이다. 필터는 jakarta.servlet.Filter 인터페이스를 구현한다. 따라서 스프링 컨텍스트 내부가 아닌 서블릿 컨테이너(예: Tomcat, Jetty 등) 레벨에서 동작한다. 서블릿 컨테이너가 직접 관리하는 필터를 서블릿 필터라고 한다.
필터는 생명주기를 가지며 그 생명주기를 처리하는 대표적인 메서드는 다음과 같다.
- init(FilterConfig filterConfig): 서블릿 컨테이너가 생성될 때 필터 인스턴스를 초기화하기 위해 1회 호출된다.
- doFilter(ServletRequest request, ServletResponse response, FilterChain chain): 클라이언트 요청이 유입될 때마다 실행된다. 전처리 작업 후 chain.doFilter()를 호출히여 다음 필터나 서블릿으로 제어를 넘긴다. 서블릿 작업이 끝난 뒤에는 후처리 작업을 수행한다.
- destroy(): 서블릿 컨테이너가 종료되거나 필터가 제거될 때 자원을 정리하기 위해 1회 호출된다.
서블릿 필터와 리액티브 필터
서블릿 환경(스프링 MVC)에는 Filter와 FilterChain이 존재하며 리액티브 환경(스프링 웹플럭스)에는 비동기 논블로킹 처리를 위한 전용 필터 모델인 WebFilter와 WebFilterChain이 존재한다. 두 필터 모델은 가로채기(interception)와 체이닝(chaining)이라는 아키텍처 구조는 동일하지만 인터페이스 명세와 동작 방식에서 차이가 있다.
| 구분 | 서블릿 | 리액티브 |
|---|---|---|
| 기본 필터 인터페이스 | jakarta.servlet.Filter |
org.springframework.web.server.WebFilter |
| 체인 인터페이스 | jakarta.servlet.FilterChain |
org.springframework.web.server.WebFilterChain |
| 메서드 시그니처 | void doFilter(request, response, chain) |
Mono<Void> filter(exchange, chain) |
| 요청/응답 객체 | HttpServletRequest, HttpServletResponse |
ServerWebExchange |
| 처리 모델 | 동기 블로킹 (스레드 풀 기반) | 비동기 논블로킹 (리액터 이벤트 루프 기반) |
| 다음 단계 위임 | chain.doFilter(request, response) |
return chain.filter(exchange) |
| 보안 체인 인터페이스 | SecurityFilterChain |
SecurityWebFilterChain |
| 시큐리티 진입점 프록시 | DelegatingFilterProxy → FilterChainProxy |
WebFilterChainProxy |
서블릿 환경에서는 서블릿 컨테이너와 스프링 빈 생명주기를 연결하기 위해 DelegatingFilterProxy라는 브릿지 서블릿 필터가 필요하지만 웹플럭스 환경에서는 서블릿 컨테이너 제약이 없으므로 WebFilterChainProxy라는 단일 WebFilter 구현체가 체인 진입점에 직접 등록된다.
서블릿 필터와 시큐리티 필터
서블릿 필터와 시큐리티 필터의 차이는 다음과 같다.
| 비교 항목 | 서블릿 필터 (Servlet Filter) | 시큐리티 필터 (Security Filter) |
|---|---|---|
| 구현 인터페이스 | jakarta.servlet.Filter |
jakarta.servlet.Filter |
| 관리 주체 | 서블릿 컨테이너 (Tomcat, Jetty 등) | 스프링 시큐리티 (FilterChainProxy) |
| 등록 위치 | 서블릿 컨테이너의 메인 필터 체인 | 스프링 시큐리티의 SecurityFilterChain 내부 |
| 스프링 빈 의존성 주입 | 기본적으로 불가 (컨테이너 라이프사이클) | 완전한 스프링 빈 지원 (@Autowired, 생성자 주입) |
| 실행 방식 | 등록된 모든 서블릿 필터가 무조건 순차 실행 | 요청 조건(RequestMatcher)과 일치하는 단일 체인 내부 필터만 실행 |
| 주된 관심사 | 인코딩 변환, 저수준 로깅, 압축, XSS 방어 | 사용자 인증, 접근 제어 인가, CSRF 검증, 세션 보안 |
| 대표적 구현체 | CharacterEncodingFilter, CorsFilter |
UsernamePasswordAuthenticationFilter, CsrfFilter |
서블릿 컨테이너는 서블릿 표준 스펙에 따라 동작하므로 스프링의 IoC 컨테이너나 빈 생명주기를 인식하지 못한다. 반면 보안 처리 로직은 데이터베이스 조회, 암호화 모듈, 비즈니스 서비스 등 다양한 스프링 빈과의 상호작용이 필수적이다. 스프링 시큐리티는 이 제약을 해결하기 위해 프록시 패턴을 사용한다.
위임 구조와 계층 아키텍처
서블릿 컨테이너와 스프링 시큐리티는 DelegatingFilterProxy라는 브릿지 필터를 통해 연결된다. 서블릿 컨테이너 입장에서는 DelegatingFilterProxy라는 보통의 서블릿 필터 하나만 인식할 뿐이며, 실제 보안 처리는 스프링 애플리케이션 컨텍스트 내부의 FilterChainProxy로 위임된다.
요청 처리 단계별 흐름
- 서블릿 컨테이너 필터 단계: 클라이언트 요청이 들어오면 서블릿 컨테이너의 파이프라인에 등록된 일반 서블릿 필터들이 먼저 실행된다. UTF-8 인코딩 설정이나 저수준 HTTP 헤더 파싱이 이 단계에서 처리된다.
- 델리게이팅 프록시(DelegatingFilterProxy) 호출: 톰캣이
DelegatingFilterProxy의doFilter를 호출한다. 이 필터는 자신이 직접 요청을 처리하지 않고 스프링 루트 컨텍스트에서springSecurityFilterChain이라는 이름의 스프링 빈(FilterChainProxy)을 지연 조회(Lazy Lookup)하여 호출을 위임한다. - 시큐리티 체인 선택:
FilterChainProxy는 자신이 관리하는 여러 개의SecurityFilterChain목록을 순회하며, 요청의 URL 및 헤더와 최초로 일치하는 체인 하나를 선택한다. - 시큐리티 필터 순차 실행: 선택된 체인에 속한 시큐리티 필터들(CORS 검사, CSRF 토큰 확인, 자격 증명 인증, 접근 권한 인가 등)이 엄격하게 정해진 순서에 따라 차례대로 실행된다.
- 디스패처 서블릿 진입: 모든 보안 필터를 성공적으로 통과하면 요청은 다시 서블릿 흐름으로 복귀하여
DispatcherServlet과 컨트롤러로 전달된다.
자세한 체인 내부의 세부 필터 실행 순서 및 리액티브(WebFlux) 런타임과의 아키텍처 대칭 구조는 스프링 시큐리티의 필터 체인과 인증/인가 문서에서 확인할 수 있다.
직접 필터를 등록할 때의 주의점
스프링 부트 환경에서 커스텀 필터를 만들 때 @Component 애노테이션을 붙여 등록하면, 스프링 부트의 자동 구성 메커니즘이 이 필터를 서블릿 컨테이너의 최상위 필터 체인에 자동으로 등록해 버린다.
만약 해당 필터가 시큐리티 검증(인증 주체 식별 등) 이후에 실행되어야 하는 필터이거나 SecurityFilterChain 내부에서만 특정 순서로 실행되어야 하는 필터라면, 서블릿 필터로 중복 등록되지 않도록 주의해야 한다.
- 시큐리티 체인 내부에 넣을 때:
http.addFilterBefore(...)또는http.addFilterAfter(...)를 사용해 체인에 직접 추가하고, 클래스에는@Component를 붙이지 않는다. - 서블릿 필터의 자동 등록을 방지할 때: 만약 스프링 빈으로 등록해야 한다면
FilterRegistrationBean을 정의하고registrationBean.setEnabled(false)를 설정하여 서블릿 컨테이너의 중복 등록을 차단한다.