토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

스프링 시큐리티 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

목차:


이번 섹션에선 서블릿 기반 어플리케이션에서 사용하는 스프링 시큐리티의 고수준 아키텍처를 다룬다. 인증, 인가, 취약점 공격 방어 섹션에서 하나씩 심도 있게 살펴볼 것이다.


9.1. A Review of Filters

스프링 시큐리티는 서블릿 Filter를 기반으로 서블릿을 지원하므로, 먼저 일반적인 Filter 역할을 살펴보면 좀 더 이해하기 쉬울 것이다. 아래 이미지는 단일 HTTP 요청을 처리하는 전형적인 레이어를 나타내고 있다:

FilterChain

클라이언트는 어플리케이션으로 요청을 전송하고, 컨테이너는 Servlet과 여러 Filter로 구성된 FilterChain을 만들어 요청 URI path 기반으로 HttpServletRequest를 처리한다. 스프링 MVC 어플리케이션에서의 ServletDispatcherServlet이다. 단일 HttpServletRequestHttpServletResponse 처리는 최대 한 개의 Servlet이 담당한다. 하지만 Filter는 여러 개를 사용할 수 있다. Filter는 보통 다음과 같이 사용한다:

FilterFilterChain 안에 있을 때 효력을 발휘한다.

Example 47. FilterChain Usage Example

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
}

Filter는 다운스트림에 있는 나머지 FilterServlet에만 영향을 주기 때문에, Filter의 실행 순서는 더할 나위 없이 중요하다.


9.2. DelegatingFilterProxy

스프링 부트는 DelegatingFilterProxy라는 Filter 구현체로 서블릿 컨테이너의 생명주기와 스프링의 ApplicationContext를 연결한다. 서블릿 컨테이너는 자체 표준을 사용해서 Filter를 등록할 수 있지만, 스프링이 정의하는 빈은 인식하지 못한다. DelegatingFilterProxy는 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있으면서도, 모든 처리를 Filter를 구현한 스프링 빈으로 위임해 준다.

여기 있는 그림은 DelegatingFilterProxy가 어떻게 여러 Filter로 구성된 FilterChain에 껴들어 가는지 보여준다.

DelegatingFilterProxy

DelegatingFilterProxyApplicationContext에서 Bean Filter0를 찾아 실행한다. 아래 코드는 DelegatingFilterProxy의 슈도코드다.

Example 48. DelegatingFilterProxy Pseudo Code

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}

DelegatingFilterProxy를 사용하면 Filter 빈 인스턴스를 참조를 지연시킬 수도 있다. 컨테이너는 기동하기 전에 Filter를 등록해야 하기 때문에 중요한 기능이다. 하지만 스프링은 보통 Filter 인스턴스들을 등록하는 시점 이후에 필요한 스프링 빈은 ContextLoaderListener로 로드한다.


9.3. FilterChainProxy

스프링 시큐리티는 FilterChainProxy로 서블릿을 지원한다. FilterChainProxy는 스프링 시큐리티가 제공하는 특별한 Filter로, SecurityFilterChain을 통해 여러 Filter 인스턴스로 위임할 수 있다. FilterChainProxy는 빈이기 때문에 보통 DelegatingFilterProxy로 감싸져 있다.

FilterChainProxy


9.4. SecurityFilterChain

FilterChainProxy가 요청에 사용할 스프링 시큐리티의 Filter들을 선택할 땐 SecurityFilterChain을 사용한다.

SecurityFilterChain

SecurityFilterChain에 있는 보안 필터들은 전형적인 빈이지만, DelegatingFilterProxy가 아닌 FilterChainProxy로 등록한다. FilterChainProxy을 직접 서블릿 컨테이너에 등록하거나 DelegatingFilterProxy에 등록하면 좋은 점이 있다. 먼저 스프링 시큐리티가 서블릿을 지원할 수 있는 시작점이 돼준다. 따라서 서블릿에 스프링 시큐리티를 적용하다 문제를 겪는다면 FilterChainProxy부터 디버그 포인트를 추가해 보는 것이 좋다.

FilterChainProxy는 스프링 시큐리티의 중심점이기 때문에 필수로 여겨지는 작업을 수행할 수 있다는 장점도 있다. 예를 들어 SecurityContext를 비워 메모리 릭을 방지할 수 있다. 스프링 시큐리티의 HttpFirewall을 적용해서 특정 공격 유형을 방어할 수도 있다.

게다가 SecurityFilterChain을 어떨 때 실행해야 할지도 좀 더 유연하게 결정할 수 있다. 서블릿 컨테이너에선 URL로만 실행할 Filter들을 결정한다. 하지만 FilterChainProxyRequestMatcher 인터페이스를 사용하면 HttpServletRequest에 있는 어떤 것으로도 실행 여부를 결정할 수 있다.

사실 사용할 SecurityFilterChain 자체를 결정할 때도 FilterChainProxy를 사용한다. 이 덕분에 어플리케이션에선 완전히 설정을 분리해서 여러 슬라이스를 구성할 수 있다.

Multiple SecurityFilterChain

이 이미지에는 SecurityFilterChain이 여러 개 있다. 어떤 SecurityFilterChain을 사용할지는 FilterChainProxy가 결정하며, 가장 먼저 매칭한 SecurityFilterChain을 실행한다. /api/messages/ URL을 요청하면 SecurityFilterChain0/api/** 패턴과 제일 먼저 매칭되므로, SecurityFilterChainn도 일치하긴 하지만 SecurityFilterChain0만 실행한다. /messages/ URL로 요청하면, SecurityFilterChain0/api/** 패턴과는 매칭되지 않기 때문에 FilterChainProxy는 계속해서 다른 SecurityFilterChain을 시도해 본다. 매칭되는 또다른 SecurityFilterChain 인스턴스가 없다고 가정하면, SecurityFilterChainn을 실행한다.

SecurityFilterChain0은 보안 Filter 인스턴스를 세 개만 설정했다는 점에 주목하라. 하지만 SecurityFilterChainn은 보안 Filter를 4개 설정했다. SecurityFilterChain은 고유한, 격리된 설정을 가질 수 있다는 점을 알아두자. 사실, 어플리케이션의 특정 요청은 스프링 시큐리티가 무시하길 바란다면, SecurityFilterChain에 보안 Filter를 0개 설정하는 것도 가능하다.


9.5. Security Filters

보안 필터는 SecurityFilterChain API를 사용해서 FilterChainProxy에 추가한다. 이땐 Filter의 순서가 중요하다. 보통은 스프링 시큐리티의 Filter 순서를 알아야 할 필요는 없다. 하지만 순서를 알아두면 좋을 때도 있다.

다음은 전체 스프링 시큐리티 필터의 순서를 나타낸 것이다:


9.6. Handling Security Exceptions

ExceptionTranslationFilterAccessDeniedException을 해석하고 AuthenticationException을 HTTP 응답으로 바꿔준다.

ExceptionTranslationFilterFilterChainProxy에 하나의 보안 필터로 추가된다.

ExceptionTranslationFilter

어플리케이션에서 AccessDeniedException이나 AuthenticationException을 던지지 않으면 ExceptionTranslationFilter는 아무 일도 하지 않는다.

ExceptionTranslationFilter의 슈도코드는 다음과 같다:

ExceptionTranslationFilter pseudocode

try {
    filterChain.doFilter(request, response); // (1)
} catch (AccessDeniedException | AuthenticationException e) {
    if (!authenticated || e instanceof AuthenticationException) {
        startAuthentication(); // (2)
    } else {
        accessDenied(); // (3)
    }
}

(1) Filter 리뷰 섹션에서 FilterChain.doFilter(request, response)를 호출해서 어플리케이션의 나머지 작업을 이어 처리한다고 했던 게 기억날 것이다. 즉, 어플리케이션에 있는 다른 코드에서 (i.e. FilterSecurityInterceptor 또는 메소드 시큐리티) AuthenticationException이나 AccessDeniedException이 발생하면 여기서 예외를 캐치하고 처리한다.
(2) 인증받지 않은 사용자거나 AuthenticationException이 발생했다면 인증을 시작한다.
(3) 그렇지 않으면 접근을 거부한다.


전체 목차는 여기에 있습니다.

<< >>