본문 바로가기
Programming/Spring

Interceptor 사용법

by peter paak 2020. 6. 18.
728x90

회사에서는 관리자 페이지의 보안을 OAuth2 방식으로 하고 있습니다. 유효한 토큰을 API에서 받아와 세선에 저장해놓고 request를 보낼 때 마다 세션에서 토큰이 유효한지 확인하는 작업을 하고 있습니다. 토큰은 클라이언트에서 쿠키로 관리하고 있으며 매 요청을 보낼 때마다 쿠키의 토큰을 확인하고 있습니다. 매 요청마다 session을 찾아 유효한 토큰이 있는지 살피다 보니 매우 반복적인 작업이 되었습니다. React를 사용하면서 AxiosInterceptor 기능이 있었는데 서블릿에서 기본적으로 Interceptor를 제공해주고 있습니다. 이 방식은 매 요청이 이루어질 때마다 해야되는 작업이므로 수평적으로 관리해주어야 할 필요가 있습니다. 대표적인 방법들이 Filter, Interceptor, AOP가 있습니다. 그중에서 오늘은 Interceptor를 어떻게 사용하는지 살펴보고 토큰을 가져오는 방법에 대해서 살펴보았습니다.

1. Interceptor

농구에서 인터셉터라는 용어가 있습니다. 상대방의 공을 훔친다 라는 의미로 사용됩니다.
서블릿에서는 request를 훔치다 라는 의미로 사용됩니다. request가 controller로 들어가기 전과 후에 어떠한 작업을 해주고 싶을 때 interceptor를 사용합니다.

intercpetor에 대해서 이해하려면 먼저 HandlerMappingHandlerAdaptor를 이해해야 합니다.

HandlerMapping과 HandlerAdaptor

HandlerMapping은 간단히 request의 URL과 매칭되는 Controller를 선택해주는 역할을 합니다.
HandlerAdaptorController의 메소드를 실행해주는 역할을 합니다.

추가적으로 DispatcherServlet중앙의 Servlet으로 request를 Controller로 전달하는 역할을 합니다. 적절한 controller를 선택하는 작업을 HandlerMapping에게 전달합니다. 그리고 선택된 Controller를 실행하는 작업을 HandlerAdaptor에게 전달합니다.

아래의 그립을 보겠습니다.

순서대로 보면 /login이라는 request를 처리할 때, DispatcherServletHandlerMapping을 통해 /login과 매칭되는 적절한 Controller를 찾습니다. 그리고 DispatcherServletHandlerAdaptor를 사용하여 찾은 Controller의 메소드를 실행하게 됩니다. 여기서 interceptor의 개념이 사용됩니다. 즉, Interceptor찾은 Controller의 실행 전후에 어떠한 처리를 위해서 사용하게 됩니다.

아래는 조금 더 상세한 흐름을 설명한 것입니다. 참고로 보시기 바랍니다.

  1. DispatcherServlet이 request를 받는다
  2. DispatcherServlet은 Controller를 선택하는 일을 HandlerMapping에게 전달. 선택된 Controller를 DispatcherServlet에 다시 전달
  3. DispatcherServlet은 Controller의 비즈니스 로직을 실행하는 작업을 **HandlerAdapter**에게 전달
  4. HandlerAdapter가 Controller의 비즈니스 처리 로직 호출
  5. Controller가 비즈니스 로직 실행하고 결과를 Model을 HandlerAdapter에게 전달
  6. DispatcherServlet이 view를 처리하는 일을 view이름과 함께 ViewResolver에게 전달
  7. ViewResolver는 view이름과 매칭되는 View를 리턴
  8. DispatcherServlet은 rendering 프로세스를 리턴받은 view에 적용
  9. view는 Model과 response를 render한다

Handler Interceptor

자바에서는 Handler Interceptor라는 인터페이스가 interceptor의 역할을 합니다.

여기서 handler라는 단어가 있는데 여기서는 Controller의 역할로 사용되게 됩니다. 자세한 내용은 이곳을 참조하시기 바랍니다. 통상적으로 handlerController를 포함한 HttpRequestHandler, WebRequestHandler 등과 같이 Dispatcher Servlet과 함께 일하는 것들을 통칭합니다. ControllerDispatcher Servlet과 협업하여 web request를 실행하고 view를 반환하기 때문에 handler라고 불려집니다.

handler 사이에서 어려가지 역할을 수행하는데 다음과 같이 여러번 반복되는 handler 코드를 처리할 수 있습니다

  • logging
  • model의 전역적으로 사용되는 파라미터 변경
  • session 관리
  • 권한 관리

2. Interceptor 처리 과정

앞서 말씀드렸다시피 Interceptor는 총 3개의 메소드를 가지고 있습니다.

  • prehandle : Controller 동작 이전
  • posthandle : Controller 동작 이후, View 동작 이전
  • afterCompletion : View 동작 이후

각각의 동작들은 요청이 들어온 이후 순서대로 진행이 됩니다. prehandle과 posthandle은 Controller 동작 이전과 이후로 나눠지고 afterCompletion은 View가 동작한 이후 실행 됩니다.

아래의 그림을 봐주시기 바랍니다.

  1. 먼저 /test 라는 요청이 들어오게 됩니다.
    • 이때 DispatcherServlet이 HandlerMapping을 통해 /test라는 요청에 해당하는 Controller를 선택하도록 시킵니다.
  2. Interceptor의 prehandle 메소드가 /test 요청을 Controller가 동작하기 이전에 가로챕니다.
  3. Controller가 실행됩니다
    • DispatcherServlet이 HandlerAdaptor를 통해 /test라는 요청에 해당하는 Controller를 실행하도록 시킵니다.
  4. Interceptor의 posthandle 메소드가 Controller가 실행된 이후 동작합니다.
  5. posthandle 메소드가 실행되고 View가 실행되어 response를 클라이언트에게 전달합니다.
  6. interceptor의 afterCompletion 메소드가 View가 실행된 이후에 동작합니다.

3. Interceptor 생성 방법

인터셉터를 생성하는 방법은 두가지가 있습니다.

  1. HandlerInterceptorAdaptor
  2. HandlerInterceptor

1. HandlerInterceptor

HandlerInterceptor는 Interceptor를 관리하는 인터페이스입니다.

이 인터페이스는 총 3개의 메소드를 가지고 있습니다.

  • prehandle
  • posthandle
  • afterCompletion
public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return true;
    }

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

}

2. HandlerInterceptorAdaptor

HandlerInterceptorAdaptorHandlerInterceptor를 상속받은 추상 클래스로 간편하게 사용할 수 있습니다. HandlerInterceptor와 같이 preHandle, postHandle, afterComplete 세가지 메소드를 Override하여 사용할 수 있습니다. 추가적으로 비동기 처리를 함께 할 수 있습니다

public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
    }

}

Interceptor 사용 방법

1. Interceptor 생성

@Component
public class MyInterceptor implements HandlerInterceptor {

    // 1. Controller 보내기 전
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandler");
        System.out.println(handler);

        // false이면 controller로 요청 안한다
        return true;
    }

    // 2. Controller의 handler처리 후
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandler");
        System.out.println(handler);
        System.out.println(modelAndView);
    }

    // 3. View 처리 이후
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
        System.out.println(handler);
    }
}

2. Interceptor 등록

@Configuration
@RequiredArgsConstructor
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    private final HandlerInterceptor handlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(handlerInterceptor)
                .addPathPatterns("/**");
    }
}

테스트로 확인

@AutoConfigureMockMvc
@SpringBootTest
class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    void hello() throws Exception {
        mvc.perform(get("/"))
                .andExpect(status().isOk());
    }
}
728x90