본문 바로가기
Spring/관련 지식

인터셉터(Interceptor)의 개념과 구현

by 정구정구 2026. 7. 25.

인터셉터(Interceptor)란?

Controller가 호출되기 전/후를 가로채서 공통 로직을 실행하는 Spring MVC의 기능

  • DispatcherServlet이 Handler(Controller)를 찾은 다음, 그 Controller를 호출하기 직전과 직후 시점에 끼어든다.
  • 주로 인가에 대한 책임을 가짐

 

 

왜 필터(Filter)가 아니라 인터셉터(Interceptor)에서 인가를 책임지는가?

 작은 프로젝트에서는 괜찮을 수 있지만, API가 많아지고 세분화되면 다음과 같은 이유로 필터가 인가를 처리하는데 어려움을 가지게 된다.

 

어떤 Controller가 호출될지 모름

  • Controller 메서드 단위의 판단을 할 수 없음

URL 패턴만으로는 인가 로직이 허술해짐

  • Filter로 인가를 처리하면 URL 패턴에만 의지하기 때문에, URL 구조가 바뀌면 필터도 수정해야 함
  • 같은 URL이라도 메서드에 따라 다른 인가 규칙이 필요할 수 있음 (예: GET /todos/{id}는 누구나, DELETE /todos/{id}는 작성자만) — 이건 URL 패턴만으로 구분이 안 되고, HTTP 메서드+세부 로직까지 알아야 함  

Spring Bean 생태계 밖에 있어서 활용이 제한적

  • Servlet 컨테이너가 관리하는 객체라, Spring의 DI 컨텍스트와는 별개로 동작
  • Spring의 각종 편의 기능(@Transactional, Spring Security의 SecurityContext 등)과 자연스럽게 통합되는 Interceptor 쪽이 유리

관심사 분리

  • 실패했을 때 반환해야 할 상태 코드가 다름 (인증 실패 : 401 Unauthorized / 인가 실패 : 403 Forbidden)
  • Filter에서 401을, Interceptor에서 403을 판단하면 코드가 단순해짐

 

 

API 전체 처리 경로

더보기

클라이언트 요청
    │
    ▼
① Filter (JwtFilter 등) - 요청 방향
    │  - request.setAttribute("userId", ...) 등 세팅
    │  - chain.doFilter() 호출
    ▼
② DispatcherServlet
    │  - HandlerMapping으로 어떤 Controller.method를 호출할지 결정
    ▼
③ Interceptor.preHandle (AdminInterceptor 등)
    │  - 권한 체크 등
    │  - false면 여기서 즉시 응답 방향으로 turn (Controller 호출 안 됨)
    ▼ (true인 경우만 진행)
④ ArgumentResolver (AuthUserArgumentResolver 등)
    │  - @Auth AuthUser 같은 커스텀 파라미터 조립
    │  - @Valid 검증도 이 시점에 함께 수행됨
    ▼
⑤ Controller
    │  - Service 메서드 호출
    ▼
⑥ AOP (@Around 등, AdminRequestLoggingAop, TimeCheckAop)
    │  - Service(또는 지정한 대상) 메서드 실행을 앞뒤로 감쌈
    │  - joinPoint.proceed() 호출 시점에 실제 Service 로직 실행
    ▼
⑦ Service → Repository → DB
    │
    ▼ (다시 위로 리턴하며 거슬러 올라감)
⑥' AOP 후처리
    │  - 실행시간 로깅, 응답 데이터 로깅 등
    ▼
⑤' Controller가 응답 객체 리턴
    ▼
③' Interceptor.postHandle
    │  - View 렌더링 전 (예외 없을 때만)
    ▼
   (예외 발생 시) GlobalExceptionHandler
    │  - @ExceptionHandler가 예외를 잡아서 응답으로 변환
    ▼
③'' Interceptor.afterCompletion
    │  - 예외 발생 여부와 무관하게 항상 실행
    │  - 로깅, 리소스 정리
    ▼
② DispatcherServlet이 최종 응답 작성 (JSON 직렬화 등)
    ▼
① Filter - 응답 방향
    │  - chain.doFilter() 이후 코드 실행 (응답 헤더 조작 등)
    ▼
클라이언트 응답

 

 

 

구현

HandlerInterceptor 구현

@Slf4j
@Component
@RequiredArgsConstructor
public class AdminCheckInterceptor implements HandlerInterceptor {

    private static final String START_TIME = "startTime";

    private final CommentService commentService;

    // Cotroller로 들어가기 전
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

        // 1. 요청 시간, url 기록
        long startTime = System.currentTimeMillis();
        request.setAttribute(START_TIME, startTime);
        log.info("Request URI: {}, Start Time: {}", request.getRequestURI(), startTime);

        // 2. JwtFilter 에서 set 한 userId, email, userRole 값을 가져옴
        Long userId = (Long) request.getAttribute("userId");
        String email = (String) request.getAttribute("email");
        UserRole userRole = UserRole.of((String) request.getAttribute("userRole"));

        // 3. 유저 role 체크
        if (userRole != UserRole.ADMIN) {
            log.warn("관리자가 아닌 사람이 접근했습니다.\n -> userId: {}, email: {}, userRole: {}", userId, email, userRole);
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "관리자만 할 수 있는 요청입니다.");
            return false;
        }

        return true;
    }

    // Cotroller로 들어간 후
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws IOException {

        // 1. 응답 시간, url 기록
        Long startTime = (Long) request.getAttribute(START_TIME);

        if (startTime != null) {
            long endTime = System.currentTimeMillis();
            long executeTime = endTime - startTime;
            log.info("Request URI: {} took {} ms", request.getRequestURI(), executeTime);
        }
    }
}
  • true : 검증 통과 성공
  • false : 검증 통과 실패 (Controller 실행되지 않음)

 

WebConfig에 등록

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final AdminCheckInterceptor adminCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(adminCheckInterceptor)
                .order(1)
                .addPathPatterns(
                        "/admin/users/**",
                        "/admin/comments/**"
                );
    }
}

 

  • addPathPatterns : 특정 경로로 시작하는 요청에만 동작
  • excludePathPatterns : 해당 경로로 시작하는 요청은 동작 제외

 

 

동작 화면

18:58:44.009 [INFO ] : Request URI: /admin/users/4, Start Time: 1784973524009
18:58:44.017 [INFO ] : Request URI: /admin/users/4 took 8 ms

 

19:05:16.771 [INFO ] : Request URI: /admin/users/4, Start Time: 1784973916771
19:05:16.772 [WARN ] : 관리자가 아닌 사람이 접근했습니다.
 -> userId: 1, email: wjdals0508@naver.com, userRole: USER