
스프링 시큐리티는 인증(Authentication), 인가(Authorization), 공격으로부터의 보호(protection against common attacks)를 제공하는 프레임워크이다. 명령형(imperative), 선언형(reactive) 애플리케이션을 지원하고, 사실상 스프링을 기반으로 한 애플리케이션의 보안에 있어서는 표준 프레임워크이다.
스프링 시큐리티의 동작 방식을 이해하기 위해서는 전체적인 아키텍처를 알아야 한다. 공식 문서에 따르면 다음과 같은 구성 요소들이 있다.
지금부터 차례대로 살펴볼 것이다.
스프링 시큐리티는 Servlet Filters를 기반으로 서블릿 지원을 하기 때문에 Filter의 역할에 대해 먼저 이해하는 것이 좋다. 아래 그림은 하나의 HTTP 요청이 들어왔을 때의 처리를 계층적으로 표현한 것이다.

요청이 들어올 때, 컨테이너는 Filter와 Servlet으로 구성된 FilterChain을 만든다. FilterChain은 말 그대로 여러 개의 필터가 연결된 필터 사슬이다.
컨테이너는 요청이 들어올 때 요청의 URI의 패스에 따라 어떤 FilterChain과 Servlet을 적용할지 결정한다.
스프링 MVC 애플리케이션에서 서블릿은 DispatcherServlet의 인스턴스이기 때문에, 하나의 서블릿은 하나의 요청과 응답( HttpServletRequest와 HttpServletResponse)만 처리할 수 있다.
그런데 Filter는
Filter 혹은 Servlet이 실행하는 것을 막을 수 있다.Filter 혹은 Servlet에 사용될 HttpServletRequest와 HttpServletResponse를 수정할 수 있다.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
}
위 코드는 FilterChain의 doFilter() 메서드인데, 이 메서드는 필터를 실행하기 전후로 코드를 넣을 수 있어 이후 필터와 서블릿에 영향을 끼칠 수 있다. 따라서 필터를 사용할 때는 순서에 매우 유의하여야 한다.
DelegatingFilterProxy는 스프링이 제공하는 서블릿 Filter의 구현체로 서블릿 컨테이너의 생명주기와 스프링 ApplicationContext를 연결한다.
서블릿 컨테이너의 필터 기술과 스프링의 빈 관리 및 앱 실행 기술에는 간극이 있다. 서블릿 컨테이너는 Filter를 등록하게 해주지만, 스프링은 이를 빈으로 인식하지 않는다. 따라서 관리를 하지 못한다. 이 문제를 해결하기 위해 DelegatingFilterProxy를 사용한다.
DelegatingFilterProxy를 쓰면 필터 클래스를 스프링의 빈으로 등록시켜서 스프링의 관리를 받게 하면서 필터의 기능과 스프링의 빈 의존성에 따른 이점을 함께 누릴 수 있다.

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

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

차이점은 FilterChainProxy는 서블릿 컨테이너가 아닌 스프링 시큐리티가 제공한다는 것이다. 스프링 시큐리티는 FilterChainProxy를 통해 서블릿을 지원한다.
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
SecurityFilterChain는 FilterChainProxy가 요청에 따라 어떤 필터 체인을 실행시켜야할지 파악하는데 쓰인다.

SecurityFilterChain 안의 Security Filters는 스프링 빈인데, 이것들은 DelegatingFilterProxy이 아닌 FilterChainProxy에 등록되어 있다. 이러한 이유는,
FilterChainProxy가 스프링 시큐리티에서 동작하는 모든 서블릿의 시작점이다.
서블릿 지원에 문제가 발생했을 때, 디버그를 FilterChainProxy부터 시작하면 된다.
FilterChainProxy가 스프링 시큐리티의 핵심이기 때문에, 보이지 않는 작업을 수행할 수 있다.
예를 들어 SecurityContext를 비워 메모리 누수를 막을 수 있다. 또한 스프링 시큐리티의 HttpFirewall을 적용해 애플리케이션이 특정 유형의 공격에 당하는 것으로부터 보호할 수 있다.
FilterChainProxy가 SecurityFilterChain의 호출 시기 결정에 유연성을 가져다준다.
서블릿 컨테이너에서 Filter는 오직 URL에 따라 실행된다. FilterChainProxy는 요청의 RequestMatcher 인터페이스를 활용해 실행 시점을 파악한다.

예를 들어 요청의 URL이 /api/messages일 경우, FilterChainProxy로 인해 /api/** 패턴의 SecurityFilterChain 0이 실행된다. /** 패턴의 SecurityFilterChain n도 해당은 되지만 가장 먼저 일치하는 SecurityFilterChain이 실행된다.
요청 URL이 /messages/일 경우는 SecurityFilterChain 0이 실행되지 않는다. 때문에 FilterChainProxy는 계속해서 일치하는 것을 찾다가, SecurityFilterChain n을 실행시킨다.
Security Filter는 SecurityFilterChain API로 FilterChainProxy에 삽입되어있다. (전부 외울 필요는 없지만 순서는 알 필요가 있다.)
ExceptionTranslationFilter는 AccessDeniedException과 AuthenticationException을 HTTP 응답으로 변환해준다.

이 필터는 FilterChainProxy에 Security Filter 중 하나로 들어가 있다. 예외 처리의 흐름은,
ExceptionTranslationFilter이 FilterChain.doFilter(request, response)를 실행해 전체 애플리케이션을 실행한다.AccessDeniedException이 발생한다. AccessDeniedHandler가 실행돼 접근을 제한한다.애플리케이션이 AccessDeniedException 혹은 AuthenticationException을 던지지 않는 경우는, ExceptionTranslationFilter이 아무 일도 하지 않는 것이다.
참고자료