*개요
Spring Security의 구조 메커니즘을 학습합니다. 스프링 라이브러리와 서블릿 컨테이너의 필터가 어떻게 서로 연결되어 동작하는지 학습합니다.
필터 리뷰
스프링 시큐리티의 서블릿 지원은 서블릿 Filter에 기반을 둡니다. 아래 사진은 HTTP 요청 시 핸들러의 계층입니다. 만약 사용자는 어플리케이션에 요청을 보내면 컨테이너는 FilterChain을 생성합니다. FilterChain은 Filter와 Servlet으로 이루어졌으며 URL 요청에 따라 HttpServletRequest를 수행합니다. FilterChain은 이름대로 여러 개의 필터를 체인 형태로 가질 수 있는데 순서대로 아래 방향으로 실행되면서 Filter와 Servlet의 호출을 진행하거나 막을 수 있고 HttpServletRequest와 HttpServletResponse를 변경할 수도 있습니다.
doFilter(request, response)로 순서대로 필터를 통과하며 작업을 처리합니다. 순서대로 실행되므로 순서가 중요합니다.
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
}
DelegatingFilterProxy
DelegatingFilterProxy는 서블릿 컨테이너 생명주기와 스프링 ApplicationContext를 연결하는 필터 구현체입니다. 서블릿 컨테이너는 표준 방법으로 필터를 등록하지만 스프링으로 정의한 빈을 전혀 알지 못합니다. 서블릿 컨테이너 메커니즘으로 DelegatingFilterProxy를 등록할 수 있지만, 필터를 구현하는 스프링 빈에 모든 작업을 위임합니다.
DelegatingFilterProxy는 ApplicationContext에서 Bean Filter0을 바라보다가 Bean Filter0을 호출합니다. (DelegatingFilterProxy는 org.springframework.web.filter 패키지에 있습니다.)
- DelegatingFilterProxy 수도 코드
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 인스턴스를 등록해야 합니다. 그러나 스프링은 일반적으로 ContextLoaderListener를 사용하여 스프링 빈을 로드하는데 Filter 인스턴스를 등록해야 할 때까지 수행되지 않습니다. 따라서 Filter 인스턴스를 등록하고 나서야 스프링 빈인 Filter 빈 인스턴스를 검색합니다.
FilterChainProxy
스프링 시큐리티의 서블릿 지원은 FilterChainProxy에 포함되어 있습니다. FilterChainProxy는 SecurityFilterChain을 통해 수많은 Filter 인스턴스에 위임 할 수 있도록 하는 스프링 시큐리티에서 제공하는 특수 필터입니다. FilterChainProxy도 빈이므로 DelegatingFilterProxy에 포함되어있습니다.
SecurityFilterChain
SecurityFilterChain에 있는 스큐리티 필터들은 일반적으로 Bean들인데 DelegatingFilterProxy가 아닌 FilterChainProxy에 등록됩니다. FilterChainProxy는 서블릿 컨테이너나 DelegatingFilterProxy에 직접 등록할 때 많은 이점을 제공합니다.
첫째로, 모든 스프링 시큐리티 서블릿 지원의 시작점입니다. 따라서 트러블 슈팅을 한다면, FilterChainProxy에 하는 것이 좋습니다.
둘째로, FilterChainProxy는 스프링 시큐리티 사용의 핵심이므로 옵션으로 간주되지 않는 작업들을 수행할 수 있습니다. 예를 들어, 메모리 누수를 피하기 위해 SecurityContext를 초기화합니다. 또한 공격에 대비하여 애플리케이션을 보호하기 위해 HttpFirewall도 적용합니다.
또한, 언제 SecurityFilterChain이 호출되어야 하는지 결정하는데 유연성을 제공합니다. 서블릿 컨테이너에서 Filter들은 URL 기반으로 호출됩니다. 하지만, FilterChainProxy는 RequestMatcher 인터페이스를 활용해 HttpServletRequest 정보를 기반으로 호출을 결정합니다.
마지막으로, FilterChainProxy는 어떠한 SecurityFilterChain이 사용되어야 하는지 결정합니다. 어플리케이션의 완전히 나뉜 설정도 제공합니다.
다중 SecurityFilterChain는 맨 처음으로 매칭된 SecurityFilterChain만 호출됩니다. 만약에 /api/messages/의 URL 요청이 오면, SecurityFilterChain0의 패턴 /api/** 을 확인하고, 매칭되기 때문에 이후에 n번째 SecurityFilterChain이 매칭되더라도 SecurityFilterChain0이 호출됩니다. 만약에 /messages/의 URL 요청이 오면, SecurityFilterChain0은 매칭되지 않아 다음으로 계속 이동하고 결국 SecurityFilterChain n번째에 호출됩니다.
SecurityFilterChain0은 3개의 필터가 있지만, SecurityFilterChainn은 4개의 필터가 있습니다. 각 SecurityFilterChain은 독립적으로 구성될 수 있습니다. 따라서 어떤 SecurityFilterChain은 0개의 필터를 가지고 있을 수도 있습니다.
Security Filters
Security Filter들은 SecurityFilterChain API를 사용하여 FilterChainProxy에 삽입됩니다. 모든 순서를 알 필요는 없지만 Filter가 순서를 가진다는 것은 기억합시다.
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
Handling Security Exceptions
ExceptionTranslationFilter는 AccessDeniedException과 AuthenticationException을 HTTP 응답에 넣을 수 있습니다. ExceptionTranslationFilter는 Security Filters 중에 하나로 FilterChainProxy에 삽입됩니다.
1. ExceptionTranslationFilter는 FilterChain.doFilter(request, response)로 남아있는 필터 체인을 호출합니다.
2. 만약 사용자가 인증하지 않았거나 AuthenticationException이라면 인증을 시작합니다.
- SecurityContextHolder를 초기화합니다.
- HttpServletRequest는 RequestCache에 저장됩니다. 사용자가 성공적으로 인증을 완료하면 RequestCache는 원래 요청에 응답합니다.
- AuthenticationEntryPoint는 클라이언트에게 credentials(증명서)를 요청하기 위해 사용됩니다. 예를 들어, 로그인 페이지로 이동하거나 WWW-Authenticate header를 전송합니다.
3. 만약 AccessDeniedException이라면, 권한이 거부됩니다. AccessDeniedHalder는 접근 거부를 처리하기 위해 호출됩니다.
ExceptionTranslationFilter 수도코드
try {
filterChain.doFilter(request, response); // (1)
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication(); // (2)
} else {
accessDenied(); // (3)
}
}
(1) FilterChain.doFilter(request, response)은 아직 실행되지 않은 나머지 어플리케이션을 호출합니다. 만약 남아있는 어플리케이션인 FilterSecurityInterceptor 혹은 메서드 시큐리티가 AuthenticationException 혹은 AccessDeniedException를 던지면 여기서 예외가 잡히고 처리됩니다.
(2) 사용자가 인증되지 않거나 AuthenticationException라면, 인증을 시작합니다.
(3) 그렇지 않다면 접근이 거부됩니다.
'Spring > Spring Security' 카테고리의 다른 글
Authentication 아키텍쳐 (0) | 2024.05.21 |
---|---|
Security AccessDecisionManager 구현 학습하기 (0) | 2024.05.15 |
AuthToken 사용하기 (0) | 2021.10.26 |
JWT 토큰 (0) | 2021.10.26 |
임시권한 부여하기 (0) | 2021.10.23 |