barded

[어디가게] Spring Security단에서 발생하는 Exception의 처리 불가능 본문

프로젝트/어디가게

[어디가게] Spring Security단에서 발생하는 Exception의 처리 불가능

barded 2024. 3. 18. 04:16

현재 GlobalExceptionHandler를 통해 컨트롤러단에서 발생하는 에러는 전역적으로 처리가 되어있다.

@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()));
    }

    @ExceptionHandler(BaseException.class)
    public ResponseEntity<ErrorResponse> handleBaseException(BaseException e) {
        return ResponseEntity.status(e.getStatus()).body(new ErrorResponse(e.getStatus(), e.getCode()));
    }
}

그러나 Spring Security 에서 발생하는 에러는 이러한 ExceptionHandler가 처리할 수 없다.

그 이유를 알기 위해서는 스프링 시큐리티의 작동 순서를 알아야 한다.

Spring Security의 구조를 확인해보면 Filter에서 발생한 예외는 @ControllerAdvice의 적용범위 밖이기 때문이다.

즉 예외를 Filter Chain에서 처리하여 Servlet 단에 넘겨줘야 한다고 한다.

기본적으로 Spring Security의 예외는 EntryPoint에서 처리한다.
따라서 이 EntryPoint를 커스텀 구현하여 예외를 처리하도록 해보자.

JwtAuthenticationEntryPoint

@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws IOException {
        String code = (String)request.getAttribute("exception");
        if (StringUtils.hasText(code)) {
            setResponse(response, ErrorCode.UNAUTHORIZATION);
        }
    }

    private void setResponse(HttpServletResponse response, ErrorCode code) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);

        response.setStatus(code.getStatus());
        response.getWriter()
            .write(objectMapper.writeValueAsString(new ErrorResponse(code.getStatus(), code.getCode())));
    }
}

header에서 “exception” 필드가 있는 경우에 response에 직접 값을 써 준다.

JwtFilter

@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
...

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {


        try {
            if (StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)) {
                Authentication authentication = jwtProvider.getAuthentication(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (BaseException e) {
        //에러 발생시 request header에 에러 등록
            request.setAttribute("exception", e.getCode());
        } 
        filterChain.doFilter(request, response);
    }

...
}

JwtFilter에서 토큰을 검증할 때 오류가 있는 경우 request에 “exception” 필드를 추가하여 처리한다.

위처럼 처리하면 일반 에러처럼 똑같은 에러 응답이 날라간다.