현재 진행하는 예약 시스템 프로젝트에서는 Store 타입별로 미용실만 사용가능한 api, 식당만 사용 가능한 api등, 호텔만 사용가능한 api 등이 있다. 스토어 타입별로 컨트롤러를 점검하기 위해 인터셉터를 활용하기로 했다. 인터셉터가 무엇인지 알아보자.
인터셉터(Interceptor) 란?
인터셉터는 디스패처 서블릿에서 클라이언트의 요청과 응답을 가로채어 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공한다. 이 기능은 주로 인증, 권한 검사, 로깅, 공통 로직 처리 등 반복적인 작업을 처리하는데 유용하다.
공식문서 -> 링크
디스패처서블릭과 인터셉터 동작 순서
- 클라이언트의 요청이 디스패처서블릿에 전달 되면 디스패처 서블릿은 핸들러 매핑을 통해 적절한 컨트롤러를 찾는다.
- 인터셉터의 preHandle() 메서드가 호출되어 요청을 가로채고 전처리를 수행한다.
- preHandle() 에서 오류가 없다면 HandlerAdapter 가 컨트롤러를 호출한다.
- 컨트롤러 실행 후 postHandle() 메서드가 호출된다.
- 요청이 완료되면 afterCompletion() 메서드가 실행된다.
인터셉터의 메소드
인터셉터를 추가하기 위해서는 HandlerInterceptor 인터페이스를 구현해야 한다.
HandlerInterceptor 공식문서 -> 링크
핸들러 인터셉터에는 앞서 설명한 것처럼 3가지의 메서드를 가지고 있다.
preHandle
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception
- preHandle 메서드는 컨트롤러가 실행되기 전에 요청을 가로채는 지점이다.
- HanlerMapping 이 적절한 컨트롤러를 찾은 후 HandlerAdapter가 호출하기 전에 실행된다.
- 디스패처 서블릿은 인터셉터와 핸들러로 구성된 실행 체인을 처리하며, 각 인터셉터는 요청 처리를 중단할지 결정할 수 있다.
- 주로 Http 오류를 보내거나 사용자 정의 응답을 작성하여 요청 흐름을 제어할 수 있다.
- 나는 이번 예약 시스템 프로젝트에서 여기서 store들의 Type을 검증했다.
postHandle
default void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception
- postHandle 메서드는 컨트롤러가 성공적으로 실행된 후 뷰가 렌더링 되기 전에 호출되는 메서드 이다.
- 이 메서드를 통해 인터셉터는 추가적으로 ModelAndView 객체에 데이터를 추가할 수 있다.
- 하지만 나는 RestAPI 기반의 RestController를 사용하면서 이번 프로젝트에는 사용하지 않았다. (아마 요즘은 대부분 사용안할것 같다.)
- 또한 컨트롤러 실행중 오류가 발생하면 이메서드는 실행되지 않는다.
afterCompletion
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception
- afterCompletion 메서드는 요청 처리가 완료된 후 실행된다.
- postHandle과 달리 컨트롤러 실행 결과와 상관없이 호출되므로 리소스 정리 등 후처리에 적합하다.
이렇게 생성한 인터셉터는 WevMvcConfigurer를 구현한 클래스에서 addInterceptors()를 통해 추가 할 수 있다.
Reservation 프로젝트 적용
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final StoreService storeService;
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry){
interceptorRegistry.addInterceptor(new StoreTypeInterceptor(StoreType.HAIR_SHOP, storeService))
.addPathPatterns("/api/v1/hair-shop/**");
}
}
- addPathPatterns("/api/v1/hair-shop/**") 를 통해 미용실 관련 API에 대한 검증 로직을 생성했다.
- StoreType.HAIR_SHOP 스토어 타입이 미용실을 매개변수로 전달해 인터셉터에서 검증하게 했다.
@RequiredArgsConstructor
public class StoreTypeInterceptor implements HandlerInterceptor {
private final StoreType requiredStoreType;
private final StoreService storeService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception{
Map<String, String> pathVariables =
(Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Long storeId = Long.valueOf(pathVariables.get("storeId")); // storeId 추출
StoreType storeType = storeService.getStoreType(storeId);
if(!requiredStoreType.equals(storeType)){
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"이 작업은 " + requiredStoreType.getStoreTypeName() + "에서만 가능합니다.");
}
return true;
}
}
Filter와의 차이?
이 과정을 본다면 인터셉터가 Filter와 비슷하다는 느낌이 들것이다. 차이점에 대해 알아보고 언제 무엇을 사용해야 하는지 알아보자
1. 관리되는 컨테이너
앞서 살펴보았듯 인터셉터는 디스패처서블릿과 상호작용한다. 즉 스프링 컨테이너에서 관리 되는 것이다. 하지만 필터는 스프링 이전 서블릿컨테이너에서 관리되며 요청과 응답을 가로채는 가장 외부의 단계이다.
- 필터는 디스패처 서블릿 전에 실행되며 요청과 응답을 전반적으로 처리한다.
2. 예외처리
- 앞서 보았듯이 필터는 스프링 밖에 있기에 스프링에 의한 예외처리(@ControllerAdvice, @ExceptionHandler)가 되지 않는다.
3. 객체 조작 가능여부
- 공식문서의 글을 보면 필터는 요청과 응답 객체를 교체할수 있다고 나와있다. 필터는 다음 필터를 호출하기 위해 doFilter를 통해 호출을 해 주어야 한다. 그리고 그때 우리가 원하는 Request/Response 객체를 넣어주는 것도 가능하다.
- 하지만 인터셉터는 그렇지 못하다. 인터셉터는 실행체인으로 인해 메서드가 true를 반환하면 자동으로 다음 체인을 실행하기 때문이다. 대신 해당 객체가 내부적으로 갖는 값으 조작할 수 있다.
사용 용도
필터
- 공통된 보안 및 인증/인가 관력 작업
- 모든 요청에 대한 로깅 또는 검사
- 요청과 응답의 문자 인코딩 처리
- 응답 결과 캐싱
인터셉터
- 컨트롤러 전후에 비즈니스 로직 추가
- 세부적인 권한 검증
- Contoller로 넘겨주는 정보 가공
'스프링' 카테고리의 다른 글
스프링부트 2.7x 버전과 스프링 부트 3.xx 의 차이 (0) | 2024.09.07 |
---|---|
싱글톤 패턴 (0) | 2024.07.27 |
ResponseEntity와 Rest Api에 대하여 (0) | 2024.07.18 |
@NotNull, @NotEmpty, @NotBlank (0) | 2024.07.11 |
IOC/DI - 제어의 역전/의존성 주입 (0) | 2024.07.07 |