
프로젝트를 개발했을 때 위 코드처럼 필터단에서 인증처리를 하기 위해 Security Chain에 CustomFilter를 Bean으로 생성하여 Spring Security Chain에 등록하였다.
하지만 문제가 발생하였다.
프로젝트에서는 위 코드처럼 Swagger 관련된 api들은 필터를 거치지 않도록 web.ignoring()을 걸어줬는데 커스텀 필터를 빈으로 생성하니깐 web.ignoring()이 걸리지가 않았다.
공식문서를 보면 빈으로 등록된 필터는 자동으로 ServletFilterChain에 등록된다는 것을 알 수 있다.
Filter Chain은 ServletFilterChain(ApplicationFilterChain)과 SecurityFilterChain(DelegatingFilterProxy)로 나뉘는데 커스텀 필터를 빈으로 생성하는 순간 ServletFilterChain에 SpringBoot의 자동 환경 설정에 의해 커스텀 필터가 등록되고 SecurityFilterChain에는 addFilterBefore() 메소드로 인하여 두 필터 모두 등록되게 된다.
그래도 WebSecurity.ignoring()를 했으면 두 필터 모두 ignore해줘야 되는 거 아닌가?
디버깅하면서 알아보자! (혼자서 코드를 분석한거라 잘못된 정보일수도 있으니 언제든지 댓글로 피드백 부탁합니다!.)
우선 Spring Security 아키텍처이다.
클라이언트가 요청을 보내면 FilterChain을 통해 순서대로 필터를 거쳐가는데 DelegatingFIlterProxy라는 Spring의 ApplicationContext 간 연결 시켜주는 DegratingFilterProxy라는 필터 구현을 제공한다. 즉 DelegatingFilterProxy를 통해 구현된 필터들이 스프링 컨테이너가 관리하는 빈들을 사용할 수 있게 된다.
@Bean
public WebSecurityCustomizer webSecurityCustomizer(){
return web -> web
.ignoring()
.antMatchers("/test");
}
위와 같이 "/test" URI를 ignore를 걸어주면
SecurityFilterChain 0번째에는 "/test" URI 패턴을 가진 filters size가 0인 FilterChain이 들어가고
SecurityFilterChain 1번째에는 스프링 시큐리티에서 기본으로 제공하는 필터들이 들어간다.
그래서 특정 URI에 대해서 ignore가 어떻게 되는지 보면
filterChains 안에 있는 filterChain을 모두 순회하기 위해서 iterator를 가져오고 빨간 사각형 안에 있는 특정 URI 패턴에 해당하는 request라면 해당 filterChain의 filters를 리턴하는데 현재 filterChain은 filters가 0개 이므로 빈 List를 리턴하게 된다.
그리고 SecurityFilterChain 1번째에 해당하는 기본 필터들은 모든 요청 URI에 해당하지만 SecurityFilterChain 0번째(web.ignoring()) 를 가장 우선적으로 조회하므로 1번째 SecurityFilterChain은 필터링을 하지 않는다. 즉 ignore가 되는 것이다.
위에서 리턴한 filters를 가지고 빨간 박스와 파란 박스롤 나눠서 설명하자면
빨간 박스안에 코드는 filters안에 filter가 있다면 filters를 가지고 VirtualFilterChain 가상의 필터 체인을 만들어 안에 있는 모든 필터들의 작업을 수행하게 된다.
파란 박스안에 코드는 filters안에 filter가 없다면 chain.doFIlter() 다음 필터로 이동하는데 여기서는 다시 servlet filter(ApplicationFilterChain)로 이동하게 된다.
아니 근데 빈으로 생성한 커스텀 필터는 servlet filter와 spring security filter 둘 다 존재한다면서 servlet filter에서도 커스텀 필터를 ignore해줘야 되는 거 아니야?
빈으로 생성한 커스텀 필터가 두 필터 체인에 등록되는 것을 확인해보자.

정말 두 필터체인 모두 등록되고 있다.
첫 번째 이미지를 보면 SecurityFilterChain 다음에 CustomFilter를 거친다는 것을 확인할 수 있는데 만약에 ignore된 "/test" URI를 요청하면 SecurityFilterChain에서는 ignore이 되어 필터링을 거치지 않지만 바로 다음 ApplicationFilterChain에서 CustomFilter의 작업을 수행하게 된다.
이러한 이유로 web.ignoring()이 적용이 안되는 것처럼 보였던 거였다.
근데 여기서 또 의문점을 갖게 되었다. CustomFilter가 2개가 존재한다면 클라이언트의 매 요청마다 CustomFilter를 2번 수행하는 것인가?
본인은 CustomFilter를 OncePerRequestFilter.class를 상속받았기 때문에 요청당 한번의 필터 작업을 수행할 수 있었다.
OnecePerRequestFIlter의 doFilter() 메소드를 보자면 request 속성 값에서 해당 필터의 이름이 존재한다면 이미 한번 실행한 것이므로 다음 필터로 넘어가고 존재하지 않는다면 request 속성 값에 필터의 이름에 대하여 true로 값을 넣고 필터의 작업을 수행하는 것을 확인할 수 있다.
web.ignore()로 Spring Filter Chain에는 커스텀 필터가 수행하지 않도록 했지만
Servlet Filter에도 수행하지 않도록 하려면
아래와 같이 코드를 작성하면 된다.
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<CustomFilter> registration(CustomFilter filter) {
FilterRegistrationBean<CustomFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
}
이렇게 적용하면 Servlet Filter단에 빈으로 생성되는 커스텀 필터는 자동 등록되지 않는다!
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.web.filter.registration
https://docs.spring.io/spring-security/reference/servlet/architecture.html
와 진짜 한줄기 빛 같은 글입니다