
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()
;
}
}