본문 바로가기
Spring-Boot

스프링부트 filter에서 발생한 예외를 핸들링 해보자

by 준형코딩 2024. 8. 30.

 

 

어떤 문제를 해결했는가?

 

RTR 전략을 위한 refreshToken을 재발급해 주기 위해서 기존 JWT가 만료가 되면 분기화된 에러 메세지를 사용자에게 전달해 주어야했다. 기존에 전역 에러 관리를 위한 글로벌 ExceptionHandler가 있었기에 JWT 예외가 사용자에게 전달될 것으로 생각했으나 필터에서 발생되는 예외를 처리하지 못하는 상황이 발생했다.

 

참고 (RTR) : https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation

 

 

 

그럼 ExceptionHandler는 왜 필터에서 발생된 예외를 처리하지 못했을까?

 

스프링 MVC 요청 라이프 사이클 그림

출처:https://justforchangesake.wordpress.com/2014/05/07/spring-mvc-request-life-cycle/

 

위 스프링 MVC 요청 라이프 사이클을 보면 왜 그런 문제가 발생했는지에 대해 이해할 수 있다.

먼저 ExceptionHandler는 디스패치 서블릿을 통해 들어온 요청의 예외를 핸들링하게 된다. 그전에 필터에서 예외가 발생하게 되면 ExceptionHandler가 핸들링할 수 있는 범위 밖에 있기 때문에 글로벌 예외 처리가 작동하지 않은 것이다.

 

 

그럼 필터에서 발생한 예외를 어떻게 처리해야 할까?

 

그러려면 필터가 어떤 식으로 동작하는지 간략하게 이해를 할 필요가 있다.

 

필터를 지나는 요청은 위 그림과 같이 순서대로 필터를 지나가게 되고 이후에 디스패치 서블릿을 거쳐서 명령을 수행하고 다시 거슬러 돌아가면서 사용자에게 Response를 전달하게 된다.

그럼 에러가 발생한 Filter 이전에 예외를 핸들링할 수 있는 새로운 예외 처리 필터를 만들면 되겠다는 힌트를 얻을 수 있다.

 

 

 

현재 예외가 발생하고 있는 JwtAuthenticationFilter 이전에 예외를 catch 하고 json 형태로 에러 메세지를 가공하여 사용자에게 전송해 주는 JwtExceptionHandlerFilter를 만들어주면 위 그림과 같은 구조가 된다. (만들어준 필터는 spring security에서 꼭 적용을 해주어야 하니 잊지 말자.)

 

 

 

그럼 문제가 해결된 것 같은데... 나는 여기에서 또 하나의 문제를 겪게 되었다.

 

 

포스트맨에서 위 사진과 같이 응답을 받지 못하는 문제가 발생했다. 분명히 로그를 찍어보거나 디버깅을 해보면 문제없이 예외를 핸들링하고 response를 가공해서 사용자에게 response.getWriter().write(json)을 통해서 보내주는데 왜 이런 에러가 발생했을까?

 

열심히 찾아보니 문제는 JwtExceptionHandlerFilter의 response.setStatus를 해주는 과정에서 생겼다는 것을 알 수 있었다. 나는 아무 생각 없이 response.setStatus를 통해서 response의 응답 코드를 3000번대인 커스텀 ExceptionCode로 등록을 하고 있었다. 

 

 

 

그러나 HTTP 프로토콜은 위 그림과 같이 정해진 범위의 Status Code를 가지고 있다. 이 범위를 완전히 벗어나는 3000번대를 응답 코드로 보내주었기 때문에 Postman이나 클라이언트에서 응답을 하지 못했다는 것을 알 수 있었다.

 

response.setStatus(INVALID_ACCESS_TOKEN.getCode());

 

->

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

 

위와 같이 401, Unauthorized 코드를 response에 적용해 주면 정상적으로 필터에서 예외를 캐치하고 사용자에게 커스텀 예외 응답이 보내지는 것을 확인할 수 있다.

 

 

코딩할 때 항상 그냥은 없다는 것을 느낀다. 이번 문제 상황처럼 응답 하나를 전달하는 데에도 수많은 배경지식들이 숨어있다.

하나를 만들더라도 이유가 있고 개념에 대해 깊이 이해하는 개발자가 되는 습관을 들이자.

 

 

 

 

+ 전체 코드는 아래 프로젝트 링크에서 확인할 수 있습니다.

https://github.com/Camp-Ride/backend/tree/main/src/main/java/com/richjun/campride/global/jwt