이 글은 인프런 정수원님의 강의를 복습하며 작성한 글입니다.
서블릿 필터와 스프링 빈
스프링에서 우리는 필터라는 기능을 자주 사용한다. 이 필터는 요청이 들어왔을 때, DispatcherServlet에 요청이 전달되기 전 과정까지 무언가 전처리를 해준다. 그리고 DispatcherServlet에서 응답이 나갈 때도 다시 한번 필터를 거쳐서 나가는 형태가 된다.
스프링에서 필터 기술을 지원하는 것처럼 보이기 때문에 필터를 스프링 기술이라고 생각할 수 있다. 그렇지만 엄밀히 말하면 필터는 스프링이 아닌 서블릿의 기술이다. 서블릿 필터는 서블릿 컨테이너에서 생성되고 실행된다. 그리고 스프링 빈은 스프링 컨테이너에서 생성되고 실행된다. 따라서 기본적으로 스프링 빈을 필터에서 주입 받아서 사용할 수 없는 구조다.
DelegatingFilterProxy (서블릿 필터와 스프링 빈 필터의 연결 매개체)
Spring Security는 모든 요청에 대해서 필터를 기반으로 인증/인가처리를 한다. 그런데 앞선 게시글에서 볼 수 있듯이, 나는 보란듯이 WebSecurityConfigurerAdapater 클래스를 상속받은 설정 클래스를 만들고 @Configuration으로 등록해서 사용했다. 즉, 내가 만든 스프링 빈이 서블릿 필터에 영향을 준다는 이야기다. 어떻게 이것이 가능한 것일까?
Spring Security는 서블릿 필터에서도 스프링의 기술을 사용하기 위해 서블릿 필터를 스프링 빈으로 만들었다. 이렇게 만들어진 스프링 빈 필터는 생성 시점 / 사용 시점이 같기 때문에 주입을 받아 사용할 수 있게 된다. 그런데 문제는 이 필터가 스프링 빈으로만 등록되어있기 때문에 사용자 요청이 왔을 때, 필터로서의 역할을 할 수 있는 상황은 아니라는 거다.
그런 이유 때문에 서블릿 필터 계층와 필터 타입의 스프링 빈을 연결시켜주는 매개체가 필요하다. 이 매개체가 DelegatingFilterProxy다. 모든 요청은 DelegatingFilterProxy로 온다 그리고 DelegatingFilterProxy는 필터 타입의 스프링 빈을 찾아서 요청을 위임해주는 역할을 한다.
DelegatingFilterProxy (서블릿 필터와 스프링 빈 필터의 연결 매개체)
- DelegatingFilterProxy는 "springSecurityFilterChain" 이름으로 등록된 빈을 찾음.
- 찾은 빈에게 사용자 요청 처리를 위임함.
- 찾은 빈의 실제 클래스는 FilterChianProxy임.
- 실제 보안처리는FilterChainProxy에서 처리함.
FilterChainProxy
앞서 DelegatingFilterProxy는 실제 사용자 요청에 대한 인증/인가처리를 하지 않고, 스프링 빈에 등록된 필터를 불러와 처리를 위임한다고 했다. 이 때, DelegatingFilterProxy가 호출하는 스프링 빈이 FilterChainProxy 타입이다. FilterChainProxy의 설명은 아래에서 확인할 수 있다.
- DelegateFilterProxy는 springSecurityFilterChain 이름으로 만들어진 스프링 빈을 검색한다.
- FilterChainProxy는 "springSecurityFilterChain"이란 이름으로 생성되는 필터 빈이다.
- FilterChainProxy는 스프링 빈이면서, 서블릿 필터를 구현함.
- FilterChainProxy는 DelegateFilterProxy로부터 요청을 위임받고 인증/인가처리함.
- Spring Security 초기화 시 생성되는 필터들을 관리하고 제어함.
- 스프링 시큐리티가 기본적으로 생성하는 필터
- 설정 클래스에서 API 추가 시 생성되는 필터(FormLogin() API 설정 → UserNamePasswordAuthFilter)
- FilterChainProxy는 사용자 시, 필터 순서대로 호출하며 전달함.
- 사용자 정의 필터를 생성해서 기존의 필터 전/후에 추가 가능함.
- 마지막 필터까지 인증 및 인가 예외가 발생하지 않으면 보안을 통과한다. 즉, DispatcherServlet으로 요청을 넘김
FilterChainProxy는 Spring Security 초기화 시, 설정된 API를 바탕으로 만들어진 Filter를 다음과 같이 인덱싱 형태로 관리한다. FilterChainProxy가 하는 일은 위 필터들을 순서대로 호출하며 인증/인가 처리를 해주는 역할을 한다. 현재는 14개로 되어있는데, 사용자가 Security 설정 클래스에서 어떤 설정을 하느냐에 따라 필터가 더 늘어날 수도, 줄어들 수도 있다.
DelegatingFilterProxy, FilterChainProxy 흐름
- 시작을 하게 되면 서블릿 컨테이너와 스프링 컨테이너로 나누어져있다. 주로 서블릿 컨테이너는 Tomcat, WAS로 대변되고, 스프링 컨테이너는 WAS에서 들어오는 요청을 처리해주는 형식이다.
- 사용자가 처음 요청을 하면, 서블릿 컨테이너에서 요청을 받는다. 이 때 여러 서블릿 필터들이 처리를 하게 된다. 이 필터 중에 DelegatingFilterProxy 클래스가 있다. 이 클래스는 사용자의 요청이 오면 "springSeucirytFilterChain"이라는 이름으로 된 스프링 빈을 찾아와서, delegate.request()를 이용해 요청을 위임한다.
- springSecurityFilterChain 이름으로 스프링 컨테이너에 등록된 스프링빈은 FilterChainProxy 클래스다.
- FilterChainProxy는 리스트 형식으로 스프링 시큐리티에서 사용할 서블릿 필터 타입의 스프링 빈을 가지고 있다. 리스트 형식이기 때문에 실행 순서가 보장되는 방식으로 필터의 체이닝이 시작된다.
- FilterChianProxy에서 마지막 FilterSecurityInterceptor까지 완료되면 DispatcherServlet으로 사용자 요청을 넘겨준다. 즉, Spring Web 계층으로 사용자 요청을 넘겨주는 셈이다.
전체적인 흐름은 위와 같은 방식으로 흘러간다. 중요하게 여길 부분은 DelegatingFilterProxy는 스프링 컨테이너에서 SpringSecurityFilterChain으로 등록된 스프링 빈을 찾아 보안처리를 위임하고, SpringSecurityFilterChain이름의 스프링 빈은 FilterChainProxy이고, 이 빈은 내부적으로 여러 필터를 가지고 있으며 순차적으로 필터를 통해 보안처리를 해주는 것으로 이해할 수 있다.
스프링 빈 필터가 등록되는 과정
스프링이 시작될 때, SecurityFilterAutoConfiguration 클래스가 호출된다. 여기서 DEFAULT_FILTER_NAME으로 DelegatingFilterProxyRegistrationBean이 생성되는 것을 알 수 있다.
좀 더 살펴보면 DEFAULT_FILTER_NAME은 "springSecurityFilterChain"이라는 것을 확인할 수 있다. 즉, 생성되는 DelegatingFilterProxyRegistrationBean FilterChainProxy 객체를 의미하는 것이다.
여기서 "springSecurityFilterChain"이라는 이름의 스프링 빈을 생성하고 등록시켜주는 과정을 거친다.
WebSecurityConfiguration 클래스로 넘어온다. 이 때, this.webSecurity.build()를 통해 FilterChainProxy를 만든다.
this.webSecurity.build() 통해 doBuild() 메서드로 넘어오게 된다. 여기서 init() 메서드를 호출하면 우리가 설정한 Security 설정 정보를 바탕으로 FilterChainProxy를 만들기 시작한다.
init()로 넘어오면 getHttp() 메서드를 이용해서 우리가 설정한 설정정보를 바탕으로 Http 객체를 생성하는 것을 볼 수 있다.
여기서 보면 각 생성 메서드(?)들을 통해서 설정한 클래스로 가서 값을 하나씩 불러온다.
이런 저런 처리를 해준 후, performBuild() 메서드를 수행해준다.
WebSecurity.perform()으로 넘어오면 여기서 FilterChainProxy가 생성자를 통해 만들어지는 것을 볼 수 있다. 그리고 만들어진 FilterChainProxy에는 FilterChains가 있고, 여기에 설정 정보에 따른 필터들이 만들어서 저장이 되어있는 것을 확인할 수 있다.
정리하면 SecurityFilterAutoConfig를 통해서 "springSecurityFilterChain"이라는 이름의 빈을 등록해주었고, WebSecurityConfiguration 클래스에서 실제로 FilterChainProxy를 만들어주는 것을 확인할 수 있었다.
사용자 요청이 들어온 후, 위임처리 과정
사용자의 요청(localhost:8080 접속)이 들어오면 DelegatingFilterProxy가 요청을 받는다. 받은 요청에서 인증/인가처리를 위임할 delegate가 있는지를 확인하는데 없는 경우 findWebApplicationContext()로 이동해서 스프링 컨테이너를 받아오는 작업을 한다.
스프링 컨테이너를 받아왔으면, 다시 한번 Delegate 객체를 찾기 위해 initDelegate()를 이용해서 스프링 빈을 찾으러 간다.
initDelegate 메서드로 넘어와서 빈 이름으로 스프링 컨테이너에서 스프링 빈을 찾아온다. 이 때 검색하는 이름은 "springSecurityFilterChain"인 것을 확인할 수 있다.
이제 위임 처리를 해줄 객체를 스프링 빈에서 찾아왔다. 이 객체는 위에서 볼 수 있듯이 FilterChainProxy다.
DelegatingFilterProxy는 찾아온 Delegate 객체를 넘겨주면서 invoke 시켜준다.
DelegatingFilterProxy가 위임을 하게 되면, FilterChainProxy의 doFilter() 메서드로 넘어오게 된다.
doFilter() 메서드에서 doFilterInternal로 넘어오게 되는데, 여기서 getFilters()를 통해서 실행해야 할 필터를 Filters 객체에 저장하는 것을 확인할 수 있다. 그런데 이 Filter는 리스트 형태로 저장되는 것을 확인할 수 있다. 즉, Filter와 Filter 사이는 doChain()으로 연결되는데 그런 관계가 설정이 안된 것이다.
FilterChainProxy는 다음과 같이 VirtualFilterChain() 객체를 만들어준다. 여기서 실제 Filters들이 순서에 맞게 필터와 필터 사이의 연결고리를 가지게 된 것으로 이해를 할 수 있다. 만들어진 VirtualFilterChain()을 doFilter()를 통해서 실행시켜준다.
FilterChainProxy로 들어오게 되고, 순서대로 Filter를 얻어서 nextFilter에 저장하고, doFilter를 통해서 각 필터를 실행시켜주는 것을 확인할 수 있다.
정리
- DelegatingFilterProxy는 모든 요청을 받으면 스프링 컨텍스트에서 "springSecurityFilterChain"이름으로 등록된 빈을 찾아와 인증/인가 처리를 위임한다.
- 실제 인증/인가 처리는 FilterChainProxy가 가지고 있는 Filters들 중에서 선택해서 순서대로 처리해준다.
'Spring > Spring Security' 카테고리의 다른 글
Spring Security : 계정 생성 + 권한 설정 + 인증/인가 설정 (0) | 2022.04.09 |
---|---|
Spring Security : 아키텍쳐 / 필터 초기화와 보안 클래스 다중 설정 (0) | 2022.04.06 |
Spring Security : CSRF / CSRF Filter (0) | 2022.04.05 |
Spring Security : 선언적 권한 설정과 표현식 (0) | 2022.04.05 |
Spring Security : SessionManagementFilter / ConcurrentSessionFilter (0) | 2022.04.05 |