현재 진행하던 프로젝트에서 1차 배포까지 끝내고 코드를 들여다보니 무분별 하게 사용하는 try-catch 문, 공통예외 처리를 하지 않은 코드 등등.. 코드가 너무 복잡했고 가독성이 떨어져 프로젝트의 구조를 망가뜨리는 듯 싶었다.
따라서 이렇게 발생하는 예외를 체계적으로 관리하기 위해 @ControllerAdvice와 @RestControllerAdvice 어노테이션을 통해 예외를 효과적으로 해결하려고 시도하였다.
@ExceptionHandler
를 통해 에러처리하기특정 컨트롤러에서 비즈니스 로직을 수행하다가 CustomException이 발생하면 @ExceptionHandler
애너테이션이 붙어있는 메서드가 실행되어 에러를 처리하게 된다.
@ExceptionHandler 어노테이션은 AOP를 이용한 예외처리 방식
@Controller
public class SimpleController {
// ...
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
@ControllerAdvice
를 통해 에러처리하기위처럼 Controller에서 작성하는 것이 일일이 작성하는 것이 아니라 따로 에러 핸들링 부분을 분리를 해서 처리를 할 수 있는데 이럴때 도와주는 친구가 바로 @ControllerAdvice
이다
@ControllerAdvice
@RestControllerAdvice
@RestControllerAdvice
public class ExceptionResponseHandler {
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<String> handleJwtException(UnauthorizedException e){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
}
@ControllerAdvice
에서 스프링 시큐리티에서 발생하는 예외를 전역적으로 처리하려면?스프링 시큐리티는 요청이 컨트롤러에 도달하기 전에 필터 체인에서 예외를 발생시킨다. 앞서 이야기했듯이
@ControllerAdvice
는 컨트롤러 계층에서 발생하는 예외를 처리하는데, 요청이 컨트롤러에 도달하기도 전에 이미 예외가 발생해서 요청이 컨트롤러에 도달하지도 못했기 때문에@ControllerAdvice
에서 처리를 할 수가 없다.
@ControllerAdvice
에서 스프링 시큐리티 관련 에러 처리를 한단 말인가?스프링 시큐리티에서는 사용자가 인증되지 않았거나 AuthenticationException이 발생했을 때 AuthenticationEntryPoint에서 예외 처리를 시도한다. 따라서 AuthenticationEntryPoint의 구현체를 적절하게 이용한다면 @ControllerAdvice
로 스프링 시큐리티 예외를 처리할 수 있을 것이라 한다
즉 예외들을 어떻게 처리할 지부터 정해놓자
@RestControllerAdvice
public class ExceptionResponseHandler {
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<String> handleJwtException(UnauthorizedException e){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
}
해당 코드에서는 UnauthorizedException 이 발생하면 응답바디에 에러 메시지를 주기로 하였다
@Component
public class JwtExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (UnauthorizedException ex) {
logger.info(ex);
request.setAttribute("Exception", ex);
filterChain.doFilter(request, response);
}
}
}
@ExceptionHandler
로 스프링 시큐리티 예외를 처리하기 위해 security
패키지에 AuthenticationEntryPoint 구현체를 작성한다.
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final HandlerExceptionResolver resolver;
public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver")HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.info("error entry point");
resolver.resolveException(request,response,null, (Exception) request.getAttribute("Exception"));
}
}
이 엔트리포인트는 스프링 시큐리티에서 인증과 관련된 예외가 발생했을 때 이를 처리하는 로직을 담당한다.
여기서 HandlerExceptionResolver의 빈을 주입받고 있는데, HandlerExceptionResolver의 빈이 두 종류가 있기 때문에 @Qualifier
로 handlerExceptionResolver
를 주입받을 것이라고 명시해줘야 한다고 한다
commence()
에서 스프링 시큐리티의 인증 관련 예외를 처리하게 된다.
@ControllerAdvice
에서 모든 예외를 처리하여 응답할 것이기 때문에 여기에 별다른 로직은 작성하지 않고 HandlerExceptionResolver에 예외 처리를 위임한다.
이렇게 시큐리티에서 발생한 에러도 @ControllerAdvice 에서 처리가 가능하다!
이제 모든 에러핸들링을 ExceptionResponseHandler 에서 처리하도록 코드를 리팩토링 할 예정이다!
[Spring] @ExceptionHandler와 @ControllerAdvice란 무엇인가?
[Spring Security] Spring Security 예외를 @ControllerAdvice와 @ExceptionHandler를 사용하여 전역으로 처리해보자
Springboot JWT 로그인 - (6) RefreshToken 방식을 사용하도록 변경 & 코드 정리
[Spring] Exception 예외 처리 - AOP와 @RestControllerAdvice를 이용한 ErrorHandling