인터셉터(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'Spring > 관련 지식' 카테고리의 다른 글
| AOP(Aspect-Oriented Programming)의 개념과 구현 (0) | 2026.07.27 |
|---|---|
| JWT(JSON Web Token) (0) | 2026.07.22 |
| JPA에서 Enum 활용 방법 (ORDINAL vs STRING 등) (0) | 2026.07.21 |
| N+1 문제의 개념과 해결 방법 (0) | 2026.07.09 |
| IoC/DI, 스프링 컨테이너와 Bean의 개념 (0) | 2026.07.07 |