문제
Filter 내부에서 발생하는 에러를 커스텀 에러로 만들어서 아래와 같이 클래스를 생성했다.
public class LoginFailedException extends RuntimeException {
public LoginFailedException(String message) {
super(message);
}
}
@ExceptionHandler를 통해 해당 에러의 예외처리를 실행하도록 했다.
@ExceptionHandler(LoginFailedException.class)
public ResponseEntity<ErrorResponseDto> handleLoginFailedException(LoginFailedException e, HttpServletRequest request){
...
return new ResponseEntity<>(responseDto,HttpStatus.BAD_REQUEST);
}
하지만 super(message)를 통해 서버에만 로그가 출력되고, 클라이언트로는 예외처리가 제대로 수행하지 않아 ServletContainer가 기본적으로 전달하는 500 서버 에러가 클라이언트로 전달됐다.
원인

@ExceptionHandler는 Spring MVC 컨트롤러의 예외처리만 잡아주기 때문에 그 이전에 발생하는 예외는 처리하지 않는다.
Filter는 사용자 요청과 DispatcherServlet 사이의 과정에서 수행되기 때문에 @ExceptionHandler로 예외처리할 수 없었다.
따라서 filter 클래스 안에서 예외처리를 진행해야 했다.
해결
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestURI = httpRequest.getRequestURI();
log.info("로그인 필터 로직 실행");
log.info("request URI = {}", requestURI);
if (!isWhiteList(requestURI)) {
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute(LOGIN_USER) == null) {
// 여기서 발생하는 예외처리 필요
}
}
chain.doFilter(request, response);
}
방법은 간단하게 if문 내부에서 예외가 발생하면 try-catch문으로 처리하는 것이다.
여기서 문제점이 있다면 doFilter의 반환 타입은 void라 아무것도 반환하지 않지만, 나는 클라이언트로 에러 메시지를 전달해야 하는 것이다.
만약 반환 타입을 ResponseEntity 등으로 반환한다면, 예외가 발생하지 않는 상황에서도 어떤 데이터를 반환해야 했다.
그래서 나는 HttpServletResponse를 사용해서 내부에 에러 메시지를 담고, return 하지 않아도 Spring이 자동으로 리턴해주도록 수정했다.
if (session == null || session.getAttribute(LOGIN_USER) == null) {
// ObjectMapper를 통해 Dto를 HttpServletResponse에 담아서 JSON 형식으로 전달
ErrorResponseDto responseDto = new ErrorResponseDto(Timestamp.valueOf(LocalDateTime.now()), UNAUTHORIZED.getStatus(), UNAUTHORIZED.getError(), UNAUTHORIZED.getCode(), UNAUTHORIZED.getMessage(), requestURI);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(responseDto);
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpResponse.setContentType("application/json");
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.getWriter().write(json);
return;
}
{
"timestamp": "2025-03-26T14:26:45",
"status": 400,
"error": "BAD_REQUEST",
"code": "C001",
"message": "잘못된 입력값입니다",
"path": "/api/login"
}
위와 같이 정해진 형식으로 통일해서 에러메시지를 전달하고 싶었기에 ErrorResponseDto를 사용했고, 이렇게 만들어진 객체를 JSON으로 변환해서 response에 담기 위해 ObjectMapper를 활용했다.
ObjectMapper는 객체를 JSON으로 변환해주는 클래스이다.
그리고 setStastus, setContentType, setCharacterEncoding으로 전달할 데이터의 상태코드, 타입, 인코딩 설정을 한 뒤 response에 JSON을 담아 응답하는 방식으로 이 문제를 해결했다.
결과

로그인 하지 않은 상태로 API 요청을 시도하면, 로그인 필터에서 예외를 발생시켜 이미지와 같이 정해진 형식의 에러메시지를 응답한다.
'트러블 슈팅' 카테고리의 다른 글
직렬화 실패 이슈 해결 (0) | 2025.04.10 |
---|---|
[Java/Spring] Spring Data JPA 메서드명 설정, JPQL 사용 (0) | 2025.04.08 |
[Java/Spring] 분리된 테이블의 데이터 반환 트러블 슈팅 (+레이어 리팩토링) (0) | 2025.03.25 |
[Java/Spring] 406 Not Acceptable / 검증 어노테이션 에러 메시지 변경 (0) | 2025.03.24 |
[Java] 키오스크 프로그램에 스트림 적용 중 발생한 이슈 (0) | 2025.03.13 |