Interception

2025. 2. 28. 00:15Spring Framework/Web on Servlet Stack

Spring MVC에서 인터셉터(Interceptor)Http request가 컨트롤러(Handler)에 도달하기 전/후, 또는 요청이 완전히 완료된 후에 특정 로직을 실행하는 기능입니다.
이를 활용하면 요청 전/후에 공통적으로 적용해야 하는 기능(예: 로깅, 인증, 권한 검사, 성능 모니터링 등)을 손쉽게 구현할 수 있습니다.

1. Spring MVC의 인터셉터(HandlerInterceptor) 개념

Spring MVC의 모든 HandlerMapping 구현체인터셉터(Interceptor)를 지원합니다.
인터셉터는 HandlerInterceptor 인터페이스를 구현하여 사용할 수 있습니다.

📌 인터셉터의 주요 메서드

메서드 실행 시점 반환 타입 설명
preHandle(..) 컨트롤러 실행 boolean - true 반환 → 컨트롤러 실행 계속 진행
- false 반환 → 실행 중단 (응답 직접 처리 가능)
postHandle(..) 컨트롤러 실행 후, 뷰 렌더링 전 void - 컨트롤러가 실행된 후 실행됨
- 모델(Model) 데이터를 수정할 수 있음
afterCompletion(..) 요청 완료 후 (뷰 렌더링 이후) void - 요청 처리가 완료된 후 실행됨
- 리소스 정리, 로깅 등에 사용

📌 즉, preHandle → 컨트롤러 실행 → postHandle → 뷰 렌더링 → afterCompletion 순서로 실행됨.

2. 인터셉터(Interceptor) 구현 방법

인터셉터를 사용하려면 HandlerInterceptor 인터페이스를 구현하면 됩니다.

예제: 요청 실행 전/후에 로깅하는 인터셉터

import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("[preHandle] 요청 시작: " + request.getRequestURI());
        return true; // true → 컨트롤러 실행 계속 진행
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("[postHandle] 컨트롤러 실행 후");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("[afterCompletion] 요청 완료");
    }
}

📌 설명

  • preHandle(): 요청이 컨트롤러에 도달하기 전에 실행됨 (여기서 false를 반환하면 요청이 중단됨)
  • postHandle(): 컨트롤러 실행 후, 뷰가 렌더링되기 전에 실행됨
  • afterCompletion(): 뷰 렌더링이 완료된 후 실행됨 (예외가 발생해도 실행됨)

3. 인터셉터 등록 방법

인터셉터를 만든 후, Spring MVC에 등록해야 합니다.

예제: WebMvcConfigurer를 사용하여 인터셉터 등록

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/api/**")  // 특정 경로에만 적용
                .excludePathPatterns("/api/login"); // 특정 경로 제외
    }
}

📌 설명

  • /api/** 경로의 모든 요청에 대해 LoggingInterceptor를 적용
  • /api/login 요청은 인터셉터 적용에서 제외

4. 인터셉터 동작 방식

요청이 들어왔을 때, 인터셉터가 실행되는 순서는 다음과 같습니다.

📌 인터셉터 실행 흐름

[preHandle 실행] → 컨트롤러 실행 → [postHandle 실행] → 뷰 렌더링 → [afterCompletion 실행]

📌 preHandle()에서 false 반환 시 흐름

[preHandle 실행] → 요청 종료 (컨트롤러 실행되지 않음)

즉, preHandle에서 false를 반환하면 요청 처리가 중단되고, 이후 로직이 실행되지 않습니다.

5. @ResponseBody, ResponseEntity와 인터셉터의 한계

@ResponseBody 또는 ResponseEntity를 사용하면 응답이 postHandle() 실행 전에 커밋(Commit)됩니다.
즉, postHandle()에서 응답을 변경할 수 없습니다.

📌 예제: JSON 응답을 반환하는 컨트롤러

@RestController
public class UserController {

    @GetMapping("/user")
    public ResponseEntity<String> getUser() {
        return ResponseEntity.ok("User Data");
    }
}

📌 postHandle()에서 응답을 수정하려 해도 실패

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    response.setHeader("X-Custom-Header", "CustomValue"); // 동작하지 않음!
}

postHandle() 실행 전에 응답이 커밋되었기 때문에, 헤더 추가가 적용되지 않음.

🚀 해결 방법: ResponseBodyAdvice 사용

이 문제를 해결하려면 ResponseBodyAdvice를 사용하여 응답 데이터를 가공할 수 있습니다.

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
public class CustomResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(Class<? extends HttpMessageConverter<?>> converterType, Class<?> clazz) {
        return true; // 모든 컨트롤러에 적용
    }

    @Override
    public Object beforeBodyWrite(Object body, HttpInputMessage inputMessage, HttpOutputMessage outputMessage, HttpHeaders headers, Class<? extends HttpMessageConverter<?>> converterType, Class<?> clazz) {
        headers.add("X-Custom-Header", "CustomValue"); // 응답 헤더 추가
        return body;
    }
}

📌 설명

  • ResponseBodyAdvice@ResponseBodyResponseEntity 응답을 수정할 수 있도록 도와줌.
  • beforeBodyWrite()에서 응답을 가공할 수 있음.

6. 인터셉터를 보안(Security) 목적으로 사용하면 안 되는 이유

인터셉터는 보안 기능(예: 인증, 권한 관리)에 사용할 수도 있지만, 완벽한 보안 레이어로 사용하기에는 한계가 있습니다.

🚨 인터셉터를 보안 목적으로 사용하면 발생하는 문제점

1️⃣ 컨트롤러의 @RequestMapping 경로와 일치하지 않을 가능성

  • Spring Security는 메서드 레벨에서 보안 검사를 하지만, 인터셉터는 경로 기반으로 작동함.
  • 특정 컨트롤러에는 적용되지 않을 수도 있음.

2️⃣ Spring Security보다 늦게 실행됨

  • Spring Security는 Servlet Filter에서 먼저 실행됨.
  • 인터셉터는 컨트롤러 호출 전에 실행되지만, 필터보다 늦게 실행됨.

📌 해결책: 보안은 Spring Security를 사용!

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
        );
        return http.build();
    }
}

보안은 인터셉터가 아니라, Spring Security의 Filter에서 처리하는 것이 바람직합니다.

🚀 Summary

  • 인터셉터는 HandlerInterceptor를 구현하여 요청 전/후에 공통 로직을 실행하는 기능.
  • preHandle(), postHandle(), afterCompletion()을 통해 요청의 흐름을 제어할 수 있음.
  • ResponseBodyAdvice를 활용하면 @ResponseBody 응답도 수정 가능.
  • 보안(Authentication, Authorization)은 Spring Security를 사용하는 것이 가장 안전함. 🚀

 

출처 : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-interceptor.html

'Spring Framework > Web on Servlet Stack' 카테고리의 다른 글

View Resolution  (0) 2025.02.28
Exceptions  (0) 2025.02.28
Path Matching  (0) 2025.02.27
Processing  (0) 2025.02.27
Servlet Config  (0) 2025.02.27