최근 디버깅을 하던중에 Security에서 Filter가 같은 요청인데도 중복으로 실행되는 이슈를 발견했습니다.
처음엔 코드가 꼬인 줄 알았는데, 알고 보니 필터 등록 방식의 차이 때문이었습니다.
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 {
// ...
}
이렇게 두 군데 모두 등록되어 있었고, 그 결과… 필터가 두 번 실행되는 현상이 발생했습니다.
이유는 Spring Security와 Servlet Container의 필터 등록 방식이 다르기 때문입니다.
결국 하나의 요청에 대해 두 필터 체인 모두 JwtAuthenticationFilter를 호출하게 되는 것!
디버깅 화면을 보면 아래처럼 동작 순서를 확인할 수 있습니다.
🔍 ObservationFilterChain
Spring Security가 관찰(observation) 기능을 활성화하면 등장
내부적으로는 FilterChainProxy → JwtAuthenticationFilter 순으로 동작

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

같은 필터가 두 체인에 모두 포함되어 있기 때문에 결과적으로 중복 실행됐습니다.
<요청흐름>
1. 클라이언트 요청
2. Servlet 필터 체인 (ApplicationFilterChain) ← @Component 등록된 필터들 여기 포함
3. Spring Security 필터 체인 (FilterChainProxy)
4. DispatcherServlet
5. 컨트롤러 & 서비스 로직
이 순서를 보면, Servlet 필터 체인은 Spring Security보다 바깥쪽에 있다는 걸 알 수 있습니다.
결론은 @Component 제거해서 아주 간단하게 해결했습니다
JwtAuthenticationFilter는 수동으로 등록했기 때문에 스프링 컨테이너가 자동으로 등록하지 않도록 해야하는게 맞는것 같습니다.
https://docs.spring.io/spring-security/reference/servlet/architecture.html

[Architecture :: Spring Security 발췌]