에러 처리 - Filter에서 발생한 에러 처리하기
2025. 2. 19. 01:21ㆍ스프링
Filter에서 JWT에 대한 에러를 처리하려는데, @ControllerAdvice에서 에러를 잡아내지 못해서
제가 만든 ErrorResponse의 형태로 에러를 보내는 것에 문제가 생겼다는 것을 알게되었습니다.
Filter는 DispatcherServlet 전에 실행이 되므로 @ControllerAdvice가 발생한 예외를 잡아서 처리할 수 없습니다.
기존의 JwtFilter
public class JwtFilter extends OncePerRequestFilter {
private final JwtValidator jwtValidator;
public JwtFilter(JwtValidator jwtValidator) {
this.jwtValidator = jwtValidator;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = getJwtFromRequest(request);
if(token == null) throw new CustomException(ErrorCode.TOKEN_NOT_PROVIDED);
if(!jwtValidator.isExpired(token, true)) {
String username = jwtValidator.getUsername(token, true);
String role = jwtValidator.getRole(token, true);
Role roleEnum = Role.valueOf(role);
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
userEntity.setPassword(null);
userEntity.setRole(roleEnum);
CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
} else {
// 토큰이 유효하지 않다면 401 Unauthorized 응답
throw new CustomException(ErrorCode.EXPIRED_ACCESS_TOKEN);
}
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
방법 1. Filter 내부에서 처리
try-catch 문을 이용해서 Filter 내부에서 처리를 하면 아래와 같습니다.
- 변경된 JwtFilter
public class JwtFilter extends OncePerRequestFilter {
private final JwtValidator jwtValidator;
private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
public JwtFilter(JwtValidator jwtValidator) {
this.jwtValidator = jwtValidator;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String token = getJwtFromRequest(request);
if (token == null) throw new CustomException(ErrorCode.TOKEN_NOT_PROVIDED);
if (!jwtValidator.isExpired(token, true)) {
String username = jwtValidator.getUsername(token, true);
String role = jwtValidator.getRole(token, true);
Role roleEnum = Role.valueOf(role);
UserEntity userEntity = new UserEntity();
userEntity.setUsername(username);
userEntity.setPassword(null);
userEntity.setRole(roleEnum);
CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
} else {
throw new CustomException(ErrorCode.EXPIRED_ACCESS_TOKEN);
}
} catch (CustomException e) {
setErrorResponse(response, e.getErrorCode(), request.getRequestURI());
}
}
private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode, String path) throws IOException {
response.setStatus(errorCode.getHttpStatus().value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
ErrorResponse errorResponse = new ErrorResponse(
errorCode.getHttpStatus(),
errorCode.getCode(),
errorCode.getMessage(),
path
);
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
이렇게 catch문에서 원하는 형태로 만들어서 클라이언트에게 응답해주면 됩니다.
방법 2. 전역 예외 처리를 하는 Filter를 추가하기
- GlobalExceptionFilter
@Component
public class GlobalExceptionFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper;
public GlobalExceptionFilter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (CustomException e) {
setErrorResponse(response, e.getErrorCode(), request.getRequestURI());
} catch (Exception e) {
setErrorResponse(response, ErrorCode.INTERNAL_SERVER_ERROR, request.getRequestURI());
}
}
private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode, String path) throws IOException {
response.setStatus(errorCode.getHttpStatus().value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
ErrorResponse errorResponse = new ErrorResponse(
errorCode.getHttpStatus(),
errorCode.getCode(),
errorCode.getMessage(),
path
);
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
- SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
private final JwtProvider jwtProvider;
private final JwtValidator jwtValidator;
private final GlobalExceptionFilter globalExceptionFilter;
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration,
JwtProvider jwtProvider,
JwtValidator jwtValidator,
GlobalExceptionFilter globalExceptionFilter) {
this.authenticationConfiguration = authenticationConfiguration;
this.jwtProvider = jwtProvider;
this.jwtValidator = jwtValidator;
this.globalExceptionFilter = globalExceptionFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.formLogin(form -> form.disable())
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.addFilterAt(new LoginFilter(authenticationManager(this.authenticationConfiguration), this.jwtProvider), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtFilter(jwtValidator), LoginFilter.class)
.addFilterBefore(globalExceptionFilter, JwtFilter.class);
return http.build();
}
}
이렇게 FilterChain의 제일 첫번 째 필터로 등록해 요청을 감싸서 예외를 잡도록 설정합니다.
이렇게 한다면 이 필터이후에 등록된 모든 필터에서 발생한 예외가 이 필터에서 처리되므로 일관된 에러 응답 유지가 가능합니다.
'스프링' 카테고리의 다른 글
에러 해결: Content-Type 'multipart/form-data' is not supported (0) | 2025.04.20 |
---|---|
에러 처리 - 커스텀 Exception (0) | 2025.02.05 |
에러 처리 - 지역적, 전역적 처리 (0) | 2025.01.13 |
Spring Security에 JWT 적용하기 (2) (0) | 2024.11.13 |
Spring Security에 JWT 적용하기 (0) | 2024.11.02 |