Tomcat 필터 중 Spring Security에 위임해서 등록한 필터가 동작하게 되는 것에 대해서 정리한 포스트입니다.
🐻 Architecture
스프링 프레임워크는 Tomcat 서블릿 컨테이너 위에서 돌아가므로 Tomcat의 서블릿 필터 기능에 의존합니다.
Spring Security를 사용하게 되면, Tomcat의 Filter Chain에 스프링 시큐리티 전용 필터를 하나 끼워 넣어 모든 요청을 가로채는데, DelegatingFilterProxy가 Spring Security의 Filter Chain Proxy에 인증과 인가를 위임합니다.
DelegatingFilterProxy는 기타 로직이 하나도 없이 단지 스프링 프레임워크 내부의 FilterChainProxy에게 위임하는 프록시 역할만을 할 뿐입니다.
public class DelegatingFilterProxy extends GenericFilterBean {
private volatile Filter delegate; // <- 스프링의 Filter Chain Proxy
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain filterChain
) {
// delegate null 검사
// Let the delegate perform the actual doFilter operation
invokeDelegate(delegateToUser, request, response, filterchain);
}
// 실제 필터 요청 처리
protected void invokeDelegate(
Filter delegate, // 스프링의 filter chain proxy
ServletRequest request,
ServletResponse response,
FilterChain filterChain
) {
delegate.doFilter(request, response, filterChain);
}
}
🐻 Filter Chain Proxy
Delegating Filter Proxy 가 스프링의 FilterChainProxy에 인증 인가를 위임하고, FilterChainProxy는 사용자 요청을 스프링 시큐리티가 관리하는 SecurityFilterChain에 요청을 프록시 합니다.
Filter Chain Proxy에 등록된 SecurityFilterChain들을 이용해서 요청의 인증 및 인가와 관련된 여러 사항을 검증하고, 적절한 응답을 반환하는 책임을 가집니다.
Filter가 chain.doFilter(...)를 호출하지 않으면(return) 그 시점에서 필터 체인 실행은 종료됩니다. 즉, 이후의 필터들은 실행되지 않습니다.
SecurityFilterChain 스프링 시큐리티를 한 번이라도 사용해 보았다면, 본 기억이 있을 것입니다. 스프링 시큐리티를 적용하면 빈으로 SecurityFilterChain을 등록해 줍니다.
등록한 빈이 스프링 컨텍스트에서 관리되며, FilterChainProxy에서 등록한 SecurityFilterChain를 이용해 사용자의 HTTP 요청을 인증, 인가 처리를 합니다.
실제로 프록시에서 빈으로 등록한 SecurityFilterChain을 모두 받아서 리스트로 관리하는 것을 알 수 있습니다. 이는 요청에 따라 다른 SecurityFilterChain을 구분할 수 있다고 합니다. 기본으로 등록되는 개수는 1개입니다.
Spring Security의 FilterChainProxy는 시큐리티 필터 체인의 핵심 조정자 역할을 합니다. 작업을 처리하고 예외를 캐치하고, 자원을 정리하는 프록시 역할을 합니다.
앞서 DelegatingFilterProxy에서 FilterChainProxy에게 doFilter 메소드를 사용하여 위임하였습니다. doFilter 메소드에 대해서 살펴보겠습니다.
🐻 doFilter: FilterChainProxy
가장 추상적으로 해당 메소드의 역할은 등록한 SecurityFilterChain에 요청의 인증과 인가를 처리하는 필터를 처리하는 메소드입니다.
여기서 가장 핵심적인 부분은 try 블록 안에 있는 doFilterInternal 메소드입니다. 내부적으로 어떤 일을 하고 있는지 살펴보겠습니다.
doFilterInternal: FilterChainProxy
코드를 살펴보면, 가장 먼저 getFilter 메소드를 이용하여 List<Filter> 리스트를 반환받고 있습니다. 해당 get 메소드가 무엇을 가지고 오는지 살펴보겠습니다.
여기서 this.filterChain 은 우리가 등록한 SecurityFilterChain입니다. 기본으로 한 개이지만, 커스텀에 따라서 여러 개가 될 수 있습니다. 별다른 커스텀을 해주지 않았기 때문에 등록한 SecurityFilterChain 빈을 반환할 것입니다.
그리고 로깅 레벨에 따라서 등록된 SecurityFilterChain의 개수가 몇 개인지 출력한 후 사용자의 요청(URI)을 chain이 처리할 수 있다면, chain이 가지고 있는 Filters를 반환합니다.
여기서 반환하는 Filter 들은 스프링 컨텍스트에서 관리되는 SecurityFilterChain의 Filters입니다.
🐻 doFilterInternal: filters를 모두 doFilter
FilterChainProxy의 마지막, 실제 등록된 필터를 모두 처리할 수 있도록 하는 마지막 단계입니다. 우리가 등록한 필터를 이 시점에 요청을 검증하게 됩니다.
filterChainDecorator 타입에 대해서 정확하게는 모르겠지만 FilterChainProxy에서 decorate 메소드를 통해서 VirtualFilterChainDecorator 타입의 decoreate 메소드를 통해 반환하는 것을 알 수 있습니다.
VirtualFilterChainDecorator
VirtualFilterChainDecorator 객체는 Inner static final 객체입니다. 여기서 핵심은 SecurityFilterChain의 List<Filter> 를 decorator 패턴을 사용하여 VirtualFilterChain 객체로 감싸서 반환하는 것입니다.
반환 타입인 VirtualFilterChain 클래스도 FilterChainProxy 클래스 안에 존재하는 Inner 클래스입니다. 여기서는 데코레이터 패턴을 이용해서 스프링 시큐리티는 체인의 흐름을 재귀적으로 관리하기 위해 디자인된 구조입니다.
코드를 살펴보면 객체 안에 필터를 초기화한 후 doFilter 메소드가 호출되면 모든 필터를 호출합니다. 그렇기에 필터가 체인으로 연결되어 있다라고 생각합니다.
마무리로 모든 필터의 작업이 마무리되고 완료된다면, return 없이 필터의 모든 작업이 완료된 후 Dispatcher Servlet으로 컨트롤러가 매핑되고 비즈니스 로직이 실행됩니다.
여기까지가 사용자의 요청을 처리하는 스프링 시큐리티의 필터 처리의 가장 큰 흐름이었습니다. 다음 포스트에서는 SecurityFilterChain이 실제 가지고 있는, 기본으로 스프링에서 제공하는 필터들에 대해서 살펴보고, 어떻게 사용하는지에 대해서 살펴보겠습니다. 감사합니다.