[Tistory] Spring Boot Servlet Filter에서 에러 코드 변경하기

원글 페이지 : 바로가기

문제 상황 Spring 으로 프로젝트를 진행하던 도중 Filter 를 이용해서 JWT 검증 로직을 만들었는데, 토큰이 만료되면 에러코드를 다르게 보내주려고 했었다. 허나.. 구글링해서 얻은 답변인 @ResponseStatus() 에너테이션을 이용해도 에러 코드가 바뀌지 않고 ResponseStatusException 객체를 이용해봐도 안되길래 스택오버플로우를 통해 알아낸 방법인 application.properties에 server.error.include-message = always 설정 넣어주기를 해봐도 바뀌지 않았다…. 기존 코드 //JwtAuthenticationFilter

@Component
@Getter
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

//cors 설정
response.setHeader(“Access-Control-Allow-Origin”, “*”);
response.setHeader(“Access-Control-Allow-Credentials”, “true”);
response.setHeader(“Access-Control-Allow-Methods”,”*”);
response.setHeader(“Access-Control-Max-Age”, “3600”);
response.setHeader(“Access-Control-Allow-Headers”,
“Origin, X-Requested-With, Content-Type, Accept, Authorization”);
response.setHeader(“Content-Type”, “*”);

if(“OPTIONS”.equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK); //option 요청일때 필터검증 안함
}else {
// 진짜 요청일때 필터 검증
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
Claims claims = jwtTokenProvider.parseJwtToken(authorizationHeader);
request.setAttribute(“claims”, claims); // jwt 정보 컨트롤러에서 사용할 수 있게 request에 담기
filterChain.doFilter(request, response);
}
}
}
//JwtTokenProvider 클래스 내부

public Claims parseJwtToken(String authorizationHeader) {
validationAuthorizationHeader(authorizationHeader);
String token = extractToken(authorizationHeader);

//토큰 검증
Claims claims = (Claims) validateToken(token);
return claims;
}

/**
* 토큰 검증 메서드
* @param token
* @return claims
*/
public Object validateToken(String token) throws ExpiredJwtException {
try {
return Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException exception) {
log.info(“토큰 만료”);
throw new ResponseStatusException(
HttpStatus.UNAUTHORIZED, “토큰이 만료되었습니다.”, exception
);
} catch (JwtException | IllegalArgumentException exception) {
log.info(“jwtException : {}”, exception);
throw exception;
}

}
JwtTokenProvider 의 Jwt.parser() 에서 ExpiredJwtException 에러를 뱉어주면 ResponseStatusException 객체가 에러코드를 401 로 바꿔줄거라고 생각했다… 그러나 결과는… 그놈의 500 …. 문제 원인 디버깅을 통해 혹시…? 라는 마음으로 필터 말고 컨트롤러 에 위 방식들을 적용해보자 놀랍게도 !!!!!! 그렇다, 필터와 컨트롤러는 에러를 처리하는 방식이 달라서 생기는 문제였다. 정확히는 JwtTokenProvider 에서 에러를 throw 해줘도 그 에러가 바로 Response 로 오는것이 아닌 필터를 거쳐 에러가 다르게 변형되는 것이 문제였다. 해결 방법 그래서, 에러 처리를 JwtTokenProvider 에서 해주지 말고 Jwt.parser() 가 throw 해주는 에러를 JwtAuthenticationFilter 에서 처리해주는 것으로 변경했다. @Component
@Getter
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

//cors 설정
response.setHeader(“Access-Control-Allow-Origin”, “*”);
response.setHeader(“Access-Control-Allow-Credentials”, “true”);
response.setHeader(“Access-Control-Allow-Methods”,”*”);
response.setHeader(“Access-Control-Max-Age”, “3600”);
response.setHeader(“Access-Control-Allow-Headers”,
“Origin, X-Requested-With, Content-Type, Accept, Authorization”);
response.setHeader(“Content-Type”, “*”);

if(“OPTIONS”.equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK); //option 요청일때 필터검증 안함
}else { // 진짜 요청일때 필터 검증

String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
try {
Claims claims = jwtTokenProvider.parseJwtToken(authorizationHeader);
request.setAttribute(“claims”, claims); // jwt 정보 컨트롤러에서 사용할 수 있게 request에 담기

} catch (ExpiredJwtException jwtException) {
log.info(“토큰 만료”);

ObjectMapper mapper = new ObjectMapper();

response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(“UTF-8”);

ResponseStatusException responseStatusException = new ResponseStatusException(
HttpStatus.UNAUTHORIZED, “토큰이 만료되었습니다.”);

mapper.writeValue(response.getWriter(), responseStatusException);

}catch (JwtException | IllegalArgumentException exception) {
log.info(“jwtException : {}”, exception);
throw exception;
}

filterChain.doFilter(request, response);
}
}

} Claims 를 받아오는 과정에서 에러가 발생하므로 이 부분을 try-catch 문으로 감싸주고 doFilter 메서드로 현재 필터 다음 과정으로 넘어가므로 response 객체에 에러를 담아서 보내주었다. //JwtTokenProvider 클래스 내부

public Claims parseJwtToken(String authorizationHeader) {
validationAuthorizationHeader(authorizationHeader);
String token = extractToken(authorizationHeader);

//토큰 검증
Claims claims = (Claims) validateToken(token);
return claims;
}

/**
* 토큰 검증 메서드
* @param token
* @return claims
*/
public Object validateToken(String token) throws ExpiredJwtException {

return Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
}

위 코드로 변경하여 에러를 보냈더니 아주 이쁘게 에러가 변경된 모습 출처 : https://velog.io/@jonghyun3668/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%ED%95%84%ED%84%B0%EC%97%90%EC%84%9C-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다