[Spring Security] SecurityFilterChain 파헤치기

본 포스팅은 Spring Security의 전체적인 흐름과 FilterChain이 어떻게 동작하는지 이해하는 것을 목표로 합니다.

 

우선 클라이언트가 HTTP 요청을 했을때 security가 어떻게 작동하는지 흐름부터 살펴본 뒤, 하나씩 알아보겠습니다.

 

  1. 클라이언트의 HTTP 요청이 서버에 도착합니다.
  2. "DelegatingFilterProxy"라는 서블릿 필터가 해당 요청을 가장 먼저 받아 "FilterChainProxy"에게 요청을 처리하도록 위임합니다.
  3. "FilterChainProxy"는 자신이 관리하고 있는 여러개의 "SecurityFilterChain"에게 순서대로 요청을 넘깁니다.
  4. 요청에 적합한 "SecurityFilterChain"내의 filter가 차례대로 실행되며, 각각의 filter는 보안(인증, 인가 등등) 관련 작업을 수행합니다.
  5. 인증 및 인가 관련 검사를 모두 통과하면 요청은 컨트롤러로 전달되고 응답값을 반환합니다.

 

이제 본 포스팅의 목적인 DelegatingFilterProxy, FilterChainProxy, SecurityFilterChain이 각각 무엇인지 하나씩 알아보도록 하겠습니다.

 

 

DelegatingFilterProxy


DelegatingFilterProxy는 클라이언트의 요청을 가장 먼저 받는데 DelegatingFilterProxy 자신이 요청을 처리하는 것이 아닌 이름에서 알 수 있듯이 다른 이에게 위임해서 요청을 처리하도록 합니다.

 

DelegatingFilterProxy는 위임할 대상을 찾는데 bean 이름이 "springSecurityFilterChain"이라는 이름을 갖는 bean을 찾습니다. spring security에서 이 이름의 bean은 대체로 "FilterChainProxy" bean을 참조합니다.

 

실제로 DelegatingFilterProxy가 위임할 대상을 찾고 위임하는 메서드인 doFilter() 메서드를 살펴봅시다.

 

 

위 코드를 보면 findWebApplicationContext()메서드를 호출해 WebApplicationContext를 가져온 뒤, initDelegate() 메서드를 호출합니다.

 

initDelegate() 메서드는 실제로 위임받을 FilterChainProxy bean을 WebApplicationContext에서 찾아 반환하는 역할을 합니다.

아래 initDelegate() 메서드의 코드를 살펴봅시다.

 

 

getTargetBeanName() 메서드로 위임받을 bean의 이름을 가져옵니다. 별도로 설정하지 않으면 bean의 기본값은 "springSecurityFilterChain"입니다.

 

 

wac.getBean() 메서드를 호출하여 WebApplicationContext에서 파라미터로 넘겨준 bean 이름의 Filter bean을 검색해서 가져옵니다. 두 번째 파라미터인 Filter.class는 가져온 bean이 Filter 타입인지 확인하는 용도입니다.

 

이후 초기화된 Filter를 반환합니다.

 

 

FilterChainProxy


DelegatingFilterProxy가 요청을 처리를 위임할 대상 bean을 WebApplicationContext에서 찾는데 그 bean이 "FilterChainProxy"라는 것을 확인했습니다.

 

FilterChainProxy에 대해 알아보겠습니다.

 

FilterChainProxy는 아래 그림처럼 여러개의 SecurityFilterChain 객체를 관리하고, 클라이언트로의 요청 URL에 따라 요청을 처리할 SecurityFilterChain이 결정됩니다.

 

 

 

FilterChainProxy는 SecurityFilterChain을 하나 갖고 있을 수도 있고, 여러 개의 SecurityFilterChain을 갖고 있을 수도 있습니다.

 

FilterChainProxy 클래스의 코드를 보면 SecurityFilterChain을 list로 관리하고 있는 것을 확인할 수 있습니다.

SecurityFilterChain의 개수는 사용자가 어떻게 SecurityFilterChain을 어떻게 설정하냐에 따라 달라집니다.

 

클라이언트의 요청을 FilterChainProxy는 요청 URL에 따라 어떤 SecurityFilterChain에게 요청을 처리하도록 위임할지 결정이 됩니다.

요청을 처리할 SecurityFilterChain이 결정되면 SecurityFilterChain의 FIlter들이 순서대로 실행되어 인증 및 인가 작업들이 이루어집니다.

 

이제 실제로 보안 작업을 처리하는 SecurityFilterChain의 FilterChain에 대해 알아보겠습니다.

 

 

FilterChain


Spring Security는 여러가지 보안 관련 filter들이 존재하며, HTTP 요청이 이 FilterChain을 통과하면서 보안 관련 작업을 수행합니다.

 

Spring Security에는 다양한 filter가 존재하기 때문에 사용자가 어떤 보안 관련 작업을 수행할지에 따라 filter를 활용할 수 있습니다. 여기서는 대표적인 Filter 몇 가지만 살펴보도록 하겠습니다.

 

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter는 SecurityContext객체를 생성하고 저장하는 역할을 합니다.

SecurityContext는 Authentication(사용자의 인증 정보) 객체를 저장하고 있습니다.

 

SecurityContextPersistenceFilter클래스의 doFilter() 메서드의 일부분을 살펴보겠습니다.

 

 

SecurityContextPersistenceFilter는 HttpSessionSecurityContextRepository를 사용해서 SecurityContext 객체를 생성하고 session에 저장하기 때문에 가장 먼저 session에서 저장된 SecurityContext가 있는지 없는지 검색합니다.

 

HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);

 

이 부분이 session에서 SecurityContext를 검색하는 코드입니다.

this.repo가 SecurityContextRepository으로 HttpSessionSecurityContextRepository을 구현하고 있습니다.

 

 

loadContext()메서드를 통해서 session에 저장한 인증 정보가 있을 경우(이전 사용자의 인증 정보가 있는 경우)

SecurityContext 객체를 가져와서 SecurityContextHolder에 저장합니다. 이렇게 SecurityContextHolder에 Authentication을 저장하고 있는 SecurityContext를 SecurityContextHolder에 저장함으로써 다음 filter에서 SecurityContextHolder를 사용해 현재 사용자의 인증 정보에 접근할 수 있게 됩니다.

 

session에 인증 정보가 없을 경우

SecurityContext를 생성해서 SecurityContextHolder에 저장합니다.

 

모든 HTTP 요청이 처리되면 clearContext()메서드를 통해 SecurityContextHolder을 지웁니다. 이렇게 하지 않으면 다른 스레드나 다음 요청에서 현재 SecurityContext의 정보를 참조할 수도 있습니다.

 

 

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter는 사용자의 아이디와 비밀번호를 이용해 인증을 수행하는 filter입니다.

 

UsernamePasswordAuthenticationFilter의 동작 순서

  1. 사용자의 아이디와 비밀번호를 이용해 UsernamePasswordAuthenticationToken.unauthenticated() 메서드를 통해 UsernamePasswordAuthenticationToken 객체를 만드는데 이 객체는 아직 인증되지 않은 객체입니다.
  2. UsernamePasswordAuthenticationToken은 AuthenticationManager에게 인증 작업을 위임하고 AuthenticationManager는 인증 작업 처리를 AuthenticationProvider에게 위임을 합니다.
  3. 인증이 완료되면 인증이 완료된 "Authenticaion"객체를 SecurityContext에 저장합니다.
  4. 만일 인증이 실패하면 실패했을 때, 어떻게 처리할지 핸들러를 정의할 수 있습니다. 

 

 

SessionManagementFilter

SessionManagementFilter는 session관련 작업을 하는 filter입니다. 

세션 고정 공격 방지, 동시 세션 제어, 세션 타임아웃 등등 세션 관련 보안 문제를 처리합니다

 

SessionFixationProtectionStrategy

세션 고정 공격은 공격자가 특정한 session id를 사용자에게 강제로 전달하여, 사용자가 해당 session id로 인증하면 공격자가 그 session을 이용해 사용자의 권한을 얻는 공격입니다.

 

이 공격을 방지하기 위한 전략 중 하나로 사용되는 것이 SessionFixationProtectionStrategy입니다.

인증에 성공한 사용자에 대해 기존 session은 버리고 새로운 session을 생성해서 부여하여 방지합니다.

 

ConcurrentSession

동일한 사용자로부터 최대 2개의 session만 허용하도록 하여 계정 공유나 동시 접속을 제한할 수 있습니다.

 

 

 

 

 

Spring Security 전체적인 흐름과 인증 및 인가 작업을 진행하는 FilterChain에 대해 간단하게 알아보았습니다.

Filter의 종류에는 위에서 소개한 것 외에도 다양한 Filter가 존재하기 때문에 공식문서를 확인해 주세요

 

 

 

https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/#servlet-delegatingfilterproxy

 

Spring Security Reference

In Spring Security 3.0, the codebase was sub-divided into separate jars which more clearly separate different functionality areas and third-party dependencies. If you use Maven to build your project, these are the modules you should add to your pom.xml. Ev

docs.spring.io