💡 개요
오늘은 Filter에서 발생하는 예외는 어떻게 처리해야 하는지에 대해 정리해 보자.
📕 Filter 예외
Filter는 서버에게 요청을 전달하기 전, 인증・인가나 보안과 관련된 작업을 거치는 공간이다.
여기서 서버란 Dispatcher Servlet을 말한다.
그런데 보통 Spring 환경에선 내부에서 발생한 예외를 처리하기 위해 GlobalExceptionHandler를 사용한다.
그리고 GlobalExceptionHandler는 Dispatcher Servlet 내부에 위치한다.
만약 Filter에서 예외가 발생하면 어떻게 될까?
우리는 공통 예외 처리를 위해 정성 들여 GlobalExceptionHandler를 완성했지만, 요청이 Filter 단계에서 예외가 발생하면 DispatcherServlet까지 도달하지 못해 정작 사용할 수 없다.
Filter에서는 주로 JWT 토큰 인증・인가와 관련된 예외가 발생한다.
만약 Filter에서 예외 처리를 따로 하지 않으면, 클라이언트는 어떤 예외가 발생했는지 정확한 응답을 받지 못할 수 있다.
다음은 예외 처리를 따로 하지 않은 상황에서 발생하는 예외 응답이다.
{
"timestamp": "2025-03-12T05:03:48.661+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v1/members/detail"
}
따라서 Filter에서도 별도로 예외 처리를 해주어야 한다.
🛠️ Filter 예외 처리
다음은 JwtFilter.validate() 에서 토큰 만료 여부나 형태 검증과 같은 인증 관련 코드이다.
private boolean validate(final String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (ExpiredJwtException e) {
throw new RestApiException(JwtErrorCode.EXPIRED_JWT);
} catch (Exception e) {
throw new RestApiException(JwtErrorCode.INVALID_JWT);
}
}
만약 validate()에서 예외가 발생해서 RestApiException 을 던지면 JwtFilter.doFilterInternal() 에서 예외를 잡고, FilterExceptionHandler.handleExceptionInternal() 을 사용해 예외를 처리한다.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String token = resolveToken(request);
validate(token);
String email = getEmailFromToken(token);
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(MemberRole.GENERAL.toString()));
UserDetails userDetails = new org.springframework.security.core.userdetails.User(
email,
"null",
authorities
);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
// 접근한 유저의 authentication 객체를 SecurityContextHolder에 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (RestApiException e) {
FilterExceptionHandler.handleExceptionInternal(response, e.getErrorCode());
}
}
public class FilterExceptionHandler {
public static void handleExceptionInternal(HttpServletResponse response, ErrorCode errorCode) throws IOException {
ErrorResponse errorResponse = new ErrorResponse(errorCode.getHttpStatus().value(), errorCode.getMessage());
response.setStatus(errorCode.getHttpStatus().value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
이렇게 FilterExceptionHandler 을 구현하면 다음과 같이 예외를 응답한다.
{
"code": 400,
"message": "토큰이 NULL일 수 없습니다."
}
'개발 일기' 카테고리의 다른 글
[개발 일기] 2025.03.14 - 인메모리는 휘발성이라매 (0) | 2025.03.14 |
---|---|
[개발 일기] 2025.03.13 - @BeforeAll (0) | 2025.03.13 |
[개발 일기] 2025.03.11 - compareTo() (0) | 2025.03.11 |
[개발 일기] 2025.03.10 - 날짜 데이터 타입 (0) | 2025.03.10 |
[개발 일기] 2025.03.09 - InnoDB (0) | 2025.03.09 |