ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

     

    Architecture :: Spring Security

    Spring Security’s Servlet support is based on Servlet Filters, so it is helpful to look at the role of Filters generally first. The following image shows the typical layering of the handlers for a single HTTP request. The client sends a request to the ap

    docs.spring.io

    Spring Security 의 구조를 먼저 이해할 필요가 있다. Spring Boot 에서 Spring Security 는 두개의 필터 체인을 생성한다. FilterChain(ApplicationFilterChain)과 SecurityFilterChain 이다.

    원본에 화살표 추가. 원본출처: https://docs.spring.io/spring-security/reference/servlet/architecture.html

    왼쪽에 FilterChain 은 기존의 WAS 의 필터체인(ApplicationFilterChain)이다. 오른쪽의 SecurityFilterChain 은 Spring Security 에서 사용하는 필터체인이고 실제 인증 절차가 진행된다.

    거칠게 요약을 하면, SecurityFilterChain 이라는 기존 필터체인과 분리된 필터체인이 존재하는데, HTTP 요청을 가져와야 하기 때문에 특별한 필터(DelegatinFilterProxy)를 기존 WAS의 FilterChain 에 추가하여 SecurityFilterChain 으로 연결시켜 주는 역할을 하도록 한 것이다.

    필터가 요청을 받는 순서는 화살표와 같다. 요청은 ApplicationFilterChain의 순서대로 전달되다가 DelegatinFilterProxy 에서 SecurityFilterChain 로 전달되어 그 내부 필터들을 순서대로 지난다. 그리고 다시 ApplicationFilterChain의 나머지 필터들로 전달된다.

    SecurityFilterChain 를 통해 WAS 의 필터체인에서 분리되어 좀 더 Spring 스러운 환경에서 복잡한 필터링 조건을 추가하거나 세세한 작업을 할 수 있게 되어있다.

     

    한편, SecurityFilterChain 은 여러 개 존재할 수 있다.

    원본에 화살표 추가. 원본출처: https://docs.spring.io/spring-security/reference/servlet/architecture.html

    또한, 다른 경로에 따라 다른 SecurityFilterChain 을 지나도록 할 수 있다. 예를 들어, 위의 그림처럼 /api/** 경로에 대한 SecurityFilterChain과 나머지 경로(/**) 에 대한 SecurityFilterChain을 다르게 설정했다면, 다른 경로에 대한 요청이 서로 다른 필터들을 통과하게 된다.


    2. @Component 로 Filter 추가하기

    Spring Boot 에서 Filter를 추가할 때, 두 필터체인 중 어느 필터체인에 추가되는지 알면 도움이 된다.

    @Component
    public class MyCustomFilter implement Filter {
       ...
    }

    위와 같이 @Component 어노테이션과 Filter 인터페이스를 이용하면 필터체인에 추가되는데, 이 경우 WAS의 ApplicationFilterChain에 추가된다.

    원본에 가공. 원본출처: https://docs.spring.io/spring-security/reference/servlet/architecture.html

    • 참고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으로 전달한다.

    원본에 가공. 원본출처: https://docs.spring.io/spring-security/reference/servlet/architecture.html

    HttpSecurity에 대한 설정도 SecurityFilterChain을 만들고 나머지 요청을 받게 될 것이다. 자세한 설명은 생략한다.


    4. 원인

    이제 MyCustomFilter가 왜 ignoring 설정에 영향을 받지 않는지 알 수 있다. @Component로 등록한 MyCustomFilter는 WAS의 ApplicationFilterChain 에 등록되어 있기 때문에 요청이 어느 SecurityFilterChain을 타든지와 무관하게 지나게 된다.

    아래는 /api/** 경로에 대한 요청이라도 MyCustomFilter를 포함한 ApplicationFilterChain 의 모든 필터를 지나는 것을 보여준다.

    1. MyCustomFilter를 ApplicationFilterChain에 추가

    5. 해결 방법

    MyCustomFilter가 WebSecurity.ignoring에 영향을 받게 하기 위해서는 여러 방법이 있겠지만, 가장 간단하다고 생각하는 생각하는 것은 MyCustomFilter 를 ApplicationFilterChain 이 아니라 아래와 같이 HttpSecurity가 생성하는 SecurityFilterChain에 넣는것이다.

    2. MyCustomFilter를 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 에서 확인할 수 있다.

    반응형

    댓글

Designed by Tistory.