Spring 공식 문서를 정리할 것이며,
나름의 이해와 생각을 함께 정리할 예정이다.
Spring Security?
인증, 인가, 외부 공격으로부터의 보호 기능을 제공하는 프레임워크
자바 8 이상의 Runtime 환경 필요
스프링 시큐리티는 Servlet Container와 통합되기 때문에
Servlet Container가 동작하는 어떤 어플리케이션에서도 동작한다.
시큐리티를 위해 스프링을 사용할 필요는 없다.
Sring Boot + Spring Security
Spring Boot Auto Configuration
- 스프링 시큐리티 기본 설정을 활성화하여, springSecurityFilterChain이라는 이름을 가진 servlet Filter을 빈으로 생성
이는 어플리케이션에서 보안에 관련된 모든 것에 책임을 가지게 된다. - UserDetailsService라는 bean을 생성한다.
- springSercurityFilterChain이라는 bean에 모든 요청에 대한 Servlet Container와 함께 필터를 등록한다.
- 그 외의 어플리케이션 내의 상호작용을 위해 인증된 사용자를 필요하게하거나, 기본 로그인 화면 생성 등의 일을 한다.
Architecture
서블릿 기반의 어플리케이션에서 시큐리티 아키텍쳐를 이해해보자.
client가 어플리케이션으로 요청을 보내면
container는 Filter와 HttpServletRequest를 처리하는 Servlet을 포함하는 FilterChain을 생성한다.
(Spring MVC 어플리케이션의 경우, Servlet은 DispatcherServlet의 인스턴스를 뜻한다.)
Servlet 하나로는 단일 HttpServletRequest와 HttpServletResponse 밖에 처리하지 못하지만,
Filter를 이용하여 다음과 같은 일들을 수행할 수 있다.
- downstream Filter가 호출되지 않도록 한다. 이 경우, Filter는 HttpServletResponse를 작성한다.
- downstream Filter와 Servlet에서 사용되는 HttpServletRequest와 HttpServletResponse를 수정한다.
downstream Filter란 후행 필터를 의미하고
선행 필터에서 서블릿까지 가지 않고 응답을 내려줄 수 있으며,
각각의 필터들이 요청과 응답을 수정할 수 있다는 의미로 이해된다.
Filter는 통과하는 FilterChain에서 진가를 드러낸다고 한다.
이 부분에 대해 이렇다할 이유는 나와있지 않았지만 다음과 같이 이해했다.
FilterChain은 독립적으로 구성할 수도 있고, 조건에 따라 분기하여 다른 FilterChain을 통과하도록 구성할 수 있기때문에
Filter 인스턴스들을를 재사용하여 FilterChain을 구성함에 있어 큰 이점이 있다고 이해했다.
//Filter Chain Usage Example
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
}
Filter는 downstream Filter 인스터스와 Servlet에만 영향을 주기 때문에 호출되는 순서는 매우 중요하다.
DelegatingFilterProxy
스프링 시큐리티가 제공하는 Servlet Container 생명주기와 Spring ApplicationContext를 연결하는 역할의 Filter 구현체
Servlet Container는 컨테이너 표준을 이용해 Filter를 등록할 수 있게 하지만, 스프링에서 정의된 bean을 인식하지 못한다.그래서 DelegatingFilterProxy를 등록해 Filter을 구현한 Spring bean에게 모든 역할을 위임한다.
DelegatingFilterProxy은 BeanFilter를 Application Context에서 찾아 호출한다.
// DelegatingFilterProxy Pseudo Code
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의 다른 장점은 필터 빈을 나중에 찾아도 된다는 점이다.
컨테이너는 필터를 컨테이너가 시작하기 전에 등록해야하기 때문에 인스턴스 찾기를 지연하는 것은 중요하다.
하지만, 스프링은 필터 인스터스를 등록해야 할 때까지 로드되지 않은 스프링 빈을 로드하기 ContextLoaderListener를 사용한다.
servlet Container는 컨테이너가 시작하기 전에 필터 인스턴스가 필요한데
컨테이너가 스프링 빈을 인식하지 못하기 때문에 시작전에 인스턴스를 만들어 줄 수 없다.
그렇기 때문에 Proxy필터를 이용해 초기화를 한 후, 필요한 시점에
Filter를 구현한 bean 필터를 통해 스프링의 bean을 관리할 수 있도록 위임하는 것 같다.
FilterChainProxy
시큐리티의 Servlet support는 FilterChainProxy에 포함된다.
FilterChainProxy는 시큐리티에서 제공되는 특별한 Filter인데
SecurityFilterChain을 통해 많은 필터 인스턴스에게 위임할 수 있다.
FilterChainProxy은 빈이기 때문에 DelegatingFilterProxy에 포함된다.
나중에 나오겠지만 우리는 조건에 따라 다른 SecurityFilterChain을 통해 인증과정을 거치게 할 수 있다.
그렇게 하기 위해서 분기점이 필요할 것이고 FilterChainProxy는 분기점 역할을 해주는 것 같다.
또한, 스프링 시큐리티에 관련된 필터가 모여있는 곳이기 때문에
시큐리티 관련 TroubleShooting을 할 땐 SecurityFilterChain내부를 확인하면 된다.
SecurityFilterChain
Spring Security Filter가 현재 요청에서 호출될 수 있도록 FilterChainProxy에 의해 사용
Security Filter는 bean이지만 대부분 DelegatingFilterProxy가 아닌 FilterChainProxy에 등록된다.
FilterChainProxy을 DelegatingFilterProxy에 등록하는 데 많은 이점을 제공한다.
- Security Servlet support에게 시작점을 제공한다.
Servlet support에 문제가 생기면 FilterChainProxy내에 디버깅 포인트를 추가하면된다. - FilterChainProxy은 시큐리티 사용의 중심이기 때문에, 선택사항이 아닌 작업들을 수행할 수 있다.
예를 들어, 메모리 누수를 막기 위해 Security Context를 지우거나,
시큐리티의 HttpFirewall을 특정 공격을 막기 위해 추가한다. - SecurityFilterChain이 호출되어야할 지를 결정하는데 유연함을 준다.
서블릿 컨테이너에서는 오직 URL을 기반으로 필터 인스턴스가 호출된다.
하지만 FilterChainProxy는 RequestMatcher 인터페이스를 통해 HttpServletRequest의 다른 요소들을 기반으로 호출을 결정할 수 있다.
SecurityFilterChain들은 독립적으로 설정될 수 있다. 특정 요청에 대해 시큐리티가 무시하게 하려면 Filter를 하나도 두지 않으면 된다.
우리가 Security 설정을 위해 흔히 이용하는 WebSecurityConfigurerAdapter내에서 정의되는 부분들이
FilterChain으로 만들어지는 것으로 보인다.
Security Filters
Security Filter는 SecurityFilterChain API와 함께 FilterChainProxy에 들어간다.
필터 처리 순서는 굳이 알 필요는 없지만, 알면 도움이 될 때가 있다고 한다.
대표적인 필터 처리 순서
ForceEagerSessionCreationFilter
ChannelProcessingFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth2LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
SwitchUserFilter
위의 필터들을 통해 인증, 인가 처리가 되는 것 같다.
우리는 CORS Config, JWT CONFIG 등을 작성해서 필터들을 커스터마이징하는 것으로 보인다.
Handling Security Exceptionss
AccessDeniedException과 AuthenticationException를 HTTP Response로 반환
ExceptionTranslationFilter는 FilterChainProxy에 SecurityFilter 중 하나로 등록된다.
1. AccessDeniedException과 AuthenticationException이 발생하면 Exception Translation Filter가 동작하게 되며
2. 인증되지 않았거나 인증 오류가 나면 SecurityContextHoler를 지우고,
기존 요청은 인증이 되었을 때 다시 수행될 수 있도록 RequestCache에 저장된다.
AuthenticationEntryPoint를 통해 로그인 화면으로 이동 등의 credentials 을 요구한다.
3. AccessDeniedException의 경우 AccessDeniedHandler가 호출된다.
// ExceptionTranslationFilter pseudocode
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
RequestCache에 기존 요청을 저장하기 위해 RequestChacheAwareFilter가 사용된다고 한다.
RequestCahce 구현체로 HttpSessionRequestCache가 기본값으로 사용된다.
인증되지 않은 요청을 세션에 저장하지 않고 client브라우저나 DB에 저장하게 수정할 수 있으며,
재인증과정을 거치지 않고 싶으면 기능을 비활성화할 수도 있다고 한다.
Spring Security의 전반전인 아키텍쳐에 대해 알아보았다.
100% 이해했다고 보기는 어렵지만 전체적인 구조를 파악하는데는 도움이 많이 된 것 같다.