[Spring] 30. Filter와 Interceptor 차이

[Spring] 30. Filter와 Interceptor 차이

이전 글에서는 Spring MVC의 Interceptor에 대해 살펴봤습니다. Interceptor는 요청이 Controller에 도착하기 전이나 Controller 실행 후에 공통 작업을 처리할 수 있는 기능입니다. 로그인 체크, 권한 체크, 요청 로그 기록 같은 작업에 자주 사용합니다.

이번 글에서는 Interceptor와 함께 자주 비교되는 Filter에 대해 정리하겠습니다. Filter와 Interceptor는 둘 다 요청 중간에서 공통 처리를 할 수 있지만, 동작 위치와 사용 목적이 다릅니다.

핵심은 간단합니다. Filter는 Spring MVC에 들어오기 전의 Servlet 영역에서 동작하고, Interceptor는 Spring MVC 내부에서 Controller 실행 전후에 동작합니다.


1. Filter란?

Filter는 Servlet 기술에서 제공하는 요청/응답 전처리 기능입니다. 사용자의 요청이 DispatcherServlet에 도착하기 전, 즉 Spring MVC가 요청을 처리하기 전에 먼저 실행될 수 있습니다.

사용자 요청
 → Filter
 → DispatcherServlet
 → Interceptor
 → Controller

Filter는 Spring MVC 밖의 더 앞단에서 동작합니다. 그래서 인코딩 처리, 보안 처리, 요청/응답 래핑, 공통 로그 기록 같은 작업에 사용할 수 있습니다.

예를 들어 모든 요청의 문자 인코딩을 UTF-8로 맞추거나, 요청이 들어올 때마다 공통 로그를 남기는 작업은 Filter에서 처리할 수 있습니다.


2. Interceptor란?

Interceptor는 Spring MVC에서 제공하는 공통 요청 처리 기능입니다. DispatcherServlet이 요청을 받은 뒤, Controller가 실행되기 전후에 동작할 수 있습니다.

사용자 요청
 → DispatcherServlet
 → Interceptor
 → Controller
 → Interceptor
 → View 또는 응답

Interceptor는 Spring MVC 흐름 안에서 동작하므로 Controller 정보와 더 가까운 위치에 있습니다. 그래서 로그인 체크, 권한 체크, 관리자 접근 제한, 요청 처리 시간 측정 같은 작업에 자주 사용합니다.


3. 전체 요청 흐름

Filter와 Interceptor의 위치를 함께 보면 차이가 더 명확합니다.

사용자 요청
 → Filter
 → DispatcherServlet
 → HandlerMapping
 → Interceptor preHandle()
 → Controller
 → Interceptor postHandle()
 → View 렌더링
 → Interceptor afterCompletion()
 → Filter
 → 사용자 응답

Filter는 DispatcherServlet 앞뒤에서 동작합니다. Interceptor는 DispatcherServlet이 Controller를 실행하는 과정 안에서 동작합니다.

Filter Spring MVC에 들어오기 전과 응답이 나가기 전에 동작합니다.
Interceptor Spring MVC 내부에서 Controller 실행 전후에 동작합니다.

4. Filter와 Interceptor 차이 한눈에 보기

구분 Filter Interceptor
소속 Servlet 기술 Spring MVC 기술
동작 위치 DispatcherServlet 이전 DispatcherServlet 이후, Controller 전후
주요 대상 전체 요청과 응답 Spring MVC Controller 요청
주 사용 목적 인코딩, 보안 필터, 요청/응답 래핑, 공통 로그 로그인 체크, 권한 체크, Controller 주변 공통 처리
Spring MVC 정보 접근 상대적으로 제한적 Handler 정보 등 Spring MVC 흐름과 가까움
실행 제어 chain.doFilter() 호출 여부로 제어 preHandle()의 true/false로 제어

처음에는 이렇게 기억하면 됩니다.

Filter
 → Spring MVC에 들어오기 전의 공통 처리

Interceptor
 → Controller 실행 전후의 공통 처리

5. Filter 기본 코드

Filter는 jakarta.servlet.Filter 인터페이스를 구현해서 만들 수 있습니다.

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;

public class LogFilter implements Filter {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {

        System.out.println("Filter 실행 전");

        chain.doFilter(request, response);

        System.out.println("Filter 실행 후");
    }
}

여기서 가장 중요한 부분은 chain.doFilter(request, response)입니다. 이 코드를 호출해야 다음 Filter 또는 DispatcherServlet으로 요청이 계속 진행됩니다.

chain.doFilter()
 → 다음 단계로 요청을 넘김

chain.doFilter() 호출 안 함
 → 요청 흐름 중단

6. Filter 등록하기

Spring Boot에서는 FilterRegistrationBean을 사용해 Filter를 등록할 수 있습니다.

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebFilterConfig {

    @Bean
    public FilterRegistrationBean<LogFilter> logFilter() {
        FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();

        registrationBean.setFilter(new LogFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);

        return registrationBean;
    }
}

이 설정은 모든 URL에 LogFilter를 적용합니다. setOrder()를 사용하면 여러 Filter가 있을 때 실행 순서를 지정할 수 있습니다.


7. Interceptor 기본 코드

Interceptor는 HandlerInterceptor를 구현해서 만들 수 있습니다.

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

public class LoginCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    ) throws Exception {

        System.out.println("Controller 실행 전");

        return true;
    }
}

preHandle()에서 true를 반환하면 Controller가 실행됩니다. false를 반환하면 Controller 실행이 중단됩니다.

return true
 → Controller 실행

return false
 → Controller 실행 중단

8. Interceptor 등록하기

Interceptor는 WebMvcConfigurer를 구현한 설정 클래스에서 등록합니다.

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

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginCheckInterceptor())
                .addPathPatterns("/mypage/**", "/board/write", "/board/*/edit")
                .excludePathPatterns("/member/login", "/member/join", "/css/**", "/js/**", "/images/**");
    }
}

이 설정은 마이페이지, 게시글 작성, 게시글 수정 경로에 로그인 체크를 적용합니다. 로그인 페이지, 회원가입 페이지, 정적 리소스는 검사 대상에서 제외합니다.


9. 로그인 체크는 Filter와 Interceptor 중 어디가 좋을까?

로그인 체크는 Filter로도 할 수 있고 Interceptor로도 할 수 있습니다. 다만 Spring MVC 기반 프로젝트에서 Controller 접근 권한을 검사하는 용도라면 Interceptor가 더 자연스러운 경우가 많습니다.

상황 추천
Controller별 로그인 체크 Interceptor
관리자 경로 접근 제어 Interceptor
요청/응답 전체에 대한 공통 처리 Filter
보안 프레임워크의 인증 흐름 Filter 기반으로 동작하는 경우가 많음

처음 프로젝트에서 세션 기반 로그인 체크를 직접 구현한다면 Interceptor로 시작하는 것이 이해하기 쉽습니다.


10. Filter 사용 예시: 요청 로그

모든 요청의 시작과 끝을 기록하는 간단한 Filter를 만들어보겠습니다.

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;

public class RequestLogFilter implements Filter {

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        long start = System.currentTimeMillis();
        System.out.println("요청 시작: " + httpRequest.getRequestURI());

        chain.doFilter(request, response);

        long end = System.currentTimeMillis();
        System.out.println("요청 종료: " + httpRequest.getRequestURI());
        System.out.println("처리 시간: " + (end - start) + "ms");
    }
}

Filter는 요청이 DispatcherServlet에 들어가기 전부터 볼 수 있으므로 전체 요청 로그를 남기기에 적합합니다.


11. Interceptor 사용 예시: 로그인 체크

이번에는 Interceptor로 로그인 체크를 구현해보겠습니다.

public class LoginCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
    ) throws Exception {

        HttpSession session = request.getSession(false);

        if (session == null || session.getAttribute("loginMember") == null) {
            response.sendRedirect("/member/login");
            return false;
        }

        return true;
    }
}

로그인하지 않은 사용자는 로그인 페이지로 이동시키고, false를 반환해 Controller 실행을 막습니다. 로그인한 사용자라면 true를 반환해 Controller가 정상 실행됩니다.

로그인 안 됨
 → redirect:/member/login
 → return false

로그인 됨
 → return true
 → Controller 실행

12. 요청을 막는 방식 비교

Filter와 Interceptor는 요청 흐름을 막는 방식이 다릅니다.

구분 Filter Interceptor
계속 진행 chain.doFilter(request, response) 호출 return true
요청 중단 chain.doFilter()를 호출하지 않음 return false
redirect response.sendRedirect() 후 중단 response.sendRedirect()false

이 차이를 모르면 redirect를 해놓고도 요청이 계속 진행되는 실수를 할 수 있습니다.


13. 실행 순서 예시

요청이 정상적으로 처리될 때 Filter와 Interceptor의 실행 순서는 다음과 같습니다.

1. 사용자 요청
2. Filter 실행 전 로직
3. chain.doFilter()
4. DispatcherServlet
5. Interceptor preHandle()
6. Controller 실행
7. Interceptor postHandle()
8. View 렌더링
9. Interceptor afterCompletion()
10. Filter 실행 후 로직
11. 사용자 응답

Filter의 후처리 로직은 Interceptor와 Controller 처리가 모두 끝난 뒤 다시 실행될 수 있습니다.


14. 어떤 것을 사용해야 할까?

Filter와 Interceptor 중 무엇을 사용할지는 작업의 위치와 목적에 따라 결정하면 됩니다.

작업 추천 위치
문자 인코딩 처리 Filter
요청/응답 래핑 Filter
전체 요청 로그 Filter 또는 Interceptor
세션 로그인 체크 Interceptor
관리자 권한 체크 Interceptor
Controller 실행 시간 측정 Interceptor
API 인증 토큰 검사 구조에 따라 Filter 또는 Interceptor

정답이 항상 하나로 고정되는 것은 아닙니다. 다만 Spring MVC Controller 접근 제어에 가까운 작업은 Interceptor가 이해하기 쉽고, 더 앞단에서 전체 요청을 다뤄야 하는 작업은 Filter가 적합합니다.


15. 자주 하는 실수

1) Filter와 Interceptor 위치를 반대로 이해하는 경우

Filter가 더 먼저 실행됩니다. Interceptor는 DispatcherServlet 이후, Controller 실행 전후에 동작합니다.

Filter
 → DispatcherServlet
 → Interceptor
 → Controller

2) Filter에서 chain.doFilter()를 빼먹는 경우

chain.doFilter()를 호출하지 않으면 요청이 다음 단계로 진행되지 않습니다. 특별히 요청을 막으려는 목적이 아니라면 반드시 호출해야 합니다.

chain.doFilter(request, response);

3) Interceptor에서 redirect 후 true를 반환하는 경우

로그인하지 않은 사용자를 redirect한 뒤 true를 반환하면 Controller가 계속 실행될 수 있습니다. 요청을 막아야 한다면 false를 반환해야 합니다.

response.sendRedirect("/member/login");
return false;

4) 정적 리소스를 Interceptor 검사 대상에 포함하는 경우

로그인 체크 Interceptor를 전체 경로에 적용하면서 CSS, JS, 이미지 경로를 제외하지 않으면 화면이 깨질 수 있습니다.

.excludePathPatterns("/css/**", "/js/**", "/images/**", "/favicon.ico")

5) Controller마다 로그인 체크를 계속 반복하는 경우

로그인 체크를 Interceptor로 분리했다면 Controller에서는 중복 검사 코드를 줄이는 것이 좋습니다. Controller는 요청 처리와 Service 호출에 집중해야 합니다.


16. 전체 흐름 정리

Filter와 Interceptor의 전체 흐름을 다시 정리하면 다음과 같습니다.

사용자 요청
 → Filter
 → DispatcherServlet
 → HandlerMapping
 → Interceptor preHandle()
 → Controller
 → Service
 → Repository / Mapper
 → Interceptor postHandle()
 → View 렌더링
 → Interceptor afterCompletion()
 → Filter 후처리
 → 사용자 응답

이 흐름만 정확히 기억해도 Filter와 Interceptor를 어디에 써야 하는지 판단하기 쉬워집니다.


정리

Filter와 Interceptor는 모두 요청 중간에서 공통 처리를 할 수 있는 기능입니다. 하지만 Filter는 Servlet 영역에서 DispatcherServlet보다 앞에 위치하고, Interceptor는 Spring MVC 영역에서 Controller 실행 전후에 위치합니다.

Filter Spring MVC에 들어오기 전의 요청/응답 공통 처리에 사용합니다.
Interceptor Controller 실행 전후의 공통 처리에 사용합니다.
chain.doFilter() Filter에서 다음 단계로 요청을 넘깁니다.
preHandle() Interceptor에서 Controller 실행 전 요청을 검사합니다.

처음에는 다음 기준으로 기억하면 좋습니다.

더 앞단의 전체 요청 처리
 → Filter

Spring MVC Controller 주변 처리
 → Interceptor

로그인 체크, 관리자 체크
 → Interceptor가 이해하기 쉬움

인코딩, 요청/응답 래핑, 보안 필터
 → Filter가 적합한 경우가 많음

연습해보기

아래 작업은 Filter와 Interceptor 중 어디에 두면 좋을지 생각해보세요.

작업 추천
모든 요청 URI 로그 출력 Filter 또는 Interceptor
마이페이지 로그인 체크 Interceptor
관리자 페이지 권한 체크 Interceptor
요청 인코딩 처리 Filter
Controller 실행 시간 측정 Interceptor

다음 글에서는 세션 기반 로그인 처리 흐름을 정리하겠습니다. 로그인 성공 시 세션에 사용자 정보를 저장하고, Interceptor에서 그 세션 정보를 확인하는 구조까지 연결해서 보면 실전 웹 프로젝트 흐름이 더 분명해집니다.