Spring

Spring Interceptor

teo_99 2023. 5. 3. 22:56

Spring으로 웹 서비스를 구성하면 어느 URL로 클라이언트가 접근하던 간에, 공통적으로 작업을 수행시키고 싶은 경우가 존재합니다. 예를 들어 로깅이나 인증 절차 등이 있겠죠. 그리고 이런 작업을 컨트롤러의 핸들러 메소드 각각마다 배치하게 되면 중복이 발생될 수 밖에 없습니다.
 
하지만 이런 상황에 효과적으로 사용할 수 있는 장치가 하나 있는데요, 바로 Spring Interceptor입니다. Spring Interceptor는 요청을 가로채 특정 처리를 대신 해줍니다. 그리고 곧 알게 되겠지만, 중복 제거 이외에도 방화벽과 비슷한 기능을 제공해주기도 합니다.
 

Spring Interceptor의 사용

Spring Interceptor 클래스는 다음과 같은 두 가지 방법으로 만들 수 있습니다.

  1. HandlerInterceptorAdapter 객체를 상속받는다.
  2. HandlerInterceptor 인터페이스를 구현한다.
HandlerInterceptorAdapter는 Spring 5.3 부터 Deprecated 되었기 때문에 참고해 주세요.

 
그리고 위 두 방식 중 어느 방식을 사용하든, Interceptor 클래스는 다음 세 가지 메소드를 오버라이딩 할 수 있는데요.

  1. preHandle() - 핸들러가 수행되기 전에 실행됩니다.
  2. postHandle() - 핸들러가 수행된 뒤, 뷰가 아직 반환되지 않았을 때 실행됩니다. 
  3. afterCompletion() - 요청에 대한 모든 작업이 끝난 뒤, 뷰까지 반환했을 때 실행됩니다.

상황에 맞춰 오버라이딩 해 사용하며 될 것 같습니다.
 

LoggerInterceptor 만들어보기

이제 요청이 들어왔을 때 Logging을 해주는 Interceptor를 만들어보도록 하겠습니다.
 

public class LoggerInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    ) {
    	// 여기 process가 들어갈 예정!
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex
    ) {
    	// 여기 process가 들어갈 예정!
    }
}

우선 기본적으로 오버라이딩만 진행했습니다. 
그리고 저는 Spring 5.3 이상 버전을 사용하고 있기에, HandlerInterceptor를 사용했습니다.
 
 이제 실제 Logging 로직을 넣어볼까요?
 

public class LoggerInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LoggerInterceptor.class);

    private static final String REQUEST_LOG_FORMAT = "[REQUEST][%s][%s]";
    private static final String RESPONSE_LOG_FORMAT = "[RESPONSE][STATUS : %s]";

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    ) {
        logger.info(String.format(REQUEST_LOG_FORMAT, request.getMethod(), request.getRequestURI()));
        return true;
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex
    ) {
        logger.info(String.format(RESPONSE_LOG_FORMAT, response.getStatus()));
    }
}

 
여기서 한 가지 주의깊게 보셔야 할 부분은, preHandle은 afterCompletion 메소드와 다르게 boolean 반환값을 지정해주어야 한다는 것인데요, 만약 false를 return하게 되면 요청은 핸들러로 넘어가지 않습니다. 즉, 인터셉터에서 요청을 마치 방화벽처럼 차단해낼 수 있다는 겁니다. 저는 그런 방화벽 기능을 원하지 않기에, true를 반환해줬습니다.
 
이제 Interceptor 객체를 만들었으니 사용을 해야겠죠? 사용법도 간단한데요, 단순히 Configuration 객체에서 설정만 해주면 끝납니다.
 

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

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

위처럼 Configuration 빈 객체를 구성해주고 addInterceptors 메소드를 오버라이딩합니다. 그리고 registry 객체를 통해 1. Interceptor를 등록하고, 2. 어느 범위에 한정시킬지를 지정합니다. 저같은 경우는 모든 요청에 인터셉터를 작동시키고 싶어 /** 처럼 작성했습니다.

Spring Boot가 아닌 Spring을 사용하시는 분들은 @Configuration뿐만 아니라 @EnableWebMvc 어노테이션까지 붙여야 합니다.

 
그리고 실행해보면 로그가 잘 찍히는 것을 확인할 수 있습니다.

로그가 잘 찍힌다!

 

참고 자료

https://www.baeldung.com/spring-mvc-handlerinterceptor