내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.
3. 스프링 인터셉터 - 소개
스프링 인터셉터는 서블릿 필터와 비슷하지만, 스프링 MVC가 제공하는 기술이다.
● 스프링 인터셉터의 흐름
HTTP 요청 ->WAS-> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
인터셉터는 프론트 컨트롤러(DispatcherServlet) 과 컨트롤러 사이에서 호출된다.
스프링 인터셉터에도 URL 패턴을 적용할 수 있는데, 서블릿 URL 패턴과는 다르고, 서블릿 보다 매우 정밀하게 설정할 수 있다.
● 스프링 인터셉터의 제한
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 //로그인 사용자
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(적절하지 않은 요청이라 판단, 컨트롤러 호출 X) // 비 로그인 사용자
위와 같이 로그인 여부를 체크하기에도 좋다.
또한 인터셉터는 체인으로 구성되어, 여러 체인을 추가할 수 있다.
● 스프링 인터셉터의 Interface
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {}
default void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable ModelAndView modelAndView) throws Exception {}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) throws Exception {}
}
서블릿 필터의 경우 doFilter()에서 대부분의 로직을 처리했지만, 인터셉터는 3가지를 제공한다.
컨트롤러 호출 전( preHandle ), 호출 후( postHandle ), 요청 완료 이후( afterCompletion )
인터셉터는 어떤 컨트롤러( handler )가 호출되는지 호출 정보도 받을 수 있다.
그리고 어떤 modelAndView 가 반환되는지 응답 정보도 받을 수 있다.
● 정상 흐름
- preHandle : 컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 호출 전에 호출된다.)
preHandle 의 응답값이 true 이면 다음으로 진행하고, false 이면 더는 진행하지 않는다.
false 인 경우 나머지 인터셉터는 물론이고, 핸들러 어댑터도 호출되지 않는다. 위 그림에서 1번에서 끝이 나버린다.
- postHandle : 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.)
컨트롤러에서 예외가 발생하면 호출되지 않는다.
- afterCompletion : 뷰가 렌더링 된 이후에 호출된다. (예외가 발생해도 호출된다.)
예외가 발생하면 postHandle() 는 호출되지 않으므로 예외와 무관하게 공통 처리를 하려면 afterCompletion() 을 사용해야 한다.
예외가 발생하면 afterCompletion() 에 예외 정보( ex )를 포함해서 호출된다.
4. 스프링 인터셉터 - 요청 로그
사용자가 요청을 보내올때 마다 로그를 남겨보자!
● LogInterceptor - 요청 로그 인터셉터
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String) request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}][{}]", logId, requestURI, handler);
if(ex != null){
log.error("afterCompletion error!!", ex);
}
}
}
- UUID
이전과 같이 요청 로그상에서 사용자를 구분하기 위해 UUID를 생성하였다.
- request.setAttribute()
서블릿 필터에서는 지역변수로 데이터를 공유하였지만, 스프링 인터셉터는 각각의 호출시점이 완전히 분리되어 있기 때문에 힘들다.
따라서 preHandle 에서 지정한 값을 postHandle , afterCompletion 에서 함께 사용하려면 어딘가에 담아두어야 하는데 이때 request를 사용한다.
request에 담아둔 값은 afterCompletion 에서 request.getAttribute(LOG_ID) 로 찾아서 사용한다.
또한 LogInterceptor 도 싱글톤 처럼 사용되기 때문에 맴버변수를 사용하여 공유하기에는 위험하다.
- return true
true를 반환하면 정상 호출로 인식되어, 다음 인터셉터나 컨트롤러를 호출한다.
● HandlerMethod
preHandle()는 인자로 handler를 받는데, 이를 활용할수 가 있다.
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler; //호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}
핸들러 정보는 어떤 핸들러 매핑을 사용하는가에 따라 달라진다.
스프링을 사용하면 일반적으로 @Controller , @RequestMapping 을 활용한 핸들러 매핑을 사용하는데, 이 경우 핸들러 정보로 HandlerMethod 가 넘어온다.
● 인터셉터 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
// 생략...
}
WebMvcConfigurer 가 제공하는 addInterceptors() 를 사용해서 인터셉터를 등록할 수 있다.
필터와 비교해보면 인터셉터는 addPathPatterns , excludePathPatterns 로 매우 정밀하게 URL 패턴을 지정할 수 있다.
실핼 결과는 다음과 같다.
5. 스프링 인터셉터 - 인증 체크
이전에 서블릿 필터로 개발한 인증 체크 기능을 스프링의 인터셉터로 구현해 보자!
● LoginCheckInterceptor
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("인증 체크 인터셉터 실행 {}", requestURI);
HttpSession session = request.getSession();
if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null){
log.info("미인증 사용자 요청");
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
매우 간단해졌다. 인증은 컨트롤러 호출 전에만 진행되면 되기 때문에 preHandler를 구현한 상태이다.
또한 기존의 whiteList와 같은 어느 요청을 통과시키고, 인증처리를 할지 정하는 부분이 없다.
이는 WebConfig에 등록할때 설정할수 있다.
● WebConfig - 설정하기
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/", "/members/add", "/login", "/logout", "/css/**", "/*.ico", "/error");
}
}
인터셉터를 적용하거나 하지 않을 부분은 addPathPatterns 와 excludePathPatterns 에 작성하면 된다.
모든 경로( /** )에 해당 인터셉터를 적용하되, 홈( / ), 회원가입( /members/add ), 로그인( /login ), 리소스 조회( /css/** ), 오류( /error )와 같은 부분은 로그인 체크 인터셉터를 적용하지 않는다.
기존의 서블릿 필터와 비교해보면 매우 편리한 것을 알 수 있다. 따라서 일반적으로 인터셉터를 더 자주 사용하게 된다.
'BackEnd > Spring MVC' 카테고리의 다른 글
[Spring] 예외 처리와 오류 페이지 - 2 (0) | 2022.03.14 |
---|---|
[Spring] 예외 처리와 오류 페이지 - 1 (0) | 2022.03.14 |
[Spring] 로그인 처리2 - 필터, 인터셉터 - 1 (0) | 2022.03.13 |
[Spring] 로그인 처리1 - 쿠키, 세션 - 3 (0) | 2022.03.09 |
[Spring] 로그인 처리1 - 쿠키, 세션 - 2 (0) | 2022.03.09 |
댓글