본문 바로가기
Back-End/Spring

[Spring Security] 웹 요청 처리 흐름, 서블릿 필터와 필터 체인

by 달의 조각 2022. 9. 22.

보안이 적용된 웹 요청의 일반적인 흐름

  1. 사용자가 보호된 리소스를 요청한다.
  2. 인증 관리자(컴포넌트)가 사용자의 크리덴션(Credential, 증명 수단)을 요청한다. ex) 패스워드
  3. 인증 관리자에게 크리덴션을 제공한다.
  4. 인증 관리자는 크리덴션 저장소에서 사용자의 크리덴션을 조회한다.
  5. 인증 관리자는 사용자가 제공한 크리덴션과 크리덴셜 저장소의 크리덴션을 비교해 검증한다.
  6. 유효한 크리덴션이 아니면 Exception을 throw 한다.
  7. 유효하다면, 접근 결정 관리자(컴포넌트)는 사용자가 적절한 권한을 가지고 있는지 검증한다.
  8. 적절한 권한을 부여받지 못한 사용자라면 Exception을 throw 한다.
  9. 권한이 있는 사용자라면 보호된 리소스의 접근을 허용한다.

 


 

서블릿 필터필터 체인

 

  Spring Security는 서블릿 필터를 기반으로 서블릿[각주:1]을 지원한다. 서블릿 필터는 애플리케이션의 엔드포인트에 요청이 도달하기 전에 가로채서 어떤 처리(전처리)를 할 수 있는 적절한 포인트를 제공한다. 응답을 클라이언트에 전달하기 전에 어떤 처리(후처리)를 할 수도 있다.

클라이언트가 어플리케이션으로 요청을 보내면 컨테이너는 Servlet과 여러 Filter로 구성된 FilterChain을 만들어서 요청 URI path를 기반으로 HttpServletRequest를 처리한다. Spring Security는 대략 25개의 필터를 제공하며, 이 필터들은 조건적으로 적용할 수 있고 Filter 인터페이스를 직접 구성해서 추가할 수도 있다.

단일 요청과 응답 처리는 한 개의 Servlet이 담당하지만 Filter는 여러 개를 사용할 수 있다. Filter는 나머지 Filter와 Servlet에만 영향을 주기 때문에 Filter의 실행 순서는 중요하다. [각주:2]

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // 전처리
    chain.doFilter(request, response);
    // 후처리
}

 

💟 DelegatingFilterProxy

  스프링 부트는 DelegatingFilterProxy라는 Filter 구현체로 서블릿 필터와 스프링의 ApplicationContext를 연결한다. 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있으면서도 모든 처리를 Filter를 구현한 스프링 빈으로 위임해 준다.

Servlet Filter→ DelegatingFilterProxy → SpringSecurityFilterChain
사용자 요청을 위임

 

💟 FilterChainProxy

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

DelegatingFilterProxy가 호출하는 스프링 빈의 타입으로서 요청을 위임받고 인가 처리를 한다. 스프링 시큐리티의 Filter를 사용하기 위한 진입점이다. URL 별로 여러 개 등록이 가능하다.

public class FirstFilter implements Filter {

     public void init(FilterConfig filterConfig) throws ServletException {
     
     }
     
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                          throws IOException, ServletException {
        
        //request를 이용해 다음 Filter로 넘어가기 전처리 작업
        chain.doFilter(request, response);
        //response를 이용해 response에 대한 후처리 작업
     }
     
     public void destroy() {
        //Filter가 사용한 자원을 반납하는 처리
     }
}

 

 

💟 SecurityFilterChain

  FilterChainProxy가 요청에 사용할 스프링 시큐리티의 Filter들을 선택할 때 사용한다. (= 보안을 위한 작업을 처리하는 필터의 모음으로, 요청 URI path를 기반으로 어떤 Filter와 어떤 Servlet을 매핑할지 결정한다.) 내부에 속한 보안 필터들은 전형적인 빈이지만, DelegatingFilterProxy가 아닌 FilterChainProxy로 등록한다. FilterChainProxy를 직접 서블릿 컨테이너에 등록하거나 DelegatingFilterProxy에 등록하면 스프링 시큐리티가 서블릿을 지원할 수 있는 시작점이 되어 주므로 문제가 생겼을 때 FilterChainProxy부터 디버깅을 해 보면 된다.

 

💟 Handling Security Exceptions

  ExceptionTranslationFilterFilterChainProxy에 하나의 보안 필터로 추가된다. AccessDeniedException을 해석하고 AuthenticationException을 HTTP 응답으로 바꿔 준다.

try {
    filterChain.doFilter(request, response); // (1)
} catch (AccessDeniedException | AuthenticationException e) {
    if (!authenticated || e instanceof AuthenticationException) {
        startAuthentication(); // (2)
    } else {
        accessDenied(); // (3)
    }
}
  1. FilterChain.doFilter(request, response)를 호출해서 어플리케이션의 나머지 로직을 실행한다.
  2. 인증받지 않은 사용자였거나 AuthenticationException이 발생했다면 인증을 시작한다.
    1. SecurityContextHolder를 비운다.
    2. RequestCacheHttpServletRequest를 저장한다. 인증에 성공하면 RequestCache로 요청 처리를 이어간다.
    3. AuthenticationEntryPoint는 클라이언트에 credential을 요청할 때 사용한다.
  3. 반대로 AccessDeniedException이었다면 접근을 거부한다. 요청은 AccessDeniedHandler에서 처리한다.

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

 


📚 Reference

 

  1.  서블릿(Servlet)이란 동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다. 서블릿은 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해 준다. [본문으로]
  2. Order 애너테이션을 추가하거나 Orderd 인터페이스를 구현하는 방법 및 FilterRegistrationBean으로 명시적으로 지정하여 순서를 지정할 수 있다. [본문으로]

댓글