BackEnd/Spring Security

[Spring Security] DelegatingFilterProxy, FilterChainProxy

샤아이인 2022. 8. 25.

본 글은 Spring Security docs 와 여러 블로그 들을 참고하고, 공부하면서 요약하였습니다.

 

이번 글 부터 당분간 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 picture below shows the typical layering of the handlers for a single HTTP request. The client sends a request to the appl

docs.spring.io

 

1. DelegatingFilterProxy

 

서블릿 필터는 스프링에서 정의 된 빈을 주입해서 사용할 수 없다

Servlet Filter는 Servlet 스펙에 정의되어있기 때문에 Servlet 컨테이너에서 생성이 되고, 적용된다.

따라서 Spring에서 사용하는 기술, 예를 들어 Bean과 같은것을 주입받을수는 없다.

 

하지만 개발자의 욕심은 끝이 없기 때문에 Filter에서도 Spring의 기술을 사용하고 싶었을것이다.

 

이 때 Servlet Filter는 DelegatingFilterProxy 클래스를사용해서 스프링 빈에게 요청을 위임하면 스프링 빈으로 구현한 필터를 이용해 책임을 수행하게 된다.

 

DelegatingFilterProxy는 특정한 이름을 가진 Spring Bean을 찾아서 그 Bean에게 요청을 위임한다

이때 DelegatingFilterProxy가 특정 Bean을 찾아 위임하게 된다.

"springSecurityFilterChain" 이름으로 생성된 Bean을 ApplicationContext에서 찾아 위임하게 된다.

즉, 실제 보안처리를 하지는 않는다. 단지 요청을 위힘시킬 뿐 이다.

 

 

DelegationFilteProxy의 sudo 코드를 살펴보면 다음과 같다.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0

    Filter delegate = getFilterBean(someBeanName);
    
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}

위에서 someBeanName은 "springSecurityFilterChain" 에 해당되게 된다.


서블릿 필터에서 요청에 대한 자원의 접근 전/후로 동작을 수행하는데, 서블릿 필터는 서블릿 컨테이너에서 관리되기에 스프링 빈들을 사용할 수 없다.

 

하지만, 스프링 시큐리티는 모든 요청에 대한 인증및 인가와 같은 보안 처리를 필터 기반으로 처리하고 있는데, 필터에서도 스프링의 기술(스프링 빈)을 사용하고 싶은 요구사항이 생긴다. 

 

그래서 Spring에서도 Servlet Filter를 상속한 BeanFilter을 구현했는데, 이게 바로 springSecurityFilterChain이다.

하지만 위에서 언급했듯 Servlet 필터에서는 Spring Bean을 사용할수가 없다.

 

따라서 Filter 와 Bean을 연결해주는 Class가 바로 DelegatingFilterProxy이다.

이 클래스는 서블릿 필터인데, 요청을 받아서 스프링에서 관리하는 필터에게 요청을 위임하는 역할을 맡고 있다.

 

2. FilterChainProxy

위에서 "springSecurityFilterChain" 이름으로 생성된 Bean을 ApplicationContext에서 찾아 위임하게 된다 했는데,

springSecurityFilterChain 이름을 가지고 있는 Bean이 바로 FilterChainProxy이다.

 

FilterChainProxy 는 각 필터들을 순서대로 호출하며 인증/인가처리 및 각종 요청에 대한 처리를 수행한다.

FilterChainProxy는 "springSecurityFilterChain" 이라는 이름으로 생성되는 Filter Bean 이다.

DelegatingFilterProxy로 부터 요청을 위임 받고 실제 보안 처리를 하게 된다.

 

FilterChainProxy는 시큐리티 초기화 시 생성되는 여러 필터들을 관리하고 제어한다.

관리되는 필터는 기본적으로 시큐리티가 등록시켜주는 필터도 있고, 사용자가 직접 작성한 필터가 있다.

 

사용자의 요청이 오면 필터를 순서대로 적용시키고, 예외가 발생하지 않는다면 DispatcherServlet까지 전달되게 된다.

또한 사용자정의 필터를 생성해서 기존의 필터 전,후로 추가할수가 있다.

 

▶ Flow

흐름을 정리해보면 다음과 같다.

 

1. 사용자가 자원 요청


2. Servlet Container의 필터들이 처리를 하게되고 그 중 DelegatingFilterProxy가 요청을 받게 될 경우 자신이 요청받은 요청객체를 delegate request로 요청 위임을 한다.


3. 요청 객체는 특정한 필터(springSecurityFilterChain) 에서 받게 된다. 
→ DelegatingFilterProxy가 필터로 등록 될때, targetBeanName이라는 Filed값으로 "springSecurityFilterChain"를 저장하게 된다.
이는 이전에 살펴본 FilterChainProxy의 이름이다. 

 

→ securityFilterChain 필터들을 리스트로 가지고 있는 빈(Bean)이 바로 FilterChainProxy 이다!


4. FilterChainProxy에서는 자신이 가진 각각의 필터들을 차례대로 수행하며 보안처리를 수행한다.


5. 보안처리가 완료되면 최종 자원에 요청을 전달하여 다음 로직이 수행된다.

 

3. 디버깅 해보기

직접 디버깅해보면서 결과를 살펴보자.

 

▶ 애플리케이션 로딩 시점

맨 처음 SecurityFilterAutoConfiguration에서 DelegatingFilterProxy 생성하고 Bean으로 등록하게 된다.

 

Bean을생성하기 위해 DelegatingFilterProxy의 생성자에 책임을 위임할 FilterChainProxy의 이름과 WebApplicationContext를 넘기게 된다. FilterChainProxy의 이름으로 "springSecurityFilterChain"를 넘기게 된다.

DelegatingFilterProxy가 생성 완료 되었다.

 

이후 Spring Container 쪽으로 넘어오게 된다.

Spring의 WebSecurityConfiguration 을 보면 다음과 같이 FilterChainProxy Bean을 등록하는 부분이 있다.

"springSecurityFilterChain"이라는 이름으로 Bean을 생성하고 있는데, 이 Bean이 바로 FilterChainProxy 입니다.

WebSecurityConfiguration 에서 FilterChainProxy가 생성되는 것 이다.

 

springSecurityFilterChain() 의 마지막 단락을 보면 webSecurity.build()를 통해 생성하게 된다.

 

webSecurity#build() -> 내부의 doBuild() -> WebSecurity#performBuild() 에서 FilterChainProxy를 생성하고 있다. 

protected Filter performBuild() throws Exception {
    // 생략...     

    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
}

이렇게 Bean으로 등록되게 된다.

댓글