회원가입 API는 로그인의 인증인가를 건너뛰도록 excludePathPatterns()
로 제외시켜두었는데 테스트를 돌리다보니 "로그인이 필요합니다"라는 결과가 뜨는 등 계속 LoginInterceptor
에 잡혔습니다.
처음에는 WebConfig에서 작성한 uri Pattern들의 문제이거나 loginInterceptor의 문제일 것이라 추측하였습니다.
WebConfig를 다시 돌아보며 저는 LoginInterceptor
를 InterceptorRegistry
에 등록하는 방법이 잘못되었다고 생각했습니다.
로그인 인터셉터는 배달 앱에서 회원가입이나 로그인 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 되고 있었습니다.
저희 팀이 전체적으로 코드를 어느정도 짠 이후에 함께 예외처리를 진행하자고 얘기를 했기 때문에 아직 RuntimeException
과 ResponseStatusException
을 throw
하고 있는 상태였습니다.
컨트롤러에서 만약 예외가 발생한다면 이는 HandlerExceptionResolver
로 넘어가게 됩니다. 이 때 HandlerExceptionResolver
는 다음과 같은 과정을 거치게 됩니다.
- 컨트롤러 영역에서 예외를 처리할 수 있는
@ExceptionHandler
를 찾음- 전역적으로 예외를 처리할 수 있는
@ControllerAdvice
의@ExceptionHandler
를 찾음/error
호출 (포워딩)
만약 @ExceptionHandler
로 예외를 처리하지 못하게 되면 HandlerExceptionResolver
는 /error
로 포워딩(서버 내부 전달)을 시키고 이를 BasicErrorController
가 처리합니다.
이 때, BasicErrorController
에 도달하기 전 preHandle()
의 Interceptor
가 이를 intercept하게 될 수 있는 것입니다.
그렇기 때문에 저희의 프로젝트에서는 세 가지 원인으로 문제가 발생하고 있었습니다.
/error
가 호출됨LOGIN_REQUIRED_PATH_PATTERNS
가 "/**"
로 설정되어있어 LoginInterceptor
가 /error
를 잡음따라서 저희는 우선적으로 LOGIN_REQUIRED_PATH_PATTERNS
를 "/api/**"
로 수정하여 /error
가 호출되지 않도록 했고, 예외처리를 GlobalExceptionHandler
로 핸들링해주었습니다.
그러자 정상적으로 실행되는 것을 확인했습니다.
HandlerInterceptor
의 prehandle()
로 구현한 로그인 인터셉터에 걸리면 안될 회원가입의 호출이 걸리는 일이 있었습니다. 이는 제가 테스트를 할 때 RuntimeException
이 나게끔 잘못된 값을 입력하였고 스프링 부트의 기본 오류 페이지 처리 기능이/error
를 호출하여 이것을 interceptor가 중간에 가로채어 생긴 문제였습니다.
저는 이 /error
가 interceptor
에 가로채어지지않도록 Path
의 형식을 바꾸어주고 예외를 GlobalExceptionHandler
로 핸들링해주어 이 문제를 해결하였습니다.
이번 트러블 슈팅을 통해 스프링의 기본 예외 처리 흐름을 공부할 수 있었습니다.