현재 프로젝트에 스프링 시큐리티 5.7.6을 사용 중 따라서 공식문서의 버전도 5.7.6 / 공식문서 - https://docs.spring.io/spring-security/reference/5.7.6/servlet/architecture.html
스프링 부트는 스프링 시큐리티를 기본적으로 구성하는데, 서블릿 필터를 사용한 springSecurityFilterChain 빈을 만들어 구성한다.
user의 username과 콘솔에 기록되는 임의로 생성된 암호를 사용한 UserDetailService
를 생성
서블릿에서의 스프링 시큐리티는 서블릿 필터를 사용한다. 아래의 그림은 단일 HTTP request의 일반적인 핸들러 레이어 계층을 보여주는 그림이다.
위 그림을 간단히 말하면
Filter
들 또는 Servlet
을 포함한 FilterChain
이라는 것을 만든다.DispatcherServlet
의 인스턴스이다.FilterChain
을 통해 해당 스프링 MVC에서는 DispatcherServlet
인 서블릿에 요청이 들어가기전에 여러 필터를 걸쳐 요청에 대한 처리를 할 수 있고, 반대로 클라이언트에 요청이 보내어질 때 필터를 통해 응답에 대한 처리 후 나갈 수 있다.
그리고, 최대 하나의 서블릿이 단일 HttpServletRequest 및 HttpServletResponse를 처리할 수 있다. 하지만 두 개 이상의 필터를 사용하여 다음을 수행 할 수 있다.
후속 filter
들과 서블릿을 호출 방지.
이러한 경우 FIlter
는 HttpServletResponse
를 작성한다.
후속 Filter
들과 서블릿에서 사용되는 HttpServletRequest
, HttpServletResponse
수정.
모든 Filter
들은 FilterChain
으로 통과하기 때문에 Filter
의 핵심은FilterChian
이다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 해당 필터에서 요청이 들어왔고 ServletRequest를 가지고 전 처리
chain.doFilter(request, response); // 애플리케이션에 서블릿 정보를 넘긴다.
// 애플리케이션에서 응답이 나오는 부분. ServetResponse로 응닫 처리
}
스프링은 서블릿 컨테이너(ServletContainer
)의 생명주기(lifecycle
)와 스프링 애플리케이션 컨텍스트(ApplicationContext
)를 서로 연결하기 위해 DelegatingFilterProxy
라는 이름의 Filter
구현체를 제공한다.
이러한 이유는 서블릿 컨테이너(ServletContainer
)는 자체 표준을 사용하여 Filter
들을 등록하는데, 스프링에 정의된 빈(Bean
)에 대해서는 알지 못하기 때문이다.
그리고 이러한 DelegatingFilterProxy
는 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있지만, Filter
를 구현한 스프링 빈에게 모든 작업을 위임한다.
아래 그림은 DelegatingFilterProxy
가 FilterChain
에 어떻게 등록되어 있는지 볼 수 있다.
DelegatingFilterProxy
는 애플리케이션 컨텍스트(ApplicationContext
)에서 Bean Filter0
을 찾고 호출 한다.
아래는 DelegatingFilterProxy
의 수도 코드이다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName);
// 작업을 스프링 Bean에게 위임
delegate.doFilter(request, response);
}
DelegatingFilterProxy
사용의 또다른 이점은, Filter
빈 인스턴스 검색을 지연 시킬 수 있다는 건데, 이는 컨테이너가 시작되기 전에 Filter
인스턴스를 등록해야 하기 때문에 매우 중요하다.
스프링은 일반적으로 ContextLoaderListener
를 사용해 스프링 빈을 로드하는데, 이러한 작업은 Filter
인스턴스가 등록 되기 전까지는 실행 되지 않는다.
서블릿은 자체 표준이 존재하고 스프링은 빈으로 모든 것을 정의한다. 이를 연결하기 위해 DelegatingFilterProxy
를 만들어 스프링이 빈으로 정의한 필터를 서블릿에서 사용할 수 있게 한다.
스프링 시큐리티의 서블릿 지원은 FilterChainProxy
에 포함되어 있다. 이러한 FilterCHainProxy
는 SecurityFilterChain
을 통해 많은 Filter 인스턴스를 위임할 수 있도록 스프링 시큐리티에서 제공하는 특별한 필터이다.
FilterChainProxy
는 스프링 시큐리티에서 빈으로 제공하기 때문에 DelegatingFilterProxy
에 감싸져 있다.
FilterChainProxy
는 요청에 대해 스프링 시큐리티 필터를 호출해야 하는 데, 이 때 사용되는 것이 SecurityFilterChain
이다.
SecurityFilterChain
안에 있는 시큐리티 필터들은 일반적으로 빈(Bean)이고, DelegatingFilterProxy
대신에 FilterChainProxy
에 등록이 되어있다.
여기서 FilterChainProxy
는 서블릿 컨테이너 또는 DelegatingFilterProxy
에 직접 등록은 많은 이점을 제공한다.
스프링 시큐리티 서블릿 지원에 대한 모든 시작 포인트를 제공한다. 해당 서블릿 컨테이너의 모든 보안은 FilterChainProxy
에서 시작됨을 말한다.
이러한 이유로 스프링 시큐리티의 서블릿 지원 문제를 해결하려는 경우에 FilterChainProxy
에서 디버그 포인트를 지정하는 것이 제일 좋은 디버그 시작 지점이 된다.
FilterChainProxy
는 스프링 시큐리티 사용에 있어서 핵심 부분으로, 필수로 여겨지는 여러 작업들을 FilterChainProxy
에서 실행하게 할 수 있다.
예를 든다면, 메모리 누수 방지를 위해 SecurityContext
를 삭제하거나, HttpFirewal
을 사용하여 애플리케이션을 특정 공격으로 부터 보호하는 등의 설정을 할수 있다.
SecurityFilterChain
이 호출되는 시기를 유연하게 조절할 수 있도록 한다.
서블릿 컨테이너에서 필터들은 URL 기준으로만 호출이 되는데, FilterChainProxy
는 RequestMatcher
인터페이스를 활용하여 HttpServletRequest
에 대한 모든 항목을 기준으로 호출할 수 있다.
사실은 이러한 FilterChainProxy
는 사용할 SecurityFilterChain
이 무엇인지에 따라 결정된다. 이렇게 한다면, 애플리케이션의 여러 슬라이드에 대해 완전히 별도인 구성을 제공할 수 있다.
위의 다중 시큐리티 필터체인 그림에서 FilterChainProxy
는 사용할 SecurityFilterChain
을 결정한다.
일치하는 첫번째 SecurityFilterChain
만 호출 된다. 즉, 만약 /api/messages/
URL이 요청되었다면, SecurityFilterChain0
의 패턴 /api/**
와 SecurityFilterChain n
의 패턴 /**
중 처음 매치되는 SecurityFilterChain0
이 호출 된다
그리고 만약에 /messages
의 요청이 온다면, SecurityFilterChain0
의 패턴은 /api/**
이기에 매칭되지 않을 것이다. 그러게 되면 FilterChainProxy
는 각각의 SecurityFilterChain
와 계속에서 매치를 시도하게 된다.
더는 시도할 SecurityFilterChain
이 없다면 SecurityFilterChain0
이 호출 된다.
SecurityFilterChain
은 실제 스프링 시큐리티 필터에 사용되는 필터들을 엮어놓은 체인과 같다.
FilterChainProxy
는 ScurityFilterChain
을 사용한다.
SecurityFilterChain
을 FilterChainProxy
를 통해 사용하는 장점은 아래와 같다.
1. debug point 제공
2. 필수 작업 실행 제공
3. 호출 시기 조절 제공
URL 패턴에 따라 각 SecurityFilterChain
이 존재하고, 특정 URL 요청이 들어오면 FilterChainProxy
는 해당 패턴에 맞는 SecurityFilterChain
을 사용한다.
처음으로 URL과 매치되는 SecurityFilterChain
을 사용한다.
시큐리티 필터들은 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
ExceptionTranslationFilter
는 HTTP response들에 대한AccessDeniedException
, AuthenticationException
의 변환을 할 수 있다.
이러한 ExceptionTranslationFilter
는 시큐리티 필터 중 하나로 FilterChainProxy
에 등록된다.
ExceptionTranslationFilter
는 FilterChain.doFilter(request, response)
를 호출하여 나머지 응용프로그램을 호출한다.
사용자가 인증되지않았거나, AuthenticationException
이라면, Start Authentiactaion 과정으로 이동한다.
Start Authentication
SecurityContextHolder
를 지운다.RequestCache
에 HttpServletRequest
가 저장된다. 사용자 인증이 성공적으로 되었다면, RequestCache
는 원래 요청을 재실행 하기 위해 사용된다.AuthenticationEntryPoint
는 서버에서 클라이언트로 자격 증명을 요청하기 위해 사용된다. 예를 들어, 로그인 페이지로 리다이렉트라던가, WWW-Authenticate
헤더를 전송하는 등의 예제가 있다.그 이외에는 Access Denied가 발생한다. 여기서 AccessDeniedHandler
는 access denied를 다루기 위해 호출 된다.
📌 NOTE
애플리케이션에서AccessDeniedException
또는AuthenticationException
이 터지지 않는다면,ExceptionTranslationFilter
는 어떤 동작도 하지 않는다.
ExceptionTranslationFilter
의 수도 코드는 아래와 같다.
try {
filterChain.doFilter(request, response); // - 1
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication(); // - 2
} else {
accessDenied(); // - 3
}
}
필터 부분에서 본 바와 같이 FilterChain.doFilter(request, response)는 다음 필터가 존재한다면 계속해서 체인으로 호출하는 역할이다. 그래서 FilterSecurityInterceptor
, method security
등, 애플리케이션의 다른 부분에서 AuthenticationException
, AccessDeniedException
등이 던져진다면, 여기서 캐치해 처리한다.
만약 사용자가 인증되지 않은 경우, AuthenticationException
이 터진 경우, Start Authentication을 호출한다.
그외에는 Access Denied를 호출한다.
스프링 시큐리티의 서블릿 지원은 필터로 구성된다.
서블릿 컨테이너는 스프링의 빈 등록을 알지 못하기에 DelegatingFilterProxy
를 사용하여 스프링에서 등록한 빈(Bean) 필터를 사용할 수 있게 한다.
스프링 시큐리티는 FilterChainProxy
에 포함되어 있다. 이러한 FilterChainProxy
에는 몇가지 장점이 있다.
1. 시큐리티의 시작포인트를 알수 있어 디버그 포인트를 잡기 용이
2. 필수로 여겨지는 여러 작업들을 여기서 수행가능
3. 스프링 시큐리티의 호출 시기 조절 가능
시큐리티 필터는 SecurityFilterChain
으로 구성되어 있고 FilterChainProxy
가 해당 SecurityFilterChain
을 사용한다.
예로 든다면, URL 패턴에 따른 SecurityFilterChain
이 여러개 존재하고, 특정 URL이 들어온다면, 바로 처음 매치되는 URL 패턴에 맞는 SecurityFilterChain
이 호출된다.
시큐리티의 예외는 사용자가 인증되지 않았거나, AuthenticationException
이 발생할 경우 클라이언트로부터 자격 증명을 하도록 요청하는 start Authentication을 호출한다.
그외의 예외는 Access Denied로 AccessDeniedHandler
를 호출한다.