241210 Handler Interceptor 트러블 슈팅(feat. /error)

물고기가자라면어그로·2024년 12월 10일
0

문제 발생 인지

회원가입 API는 로그인의 인증인가를 건너뛰도록 excludePathPatterns()로 제외시켜두었는데 테스트를 돌리다보니 "로그인이 필요합니다"라는 결과가 뜨는 등 계속 LoginInterceptor에 잡혔습니다.

처음에는 WebConfig에서 작성한 uri Pattern들의 문제이거나 loginInterceptor의 문제일 것이라 추측하였습니다.

원인

WebConfig를 다시 돌아보며 저는 LoginInterceptorInterceptorRegistry에 등록하는 방법이 잘못되었다고 생각했습니다.

로그인 인터셉터는 배달 앱에서 회원가입이나 로그인 API를 제외한 대부분의 API에 적용시킬 예정이었기 때문에 .addPathPatterns()로 전체 uri를 포함시키고, .excludePathPatterns()로 회원가입이나 로그인의 uri를 제외시키는 방식으로 로그인 인터셉터를 등록시키고 있었습니다.

    private static final String[] LOGIN_REQUIRED_PATH_PATTERNS = {"/**"};
    private static final String[] LOGIN_EXCLUDE_PATH_PATTERNS = {"/api/users/signup", "/api/users/login"};
    
    private final LoginInterceptor loginInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns(LOGIN_REQUIRED_PATH_PATTERNS)
                .excludePathPatterns(LOGIN_EXCLUDE_PATH_PATTERNS)
                .order(Ordered.HIGHEST_PRECEDENCE);
    }

하지만 이상한 부분을 찾기 어려웠고 디버깅을 돌려보기로 했습니다.

그러자 알게 된 것은 회원가입을 실행시켰을 경우 실행이 컨트롤러를 한 번 찍고 다시 loginInterceptor에 돌아와 "로그인을 해주세요"라는 예외처리를 발생시키고 있었던 것입니다.

loginInterceptor는 preHandle() 메서드로만 작성을 해서 컨트롤러에 진입하기 전에 intercept 되어야 하는 것이 정상인데 컨트롤러를 거친 후 돌아온다는 게 이상하게 느껴졌습니다.

그래서 preHandle로 돌아온 상황에서 스레드 및 변수 창에서 request를 열어보자 놀랍게도 requestURI에 '/error'가 찍혀있었습니다.

검색을 해보니 오류가 났을 때 예외처리를 제대로 하지 않았을 경우 Spring에서 '/error'를 호출하며 BasicErrorController 가 이를 처리해주는 것이었습니다.

왜 이것이 발생했는지 알아보니 이 handlerInterceptor의 실행여부에만 급급해서 postman으로 실행을 반복적으로 돌리다보니 이미 회원가입된 이메일로 또 회원가입을 하려고해서 중복이메일에 대한 예외처리(ResponseStatusException)가 throw 되고 있었습니다.

저희 팀이 전체적으로 코드를 어느정도 짠 이후에 함께 예외처리를 진행하자고 얘기를 했기 때문에 아직 RuntimeExceptionResponseStatusExceptionthrow 하고 있는 상태였습니다.

스프링의 기본 예외 처리 흐름

컨트롤러에서 만약 예외가 발생한다면 이는 HandlerExceptionResolver로 넘어가게 됩니다. 이 때 HandlerExceptionResolver는 다음과 같은 과정을 거치게 됩니다.

  1. 컨트롤러 영역에서 예외를 처리할 수 있는 @ExceptionHandler를 찾음
  2. 전역적으로 예외를 처리할 수 있는 @ControllerAdvice@ExceptionHandler를 찾음
  3. /error 호출 (포워딩)

만약 @ExceptionHandler로 예외를 처리하지 못하게 되면 HandlerExceptionResolver/error로 포워딩(서버 내부 전달)을 시키고 이를 BasicErrorController가 처리합니다.

이 때, BasicErrorController에 도달하기 전 preHandle()Interceptor가 이를 intercept하게 될 수 있는 것입니다.

문제 코드의 원인

그렇기 때문에 저희의 프로젝트에서는 세 가지 원인으로 문제가 발생하고 있었습니다.

  1. 중복 아이디로 회원가입을 시도하며 RuntimeException이 발생
  2. 아직 예외처리를 핸들링하고 있지 않아 /error가 호출됨
  3. LOGIN_REQUIRED_PATH_PATTERNS"/**"로 설정되어있어 LoginInterceptor/error를 잡음

해결

따라서 저희는 우선적으로 LOGIN_REQUIRED_PATH_PATTERNS"/api/**" 로 수정하여 /error 가 호출되지 않도록 했고, 예외처리를 GlobalExceptionHandler 로 핸들링해주었습니다.
그러자 정상적으로 실행되는 것을 확인했습니다.

마무리

HandlerInterceptorprehandle()로 구현한 로그인 인터셉터에 걸리면 안될 회원가입의 호출이 걸리는 일이 있었습니다. 이는 제가 테스트를 할 때 RuntimeException이 나게끔 잘못된 값을 입력하였고 스프링 부트의 기본 오류 페이지 처리 기능이/error를 호출하여 이것을 interceptor가 중간에 가로채어 생긴 문제였습니다.
저는 이 /errorinterceptor에 가로채어지지않도록 Path의 형식을 바꾸어주고 예외를 GlobalExceptionHandler로 핸들링해주어 이 문제를 해결하였습니다.
이번 트러블 슈팅을 통해 스프링의 기본 예외 처리 흐름을 공부할 수 있었습니다.

0개의 댓글

관련 채용 정보