Jwt 토큰을 통한 인증, 인가를 구현하던 중 발생한 문제점과 그에 대한 해결법을 정리했습니다.
제가 구현한 Spring 서버는 다음과 같은 프로세스를 가집니다.
- 사용자가 로그인 합니다.
- 로그인 성공시 token을 발급합니다.
- 발급 받은 토큰으로 인가가 필요한 url에 접근 할 때, header에 Token을 담아서 옵니다.
- 미들웨어인 Filter가 인가에 대한 로직을 처리후 성공한다면 비즈니스 로직을 거친후 반환합니다.
구현했을 때, 성공적으로 반환하거나 혹은 제가 원하는 예외 처리가 되었을거라고 생각하고 실행한 결과
전혀 다른 Response 타입이 나왔습니다.
저는 위와 같은 Response를 원했습니다.
하지만 위의 사진 처럼 예상하지 못한 Exception Response가 나오게 되었습니다.
이 상황은 Spring 동작 순서와 관련된 오류였습니다.
Spring은 웹 요청을 다음과 같은 순서로 진행시킵니다.
1. 클라이언트 → 내장 웹 서버 (Tomcat 등)
2. 웹 서버 → DispatcherServlet
3. DispatcherServlet → HandlerMapping → 핸들러 탐색
4. 핸들러 실행 → 비즈니스 로직 수행
5. 결과 반환 → ViewResolver 또는 HttpMessageConverter로 처리
6. DispatcherServlet → 웹 서버 → 클라이언트 응답
비즈니스 로직을 수행하며 Exception이 나오길 기대했지만 Filter를 통한 미들웨어 처리는 Http 요청후 DispatchServlet에서
Exception을 처리하게 되고 그렇기 때문에 ControllerAdvice에서 처리할 수 없었던 것이였습니다.
해결 방법은 FIlter 내부에서 Exception을 처리하는 방법이였습니다.
클라이언트 -> 웹 서버 -> DispatcherServlet -> Application 로 흐름이 이어지는 중 DispatcherServlet에서 발생한 Exception 이기 때문에 Application 보다 상위에서 Exception을 잡아줘야 했습니다.
특징 | Filter | ExceptionHandler |
---|---|---|
동작 위치 | DispatcherServlet 이전 | Controller 내부 |
적용 범위 | 모든 요청/응답 | Spring MVC 컨트롤러 |
예외 처리 가능 여부 | 가능 (직접 처리) | Controller 계층의 예외만 처리 가능 |
사용 목적 | 인증, 로깅, 요청/응답 전후 처리 | 비즈니스 로직 실행 중 발생한 예외 처리 |
다음 표를 보았을 때, 결론적으로 FIlter는 ExceptionHandler 보다 더 상위 계층이기 때문에 Filter에서 처리해주는 것이 맞다고 생각했습니다.
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
try {
final String authToken = request.getHeader(AUTHORIZATION_KEY);
final String uri = request.getRequestURI();
final HttpMethod method = HttpMethod.valueOf(request.getMethod());
if (whiteListUrl.isAvailableUri(uri, method)) {
filterChain.doFilter(request, response);
return;
}
validateAuthToken(authToken);
final String accessToken = authToken.substring(7);
validateJti(accessToken);
validateTokenActive(accessToken);
validateTokenEqualsFoundToken(accessToken);
filterChain.doFilter(request, response);
} catch (ApplicationException e) {
logger.error(e.getMessage(), e);
handleException(response, e);
}
}
private void handleException(HttpServletResponse response, ApplicationException e) {
try {
ErrorCode errorCode = e.getErrorCode();
ErrorResponse errorResponse = ErrorResponse.of(errorCode);
response.setStatus(errorCode.getStatus().value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
String jsonResponse = new ObjectMapper().writeValueAsString(errorResponse);
response.getWriter().write(jsonResponse);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
filter에서 handler를 구현하여 filter에서 exception을 처리할 수 있도록 구현하였습니다.
결론
Filter는 ControllerAdivce보다 상위 계층이기 때문에 ExceptionHandling을 Filter계층에서 해주지 않으면 ControllerAdvice는 알 수 없습니다.
'BackEnd' 카테고리의 다른 글
[Spring] @RestControllerAdvice를 통한 스프링에서의 예외처리 (1) | 2024.12.16 |
---|---|
[DB] 효율적인 설계를 위해 어떤 SQL을 사용해야 할까? RDBMS vs NoSQL과 DB에서의 수직 확장(scale-up)과 수평 확장(scale-out) (0) | 2024.06.20 |
[Spring] 스프링이 사랑한 디자인 패턴 - 1 (0) | 2024.05.29 |
[Spring] BeanFactory와 ApplicationContext 이해하기 (0) | 2024.04.03 |
[Spring] 스프링 컨테이너와 빈 (0) | 2024.04.02 |