@ControllerAdvice의 적용범위 (부제 : Filter에서의 Exception은 Controller Advice로 처리할 수 없다)

김태훈·2023년 10월 4일
post-thumbnail

최근, 클라이언트에서의 원활한 오류 처리를 위해, 백엔드에서 발생한 오류를 커스텀하였다.
다시말하면, 스프링의 BasicErrorController를 거치지 않고, 우리만의 오류 메시지로 바꿔주는 중이다.
근데, security filter부분의 에러처리에서 문제가 생겼다.

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"인증된 사용자가 아닙니다.");
    }
}

위 코드는, 리팩토링 이전의 코드이다.
다음과 같이 sendError를 보내서 WAS에서 error임을 인지하고, 다시 Filter, Servlet, Interceptor, BasicErrorController -> Interceptor -> View Resolver 를 거친다.

결국, 위 코드는 sendError 덕분에 WAS에서 error를 인지하고 BasicErrorController를 거칠 수 있었다.

1. 첫번째 시도 (throw Exception)

이를 해결하기 위해서 했던 첫번째 시도는, Filter에서 Exception을 던지는 것이었다.

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        throw new UnAuthorizedException("인증된 사용자가 아닙니다.");
    }
}

해당 Exception을 직접 ControllerAdvice에 지정해놓았다.
하지만, ControllerAdvice에서 지정한 오류 메시지가 출력되지 않았다.

문제를 찾아보니, Controller Advice가 처리할 수 있는 부분은 딱 서블릿 이후 부터이다.
즉, Filter에서 Exception을 던진다고 이를 해결해주지 않는다는 점이다. (sendError로 던지는거는 WAS에서 error로 인지하고 다시 보내서, basicErrorController로 감)

그래서, 직접 response를 커스터마이징하는 방법으로 이를 해결하였다.

근데 생각해보면 security filter와 관련한 에러처리는 모두 handler로 등록하였었다.

.exceptionHandling((exceptionHandling) -> exceptionHandling
	.accessDeniedHandler(jwtAccessDeniedHandler)
    .authenticationEntryPoint(jwtAuthenticationEntryPoint))

access가 deny될때 jwtAccessDeniedHandler가 동작하여 sendError를 보냈었다.
만약 exception을 던질 수 있었다면 이러한 handler를 등록할 필요도 없었을 것이다.
즉, filter의 특성상 당연히 오류처리는 오류 처리를 하는 handler를 등록하는 것이 맞다고 생각한다.

2. 직접 json 만들어서 response에 추가

@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        unAuthorizedExceptionHandler(response);
    }


    private void unAuthorizedExceptionHandler(HttpServletResponse response) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        try {
            String json = new ObjectMapper().writeValueAsString(new ErrorResult(HttpStatus.UNAUTHORIZED,"인증된 사용자가 아닙니다."));
            response.getWriter().write(json);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}

다음처럼 직접 json을 작성하여 보내서 이를 해결할 수 있었다.

profile
기록하고, 공유합시다

0개의 댓글