Spring Security의 Filter, Provider, Handler를 직접 구현해봤습니다. 이번에는 이러한 동작들이 실행되는 과정을 살펴보겠습니다. 


 

위와 같은 이미지로 Security Basic(0)에서 한 번 다룬 적이 있습니다. 이번엔 어떤 과정을 거치는지 살펴보겠습니다.

저희가 Custom 하게 만든 CustomAuthenticationFilter를 포함해서 여러 가지 필터들이 있습니다. 필터들이 서로 연결돼 다음 필터를 호출하는 형태입니다.

 

그중에서 FilterChain을 상속받고, ThreadLocal로 ServletRequest, ServletResponse를 ApplicationFilterChain이 있습니다. 이것은 필터들의 시작점이며, 위에서 말씀드린 것처럼 필터를 호출해서 시작하게 됩니다. 

 

 

아래 코드가 의미하는 것은 한 필터가 여러 번 호출하는 것을 막기 위함입니다. 

for(ApplicationFilterConfig filter:filters) {
    if(filter==filterConfig) {
        return;
    }
}

 

저희가 만든 CustomAuthenticationFilter를 포함해서 총 4개의 필터가 등록됩니다. 

1. CharacterEncodingFilter

- 필터명 그대로 Request에 대해서 Encoding을 해주는 필터입니다. 해당 필터가 끝나면 다음 필터인 SpringSecurityFilterChain을 호출하게 됩니다. 

 

2. SpringSecurityFilterChain(AbstractAuthenticationProcessingFilter , SecurityContextPersistenceFilter, ConcurrentSessionFilter, UsernamePasswordAuthenticationFilter)

 

- AbstractAuthenticationProcessingFilter: HTTP 기반 인증 요청의 추상 프로세서로서 인증에 대한 요청이 들어왔을 때 가장 먼저 동작하게 됩니다. 

 

- SecurityContextPersistenceFilter: Request 한 번에 반드시 한 번만 호출되며,  SecurityContext라는 녀석을 영속화를 해주는 역할을 합니다. 이 과정을 거치며, 세션에 SecurityContext를 저장하고, 요청 전반에 걸쳐 SecurityContextHolder를 이용해서 SecurityContext에 접근할 수 있게 해주는 역할을 합니다. 

 

- ConcurrentSessionFilter: session은  톰캣과 같은 서블릿 컨테이너에서 제공합니다. 따라서 스프링이 세션을 제어할 수 없습니다. 톰캣이 넘겨주는 세션을 스프링은 sessionInformation이라는 래퍼 객체를 만들어 SessionRegistry에서 관리를 함으로써 톰캣의 세션과  SessionRegistry의 세션이 일치한다고는 할 수 없지만 요청이 들어왔을 때 스프링에서 sessionInformation의 expired를 통해 해당 세션을 스프링 컨테이너로 받아 줄지 말지를 결정할 수 있기 때문에 세션을 관리하는 효과를 낼 수 있습니다. 이런 역할을 하는 필터입니다. 

 

- RequestCacheAwareFilter: 저장된 요청(인증이 성공한 요청)이 캐시되고 현재 요청과 일치하는 경우 해당 요청을 재구성합니다.

 

-ExceptionTlansrationFilter: 필터체인 내에서 생성되는 모든 예외를 처리하며 인증 성공후에 요청에 대한 정보를 캐싱하는 역할을 해준다. 

 

SessionManagementFilter: 요청을 시작한 이후 사용자가 인증되었음을 감지하고 인증한 경우 구성된 세션을 호출해줍니다.

 

- UsernamePasswordAuthenticationFilter: AbstractAuthenticationProcessingFilter를 상속받아 인증 관련 정보를 처리하는 역할을 합니다. 

 

3. CustomAuthenticationFilter

- 저희가 만든 필터로서 UsernamePasswordAuthenticationFilter 앞에 두고, 먼저 인증을 처리하는 필터입니다.

 

4. Tomcat WebSocket Filter

- 서버 구축하는데 필요한 필터입니다.(자세한 사항은 나중에 더 공부하고 추가하겠습니다.)

 

위와 같은 필터들이 차례로 호출되게 됩니다. 

 

그 과정에서 AbstractSecurityInterceptor가 동작하게 됩니다. 이것은 요청에 대해서 보안 즉 인증을 진행해야 하는 요청인지 판별하고, Authentitcation 인증이 필요할 경우 얻는 과정을 진행합니다. 

 

그렇게 Authenctication이 필요 없는 요청이었다면, 그저 AuthenticationFilter들을 호출하지 않은 채 흘러갑니다. 그리고 /login을 진행할 때 AuthenticationFilter들이 동작해서 Authentication을 얻을 수 있게 해줍니다. 이때 AbstractAuthenticationProcessingFilter이 가장 먼저 동작하게 됩니다. 

 

1. doFilter로 실행되며, 요청에 대한 Authentication을 얻으려 합니다. 

 

1-2. 해당 attemptAuthentication은 원래라면 UsernamePasswordAuthenticationFilter가 해당되겠지만 저희는 이것보다 저희가 만든 CustomAuthticationFilter를 앞에 뒀기 때문에 해당 필터가 동작합니다. 

 

 

2. 그 후 위에서 봤던 ProviderManager를 상속받아 저희가 만든 동작하게 CustomAuthenticationProvider이 동작됩니다. 해당 동작으로 권한을 가진 UsenamePasswordAuthenticationToken이 완성됩니다.

 

AbstractAuthenticationProcessingFilter에서 인증에 실패했을 때와 성공했을 때 로직을 호출해줍니다. 

 

인증에 성공했다면 저희가 설정한 CustomLoginSuccessHandler를 통해서 Session을 부여하는 등 후처리를 진행할 수 있습니다.  

 

실패시 FailureHandler가 동작합니다. 저희는 따로 정의하지 않았지만 HttpSecurity에 람다식으로도 Handler를 정의할 수 있습니다. 아래는 로그인 실패 시 /login으로 보내주는 과정을 거칩니다. 

 

 

RequestCacheAwareFilter가 존재합니다. 해당 부분은 로그인을 성공했을 때 ExceptionTlansrationFilter에서 요청 자체를 캐싱합니다. 그 후 캐싱이 존재하는지 확인한 후 케싱이 존재한다면, Request를 감싸서 인증 정보를 넣어줍니다. 

 

그 후 SessionManagementFilter를 거치는데 해당 필터가 하는 일은 요청을 시작한 이후 사용자가 인증되었음을 감지하고 인증한 경우 구성된 세션을 호출해줍니다. 

 

이렇게해서 대략적인 과정에 대한 필터를 살펴봤습니다. 이외에도 다른 필터들도 있지만 설명은 생략하겠습니다. (이미지로 모든 필터를 첨부하겠습니다.)


정리하기 

1. 여러 개의 필터들이 연쇄적으로 호출하게 되며, 인증과 인가를 진행하게 된다. 

2. 이때 인증과 인증이 필요한 URL에 해당하는 것들만 인증을 요구할 수 있도록 하는 AbstractSecurityInterceptor가 존재한다. 

3. 따라서 전체 순서도는 처음 사진에서 봤듯이 Filter -> AuthenticationManager를 구현한 ProviderManager에서 직접 Custom 한 Providers가 동작하며, 인증을 하고 SecurityContextHolder에 Authentication을 저장합니다. 

 

잘못된 부분에 대해서 지적해주신다면 감사하겠습니다.