Bldev's Blog

[스프링] 필터

2025. 5. 12.

스프링 프레임워크에서 필터(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회 호출된다.

스프링에서는 웹 애플리케이션에서 클라이언트의 HTTP 요청이 최종 목적지인 컨트롤러(핸들러)에 도달하기 전에 요청을 가로채고, 서버의 응답이 클라이언트에게 반환되기 전에 응답을 가로채 공통 처리를 수행한다. 필터(filter)와 필터 체인(filter chain)이 바로 그 역할을 수행한다.

  • 필터 (filter): HTTP 요청이나 응답을 가로채 전처리(파라미터 가공, 유효성 검사, 로깅)나 후처리(응답 압축, 헤더 추가)를 수행하는 단일 관심사의 개별 작업 객체이다.
  • 필터 체인 (filter chain): 여러 개의 필터 인스턴스를 순서대로 연결하여 연속적으로 실행되도록 제어하는 파이프라인 객체이다.

서블릿(DispatcherServlet)이나 리액티브 핸들러(DispatcherHandler)는 한 요청당 단 하나만 실행될 수 있지만 필터는 핸들러 앞단의 파이프라인으로서 목적에 따라 여러 개가 등록되어 체인을 형성하여 순차 실행될 수 있다.

동작 원리와 필터 체인의 제어

필터는 필터 체인 인터페이스를 주입받아 동작하며 다음과 같은 방식으로 요청의 흐름을 제어한다.

  1. 하류(downstream) 위임: 검사를 통과하면 체인 객체의 위임 메서드를 호출하여 다음 필터나 최종 핸들러로 제어권을 넘긴다.
  2. 요청 차단 및 조기 반환(short-circuit): 요청이 유효하지 않으면 다음 필터를 호출하지 않고 그 자리에서 직접 에러 응답을 작성해 클라이언트에게 곧바로 반환한다.
  3. 순서(order)의 중요성: 필터는 자신보다 뒤에 실행되는 하류 필터와 핸들러에만 영향을 미치므로 체인에 등록되는 순서가 전체 요청 처리 결과에 결정적인 영향을 미친다.

서블릿과 리액티브의 필터

스프링 프레임워크는 서블릿 기반의 스프링 MVC와 이벤트 루프 기반의 스프링 웹플럭스 두 가지 웹 스택을 지원하며, 각 런타임에 맞춰 서로 다른 필터 인터페이스를 사용한다.

비교 항목 스프링 MVC (서블릿 스택) 스프링 웹플럭스 (리액티브 스택)
필터 인터페이스 jakarta.servlet.Filter org.springframework.web.server.WebFilter
체인 인터페이스 jakarta.servlet.FilterChain org.springframework.web.server.WebFilterChain
인터페이스 정의 주체 자바 표준 스펙 (Jakarta EE) 스프링 프레임워크 5.0+
메서드 시그니처 void doFilter(request, response, chain) Mono<Void> filter(exchange, chain)
요청/응답 객체 HttpServletRequest, HttpServletResponse ServerWebExchange
I/O 처리 모델 동기 블로킹 (Thread-per-Request) 비동기 논블로킹 (Event Loop)
다음 단계 호출 chain.doFilter(request, response) return chain.filter(exchange)
스프링 기본 베이스 클래스 OncePerRequestFilter, GenericFilterBean WebFilter 직접 구현

서블릿 필터는 서블릿 컨테이너의 표준 스펙이다. 하나의 요청마다 전용 스레드가 할당되어 실행되며 메서드의 리턴 타입이 void이므로 스레드가 블로킹 방식으로 체인을 순차 실행한다. 스프링 MVC에서는 한 요청 안에서 포워딩이나 비동기 디스패치 시 필터가 중복 실행되는 것을 방지하기 위해 OncePerRequestFilter를 상속받아 구현하는 것이 일반적이다.

서블릿 표준의 블로킹 방식으로는 리액티브 논블로킹 처리를 지원할 수 없기 때문에 스프링 5.0에서는 리액티브 스택을 위해 WebFilter라는 인터페이스를 직접 정의하였다. 소수의 이벤트 루프 스레드가 논블로킹으로 수많은 요청을 처리해야 하므로 반환 타입이 Mono<Void>인 비동기 파이프라인 형태이다.

스프링 부트에서의 필터 등록 방식

스프링 부트 환경에서 커스텀 필터를 등록하고 순서를 지정하는 방식은 세 가지가 있다.

  1. @Component 등록: 필터 클래스를 스프링 빈으로 등록하면 스프링 부트가 필터 체인에 자동으로 등록한다.
  2. 순서 지정 (@Order): @Order 애노테이션이나 Ordered 인터페이스를 통해 체인 내 실행 우선순위를 부여할 수 있다. 숫자가 낮을수록 먼저 실행된다.
  3. FilterRegistrationBean (서블릿 전용): URL 패턴 매핑(/api/*), 특정 서블릿에만 적용, 자동 등록 활성화/비활성화(setEnabled) 등 정교한 제어가 필요할 때 사용한다.

스프링 액추에이터와 필터

스프링 부트 액추에이터(Actuator)의 핵심 HTTP 모니터링 기능으로는 요청/응답 교환 기록, 서버 처리 성능 메트릭 수집 등이 있다. 이러한 기능은 인터셉터가 아닌 필터 계층에서 동작한다. 인터셉터(HandlerInterceptor)는 DispatcherServlet 내부에서 스프링 MVC 핸들러 매핑이 성공한 요청만 가로챌 수 있기 때문에 액추에이터의 트레이싱과 메트릭 수집은 필터 단계에서 차단된 요청까지 포함한 애플리케이션에 유입된 전체 HTTP 요청 경로를 포함시켜야 하므로 최외곽의 필터 계층에서 동작한다. 이를 통해 DispatcherServlet 진입 전후 및 정적 리소스 요청, 필터 단에서 조기에 차단된 요청까지 포함한 전체 HTTP 라이프사이클을 관측할 수 있다.

HTTP 요청/응답 교환 트레이싱

HTTP 요청의 메타데이터(URI, 헤더, 소요 시간, 응답 상태 코드)를 수집하는 기능은 스프링 부트 버전에 따라 필터 및 엔드포인트 구성이 다르다.

  • 스프링 부트 2.x: HttpTraceFilter를 통해 /actuator/httptrace 엔드포인트에 기록
  • 스프링 부트 3.x+: HttpExchangesFilter를 통해 /actuator/httpexchanges 엔드포인트에 기록

서블릿 환경의 HttpExchangesFilter는 OncePerRequestFilter를 상속하며, 수집된 정보는 HttpExchangeRepository 빈(예: InMemoryHttpExchangeRepository)에 저장된다.

액추에이터의 트레이싱 필터는 기본적으로 위 엔드포인트를 통해 HTTP 요청과 응답에 대한 트레이스(trace) 정보를 제공하지만 요청과 응답 바디 페이로드에 대한 정보는 제공되지 않으며 메타데이터만 수집한다. 보안 누설 위험과 과도한 메모리 점유 등의 문제를 방지하기 위해 기본 필터는 요청 및 응답 본문을 기록하지 않는다. 서블릿의 경우 doFilterInternal() 메서드를 재정의하여 request.getInputStream()을 직접 읽는 방식도 가능하지만 이 방식은 서블릿 스트림 고갈 문제를 일으키므로 권장되지 않으며 스프링 웹이 제공하는 캐싱 래퍼인 ContentCachingRequestWrapper 및 ContentCachingResponseWrapper로 원본 요청과 응답 객체를 래핑하여 내부 값을 복사하는 커스텀 필터를 구성하는 것이 좋다.

HTTP 서버 성능 메트릭 측정 (WebMvcMetricsFilter)

액추에이터가 프로메테우스(Prometheus)나 메트릭 엔드포인트(/actuator/metrics/http.server.requests)로 노출하는 HTTP 지연 시간 및 처리량 지표도 필터를 통해 측정된다.

  • 서블릿 (WebMvcMetricsFilter): OncePerRequestFilter를 상속하여 요청마다 단 1회 실행을 보장한다. 요청 진입 시 마이크로미터(Micrometer) Timer.Sample을 생성해 시간을 측정하기 시작하며, 필터 체인 하류와 핸들러 처리가 끝난 시점에 최종 HTTP 상태 코드와 요청 URI 매핑 패턴을 태그로 결합하여 MeterRegistry에 등록한다.
  • 리액티브 (MetricsWebFilter): 스프링 웹플럭스 환경에서 WebFilter를 구현하여 논블로킹 방식으로 동일한 HTTP 메트릭을 수집한다.

참고