스프링 시큐리티는 인증(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
이 아무 일도 하지 않는 것이다.
참고자료