반응형
Interceptor에서 예외를 응답 해주는 방법
- postman 요청은 성공? 다른 서버에서는 실패?
// /error 는 스프링 부트가 제공하고 있기 때문에 커스텀 해도 스프링을 타게 되기 때문에(BasicErrorController.java)
// /api/error 와 같이 커스텀 해야한다.
request.setAttribute("message", "로그인이 필요한 서비스");
request.getRequestDispatcher("/error").forward(request, response);
문제
HandlerInterceptor에서 예외를 RestControllerAdvice가 핸들링하지 못한다.
상황이 어떤가 살펴보자.
- 소셜 로그인을 시도한다.
- authorization code가 유효하지 않으면, 예외를 발생 시킨다.
500으로 응답한다.
public abstract class SessionLogin implements HandlerInterceptor {
...
if (accessToken == null) {
throw new IllegalArgumentException("로그인이 필요합니다.");
}
...
}
위의 코드는 인터셉터에서 예외를 발생 시킨다.
하지만 이렇게 끝내면 500에러를 응답하게 된다.
스프링은 예외가 발생하면, WAS로 예외를 다시 전달한다.
그리고 WAS는 /error
로 재요청한다. /error
는 스프링이 기본으로 제공하는 api이다.BasicErrorController.java
가 정의한 컨트롤러이다.
이 컨트롤러를 타면, 스프링이 제공하는 기본 페이지를 응답하고, 500으로 반환하게 되는 것이다.
RestControllerAdvice가 핸들링 하지 못하는 이유
HandlerInterceptor
와HandlerExceptionResolver
의 위치를 살펴보자.
출처 - 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
- HandlerInterceptor, DispatcherServlet, ExceptionResolver의 동작 순서는 아래와 같다.
DispatcherServlet
→preHandler(HandlerInterceprot)
→Controller
→postHandler(HandlerInterceprot)
→DispatcherServlet
→afterCompletion
→응답
- 즉,
afterCompletion
전에 발생한 예외는 모두DispatcherServlet
으로 반환된다.afterCompletion
은 모든 처리가 끝난 후 예외 정보, 요청, 응답 정보를 모두 받는 위치에 있기 때문이다. - 그럼 결국 핸들링이 되어야 한다. 그런데 왜 안된걸까?
핸들러가 존재할 때와 존재하지 않을 때로 구분한다.
HandlerExceptionResolver
은 핸들러에 연관된 예외를 핸들링 하기 위한 것이다.
- 핸들러가 존재할 때
preHandle
에서 예외 발생- 핸들러가 존재하는지 확인(
@Controller, @RestController
) - 존재한다면,
ExceptionResolver
가 예외 해결 시도
- 핸들러가 존재하지 않을 때
preHandler
에서 예외 발생- 핸들러가 존재하는지 확인
- 존재하지 않으면, 그대로 WAS까지 예외 전달
- 이후
/error
로 재요청하며, 500 페이지 제공
결국 핸들러가 존재하지 않았기 때문에 핸들링이 되지 않는 범위였던 것이었다.
해결
그렇다면, 핸들러가 존재하지 않을 때는 어떻게 해야 HandlerExceptionResolver
의 핸들링 대상이 될까?
ServletRequest의 RequestDispatcher로 forwarding하기
getRequestDispatcher
를 사용하여forward
를 사용한다.response.sendRedirect
를 사용해도 되지만,forward
를 선택했다.
public abstract class SessionLogin implements HandlerInterceptor {
...
private boolean isAccessToken(HttpServletRequest request, HttpServletResponse response, String accessToken) throws ServletException, IOException {
if (accessToken != null) {
return true;
}
request.setAttribute("message", "로그인이 필요합니다.");
request.setAttribute("exception", "AuthenticationException");
request.getRequestDispatcher("/api/error").forward(request, response);
System.out.println("request = " + request);
return false;
}
}
...
}
- 그리고 에러를 처리할 컨트롤러를 만들었다.
@RestController
public class ErrorController {
@GetMapping("/api/error")
public void error(HttpServletRequest request) throws AuthenticationException {
String message = (String) request.getAttribute("message");
String exception = (String) request.getAttribute("exception");
if ("AuthenticationException".equals(exception)) {
throw new AuthenticationException(message);
}
}
}
이렇게 되면, 결국 컨트롤러에서 예외가 발생되기 때문에 HandlerExceptionResolver
가 핸들링할 수 있게된다.
forward를 선택한 이유
상황에 따라 다르겠지만, 현재 나의 상황에서 이유는 2가지가 있다.
- 브라우저로 리다이렉트를 해서
재요청을 하여 네트워크를 두 번
탈 이유가 없다.
그럴 이유가 없다면, 굳이 두 번 네트워크를 탈 필요가 없다고 생각한다. - request에 담은
message, exception
을ErrorController
에서 사용해야 한다.
재요청을 하게되면, request가 초기화 되기 때문에 사용할 수 없다.
JSP
를 사용해봤다면 forward
를 봤을수도 있고, 보지 못했을 수도 있다.JSP
에서 우리는 모델
에 담긴 객체를 그대로 사용하게 되는데 이는 request.setAttribute
로 서블릿이 담는 것이고,
이를 JSP로 forwarding
해서 url 변경없이 JSP화면이 제공
되고, JSP 페이지는 이를 request.getAttribte
해서
사용할 수 있는 것이다.
반응형
'Spring' 카테고리의 다른 글
@Configuration과 @TestConfiguration (0) | 2022.04.15 |
---|---|
로그인 처리는 Filter와 Interceptor중 무엇을 선택해야 할까? (1) | 2022.04.11 |
Spring Rest Docs를 사용하는 이유와 사용하지 않을 이유 (0) | 2022.03.26 |
스프링에서 싱글톤 빈을 사용해도 위험하지 않은가? (0) | 2022.02.14 |
스프링 HTTP 메시지 컨버터 (0) | 2022.02.14 |