[Spring Security] Filter가 두 번 호출되는 이슈를 해결해보자

쩡log·2025년 4월 29일

최근 디버깅을 하던중에 Security에서 Filter가 같은 요청인데도 중복으로 실행되는 이슈를 발견했습니다.

처음엔 코드가 꼬인 줄 알았는데, 알고 보니 필터 등록 방식의 차이 때문이었습니다.

1. 문제 상황 요약

Spring Security 설정에서 .addFilterBefore로 선언해 주면서 아래처럼 커스텀 필터를 수동으로 등록해놓았습니다.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, PasswordAuthenticationFilter passwordAuthenticationFilter) throws Exception {
    http
        .addFilterAt(passwordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(new JwtAuthenticationFilter(tokenProvider, tokenValidator), UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

그런데 해당 필터 클래스에 @Component를 추가한 상태였습니다.

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    // ...
}

이렇게 두 군데 모두 등록되어 있었고, 그 결과… 필터가 두 번 실행되는 현상이 발생했습니다.


2. 왜 두 번 호출될까?

이유는 Spring SecurityServlet Container의 필터 등록 방식이 다르기 때문입니다.

Security Filter Chain

  • 우리가 .addFilterBefore()로 등록한 필터는 Spring Security 체인에서 동작합니다.
  • 이 체인은 FilterChainProxy를 통해 호출되며, 보안 관련 필터들이 여기에 포함됩니다.

Servlet Filter Chain

  • 반면, @Component나 @WebFilter로 등록하면 Servlet Container에서 관리하는 필터로 등록됩니다.
  • 이 필터는 Spring Security와는 별개로 동작하며, 애플리케이션 전체 요청을 가로챕니다.

결국 하나의 요청에 대해 두 필터 체인 모두 JwtAuthenticationFilter를 호출하게 되는 것!


3. 디버깅에서 본 실제 체인

디버깅 화면을 보면 아래처럼 동작 순서를 확인할 수 있습니다.

🔍 ObservationFilterChain
Spring Security가 관찰(observation) 기능을 활성화하면 등장
내부적으로는 FilterChainProxy → JwtAuthenticationFilter 순으로 동작

🔍 ApplicationFilterChain
Servlet 기본 필터 체인
@Component로 등록된 필터가 여기에 포함되어 동작

같은 필터가 두 체인에 모두 포함되어 있기 때문에 결과적으로 중복 실행됐습니다.


<요청흐름>
1. 클라이언트 요청
2. Servlet 필터 체인 (ApplicationFilterChain) ← @Component 등록된 필터들 여기 포함
3. Spring Security 필터 체인 (FilterChainProxy)
4. DispatcherServlet
5. 컨트롤러 & 서비스 로직

이 순서를 보면, Servlet 필터 체인은 Spring Security보다 바깥쪽에 있다는 걸 알 수 있습니다.


4. 해결 방법

결론은 @Component 제거해서 아주 간단하게 해결했습니다

JwtAuthenticationFilter는 수동으로 등록했기 때문에 스프링 컨테이너가 자동으로 등록하지 않도록 해야하는게 맞는것 같습니다.


[참고글]
https://velog.io/@im_h_jo/Spring-Security-%EC%93%B0%EB%8A%94%EB%8D%B0-%ED%95%84%ED%84%B0%EA%B0%80-%EB%91%90%EB%B2%88-%ED%98%B8%EC%B6%9C%EB%90%98%EB%8A%94-%EC%9D%B4%EC%8A%88

https://docs.spring.io/spring-security/reference/servlet/architecture.html

[Architecture :: Spring Security 발췌]

0개의 댓글