[Spring] filter 예외처리

Yuri·2025년 2월 13일

Spring

목록 보기
14/21

🔫 예외처리를 구현하며 겪은 문제점과 해결방법, 새로 알게된 점을 기록합니다.

1. 배경

401 Unauthorized 상태코드는 요청이 인증되지 않았거나 적절한 인증 정보가 제공되지 않았을 때 반환하는 상태 코드이다. 주로 로그인 기능이나 인증을 처리할 때 사용한다.

💬 요구사항

  • 로그인 시 이메일과 비밀번호가 일치하지 않을 경우 HTTP Status code 401을 반환합니다.

⚙️ 개발환경

  • Spring Security 를 사용하지 않음

2. 발단

로그인 정보(이메일, 비밀번호)가 일치하지 않을 경우에는 @ExceptionHandler 를 통해 커스텀한 ErrorCode 와 메세지를 출력하고 정상적으로 401 Unauthorized 상태코드를 반환한다.

로그인하지 않은 상태에서 인증이 필요한 요청을 보낸 경우, CustomException으로 예외를 던져 401 Unauthorized 상태코드가 반환될 것이라는 예상과 달리 500 Internal Server Error 코드가 반환되었다.

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        HttpServletResponse httpResponse = (HttpServletResponse) response;

        log.info("requestURI: {}", requestURI);

        if (!isWhiteList(requestURI)) {
            HttpSession session = httpRequest.getSession(false);
            if (session == null || session.getAttribute(Const.SESSION_KEY) == null) {
                throw new CustomException(ErrorCode.UNAUTHORIZED, "로그인이 필요한 요청입니다.");
            }
            MemberContext.setMemberId((Long) session.getAttribute(Const.SESSION_KEY));
        }
        chain.doFilter(request, response);
    }

3. 전개

서버 로그를 확인하니 로그인 필터에서는 정상적으로 CustomException 을 던진다. 하지만 클라이언트에 반환할 때 서버 에러로 반환하고 있다.

@ExceptionHandler 는 @RestControllerAdvice 를 함께 사용하여 컨트롤러 전역으로 예외를 처리하도록 되어있지만 해당 예외를 처리하지 못하고 있음을 알 수 있다.

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    private ResponseEntity<ErrorResponseDto> handleException(Exception e) {
        return ErrorResponseDto.errResponseEntity(new CustomException(e));
    }
    // ...
}

4. 위기

@RestControllerAdvice 는 필터의 예외를 처리할 수 없다.

@ControllerAdivce, @RestControllerAdvice는 기본적으로 모든 Controller에 적용되는 Advice 로 Filter 단에서 발생한 예외를 핸들링 하지 못한다.

👉 필터에서 발생한 예외는 전역으로 처리할 수 없으니 별도의 처리가 필요하다!

🔗 참고
[Spring Security] Filter 에서 발생한 예외 핸들링하기

5. 절정

로그인 필터에서 로그인하지 않은 상태에서 인증이 필요한 요청을 보낸 경우 클라이언트에 401 Unauthorized 상태코드를 보내고 다음 단계로 진행하지 못하게 흐름을 중단시킨다.

▶︎ HttpServletResponse 에 json 형식의 에러메세지를 수동으로 보내기위해 별도의 메서드를 작성하였다.

private static void loginFilterExceptionHandler(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        String json = new ObjectMapper()
                .writeValueAsString(ErrorResponseDto.errResponseEntity(new CustomException(ErrorCode.UNAUTHORIZED, "로그인이 필요한 요청입니다.")));
        response.getWriter().write(json);
    }

body 안의 내용은 공통 양식을 따르기 위해 ErrorResponseDto를 이용해 만들고 객체를 String 타입으로 변환하는 ObjectMapper.writeValueAsString (jackson 라이브러리)를 사용하여 json 형식으로 만든다.

예외 흐름에서 해당 메서드를 호출하도록 코드를 수정하였다.

if (!isWhiteList(requestURI)) {
    HttpSession session = httpRequest.getSession(false);
    if (session == null || session.getAttribute(Const.SESSION_KEY) == null) {
        loginFilterExceptionHandler(httpResponse);
        return;
    }
    MemberContext.setMemberId((Long) session.getAttribute(Const.SESSION_KEY));
}

예외코드가 401 Unauthorized 상태코드로 반환되고 커스텀한 에러 메세지로 반환된 것을 확인할 수 있다.

6. 결말

  • Spring 에서 생명주기와 범위를 관리하더라도 어느 범위까지 적용되는 지 개발자는 알고 있어야한다.
  • Spring의 흐름을 명확히 알고 있어야 서버의 구멍을 발견할 수 있고 갑작스러운 500 Internal Server Error 서버 에러를 피할 수 있다.
profile
안녕하세요 :)

0개의 댓글