AbstractFilterRegistrationBean

appti·2024년 4월 4일
0

분석

목록 보기
20/23

서론

스프링 부트는 내부적으로 두 종류의 컨테이너를 가지고 있습니다.

Servlet Container는 내장 WAS(톰캣 등)이 관리하며, 스프링이 관리하는 것은 IoC Container입니다.

Servlet Container는 클라이언트의 요청을 받아 Servlet에게 전달하고, 요청 처리 결과에 따라 응답을 클라이언트에게 전달합니다.

IoC Container는 스프링에서 필요한 빈들을 관리하기 위해 빈을 등록하고, 의존 관계를 주입합니다.


스프링 부트는 일반적으로 웹 애플리케이션을 개발할 때 많이 사용하는 프레임워크입니다.

그렇기 때문에 스프링 부트에서는 다양한 필터들을 스프링 빈으로 관리할 수 있습니다.

하지만 스프링 빈이 관리되는 IoC Container와 실제 필터가 수행되는 WAS, Servlet Container는 서로 별개입니다.
그러므로 스프링 빈에 등록하기만 해서는 필터를 사용할 수 없게 됩니다.

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
        return http.build();
    }
    
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
    }
}

대표적인 필터 기반 프레임워크인 스프링 시큐리티는 위와 같이 빈으로 설정을 처리하고 있습니다.

필터 기반으로 인증/인가를 처리하기 위해서는 Servlet Container에 접근할 필요가 있는데, 이에 대한 설정은 IoC Container에서 수행하고 있는 것입니다.

결국 IoC Container에서 등록된 빈을 토대로 Filter가 생성되고 이 필터가 Servlet Container에게 전달되어야 합니다.

즉 Servlet Container와 IoC Container를 연결하는 무언가가 필요하다는 의미인데, 이 역할을 수행하는 것이 바로 AbstractFilterRegistrationBean 입니다.


이 글을 작성할 때 디버깅한 환경은 스프링 시큐리티와 스프링 MVC를 의존하고 있는 스프링 부트 3.2.2 버전입니다.

AbstractFilterRegistrationBean

AbstractFilterRegistrationBean는 추상 클래스로, 이름 그대로 스프링 빈으로 등록된 필터를 Servlet Container에 등록하는 역할을 수행합니다.

AbstractFilterRegistrationBean는 ServletContextInitializer를 확장한 RegistrationBean을 확장하고 있음을 확인할 수 있습니다.

이는 ApplicationContex refresh 과정 중에 ServletContext를 초기화하기 위해 사용된다는 의미입니다.

공통 등록 과정

공통 등록 과정을 그림으로 정리하면 다음과 같습니다.

톰캣에서 사용하는 ServletContext(= org.apache.catalina.core.ApplicationContext)를 스프링 부트의 IoC Container(= org.springframework.context.ApplicationContext)에 전달합니다.

IoC Container는 ApplicationContext를 refresh 하는 과정 중, ServletContextInitailizer를 모두 순회하며 필요한 내용을 초기화합니다.

이 때 ServletContextIntilaizer를 확장하고 있는 AbsractFilterRegistrationBean이 필터를 추가합니다.

추가되는 필터는 Auto Configuration에 의해 미리 빈으로 등록된 필터입니다.

Servlet Container는 IoC Container에서 addFilter()를 통해 받은 필터를 FilterChain에 추가합니다.

이 과정을 코드 레벨에서 살펴보도록 하겠습니다.


ApplicationContext refresh 과정 중 ServletWebServerApplicationContext에서 내장 톰캣을 생성하는 메서드를 호출합니다.

ServletWebServerFactory, 스프링 MVC 기반에서는 TomcatServletWebServerFactory에서 getWebServer()를 호출해 톰캣을 가져옵니다.

여기서 파라미터로 getSelfInitializer()를 호출하는 것을 확인할 수 있는데, 해당 메서드에서 ServletContextInitailizer가 조회되어 TomcatServletWebServerFactory에 전달됩니다.

디버깅으로 확인해보면 CharacterEncodingFilter를 가지고 있는 FiterRegistrationBean을 가져와서 onStartup() 메서드를 호출하고 있음을 확인할 수 있습니다.

여기서 ServletContext는 톰캣이 관리하는 ServletContext로, Servlet Container에서 필요한 Servlet에 접근할 수 있도록 문맥 관련 정보를 제공해주는 대상이라고 이해할 수 있습니다.

즉, Servlet Container에서 요청을 처리할 수 있는 Servlet을 찾는데, 어떤 Servlet이 요청을 처리할 수 있고 요청을 처리하기 위해 어떤 전/후처리가 필요한지 ServletContext를 통해 정보를 조회할 수 있는 것입니다.

이 때 전/후처리에 필터 작업도 포함됩니다.

필터를 ServletContext에 등록하기 위해 RegistrationBean에서 register()를 호출합니다.

추상 메서드이므로 하위 구현체에 오버라이딩된 메서드가 호출되며, 이 때 AbstractFilterRegistrationBean의 상위 클래스인 DynamicRegistrationBean.register()가 호출됩니다.

DynamicRegistrationBean.register()에서는 addRegistration()을 호출하며, 이 또한 추상 메서드이기 때문에 AbstractFilterRegistrationBean.addRegistration()가 호출됩니다.

이후 getFilter()를 호출해 ServletContext에 등록하고자 하는 필터를 조회합니다.

이 때 getFilter()는 추상 메서드로, 하위 구현체에 오버라이딩 된 getFilter()를 호출해 필터를 조회하고 추가하게 됩니다.

이 때 getFilter()는 추상 메서드로, 하위 구현체에 오버라이딩 된 getFilter()를 호출해 필터를 조회하고 추가하게 됩니다.

이는 톰캣의 ApplicationContext에서 FilterDef를 생성해 추가하게 됩니다.

FilterRegistrationBean

FilterRegistrationBean은 특정 Filter를 Servlet Container에 직접 등록하기 위해 사용되는 ServletContextInitializer입니다.

다음과 같은 필터가 FilterRegistrationBean으로 Servlet Container에 추가됩니다.

  • OrderedCharacterEncodingFilter
  • OrderedFormContentFilter
  • OrderedRequestContextFilter

위와 같이 AutoConfiguration으로 빈으로 등록된 필터입니다.

생성 과정

ApplicationContext refresh 과정 중 ServletContextIntializer를 순회하며 Servlet Container에게 필요한 초기화를 진행한다고 했었습니다.

이 때 사용되는 것은 getServletContextInitalizerBeans()로 호출되는 것은 ServletContextInitializerBeans 입니다.

동작 도중 Filter 타입의 초기화를 처리할 ServletContextInitializer가 있는지 확인하며, 이 때 Auto Configuration으로 등록된 필터 관련 빈들이 FilterRegistrationBean으로 생성됩니다.

필터 등록 과정

getFilter()로 등록할 필터를 찾아 ServletContext에 등록합니다.

FilterRegistrationBean은 ServletContextInitalizerBeans 초기화 과정에서 세팅한 필터를 그대로 넘겨줍니다.

즉, Auto Configuration으로 등록된 필터가 그대로 톰캣의 Servlet Container에 등록됩니다.

디버깅으로 확인해보면 실제로 characterEncodingFilter가 그대로 등록되어 있음을 확인할 수 있습니다.

DelegatingFilterProxyRegistrationBean

DelegatingFilterProxyRegistrationBean은 이름 그대로 DelegatingFilterProxy를 필터로 등록하는 역할을 수행하는 빈입니다.

DelegatingFilterProxy가 사용되는 대표적인 케이스는 스프링 시큐리티이므로, 이에 초점을 맞춰 분석하도록 하겠습니다.

DelegatingFilterProxy

DelegatingFilterProxy는 다른 필터들과 달리 ApplicationContext에서 실행할 필터를 찾아서 실행하는 프록시 필터입니다.

위 그림과 같이 RegistrationBean에 의해 등록된 DelegatingFilterProxy는 Servlet Context의 FilterChain에 있다가 요청을 받으면 이를 스프링 빈으로 등록된 필터에게 위임합니다.

코드를 보면, initDelegate()를 통해 스프링 부트의 IoC Container를 조회해 위임할 필터를 찾아 invoke하는 것을 확인할 수 있습니다.

생성 과정

스프링 시큐리티 의존성을 추가할 경우 확인할 수 있는 SecurityFilterAutoConfiguration에서 DelegatingFilterProxyRegistrationBean이 생성됩니다.

특이사항으로 @ConditionalOnBean의 조건으로도, DelegatingFilterProxyRegistrationBean의 생성자로도 DEFAULT_FILTER_NAME을 전달하고 있다는 점입니다.

DEFAULT_FILTER_NAME은 springSecurityFilterChain입니다.

DelegatingFilterProxyRegistrationBean의 targetBeanName으로 저장이 됩니다.

이 targetBeanName은 요청을 위임하고자 하는 필터의 이름을 의미합니다.
그러므로 @ConditionalOnBean을 통해 targetBeanName이 있을 때만 DelegatingFilterProxyRegistrationBean을 등록하는 것입니다.

필터 등록 과정

getFilter() 호출 시 DelegatingFilterProxyRegistrationBean에 지정한 targetBeanName인 springSecurityFilterChain과 빈 이름을 통해 조회할 스프링의 IoC Container인 ApplicationContext를 전달해 DelegatingFilterProxy를 생성해 반환합니다.

톰캣을 확인하면 springSecurityFilterChain이 등록된 것을 확인할 수 있습니다.

이후 과정을 통해 등록된 springSecurityFilterChain 필터의 클래스를 DelegatingFilterProxyRegistrationBean으로 세팅합니다.

필터 동작 과정

DelegatingFilterProxy.doFilter() 호출 시 initDelegate()를 통해 위임할 필터를 찾게 됩니다.

스프링의 IoC Container인 ApplicationContext에서 targetBeanName, 즉 이름이 springSecurityFilterChain인 필터를 찾아서 반환합니다.

이 때 CompositeFilter가 반환되며, 이 중 스프링 시큐리티와 직접적으로 관계가 있는 것은 FilterChainProxy 입니다.

이후 invokeDelegate() -> CompositeFilter.doFilter()를 호출합니다.

CompositeFilter.doFilter()는 CompositeFilter 내부 클래스인 VirtualFilterChain.doFilter()를 호출합니다.

VirtualFilterChain의 생성자에서 CompositeFilter에 있는 모든 필터를 등록합니다.

이후 여러 필터를 인덱스 기반을 순회하며 필터를 호출합니다.
CompositeFilter에 등록된 모든 필터를 조회했다면 기존 FilterChain(= originalChain)의 doFilter()를 호출해 CompositeFilter 다음의 필터에게 요청을 전달합니다.

FilterRegistrationBean & DelegatingFilterProxyRegistrationBean

두 Filter 관련 RegistrationBean의 공통점과 차이점은 다음과 같습니다.

  • 공통점
    • 스프링에서 관리하는 필터를 WAS에 등록하기 위한 기능을 제공합니다.
  • 차이점
    • FilterRegistrationBean
      • 스프링에 등록된 필터를 그대로 WAS의 FilterChain에 등록합니다.
      • 스프링에서 빈 생명주기 관리 및 의존성 주입이 가능합니다.
    • DelegatingFilterProxyRegistartionBean
      • 스프링에 등록된 필터를 ApplicationContext에서 찾아 실행하는 기능입니다.
      • 실제로 동작하는 필터는 WAS의 FilterChain에 등록되지 않습니다.
        • 스프링에 등록된 필터를 그대로 사용합니다.

결론

  • 필터는 Servlet Conatiner에서 실행되어야 합니다.
  • IoC Container에서 필터를 관리하기 위해서는 이 필터들을 Servlet Container에게 전달할 필요가 있습니다.
    • 이를 위해 스프링에서는 AbstractFilterRegistrationBean를 통해 IoC Container에서 관리하고 있던 필터들을 Servlet Container에 등록합니다.
    • AbstractFilterRegistrationBean은 ServletInitializer를 확장했으므로 이러한 필터들은 ApplicationContext refresh 시점에 등록됩니다.
  • CharacterEncodingFilter와 같은 일반적인 필터는 FilterRegistrationBean에 의해 직접 등록되며, FilterChainProxy와 같이 특수한 필터는 DelegatingFilterProxyRegistartionBean에 의해 처리됩니다.
profile
안녕하세요

0개의 댓글