-
Spring Boot 에서 @Component로 추가한 filter가 WebSecurity의 ignoring에 의해 무시되지 않는 이유spring 2023. 1. 31. 16:16반응형
Spring Boot 는 WAS 를 내장하고 있어서 WAS 에서 사용하는 Filter 를 Bean 으로 등록하여 쉽게 사용 가능하다. 새 필터를 등록하고 싶다면 Filter 인터페이스를 상속하고 @Component 어노테이션만 붙이면 된다. Spring Boot 가 알아서 필터 체인에 추가시켜 준다.
그런데 Spring Security 가 필터를 처리하는 방식을 간과하면 실수할 수 있는 부분이 있다. 예를 들어 위와 같은 방법으로 필터를 추가하고 나서 WebSecurityConfigurerAdapter 에서 WebSecurity를 통해 특정 경로에 대해 ignoring 설정을 한 경우가 있다. 이 때, ignoring 설정한 경로로 접근하면 등록한 필터를 지나지 않을 것이라 생각하기 쉽지만 그렇지 않다. ignoring된 경로로 접근하더라도 필터를 지나는 것을 볼 수 있다.
이 글에서는 이러한 현상이 발생하는 경우와 그 이유를 확인해 보았다.
(FilterRegistrationBean 을 이용하여 추가하거나 @WebFilter 를 이용하여 추가한 필터에도 비슷한 현상이 있다.)
Spring 버전
Spring Security 의 경우 Deprecated 된 부분이 있기 때문에 나중에 변경될 수 있다. 이번에 사용한 버전은 아래와 같다.
- Spring Boot: 2.7.8
- Spring Security: 5.7.6
1. Spring Security 구조
참고: https://docs.spring.io/spring-security/reference/servlet/architecture.html
Spring Security 의 구조를 먼저 이해할 필요가 있다. Spring Boot 에서 Spring Security 는 두개의 필터 체인을 생성한다. FilterChain(ApplicationFilterChain)과 SecurityFilterChain 이다.
왼쪽에 FilterChain 은 기존의 WAS 의 필터체인(ApplicationFilterChain)이다. 오른쪽의 SecurityFilterChain 은 Spring Security 에서 사용하는 필터체인이고 실제 인증 절차가 진행된다.
거칠게 요약을 하면, SecurityFilterChain 이라는 기존 필터체인과 분리된 필터체인이 존재하는데, HTTP 요청을 가져와야 하기 때문에 특별한 필터(DelegatinFilterProxy)를 기존 WAS의 FilterChain 에 추가하여 SecurityFilterChain 으로 연결시켜 주는 역할을 하도록 한 것이다.
필터가 요청을 받는 순서는 화살표와 같다. 요청은 ApplicationFilterChain의 순서대로 전달되다가 DelegatinFilterProxy 에서 SecurityFilterChain 로 전달되어 그 내부 필터들을 순서대로 지난다. 그리고 다시 ApplicationFilterChain의 나머지 필터들로 전달된다.
SecurityFilterChain 를 통해 WAS 의 필터체인에서 분리되어 좀 더 Spring 스러운 환경에서 복잡한 필터링 조건을 추가하거나 세세한 작업을 할 수 있게 되어있다.
한편, SecurityFilterChain 은 여러 개 존재할 수 있다.
또한, 다른 경로에 따라 다른 SecurityFilterChain 을 지나도록 할 수 있다. 예를 들어, 위의 그림처럼 /api/** 경로에 대한 SecurityFilterChain과 나머지 경로(/**) 에 대한 SecurityFilterChain을 다르게 설정했다면, 다른 경로에 대한 요청이 서로 다른 필터들을 통과하게 된다.
2. @Component 로 Filter 추가하기
Spring Boot 에서 Filter를 추가할 때, 두 필터체인 중 어느 필터체인에 추가되는지 알면 도움이 된다.
@Component public class MyCustomFilter implement Filter { ... }
위와 같이 @Component 어노테이션과 Filter 인터페이스를 이용하면 필터체인에 추가되는데, 이 경우 WAS의 ApplicationFilterChain에 추가된다.
- 참고1) 아래와 같이 FilterRegistrationBean 을 통해 추가하는 경우도 WAS의 ApplicationFilterChain에 추가된다.
@Bean public FilterRegistrationBean<MyCustomFilter> filterRegistrationBean() { FilterRegistrationBean<MyCustomFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new MyCustomFilter()); ... return registrationBean; }
- 참고2) @ServletComponentScan 어노테이션과 @WebFilter 어노테이션을 통해 추가하는 경우도 WAS의 ApplicationFilterChain에 추가된다.
3. HttpSecurity, WebSecurity 설정과 SecurityFilterChain
Spring security 에 대한 설정을 하는 경우 보통 WebSecurityConfigurerAdapter 를 상속받아 HttpSecurity, WebSecurity 를 방법을 사용한다.
(Spring Security 5.7.6 기준으로 WebSecurityConfigurerAdapter의 경우 deprecated 처리되었다. 이제 SecurityFilterChain와 WebSecurityCustomizer를 이용하여 설정을 하면 되는데, 필터 구조에 대한 부분은 동일하다.)
예를 들어 아래와 같이 WebSecurity 를 이용하여 "/api/**" 경로에 대한 ignoring 설정을 할 수 있다.
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.authorizeRequests() ... } @Override public void configure(WebSecurity webSecurity) { webSecurity .ignoring() .antMatchers("/api/**"); } }
이 경우 생성되는 구조를 그림으로 표시하면 아래와 같다. WebSecurity.ignoring() 은 필터가 없는 SecurityFilterChain 을 만들게 되고, "/api/**" 경로에 대한 요청을 이쪽 SecurityFilterChain으로 전달한다.
HttpSecurity에 대한 설정도 SecurityFilterChain을 만들고 나머지 요청을 받게 될 것이다. 자세한 설명은 생략한다.
4. 원인
이제 MyCustomFilter가 왜 ignoring 설정에 영향을 받지 않는지 알 수 있다. @Component로 등록한 MyCustomFilter는 WAS의 ApplicationFilterChain 에 등록되어 있기 때문에 요청이 어느 SecurityFilterChain을 타든지와 무관하게 지나게 된다.
아래는 /api/** 경로에 대한 요청이라도 MyCustomFilter를 포함한 ApplicationFilterChain 의 모든 필터를 지나는 것을 보여준다.
5. 해결 방법
MyCustomFilter가 WebSecurity.ignoring에 영향을 받게 하기 위해서는 여러 방법이 있겠지만, 가장 간단하다고 생각하는 생각하는 것은 MyCustomFilter 를 ApplicationFilterChain 이 아니라 아래와 같이 HttpSecurity가 생성하는 SecurityFilterChain에 넣는것이다.
이 경우 /api/** 경로의 요청이 MyCustomFilter를 지나지 않게 된다.
MyCustomFilter를 SecurityFilterChain 에 넣으려면 HttpSecurity를 설정하는 곳에서 필터를 추가하면 된다. 추가하고자 하는 필터가 Spring Security 에서 제공하는 필터거나 이를 상속했다면, http.addFilter(new 필터()) 로 추가할 수 있다. 그렇지 않다면 addFilterAfter(new 필터(), 기존필터.class) 형식이나 addFilterBefore(new 필터(), 기존필터.class) 형식으로 추가할 수 있다.
@Override protected void configure(HttpSecurity http) throws Exception { ... http.addFilterBefore(new MyCustomFilter(), DisableEncodeUrlFilter.class); ... }
기존 필터들의 순서는 https://docs.spring.io/spring-security/reference/servlet/configuration/xml-namespace.html#ns-custom-filters 에서 확인할 수 있다.
반응형