Search

Filter 에 따른 Exception Custom - JWT 예외 처리하기

태그
JWT
Exception
spring security
spring boot 3
분류
Spring Boot
목차
 같이보면 좋은 글

문제의 발견

이전에 Jwt 관련 예외 처리를 아래와 AuthenticationEntryPoint 에 작성한 적이 있습니다. 테스트 코드를 작성 하기전, 한번 확인하기 위해 시도해봤는데 아래와 같은 응답이 반환 되었습니다.
코드에 작성한대로라면 AUTH_01 이 반환되어야 했습니다.
} else if (authException.getCause() instanceof ExpiredJwtException) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); setResponse(response, "TOKEN_01"); } else if (authException.getCause() instanceof MalformedJwtException) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); setResponse(response, "TOKEN_02"); }
Java
복사

왜 그럴까?

이전에 작성한 Filter 에 따른 Exception Custom - EntryPoint 란? 글을 보면 알 수 있듯이 CustomAuthenticationEntryPoint 에서 override 한 commence 는 AuthenticationException 만 처리하기 때문입니다. 때문에 여기서 ExpiredJwtException 를 잡을 수 없습니다.

해결방법

로그인 이후의 모든 요청은 Jwt 인증 필터를 거친 뒤 시큐리티 인증 필터를 거치도록 구현하였습니다. 그리고 인증이 완료된 유저는 인가 과정을 통과하면 원하는 자원을 받을 수 있습니다.
따라서 OncePerRequestFilter 를 상속받아 구현한 JWTAuthenticaionFilter 에서 예외처리를 수행하였습니다. 저의 경우 다른 컴포넌트 에서 토큰 만료 예외만 던지도록 구현했기 때문에 해당 예외만 캐치가 가능합니다. 로직에서 발생할 수 있는 예외를 잡을 수 있으니, 상황에 맞게 수정하여 사용해주세요.
package com.planner.travel.global.jwt; import com.planner.travel.global.jwt.token.TokenAuthenticator; import com.planner.travel.global.jwt.token.TokenExtractor; import com.planner.travel.global.jwt.token.TokenValidator; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpStatus; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.security.SignatureException; @RequiredArgsConstructor @Slf4j public class JWTAuthenticationFilter extends OncePerRequestFilter { private final TokenExtractor tokenExtractor; private final TokenValidator tokenValidator; private final TokenAuthenticator tokenAuthenticator; @Override protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { String requestURI = request.getRequestURI(); if (requestURI.equals("/api/v1/auth/signup") || requestURI.equals("/api/v1/auth/login") || requestURI.startsWith("/api/v1/auth/token") || requestURI.startsWith("/docs")) { filterChain.doFilter(request, response); return; } String accessToken = tokenExtractor.getAccessTokenFromHeader(request); if (accessToken != null) { try { tokenValidator.validateAccessToken(accessToken); tokenAuthenticator.getAuthenticationUsingToken(accessToken); } catch (ExpiredJwtException e) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); setResponse(response, "TOKEN_01"); return; } } filterChain.doFilter(request, response); } private void setResponse(HttpServletResponse response, String errorCode) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.getWriter().println( "{\"errorCode\" : \"" + errorCode + "\"}" ); } }
Java
복사

결과

JWTAuthenticationFilter 를 거치면서 catch 한 예외인 ExpiredJwtException 에 대한 응답이 잘 반환되는 것을 확인할 수 있습니다.

AUTH_03

해당 예외는 CustomAuthenticationEntryPoint 에서 처리한 오류 메세지 입니다. 토큰 인증이 실패 했기 때문에 유저 인증 또한 실패로 끝나게 됩니다. 이 과정에서 InsufficientAuthenticationException 가 발생합니다. 저의 경우 CustomAuthenticationEntryPoint 에서 아래와 같이 작성해주었기 때문에 해당 응답 또한 같이 반환됩니다.
} else if (authException instanceof InsufficientAuthenticationException) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); setResponse(response, "AUTH_03"); }
Java
복사

Request 처리 흐름 정리

OncePerRequestFilter 와 Spring Security

OncePerRequestFilter 는 서블릿 필터이며 요청 한번 당 실행되도록 보장하는 필터 클래스 입니다. 인증과 보안 목적으로 사용됩니다. 예로는 UsernamePasswordAuthenticationFilter 가 있으며, 이 필터 자체가 특정 목적을 수행하기 보다는 많은 security filter 들이 이를 상속받아 각종 보안 관련 기능을 수행합니다. 즉, 이 필터는 요청이 유효한지 확인하고, 요청에 필요한 전처리 작업을 수행합니다. 이 과정에서 인증 실패와 같은 상황이 발생할 경우 조기에 요청 처리를 중단할 수도 있습니다.

DispatcherServlet

서블릿 필터를 거친 요청은 이곳에 도달합니다. DispatcherServelt 은 요청을 해석하고, 핸들러 매핑을 사용하여 요청을 적절한 컨트롤러로 라우팅 합니다.

AuthenticationEntryPoint

인증 과정에서 문제가 발생하거나, 요청이 인증되지 않은 상태에서 보호된 리소스에 접근하려 할 때 사용합니다. 예외에 따른 상태 코드나 수행할 행동을 정할 수 있습니다. 일반적으로, AuthenticationEntryPoint는 OncePerRequestFilter 에서 발생한 인증 실패 처리를 담당합니다. 관련된 내용은 Filter 에 따른 Exception Custom - EntryPoint 란? 을 확인 해주세요.

결론

필터 (OncePerRequestFilter) → DispatcherServlet → 컨트롤러 의 흐름을 따르며 인증 실패와 같은 예외 상황이 발생한다면 AuthenticationEntryPoint 가 이를 처리하여 적절한 응답을 생성합니다.