작업 일지#6 서비스에 Security를 적용해보자 -2- 스프링 시큐리티의 구조 이해하기

2021. 11. 25. 21:23작업일지

개요

저번 게시글(https://postwithmemory.tistory.com/8)에서는%EC%97%90%EC%84%9C%EB%8A%94) 인증 방식에 대해 간단히 정리하고, 시큐리티를 적용한 이유에 대해서 간단히 살펴 보았다. 그리고, 인증과 인가와 관련된 서비스 로직의 플로우 차트를 간단하게 그려보았다. 이번 게시글에서는 Spring Security의 구조를 간단히 짚어보고, 프레임워크에 플로우 차트를 적용하는 과정을 정리해볼까 한다.

INDEX

  • 스프링 시큐리티의 구조
    • Filters
    • DelegatingFilterProxy
    • FilterChainProxy
    • SecurityFilterChain
    • Security Filters
    • Handling Security Exception
  • 인증

스프링 시큐리티의 구조

Filters

스프링 시큐리티를 매우 간단하게 설명해보자면, 보안과 관련된 서블릿 Filter를 스프링 컨테이너와 연동 시키는 기능을 지원하는 프레임워크라 할 수 있다.

필터의 경우, 서블릿 request/response와 달리, 하나의 컨테이너에 여러 개를 만들어서, 서로 연결 시켜 하나의 체인을 형성할 수 있다. 필터의 코드 구조를 간단하게 예를 들자면 다음과 같을 수 있다.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

주의 해야할 점은 하나의 필터는 다운 스트림에 있는 나머지 필터와 서블릿에 영향을 주기 때문에 필터의 순서는 정말 중요하다.

DelegatingFilterProxy

스프링은 DelegatingFilterProxy라는 Filter 구현체로 서블릿 컨테이너를 ApplicationContext와 연결시킨다.

일반적으로 Filter 자체는 서블릿 컨테이너에서 등록하므로, 스프링이 정의하는 빈을 인식하지 못한다. 하지만 DelegatingFilterProxy을 통해, 필터를 표준 서블릿 컨테이너 매커니즘으로 등록할 수 있으면서, 모든 처리를 Filter를 구현한 스프링 빈으로 위임해준다.

FilterChainProxy

스프링 시큐리티는 DelegatingFilterProxy를 감싼 FilterChainProxy을 통해 서블릿을 지원한다.

SecurityFilterChain

스프링 시큐리티는 FilterChainProxty를 통해 SecurityFilterChain에 보안 관련 필터체인을 요청한다. SecurityFilterChain의 경우, URI에 따라, SecurityFilterChain을 다르게 분리해서 적용할 수 있다.

Security Filters

보안 필터는 SecurityFilterChain API를 사용해서 FilterChainProxy에 추가한다. 당연하게도 필터의 순서는 중요하지만, 일반적으로는 스프링 시큐리티의 Filter 순서를 알아야 할 필요는 없다.

Handling Security Exception

SecurityFilterChain의 구성요소 중에는 ExceptionTranslationFilter가 있는데, 이 Filter는 AccessDeniedException이나, AuthenticationException을 감지하여, HTTP 응답으로 바꿔준다.

그 외의 Filter들

스프링 시큐리티 자체에서 제공하는 Filter들의 종류는 매우 다양하다. 드문 경우긴 하지만, SecurityFilterChain API를 활용하여, 보안 필터를 추가할 때, Filter의 순서를 아는 것이 중요하다. 스프링 시큐리티에서 제공하는 필터의 목록은 다음과 같다.

  • ChannelProcessingFilter
  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

인증/인가

스프링 시큐리티는 보안과 관련된 필터 뿐만 아니라, 사용자의 인증(Authentication)과 인가(Authorization)에 대한 api도 제공한다. 인증의 의미와 종류에 대한 설명은 https://postwithmemory.tistory.com/7 게시글을 참고하면 좋겠다.

인증(Authentication)

디테일한 내용은 공식 문서에 자세히 나와있다는 점에서 모든 구성요소를 자세히 이야기 하는 것은 큰 의미가 없을 것 같다. 그래서, 우선은 이해하기 쉽게... 그리고, 프레임워크가 알아서 해주는 부분은 최대한 추상화하고, 개발자가 알아야 하거나, 구현해야하는 부분 위주로, 정리 해보겠다.

우선, 스프링 시큐리티로 인증을 실행할 경우의 흐름을 최대한 간단하게 그림으로 표현 해보았다.

authentication_flow drawio

여기에 나온 순서는 외울 필요도 없고, 전부 다 커스터마이징할 생각이 없다면, 구성요소 하나하나를 자세하게 알아야만하는 이유도 없다. 다만 꼭 알고 넘어가야하는 부분은 4 - 8까지의 과정이다. 작동원리야 어쨋든, 로그인시 입력한 인증정보 토큰 값(일반적으로 아이디/비밀번호를 검증받고 시큐리티로부터 받은 일종의 일련번호)은 결과적으로 Manager 로부터 받아와서, Provider 부터 UserDetails 까지 개발자의 입맛에 따라 구성할 줄 알면, 큰 무리는 없을 거라고 본다.

1-10까지 간략하게 설명해보자면...

  1. AuthenticationFilter에 요청.
    • 여기서 말하는 AuthenticationFilter는 위에서 이야기한 SecurityFilterChain에 해당하는 내용이다.
    • 일반적인 방식으로 (아이디/비밀번호 입력) 인증을 시도한다면, UsernamePasswordAuthenticationFilter를 통해 검증을 시도한다.
  2. UsernamePasswordAuthenticationToken 발급
    • 검증이 통과되면 UsernamePasswordAuthenticationToken을 발급 하는데, 이 토큰은 일종의 Authentication을 나타낸다.
    • 이해하기가 어렵다면, 그냥 인증용 객체라고 봐도 무방하다.
  3. AuthenticationManager
    • 일종의 위임 객체로 ProviderManager.authenticate(Authentication authentication)을 통해, AuthenticationProvider에 인증 로직을 위임한다.
    • AuthenticationProvider는 개발자가 직접 인터페이스를 구현하여 사용할 수도 있고, 그냥 프레임워크에서 제공하는 AuthenticationProvider 구현체인 DaoAuthenticationProvider를 사용할 수도 있다.
    • 커스텀 AuthenticationProvider를 구현하는 방법은 https://www.baeldung.com/spring-security-authentication-provider를 활용해보자.
    • 그림에서 표현한 4 - 8의 과정은 DaoAuthenticationProviderUserDetailsService의 로직 흐름을 그대로 표현하였다. 자세한 내용을 알고 싶으면, api 문서나 혹은 intelliJ와 같은 IDE를 활용하여 소스코드를 직접 추적해보는 것을 추천한다.

 

마무리

Security를 적용하는 것 자체가 나한테 꽤 어렵게 받아들여졌다. 알아야 하는 개념도 많아서, 바로 코드로 들어가는데 까지 준비하는 시간이 생각보다 길어져서, 계속 코드를 구현한다기 보다는, 이론을 정리하는 단계가 많아지고 있다.

지금까지는 Security를 적용하기 전에, 인증의 개념을 정리해보고, Spring Security의 구조를 살펴보고 요약을 해보았다. 다음 일지에서는 내가 설계한 로직을 프레임워크에 어떻게 적용하는지에 대한 과정을 정리해보도록 하겠다.