RefreshToken 이 유효하지 않은 경우 예외를 던지고 쿠키가 삭제되도록 구현했다.
Controller 에서 사용했던 것 처럼 @CookieValue 를 사용해 쿠키를 인자로 받으려고 했지만 해당 테스트가 계속 실패했다.(해당 ExceptionHandler가 동작하지 않았다)
@ExceptionHandler(RefreshTokenNotFoundException.class)
public ResponseEntity<ExceptionResponse> handleRefreshTokenNotFoundException(final UnauthorizedException e,
final HttpServletResponse response,
@CookieValue("refreshToken") final Cookie cookie) {
cookie.setMaxAge(0);
response.addCookie(cookie);
return handleUnauthorizedException(e);
}
Controller 와 다르게 ExceptionHandler 에서는 @CookieValue를 지원하는 ServletCookieValueMethodArgumentResolver 를 가지고 있지 않다. 그래서 Cookie 로 바인딩하려고 할 때 처리할 ArgumentResolver 를 찾을 수 없어서 테스트가 실패했던 것이다.(아래 사진 참고)
아래 코드와 같이 ExceptionHandler 는 Controller 처럼 다양한 argumentResolver 들을 지원해 주지 않는다.
// ExceptionHandlerExceptionResolver
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
return resolvers;
}
// RequestMappingHandlerAdapter
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
@CookieValue 대신 스프링에서 지원하는 WebUtils 를 사용해서 쿠키값을 가져왔다.
@ExceptionHandler(RefreshTokenNotFoundException.class)
public ResponseEntity<ExceptionResponse> handleRefreshTokenNotFoundException(final UnauthorizedException e,
final HttpServletRequest request,
final HttpServletResponse response) {
final Cookie cookie = WebUtils.getCookie(request, "refreshToken");
cookie.setMaxAge(0);
response.addCookie(cookie);
return handleUnauthorizedException(e);
}
ExceptionHandler 와 같이 쓰이는 ControllerAdvice 는 어디서 동작할까?
스프링에서 예외가 발생하면(Dispatcher Servlet 이 Handler 를 찾고 실행시키는 과정에서 예외가 발생하는 경우) HandlerExceptionResolver 가 예외를 처리한다.
아래 사진과 같이 HandlerExceptionResolver 중에서 HandlerExceptionResolverComposite 안에 있는 ExceptionHandlerAdviceCache 에 있다.
정리하면 HandlerExceptionResolvers(ArrayList) 는 DefaultErrorAttributes 와 HandlerExceptionResolverComposite(ArrayList)을 가지고 있다. 이 중 HandlerExceptionResolverComposite 안에 있는 Resolver(ExceptionHandlerExceptionResolver) 중 ExceptionHandlerAdviceCache(LinkedHashMap)에 들어있다.