스프링 시큐리티에 authenticationEntryPoint 를 사용하여 ExceptionHandling 을 할 때 원하지 않는 응답으로 나오는 이슈가 있었다.
원하지 않는 응답이란, 앞 단에 JwtToken 을 검사하고 인증정보를 SecurityContextHolder 에 넣어주는 로직을 넣어두었는데 해당 필터를 거치기도 전에 AuthenticationErrorHandler 로 빠져버리며 '401' 스테이터스를 출력하였다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationErrorHandler authenticationErrorHandler;
public WebSecurityConfig(AuthenticationErrorHandler authenticationErrorHandler) {
this.authenticationErrorHandler = authenticationErrorHandler;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler);
}
}
@Component
public class AuthenticationErrorHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
결론부터 얘기하자면 전송하는 API URL 에 슬레시(/) 가 두번 연속 들어가 더블 슬레시(//)로 들어갔기 때문이였다.
이제부터 이 전송이 스프링 시큐리티 내부적으로 어떻게 처리되는지, ErrorHandler 로 빠지지 않게 할 수 있는 방법이 무엇인지 알아보자
스프링 시큐리티는 프록시 패턴으로 된 필터에서 필터를 돌리기 전 기본적인 방화벽(Firewalled) 검사를 진행한다.
FilterChainProxy.java
해당 firewall 에 대한 Bean 설정이 따로 안되어 있다면 StrictHttpFirewall.java 를 기본설정으로 두고 요청/응답에 대한 검사를 한다.
몇가지 기초 블랙리스트 설정을 거치고 해당된다면 RequestRejectedException 을 발행한다.
StrictHttpFirewall.java
문제는 해당 블랙리스트 기초 설정에 더블 슬레시(//)가 포함되어 있어 RequestRejectedException 가 발행되므로 나비효과가 시작되었다.
StandardWrapperValve.java
- 요청부에 발생된 exception 클래스를 set 해주는 모습
ExceptionTranslationFilter.java
요청부가 RequestRejectedException 이 떨어지면 응답부에 출력을 줄 때 AccessDeniedException 이 발생되고 요청부에서 이미 SecurityContextHolder 에 대한 Clear 가 된 시점에서 결국 AnonymousAuthenticationToken 으로 초기화 되었기 때문에 AuthenticationErrorHandler 에 InsufficientAuthenticationException 이 발생되면서 '401' 스테이터스가 출력되었다.