[Spring Security] Spring security 구조

sangwoo·2022년 8월 10일
0

💡 참고

현재 프로젝트에 스프링 시큐리티 5.7.6을 사용 중 따라서 공식문서의 버전도 5.7.6 / 공식문서 - https://docs.spring.io/spring-security/reference/5.7.6/servlet/architecture.html

스프링 부트가 자동으로 설정

  • 스프링 부트는 스프링 시큐리티를 기본적으로 구성하는데, 서블릿 필터를 사용한 springSecurityFilterChain 빈을 만들어 구성한다.

  • user의 username과 콘솔에 기록되는 임의로 생성된 암호를 사용한 UserDetailService를 생성

1. 필터📍

서블릿에서의 스프링 시큐리티는 서블릿 필터를 사용한다. 아래의 그림은 단일 HTTP request의 일반적인 핸들러 레이어 계층을 보여주는 그림이다.

위 그림을 간단히 말하면

  1. 클라이언트는 애플리케이션에게 요청을 보낸다.
  2. 컨테이너는 요청 URI를 기반으로 처리해야하는 Filter들 또는 Servlet을 포함한 FilterChain이라는 것을 만든다.
  3. 이때 스프링 MVC에서 서블릿은 DispatcherServlet의 인스턴스이다.

FilterChain을 통해 해당 스프링 MVC에서는 DispatcherServlet인 서블릿에 요청이 들어가기전에 여러 필터를 걸쳐 요청에 대한 처리를 할 수 있고, 반대로 클라이언트에 요청이 보내어질 때 필터를 통해 응답에 대한 처리 후 나갈 수 있다.

그리고, 최대 하나의 서블릿이 단일 HttpServletRequest 및 HttpServletResponse를 처리할 수 있다. 하지만 두 개 이상의 필터를 사용하여 다음을 수행 할 수 있다.

  • 후속 filter들과 서블릿을 호출 방지.
    이러한 경우 FIlterHttpServletResponse를 작성한다.

  • 후속 Filter들과 서블릿에서 사용되는 HttpServletRequest, HttpServletResponse 수정.

모든 Filter들은 FilterChain으로 통과하기 때문에 Filter의 핵심은FilterChian 이다.

필터 체인 사용 예제

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// 해당 필터에서 요청이 들어왔고 ServletRequest를 가지고 전 처리
    chain.doFilter(request, response); // 애플리케이션에 서블릿 정보를 넘긴다.
    // 애플리케이션에서 응답이 나오는 부분. ServetResponse로 응닫 처리
}

2. DelegatingFilterProxy📍

스프링은 서블릿 컨테이너(ServletContainer)의 생명주기(lifecycle)와 스프링 애플리케이션 컨텍스트(ApplicationContext)를 서로 연결하기 위해 DelegatingFilterProxy라는 이름의 Filter 구현체를 제공한다.

이러한 이유는 서블릿 컨테이너(ServletContainer)는 자체 표준을 사용하여 Filter들을 등록하는데, 스프링에 정의된 빈(Bean)에 대해서는 알지 못하기 때문이다.

그리고 이러한 DelegatingFilterProxy는 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있지만, Filter를 구현한 스프링 빈에게 모든 작업을 위임한다.

아래 그림은 DelegatingFilterProxyFilterChain에 어떻게 등록되어 있는지 볼 수 있다.

DelegatingFilterProxy는 애플리케이션 컨텍스트(ApplicationContext)에서 Bean Filter0을 찾고 호출 한다.

아래는 DelegatingFilterProxy의 수도 코드이다.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	
    
	Filter delegate = getFilterBean(someBeanName);
    
    // 작업을 스프링 Bean에게 위임
	delegate.doFilter(request, response);
}

DelegatingFilterProxy 사용의 또다른 이점은, Filter 빈 인스턴스 검색을 지연 시킬 수 있다는 건데, 이는 컨테이너가 시작되기 전에 Filter인스턴스를 등록해야 하기 때문에 매우 중요하다.

스프링은 일반적으로 ContextLoaderListener를 사용해 스프링 빈을 로드하는데, 이러한 작업은 Filter 인스턴스가 등록 되기 전까지는 실행 되지 않는다.

📄 DelegatingFilterProxy 정리

서블릿은 자체 표준이 존재하고 스프링은 빈으로 모든 것을 정의한다. 이를 연결하기 위해 DelegatingFilterProxy를 만들어 스프링이 빈으로 정의한 필터를 서블릿에서 사용할 수 있게 한다.

3. FilterChainProxy 📍

스프링 시큐리티의 서블릿 지원은 FilterChainProxy에 포함되어 있다. 이러한 FilterCHainProxySecurityFilterChain을 통해 많은 Filter 인스턴스를 위임할 수 있도록 스프링 시큐리티에서 제공하는 특별한 필터이다.

FilterChainProxy는 스프링 시큐리티에서 빈으로 제공하기 때문에 DelegatingFilterProxy에 감싸져 있다.

4. SecurityFilterChain 📍

FilterChainProxy는 요청에 대해 스프링 시큐리티 필터를 호출해야 하는 데, 이 때 사용되는 것이 SecurityFilterChain 이다.

SecurityFilterChain안에 있는 시큐리티 필터들은 일반적으로 빈(Bean)이고, DelegatingFilterProxy 대신에 FilterChainProxy에 등록이 되어있다.

여기서 FilterChainProxy는 서블릿 컨테이너 또는 DelegatingFilterProxy에 직접 등록은 많은 이점을 제공한다.

  1. 스프링 시큐리티 서블릿 지원에 대한 모든 시작 포인트를 제공한다. 해당 서블릿 컨테이너의 모든 보안은 FilterChainProxy에서 시작됨을 말한다.

    이러한 이유로 스프링 시큐리티의 서블릿 지원 문제를 해결하려는 경우에 FilterChainProxy에서 디버그 포인트를 지정하는 것이 제일 좋은 디버그 시작 지점이 된다.

  2. FilterChainProxy는 스프링 시큐리티 사용에 있어서 핵심 부분으로, 필수로 여겨지는 여러 작업들을 FilterChainProxy에서 실행하게 할 수 있다.

    예를 든다면, 메모리 누수 방지를 위해 SecurityContext를 삭제하거나, HttpFirewal을 사용하여 애플리케이션을 특정 공격으로 부터 보호하는 등의 설정을 할수 있다.

  3. SecurityFilterChain이 호출되는 시기를 유연하게 조절할 수 있도록 한다.
    서블릿 컨테이너에서 필터들은 URL 기준으로만 호출이 되는데, FilterChainProxyRequestMatcher 인터페이스를 활용하여 HttpServletRequest에 대한 모든 항목을 기준으로 호출할 수 있다.

사실은 이러한 FilterChainProxy는 사용할 SecurityFilterChain이 무엇인지에 따라 결정된다. 이렇게 한다면, 애플리케이션의 여러 슬라이드에 대해 완전히 별도인 구성을 제공할 수 있다.


위의 다중 시큐리티 필터체인 그림에서 FilterChainProxy는 사용할 SecurityFilterChain을 결정한다.

일치하는 첫번째 SecurityFilterChain만 호출 된다. 즉, 만약 /api/messages/ URL이 요청되었다면, SecurityFilterChain0의 패턴 /api/**SecurityFilterChain n의 패턴 /** 중 처음 매치되는 SecurityFilterChain0이 호출 된다

그리고 만약에 /messages의 요청이 온다면, SecurityFilterChain0의 패턴은 /api/**이기에 매칭되지 않을 것이다. 그러게 되면 FilterChainProxy는 각각의 SecurityFilterChain와 계속에서 매치를 시도하게 된다.

더는 시도할 SecurityFilterChain이 없다면 SecurityFilterChain0이 호출 된다.

📄 SecurityFilterChain 정리

  • SecurityFilterChain은 실제 스프링 시큐리티 필터에 사용되는 필터들을 엮어놓은 체인과 같다.

  • FilterChainProxyScurityFilterChain을 사용한다.

  • SecurityFilterChainFilterChainProxy를 통해 사용하는 장점은 아래와 같다.
    1. debug point 제공
    2. 필수 작업 실행 제공
    3. 호출 시기 조절 제공

  • URL 패턴에 따라 각 SecurityFilterChain이 존재하고, 특정 URL 요청이 들어오면 FilterChainProxy는 해당 패턴에 맞는 SecurityFilterChain을 사용한다.

  • 처음으로 URL과 매치되는 SecurityFilterChain을 사용한다.

5. Security Filters 📍

시큐리티 필터들은 SecurityFilterChain API에 의해 FilterChainProxy에 등록이 된다. 이러한 필터들은 순서가 중요한데, 물론, 모든 순서를 알 필요는 없지만 어느정도 참고 한다면, 도움이 될 때가 있다.

아래는 스프링 시큐리티 필터 순서의 일반적인 리스트이다.

  • ForceEagerSessionCreationFilter

  • 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

6. Security 예외 처리 📍

ExceptionTranslationFilter는 HTTP response들에 대한AccessDeniedException, AuthenticationException의 변환을 할 수 있다.

이러한 ExceptionTranslationFilter는 시큐리티 필터 중 하나로 FilterChainProxy에 등록된다.

  1. ExceptionTranslationFilterFilterChain.doFilter(request, response)를 호출하여 나머지 응용프로그램을 호출한다.

  2. 사용자가 인증되지않았거나, AuthenticationException이라면, Start Authentiactaion 과정으로 이동한다.
    Start Authentication

    • SecurityContextHolder를 지운다.
    • RequestCacheHttpServletRequest가 저장된다. 사용자 인증이 성공적으로 되었다면, RequestCache는 원래 요청을 재실행 하기 위해 사용된다.
    • AuthenticationEntryPoint는 서버에서 클라이언트로 자격 증명을 요청하기 위해 사용된다. 예를 들어, 로그인 페이지로 리다이렉트라던가, WWW-Authenticate 헤더를 전송하는 등의 예제가 있다.
  3. 그 이외에는 Access Denied가 발생한다. 여기서 AccessDeniedHandler는 access denied를 다루기 위해 호출 된다.

📌 NOTE
애플리케이션에서 AccessDeniedException 또는 AuthenticationException이 터지지 않는다면, ExceptionTranslationFilter는 어떤 동작도 하지 않는다.

ExceptionTranslationFilter의 수도 코드는 아래와 같다.

try {
	filterChain.doFilter(request, response); // - 1
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication(); // - 2
	} else {
		accessDenied(); // - 3
	}
}
  1. 필터 부분에서 본 바와 같이 FilterChain.doFilter(request, response)는 다음 필터가 존재한다면 계속해서 체인으로 호출하는 역할이다. 그래서 FilterSecurityInterceptor, method security등, 애플리케이션의 다른 부분에서 AuthenticationException, AccessDeniedException 등이 던져진다면, 여기서 캐치해 처리한다.

  2. 만약 사용자가 인증되지 않은 경우, AuthenticationException이 터진 경우, Start Authentication을 호출한다.

  3. 그외에는 Access Denied를 호출한다.

7. 정리 📍

  • 스프링 시큐리티의 서블릿 지원은 필터로 구성된다.

  • 서블릿 컨테이너는 스프링의 빈 등록을 알지 못하기에 DelegatingFilterProxy를 사용하여 스프링에서 등록한 빈(Bean) 필터를 사용할 수 있게 한다.

  • 스프링 시큐리티는 FilterChainProxy에 포함되어 있다. 이러한 FilterChainProxy에는 몇가지 장점이 있다.
    1. 시큐리티의 시작포인트를 알수 있어 디버그 포인트를 잡기 용이
    2. 필수로 여겨지는 여러 작업들을 여기서 수행가능
    3. 스프링 시큐리티의 호출 시기 조절 가능

  • 시큐리티 필터는 SecurityFilterChain으로 구성되어 있고 FilterChainProxy가 해당 SecurityFilterChain을 사용한다.

  • 예로 든다면, URL 패턴에 따른 SecurityFilterChain이 여러개 존재하고, 특정 URL이 들어온다면, 바로 처음 매치되는 URL 패턴에 맞는 SecurityFilterChain이 호출된다.

  • 시큐리티의 예외는 사용자가 인증되지 않았거나, AuthenticationException이 발생할 경우 클라이언트로부터 자격 증명을 하도록 요청하는 start Authentication을 호출한다.

  • 그외의 예외는 Access DeniedAccessDeniedHandler를 호출한다.

0개의 댓글