[스프링시큐리티] 스프링 시큐리티 에러 핸들링 방법

hyelim·2023년 8월 18일
1

Spring

목록 보기
5/6
post-thumbnail

현재 진행하던 프로젝트에서 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

  • @ControllerAdvice 어노테이션은 @ExceptionHandler, @ModelAttribute, @InitBinder 가 적용된 메서드들에 AOP를 적용해 Controller 단에 적용하기 위해 고안된 어노테이션
  • 클래스에만 선언하면 되며, 모든 @Controller에 대한 전역적으로 발생할 수 잇는 예외를 잡아서 처리
  • @Componet가 선언되어있어 빈으로 관리되어 사용

@RestControllerAdvice

  • @RestControllerAdvice와 @ControllerAdvice의 차이는 @RestControllerAdvie = @ControllerAdvice + @ResponseBody
  • 따라서 @RestControllerAdvie로 선언하면 따로 @ResponseBody를 붙혀주지 않아도 객체를 리턴할 수 있다
  • 에러 메세지를 DTO 객체에 담아 리턴해 주는 방식으로 사용된다
@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로 스프링 시큐리티 예외를 처리할 수 있을 것이라 한다

1. 발생하는 에러를 처리하는 Handler를 추가하자

즉 예외들을 어떻게 처리할 지부터 정해놓자

@RestControllerAdvice
public class ExceptionResponseHandler {

    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<String> handleJwtException(UnauthorizedException e){
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
    }

}

해당 코드에서는 UnauthorizedException 이 발생하면 응답바디에 에러 메시지를 주기로 하였다

2. JwtExceptionFilter에서 예외를 넘겨주기

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

    }

}

3. AuthenticationEntryPoint의 구현체를 작성해라

@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에 예외 처리를 위임한다.

3. SecurityConfig의 필터 체인에 작성한 엔트리포인트를 추가해준다

이렇게 시큐리티에서 발생한 에러도 @ControllerAdvice 에서 처리가 가능하다!

이제 모든 에러핸들링을 ExceptionResponseHandler 에서 처리하도록 코드를 리팩토링 할 예정이다!

[Spring] @ExceptionHandler와 @ControllerAdvice란 무엇인가?

[Spring Security] Spring Security 예외를 @ControllerAdvice와 @ExceptionHandler를 사용하여 전역으로 처리해보자

Springboot JWT 로그인 - (6) RefreshToken 방식을 사용하도록 변경 & 코드 정리

[Spring] Exception 예외 처리 - AOP와 @RestControllerAdvice를 이용한 ErrorHandling

profile
기록용

0개의 댓글