개요
개인 프로젝트에서 JWT를 적용하던 중, 클라이언트에서 유효기한이 만료된 access token 때문에 생기는 예외를 servlet filter에서 처리할 수 있는 방법을 찾아 이 글을 작성하게 되었다.
ExpiredJwtException
Java에서 JWT를 도입하기 위해서는 io.jsonwebtoken 라이브러리를 사용해야 한다.
여기서, token을 parsing 할때 사용되는 JwtParser라는 인터페이스는 parseClaimsJws 메소드를 가지고 있고, 이 메소드는 매개변수로 받은 token에 이상이 있을 경우 여러 JWT 관련 예외들을 던진다. 그 중에서 ExpiredJwtException은 token의 유효기한이 만료되었을 경우 던져지는 예외이다.
현재 진행 중인 프로젝트를 예시로 잠깐 살펴보자.
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String accessToken = jwtTokenProvider.retrieveToken((HttpServletRequest) request);
if (accessToken != null) {
Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
위의 코드는 요청으로 전달되는 token을 확인하는 servlet filter이다. 여기서 jwtTokenProvider.getAuthentication(accessToken) 은 token을 parsing해서 인증 정보를 추출하는 역할을 한다. 그 말은 즉, 이 filter에서 기간이 만료된 token을 받을 시, 위에서 언급한 ExpiredJwtException이 생길 수 있다는 것이다.
해결 방법
그렇다면, 위의 예외가 발생할 경우, 어떻게 처리할 수 있을까?
처음에는 Spring의 @Controller 에서 생기는 예외를 처리하는 @ControllerAdvice를 사용하면 어떨까? 라는 생각을 해보았다. 하지만 조금만 생각해보면, 이 방법은 힘들다는 것을 알 수 있다. 왜나하면 @ControllerAdvice는 말 그대로 @Controller에서 생기는 예외를 처리하기 때문이다.
더 정확히 말하면, @ControllerAdvice는 Spring의 DispatcherServlet 이후에 발생하는 예외만 처리할 수 있는 전역 컨트롤러이다. 결국, servlet filter는 DispatcherServlet이 호출되기 전에 실행되기 때문에, 이곳에서 발생하는 예외는 @ControllerAdvice가 처리할 수 없다.
물론, 위의 JwtAuthenticationFilter에서 JwtException이 발생하는 부분에 try-catch 문을 사용해 예외를 잡고, redirect를 보내는 방법을 선택할 수도 있다. 하지만, 그렇게 하면 예외를 처리하는 코드를 추가해야 하는 문제가 있고, JwtException외에도 다른 예외가 발생할 경우, Authentication을 확인하는 코드보다 예외를 처리하는 코드가 더 많아질 가능성이 있다 (SRP 위반 가능성 ↑). 그렇기 때문에 해당 예외를 처리하는 코드를 따로 분리할 수 있는 방법을 찾을 수 있다면 더 이상적일 것이다.
Servlet fitler는 체이닝이 가능하다. 즉, filter 전, 후로 다른 filter를 연결시킬 수 있다는 것이다. 이 방법을 응용한다면 쉽게 filter에서 발생하는 예외를 처리할 수 있다.
@Slf4j
public class JwtExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (JwtException e) {
...
}
}
}
먼저 예외를 처리하는 filter를 하나 만든다. 이 filter는 예외가 발생하는 JwtAuthenticationFilter가 호출되기 전에 적용하는 filter가 될 것이므로, 이 코드에서 filterChain.doFilter(request, response)는 다음 필터인 JwtAuthenticationFilter를 호출하는 메소드가 된다.
그 후, Configuration에서 위의 filter들을 등록하면 정상적으로 JwtExceptionFilter가 JwtAuthenticationFilter에서 발생하는 예외를 잡아 처리할 수 있게 된다.
참고로, 위에서 언급한 filter들은 Spring Security에 사용되는 filter이므로, 아래와 같이 HttpSecurity의 addFilterBefore 메서드를 사용해 적용하고 싶은 위치에 해당 filter들을 적용했다.
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfig {
...
private final JwtTokenProvider jwtTokenProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(
new JwtAuthenticationFilter(jwtTokenProvider),
OAuth2LoginAuthenticationFilter.class
)
.addFilterBefore(
new JwtExceptionFilter(),
JwtAuthenticationFilter.class
);
return http.build();
}
...
}
'Spring' 카테고리의 다른 글
[Spring] MultipartFile 테스트하는 방법 (0) | 2024.01.17 |
---|---|
[Spring] Spring REST Docs 상세 설정 (0) | 2024.01.02 |
[Spring / S3] S3Mock을 사용하여 S3 테스트하기 (0) | 2023.12.24 |
[Spring] Spring Data JPA의 Pagination (0) | 2023.12.24 |
[Spring] Spring에서 API 문서 자동화하기 (3) (0) | 2023.12.24 |