본문 바로가기
Spring

[Spring] Filter(필터) VS Interceptor(인터셉터) 정리 및 차이

by 개미가되고싶은사람 2024. 8. 1.

목차

    Spring 프레임워크를 사용하면서 필터와 인터셉터는 자주 접하게 되는 개념입니다. 이 두 가지는 모두 요청과 응답을 처리하는 데 중요한 역할을 하지만, 그 기능과 사용 목적에는 차이가 있습니다. 이번 글에서는 왜 필터와 인터셉터를 사용하는지, 각각의 개념과 역할, 간단한 구현, 그리고 필터와 인터셉터의 차이점을 정리해보겠습니다.


    체인 관련 내용은 아래 포스팅을 읽어주세요!!

    https://jun-itworld.tistory.com/28

     

    [JSP&Javascript] FilterChain 이란?

    Filter는 요청(Request)과 응답(Response)에 대한 정보들을 변경할 수 있게 개발자들에게 제공하는 서블린 컨테이너 입니다. FilterChain은 이런 Filter가 여러개 모여서 하나의 체인을 형성하는 것 인데요,

    jun-itworld.tistory.com

     

    1. 왜 필터와 인터셉터를 사용하는가?

    Filters와 Interceptor는 웹 애플리케이션의 요청과 응답을 가로채고 처리하여 보안, 로깅, 인증, 권한 부여 등 다양한 기능을 추가함으로써, 코드의 재사용성과 유지보수성을 향상시키는 중요한 역할을 합니다.

     

     

     

     

    2. 필터의 개념과 역할

    필터는 서블릿 컨테이너 레벨에서 동작하며, URL 패턴에 따라 요청과 응답의 사전 및 사후 처리를 담당하여 디스패처 서블릿(Dispatcher Servlet) 으로 전달합니다.예를 들어, 요청이 서블릿에 도달하기 전에 특정 조건을 검사하거나, 응답이 클라이언트에게 전달되기 전에 데이터를 변형할 수 있습니다.

     

    디스패처 서블릿은 Spring의 가장 앞에 위치한 프론트 컨트롤러이기 때문에, 필터는 Spring의 범위를 벗어난 곳에서 처리됩니다. 즉, 필터는 Spring 컨테이너에 등록된 빈이 아닌 Tomcat과 같은 웹 컨테이너(서블릿 컨테이너)에 의해 관리되며, 디스패처 서블릿의 전후로 처리됩니다. 이 과정을 그림으로 표현하면 다음과 같습니다.



     

    2-1 필터 인터페이스 특징 및 주요 메소드

    필터 인터페이스를 구현체를 만들고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리합니다

    init() 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출됩니다.
    즉 필터가 사용할 때 필요한 초기 설정을 수행합니다.
    doFilter() HTTP/S 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현
    destroy() 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출

     

     

     

    2-2 간단한 필터 구현

    간단하게 HTTP 요청을 로그로 남기는 필터를 만들어보겠습니다.

    @Slf4j
    public class LogFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("log filter init");
        }
    
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("log filter doFilter");
    
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String requestURI = httpRequest.getRequestURI();
    
            String uuid = UUID.randomUUID().toString();
    
            try {
                log.info("request [{}][{}]", uuid, requestURI);
                chain.doFilter(request, response);
            }catch (Exception e){
                throw e;
            }finally {
                log.info("response [{}][{}]", uuid, requestURI);
            }
        }
    
        @Override
        public void destroy() {
            log.info("log filter destroy");
        }
    }

     

    ServletRequest는 서블릿 컨테이너가 처리하는 모든 유형의 요청을 나타내는 인터페이스입니다. 그러나 실제 웹 애플리케이션에서 대부분의 요청은 HTTP 요청입니다. 따라서, HttpServletRequest httpRequest = (HttpServletRequest) request로 다운캐스팅하면 HTTP 요청에 대한 로그를 남길 수 있습니다.

    코드에서 chain.doFilter(request, response)는 필터 체인에서 다음 필터를 호출하거나, 다음 필터가 없으면 서블릿을 호출하는 역할을 합니다. 이 메서드가 호출되지 않으면 요청이 다음 단계로 진행되지 않으므로, 필터 체인에서 다음 필터나 서블릿이 호출되지 않게 됩니다.

     

     

    이제 이렇게 구현한 필터를 어떻게 애플리케이션에 등록하지는 알려드리겠습니다.

     

     

     

     

    2-3 필터 등록 방법

    1. @WebFilter 사용

    @WebFilter는 특정 URL 사용이 가능하지만  필터가 실행되는 순서를 제어하는 기능을 제공하지 않습니다.

    @Slf4j
    @WebFilter(urlPatterns = "/*")
    public class LogFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("===init===");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("===doFilter===");
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
            log.info("===destroy===");
        }
    }
    @SpringBootApplication
    @ServletComponentScan // 해당 애노테이션 등록
    public class TestApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(TestApplication.class, args);
    	}
    
    }

     

     

    2. @Component 사용

    @Component는 @Order를 사용해 우선 순위 제어가 가능하지만 특정 URL 패턴에 추가하는 기능이 매우 어렵습니다.

    @Slf4j
    @Component
    @Order(2)
    public class LogFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("111init111");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("111doFilter111");
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
            log.info("111destroy111");
        }
    }

     

    3. FilterRegistrationBean 사용

    @Slf4j
    public class LogFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("log filter init");
        }
    
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("log filter doFilter");
    
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String requestURI = httpRequest.getRequestURI();
    
            String uuid = UUID.randomUUID().toString();
    
            try {
                log.info("request [{}][{}]", uuid, requestURI);
                chain.doFilter(request, response);
            }catch (Exception e){
                throw e;
            }finally {
                log.info("response [{}][{}]", uuid, requestURI);
            }
        }
    
        @Override
        public void destroy() {
            log.info("log filter destroy");
        }
    }
    @Configuration
    public class WebConfig {
    
        @Bean
        public FilterRegistrationBean<Filter> logFilter() {
            FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(new LogFilter());
            filterRegistrationBean.setOrder(1);
            filterRegistrationBean.addUrlPatterns("/*");
    
            return filterRegistrationBean;
        }
    }

    FilterRegistrationBean은 Spring 애플리케이션에서 필터를 등록하는 방법 중 하나로 가장 보편적인 방법입니다. 실행 순서, URL 패턴 등등 필터가 적용되는 방법을 세부적으로 제어할 수 있습니다.

     

     

     

     

    3. 인터셉터의 개념과 역할

    Dispatcher Servlet에 도달하기 전에 요청과 응답을 처리하는 필터와 달리 인터셉터는 Spring Framework의 일부이며 Spring 컨텍스트 내에서 작동하여 컨트롤러에 도달하기 전과 후에 요청을 처리합니다. 기본적으로 인터셉터는 필터 및 Dispatcher Servlet 이후에 작동하여 컨트롤러 실행 중에 인증 확인이나 로깅과 같은 작업이 수행되도록 합니다.

    예를 들어, 요청이 컨트롤러 메서드에 도달하기 전에 인증을 확인하거나, 응답 데이터를 로깅할 수 있습니다.




    3-1 주요 메소드 (preHandle, postHandle, afterCompletion)

    서블릿 필터 호출 시  단순하게 doFilter() 하나만 제공되지만, 인터셉터는  호출 전(preHandle), 호출 후(postHandle), 요청 완료 이후(afterCompletion)와 같이 세부적으로 제공합니다.

    • preHandle: 요청이 컨트롤러에 도달하기 전에 실행되며(정확히는 핸들러 어댑터 호출 전), 응답값이 true이면 컨트롤러가 호출되고, false면 더 진행하지 않습니다. 
    • postHandle: 컨트롤러가 요청을 처리한 후 뷰가 렌더링되기 전에 실행
    • afterCompletion: 요청이 완전히 처리되고 뷰가 렌더링된 후 호출

    스프링 인터셉터 호출 흐름

     

    그럼 만약 컨트롤러에서 예외가 발생하면 어떻게 될까??

     

    만약 컨트롤러가 어떤 예외를 발생했다면 앞에 설명했지만 preHandle()는 컨트롤러 호출 전에 실행 되니까 정상적으로 실행 될 것습니다. 그러면 postHandle(), afterCompletion은 호출이 안 될 것이다. 라고 생각하겠지만 afterCompletion은 예외와 무관하게 항상 실행됩니다 또한 예외 발생 시 afterCompletion은 예외 정보를 포함해서 호출됩니다.

     

     

    3-2 간단한 인터셉터 구현

    요청과 응답의 각 단계에서 로그를 남기고 코드입니다.

    @Slf4j
    public class LogInterceptor implements HandlerInterceptor {
    
        public static final String LOG_ID = "logId";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String requestURI = request.getRequestURI();
            String uuid = UUID.randomUUID().toString();
    
            request.setAttribute(LOG_ID, uuid);
    
            if (handler instanceof HandlerMethod) {
                HandlerMethod hm = (HandlerMethod) handler; // 호출할 컨트롤러 메소드의 모든 정보를 포함
            }
    
            log.info("request [{}][{}][{}]", uuid, requestURI, handler);
    
            return true; // true 면 정상 호출이다. 다음 인터셉터나 컨트롤러가 호출된다
            // 이걸 이용해 핸들러에 대한 정보를 사용할 수 있다,
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("postHandle [{}]", modelAndView);
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            String requestURI = request.getRequestURI();
            String uuid = (String) request.getAttribute(LOG_ID);
    
            log.info("response [{}][{}][{}]", uuid, requestURI, handler);
    
            if (ex != null) {
                log.error("afterCompletion error", ex);
            }
        }
    }

    인터셉터를 구현하면서  주의사항

    인터셉터는 호출 시점이 완전히 분리되어 있습니다. 따라서 preHandle에서 지정한 값을 postHandle , afterCompletion에서 함께 사용하려면 어딘가에 담아두어야 합니다. LogInterceptor도 싱글톤 처럼 사용되기 때문에 맴버변수로 사용하면 위험합니다. 따라서 request에 담아두었다가 afterCompletion에서 request.getAttribute(LOG_ID) 로 찾아서 사용해야 합니다.

     

     

    3-3 인터셉터의 등록 방법

    WebMvcConfigurer를 통한 인터셉터 추가

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LogInterceptor())
                    .order(1)
                    .addPathPatterns("/**") // : 인터셉터를 적용할 URL 패턴을 지정
                    .excludePathPatterns("/css/**", "/*.ico", "/error"); // 인터셉터에서 제외할 패턴을 지정
    
        }
    
    }

    서블릿 필터와 비교해보면 스프링 인터셉터는 addPathPatterns , excludePathPatterns 로 매우 정밀하게 URL 패턴을 지정할 수 있습니다.

     

    패턴을 지정할 때 에는 아래에 있는 공식 문서를 참고 하는게 좋습니다.

    https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/pattern/PathPattern.html

     

    PathPattern (Spring Framework 6.1.11 API)

    Compare this pattern with a supplied pattern: return -1,0,+1 if this pattern is more specific, the same or less specific than the supplied pattern.

    docs.spring.io

     

     

     

    4. 필터와 인터셉터의 차이점 정리

      필터 인터셉터
    동작 시기 서블릿 컨테이너 도달하기 전에  동작 Spring Context에서 컨트롤러 도달하기 전에 동작
    적용 범위 모든(특정) 요청과 응답에 대해 동작 Spring 컨트롤러가 처리하는 요청과 응답 대상
    사용 목적 주로 보안, 로깅, 데이터 변형 등에 사용 인증, 인가, 로깅 등에 사용



     

     

     

     

     

    참고