본문 바로가기
Spring

Spring Interceptor 적용

by meteorkim 2024. 5. 24.

Interceptor 사용 이유

Spring에서 Interceptor를 사용하는 이유는 주로 다음과 같은 기능을 제공하기 위함입니다:

  1. 공통 처리 로직의 분리: Interceptor를 사용하면 여러 컨트롤러에 공통적으로 적용해야 하는 로직(예: 인증, 로깅, 데이터 검증 등)을 한 곳에서 관리할 수 있습니다. 이를 통해 코드의 중복을 줄이고 유지보수를 용이하게 합니다.
  2. 전후 처리: Interceptor는 요청(request)이 컨트롤러에 도달하기 전(preHandle)과 응답(response)이 사용자에게 반환되기 전(postHandle) 그리고 요청 처리가 완료된 후(afterCompletion)에 특정 로직을 수행할 수 있는 기회를 제공합니다. 이를 통해 요청의 전처리와 후처리를 유연하게 구성할 수 있습니다.
  3. 보안: Interceptor를 사용하여 모든 요청에 대해 인증 및 인가를 검사할 수 있습니다. 예를 들어, 로그인 여부를 확인하거나 특정 권한을 가진 사용자만 접근할 수 있도록 제한할 수 있습니다.
  4. 성능 모니터링 및 로깅: Interceptor를 사용하여 요청 및 응답 시간을 기록하거나, 요청의 내용을 로깅할 수 있습니다. 이를 통해 애플리케이션의 성능을 모니터링하고 분석할 수 있습니다.
  5. 데이터 변환 및 처리: Interceptor를 사용하여 요청 데이터를 변환하거나, 특정 헤더를 추가하는 등의 작업을 수행할 수 있습니다. 이를 통해 데이터의 일관성을 유지하고 추가적인 처리를 할 수 있습니다.

Filter, Interceptor, AOP 흐름

Interceptor와 Filter는 Servlet 단위에서 실행된다. 반면 AOP는 메서드 앞에 Proxy패턴의 형태로 실행된다.
실행 순서를 보면 Filter 가 가장 밖에 있고 그 안에 Interceptor, 그 안에 AOP 가 있는 형태이다.
따라서 요청이 들어오면 Filter -> Interceptor -> AOP -> Interceptor -> Filter 순으로 거치게 된다.

  1. 서버를 실행시켜 서블릿이 올라오는 동안에 init이 실행되고, 그 후 doFilter가 실행된다.
  2. 컨트롤러에 들어가기 전 preHandler가 실행된다
  3. 컨트롤러에서 나와 postHandler, after Completion, doFilter 순으로 진행이 된다.
  4. 서블릿 종료 시 destroy가 실행된다.

HandlerInterceptor 인터페이스

인터셉터를 구현하기 위해서는 org.springframework.web.servlet.HandlerInterceptor 인터페이스를 구현해야 하며, 이는 다음의 3가지 메서드를 가지고 있습니다.

preHandle 메서드
preHandle 메서드는 컨트롤러가 호출되기 전에 실행된다. 그렇기 때문에 컨트롤러 이전에 처리해야 하는 전처리 작업이나 요청 정보를 가공하거나 추가하는 경우에 사용할 수 있다. 또한 preHandle 메서드의 3번째 파라미터인 handler 파라미터는 @RequestMapping이 붙은 메서드의 정보를 추상화한 객체입니다.

  • Controller가 호출되기 전에 실행되는 메서드
  • 반환값이 true인 경우 컨트롤러 호출, false인 경우 작업 중단

postHandle 메서드
postHandle 메서드는 컨트롤러를 호출된 후에 실행된다. 그렇기 때문에 컨트롤러 이후에 처리해야 하는 후처리 작업이 있을 때 사용할 수 있습니다.

  • Controller가 호출된 후 View가 Rendering 되기 전에 실행되는 메서드
  • ModelAndView 파라미터를 통해 Controller 결과 데이터 조작이 가능

afterCompletion 메서드
afterCompletion 메서드는 이름 그대로 모든 뷰에서 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행됩니다.

  • Controller가 호출된 후 View가 정상적으로 Rendering 된 후에 실행되는 메서드
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 {
    }
}

WebMvcConfigurer Setting

Interceptor를 사용할 수 있도록 설정해 주어야 한다.

@Configuration
@RequiredArgsConstructor
public class InterceptorWebConfig implements WebMvcConfigurer {

    private final RequestInfoLoggingInterceptor requestInfoLoggingInterceptor;

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

ContentCachingRequestWrapper, ContentCachingResponseWrapper

HttpServletRequest, HttpServletResponse 서블릿 객체는 한번 읽으면 이후 읽지 못하므로
Interceptor 클래스에서 request, response를 컨트롤하기 위해서는 Filter를 통해 request, response를 캐싱해 여러 번 읽을 수 있도록 만들어 주어야 한다 (자세한 학습 필요)

Wrapping Ex

@Component
public class ServletWrappingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
        filterChain.doFilter(requestWrapper, responseWrapper);
        responseWrapper.copyBodyToResponse();
    }
}

ExceptionHandler + Interceptor

이 경우 Controller에서 발생한 Exception을 ExceptionHandler에서 처리하기 때문에 afterCompletion에서 Exception 이 null로 들어오게 된다.

'Spring' 카테고리의 다른 글

[Spring] CORS 문제 해결  (0) 2024.05.27