[Spring] 인터셉터란? + 요청 로그 인터셉터, 인증 체크 인터셉터 예시

728x90

 

인터셉터

스프링 인터셉터

사용자 인가 없이 URL 을 직접 호출해서 페이지에 접속하는 것을 막아주는 역할을 한다.

스프링 MVC 가 제공하는 기술로, 필터와 비슷하지만 필터보다 더 편리하고, 정교하다.

 

인터셉터 흐름

HTTP 요청 > WAS > 필터 > 서블릿 > 인터셉터 > 컨트롤러

 

스프링 인터셉터는 디스패처 서블릿이 호출된 뒤, 컨트롤러 호출 직전에 호출된다.

(스프링 MVC 가 제공하는 기술이기 때문에 스프링 MVC 시작점인 디스패처 서블릿 이후에 등장한다.)

인터셉터에서 요청을 검토하여 적절하지 않은 요청이면 컨트롤러를 호출하지 않는다.

 

인터셉터 체인

HTTP 요청 > WAS > 필터 > 서블릿 > 인터셉터1 > 인터셉터2 > 인터셉터3 > 컨트롤러

 

또한 인터셉터는 체인으로 구성되어 중간에 인터셉터를 추가할 수 있다.

 

인터셉터 인터페이스

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 {}
}

 

인터셉터는 preHandle, postHandle, pafterCompletion 과 같이 세분화되어 있으며, 

handler 의 호출 정보, modelAndView 의 반환 정보도 받을 수 있다.

 

인터셉터 인터페이스를 구현하고 등록하면, 스프링 컨테이너가 싱글톤 객체 로 생성하고, 관리한다.

preHandle : 컨트롤러 호출 전에 호출, true 이면 다음으로 진행하고 false 면 더 이상 진행하지 않는다.

postHandle : 컨트롤러 호출 후에 호출, 컨트롤러 예외 발생 시 호출 X

afterCompletion : 뷰가 렌더링 된 이후 호출, 컨트롤러 예외 발생해도 항상 호출

 

            preHandle
         ↗ 
  preHandle
 ↗ 
   
클라이언트 HTTP 요청
→ → → →
Dispatcher
Servlet
 handle(handler)
→ → → → → →
← ← ← ← ← ←
ModelAndView 반환
핸들러
어댑터
③  핸들러 호출
→ → → →
핸들러
(컨트롤러)
 
 render(model)
호출
 ↘  postHandle
      postHandle
       
           ⑦ afterCompletion
              afterCompletion
   
HTML 응답
← ← ← ←
View        

 

요청 로그 인터셉터

@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);
    if (ex != null) {
      log.error("afterCompletion error!!", ex);
    }
  }
}

 

UUID : Http 요청을 구분하기 위해 요청당 임의의 uuid 생성

request.setAttribute(LOG_ID, uuid) 

  : 서블릿 필터의 경우 doFilter( ) 에서  요청이 들어와서 응답이 나갈 때까지 같은 실행 흐름에서 동작하지만

  : 스프링 인터셉터의 경우 preHandle( ), postHandle( ), afterCompletion( ) 는 실행 시점이 다르기 때문에

  : request 에 담아 한 요청에 같은 uuid 를 사용할 수 있다. 

return true : true 면 정상 호출로, 다음 인터셉터나 컨트롤러가 호출된다.

Object handler

  : 스프링을 사용하여 @Controller, @RequestMapping 을 활용한 경우 > HandlerMethod

  : @Controller 가 아닌 /resources/static 과 같은 정적 리소스가 호출되는 경우 > ResourceHttpRequestHandler

 

@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() 를 사용하여 등록하면 된다.

 

registry.addInterceptor(new LogInterceptor()) : 등록할 인터셉터를 지정

order() : 인터셉터의 순서 (낮을수록 먼저 동작)

addPathPatterns() : 인터셉터를 적용할 URL 패턴 지정

excludePathPatterns() : 인터셉터에서 제외할 패턴 지정

 

인증 체크 인터셉터

@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(false);
    
    if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
      log.info("미인증 사용자 요청");
      response.sendRedirect("/login?redirectURL=" + requestURI);
      return false;
    }
    
    return true;
  }
}

response.sendRedirect("/login?redirectURL=" + requestURI);

  : 미인증 사용자를 로그인 화면으로 리다이렉트

  : 현재 요청 경로 requestURI 를 쿼리 파라미터로 함께 전달하여 로그인 성공 시 해당 경로로 이동할 수 있도록 함

return false;

  : 다음 인터셉터나 컨트롤러가 호출되지 않는다.

 

@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 에 작성하면 된다.

 

registry.addInterceptor(new LoginCheckInterceptor()) : 등록할 인터셉터를 지정

order() : 인터셉터의 순서 (낮을수록 먼저 동작)

addPathPatterns() : 인터셉터를 적용할 URL 패턴 지정

excludePathPatterns() : 인터셉터에서 제외할 패턴 지정

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 MVC 2(김영한) - 인프런

728x90