Spring Security는 필터를 이용하여 웹 요청을 가로챈 후 사용자를 인증하고, 인증된 사용자가 적절한 권한을 지니고 있는지 확인한다.
AuthenticationManager
사용자 인증 관련 처리
AccessDecisionManager
사용자가 보호받는 리소스에 대한 접근할 수 있는 적절한 권한이 있는지 확인(인가)
사용자의 웹 요청 → DelegatingFilterProxy → springSecurityFilterChain (FilterChainProxy)
if (SecurityContextHolder.getContext().getAuthentication() == null) {
Authentication authentication = this.createAuthentication((HttpServletRequest)req);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
...
}
AnonymousAuthenticationToken
을 생성하여 SecurityFilter에서 null 대신 사용 한다.protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(this.key, this.principal, this.authorities);
token.setDetails(this.authenticationDetailsSource.buildDetails(request));
return token;
}
AnonymousAuthenticationFilter
커스텀@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.anonymous()
.principal("thisIsAnonymousUser")
.authorities("ROLE_ANONYMOUS", "ROLE_UNKNOWN")
.and()
;
}
직접 커스텀하여 사용할 일은 거의 없는거 같다.
로그인 안한 상태에서 접근 거부가 일어나는 단계
FilterSecurityInterceptor에서 접근 거부 예외 발생
→ ExceptionTranslationFilter가 해당 예외(AccessDeniedException)를 catch하여 처리
→ if(isAnonymous) {sendStartAuthentication()}
→ requestCache.saveRequest()를 통해 원래 접근하려고 하였던 페이지(요청) 저장
로그인
RequestCacheAwareFilter에서 this.requestCache.getMatchingRequest()를 통해 저장되었던 원래 요청 가지고 온다.
FilterSecurityInterceptor
필터 체인 상에서 FilterSecurityInterceptor
는 ExceptionTranslationFilter
의 바로 아래에 위치한다. 따라서 FilterSecurityInterceptor
실행 중 발생할 수 있는 예외(AuthenticationException
, AccessDeniedException
)를 ExceptionTranslationFilter
에서 처리한다. 이는 커스텀 필터에서 발생하는 예외를 ExceptionTranslationFilter
에서 처리하기 위해서는 커스텀 필터가 ExceptionTranslationFilter
에 위치해야함을 의미한다.
AuthenticationEntryPoint
Filter Chain에서 예외 발생
ExceptionTransalationFilter가 예외를 catch
Is AuthenticationException?
requet를 캐싱하고 AuthenticationEntryPoint를 이용하여 사용자를 로그인 페이지로 포워딩
Is AccessDeniedException? OR Remember-me 인증?
Anonymous User인지 확인하고 만약 맞다면 기존 요청을 캐싱하고 사용자를 로그인 페이지로 포워딩한다. 만약 아니라면 AccessDeniedHanlder가 실행된다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
;
}
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, e) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication != null ? authentication.getPrincipal() : null;
log.warn("{} is denied", principal, e);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("text/plain");
response.getWriter().write("## ACCESS DENIED ##");
response.getWriter().flush();
response.getWriter().close();
};
}
server:
port: 443
ssl:
enabled: true
key-alias: prgrms_keystore
key-store: classpath:prgrms_keystore.p12
key-store-password: prgrms123
key-password: prgrms123
trust-store: classpath:prgrms_truststore.p12
trust-store-password: prgrms123
ChannelProcessingFilter 설정을 통해 HTTPS 채널을 통해 처리해야 하는 웹 요청을 정의할 수 있다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.requiresChannel()
.anyRequest().requiresSecure()
;
}
}