[Spring] 공식문서를 기반으로 스프링 시큐리티(Spring Security) 기본 개념과 구조 파악하기

kai6666·2022년 7월 30일
2

TIL. Spring

목록 보기
9/11

스프링 시큐리티란?

스프링 시큐리티는 인증(Authentication), 인가(Authorization), 공격으로부터의 보호(protection against common attacks)를 제공하는 프레임워크이다. 명령형(imperative), 선언형(reactive) 애플리케이션을 지원하고, 사실상 스프링을 기반으로 한 애플리케이션의 보안에 있어서는 표준 프레임워크이다.

Spring Security Architecture

스프링 시큐리티의 동작 방식을 이해하기 위해서는 전체적인 아키텍처를 알아야 한다. 공식 문서에 따르면 다음과 같은 구성 요소들이 있다.

  • Filters
  • DelegatingFilterProxy
  • FilterChainProxy
  • SecurityFilterChain
  • Security Filters
  • Handling Security Exceptions

지금부터 차례대로 살펴볼 것이다.


Filters

스프링 시큐리티는 Servlet Filters를 기반으로 서블릿 지원을 하기 때문에 Filter의 역할에 대해 먼저 이해하는 것이 좋다. 아래 그림은 하나의 HTTP 요청이 들어왔을 때의 처리를 계층적으로 표현한 것이다.

요청이 들어올 때, 컨테이너는 FilterServlet으로 구성된 FilterChain을 만든다. FilterChain은 말 그대로 여러 개의 필터가 연결된 필터 사슬이다.

컨테이너는 요청이 들어올 때 요청의 URI의 패스에 따라 어떤 FilterChainServlet을 적용할지 결정한다.

스프링 MVC 애플리케이션에서 서블릿은 DispatcherServlet의 인스턴스이기 때문에, 하나의 서블릿은 하나의 요청과 응답( HttpServletRequestHttpServletResponse)만 처리할 수 있다.

그런데 Filter

  • 다운스트림 Filter 혹은 Servlet실행하는 것을 막을 수 있다.
  • 다운스트림 Filter 혹은 Servlet에 사용될 HttpServletRequestHttpServletResponse수정할 수 있다.
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
}

위 코드는 FilterChaindoFilter() 메서드인데, 이 메서드는 필터를 실행하기 전후로 코드를 넣을 수 있어 이후 필터와 서블릿에 영향을 끼칠 수 있다. 따라서 필터를 사용할 때는 순서에 매우 유의하여야 한다.


DelegatingFilterProxy

DelegatingFilterProxy는 스프링이 제공하는 서블릿 Filter의 구현체로 서블릿 컨테이너의 생명주기와 스프링 ApplicationContext를 연결한다.

서블릿 컨테이너의 필터 기술과 스프링의 빈 관리 및 앱 실행 기술에는 간극이 있다. 서블릿 컨테이너는 Filter를 등록하게 해주지만, 스프링은 이를 빈으로 인식하지 않는다. 따라서 관리를 하지 못한다. 이 문제를 해결하기 위해 DelegatingFilterProxy를 사용한다.

DelegatingFilterProxy를 쓰면 필터 클래스를 스프링의 빈으로 등록시켜서 스프링의 관리를 받게 하면서 필터의 기능과 스프링의 빈 의존성에 따른 이점을 함께 누릴 수 있다.

DelegatingFilterProxyApplicationContext에서 Bean Filter를 찾아서 실행한다. 이 말인 즉슨 DelegatingFilterProxy 자체는 인증, 인가를 하지 않는다는 의미다. 그저 Filter 인터페이스를 상속받은 스프링 빈을 Delegate하는 중간다리 역할일 뿐이다. (Delegate = 대리자)



FilterChainProxy

FilterChainProxy는 스프링이 지원하는 특수한 Filter다.

FilterChainProxy는 보통 스프링 빈이기 때문에 DelegatingFilterProxy로 감싸져 있다. 주요 역할은 요청에 따라 필요한 필터를 실행시키는 것인데, DelegatingFilterProxy와 마찬가지로 어떤 로직을 수행한다기보다는 위임자의 역할을 수행한다.

차이점은 FilterChainProxy는 서블릿 컨테이너가 아닌 스프링 시큐리티가 제공한다는 것이다. 스프링 시큐리티는 FilterChainProxy를 통해 서블릿을 지원한다.


SecurityFilterChain

public interface SecurityFilterChain {

	boolean matches(HttpServletRequest request);

	List<Filter> getFilters();

}

SecurityFilterChainFilterChainProxy가 요청에 따라 어떤 필터 체인을 실행시켜야할지 파악하는데 쓰인다.

SecurityFilterChain 안의 Security Filters는 스프링 빈인데, 이것들은 DelegatingFilterProxy이 아닌 FilterChainProxy에 등록되어 있다. 이러한 이유는,

  • FilterChainProxy가 스프링 시큐리티에서 동작하는 모든 서블릿의 시작점이다.
    서블릿 지원에 문제가 발생했을 때, 디버그를 FilterChainProxy부터 시작하면 된다.

  • FilterChainProxy가 스프링 시큐리티의 핵심이기 때문에, 보이지 않는 작업을 수행할 수 있다.
    예를 들어 SecurityContext를 비워 메모리 누수를 막을 수 있다. 또한 스프링 시큐리티의 HttpFirewall을 적용해 애플리케이션이 특정 유형의 공격에 당하는 것으로부터 보호할 수 있다.

  • FilterChainProxySecurityFilterChain의 호출 시기 결정에 유연성을 가져다준다.
    서블릿 컨테이너에서 Filter는 오직 URL에 따라 실행된다. FilterChainProxy는 요청의 RequestMatcher 인터페이스를 활용해 실행 시점을 파악한다.

예를 들어 요청의 URL이 /api/messages일 경우, FilterChainProxy로 인해 /api/** 패턴의 SecurityFilterChain 0이 실행된다. /** 패턴의 SecurityFilterChain n도 해당은 되지만 가장 먼저 일치하는 SecurityFilterChain이 실행된다.

요청 URL이 /messages/일 경우는 SecurityFilterChain 0이 실행되지 않는다. 때문에 FilterChainProxy는 계속해서 일치하는 것을 찾다가, SecurityFilterChain n을 실행시킨다.



Security Filters

Security Filter는 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


Handling Security Exceptions

ExceptionTranslationFilterAccessDeniedExceptionAuthenticationException을 HTTP 응답으로 변환해준다.


이 필터는 FilterChainProxySecurity Filter 중 하나로 들어가 있다. 예외 처리의 흐름은,

  • ExceptionTranslationFilterFilterChain.doFilter(request, response)를 실행해 전체 애플리케이션을 실행한다.
  • 인증되지 않은 유저이거나 AuthenticationException인 경우 인증을 시작한다.
    • SecurityContextHolder를 비운다.
    • RequestCache에 HttpServletRequest를 저장한다.
      - 성공적으로 인증할 경우 RequestCache로 원래 요청으로 돌아간다.
    • AuthenticationEntryPoint는 유저에게 자격 증명(credentials)을 요청할 때 쓴다.
  • 인증이 되지 않을 경우, AccessDeniedException이 발생한다. AccessDeniedHandler가 실행돼 접근을 제한한다.

애플리케이션이 AccessDeniedException 혹은 AuthenticationException을 던지지 않는 경우는, ExceptionTranslationFilter이 아무 일도 하지 않는 것이다.


참고자료

profile
성장 아카이브

0개의 댓글