[Java & Spring Filter] Filter

Spring에서 필터(Filter) 관련 정리

Spring에서 필터(Filter)클라이언트 요청을 가로채서 사전/사후 처리하는 기능을 수행합니다.
Spring Boot에서는 서블릿 컨텍스트와 Spring 컨텍스트가 따로 존재하며, 필터는 원래 서블릿 컨텍스트(ServletContext)에서 실행됩니다.
그러나 Spring Boot에서는 FilterRegistrationBean을 사용하여 Spring 컨텍스트의 빈을 필터에서 사용할 수 있습니다.


1. 필터(Filter)의 개념

필터란?

  • 요청(Request)과 응답(Response)을 가로채서 전처리/후처리하는 역할.
  • 서블릿(Servlet) 기반에서 동작하며, Spring MVC의 DispatcherServlet 이전에 실행됨.
  • 주로 보안(인증, CORS), 로깅, 데이터 변환, 응답 압축 등에 사용됨.

필터와 인터셉터의 차이점

비교 항목필터 (Filter)인터셉터 (Interceptor)
실행 위치DispatcherServlet 이전 (서블릿 레벨)DispatcherServlet 이후 (Spring MVC 레벨)
적용 범위모든 요청 (정적 리소스 포함)Spring MVC 컨트롤러 요청만 적용
사용 목적CORS, 로깅, 보안, 인코딩 설정인증, 권한 체크, 로깅, 트랜잭션 관리
등록 방식FilterRegistrationBean 또는 @ComponentWebMvcConfigurer에서 addInterceptors()
Spring Security 영향Security Filter Chain 에서 동작Security 필터 이후 동작 가능

2. 필터(Filter) 구현 및 등록 방법

Spring Boot에서는 3가지 방법으로 필터를 등록할 수 있습니다.

방법 1: FilterRegistrationBean을 사용한 수동 등록 (추천)

  • Spring 컨텍스트에서 필터를 관리하면서, 실행 순서를 제어할 수 있음.
  • 필터에서 @Autowired 빈을 사용할 수 있음.
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    private final SomeService someService;

    public FilterConfig(SomeService someService) {
        this.someService = someService;
    }

    @Bean
    public FilterRegistrationBean<CustomFilter> loggingFilter() {
        FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CustomFilter(someService)); // ✅ 빈 주입 가능
        registrationBean.addUrlPatterns("/*"); // 모든 요청에 필터 적용
        registrationBean.setOrder(1); // 필터 실행 순서 지정
        return registrationBean;
    }
}

필터 클래스 구현

import javax.servlet.*;
import java.io.IOException;

public class CustomFilter implements Filter {

    private final SomeService someService;

    public CustomFilter(SomeService someService) {
        this.someService = someService;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        someService.doSomething(); // ✅ Spring 빈 사용 가능
        chain.doFilter(request, response);
    }
}

📌 Spring 컨텍스트에서 필터를 관리하기 때문에 @Autowired 빈을 주입할 수 있음!
📌 필터 실행 순서(setOrder)를 지정할 수 있어 다른 필터보다 먼저/나중에 실행 가능.


방법 2: @Component를 사용한 자동 등록

  • Spring이 자동으로 필터를 감지하여 등록.
  • Spring 빈을 사용할 수 있지만, 실행 순서 제어가 어려움.
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;

@Component // ✅ 필터를 Spring 빈으로 등록
public class CustomFilter implements Filter {

    private final SomeService someService;

    public CustomFilter(SomeService someService) {
        this.someService = someService;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        someService.doSomething();
        chain.doFilter(request, response);
    }
}

📌 단점: @Component로 등록하면 실행 순서를 setOrder로 지정할 수 없음.


방법 3: @WebFilter를 사용한 서블릿 기반 등록

  • Spring과 무관하게 서블릿 컨텍스트에서 필터를 등록.
  • Spring의 빈을 직접 주입할 수 없음.
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/*") // ✅ 서블릿 컨텍스트에서 필터 등록
public class CustomFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("Request received");
        chain.doFilter(request, response);
    }
}

📌 단점: Spring 빈을 사용할 수 없음 → SomeService someService;를 주입하면 NullPointerException 발생.


3. 필터에서 빈 사용 가능 여부

방법빈(@Autowired) 사용 가능 여부실행 순서 지정 가능 여부
FilterRegistrationBean 사용 (추천)✅ 가능✅ 가능
@Component 사용✅ 가능❌ 불가능
@WebFilter 사용 (서블릿 방식)❌ 불가능❌ 불가능

필터에서 빈을 사용하려면 FilterRegistrationBean을 사용해야 함!
🚨 @WebFilter는 서블릿 컨텍스트에서 관리되므로 빈을 주입할 수 없음.


4. 필터 실행 순서

필터는 실행 순서를 설정할 수 있습니다.

✅ 필터 실행 순서 지정 (setOrder)

@Bean
public FilterRegistrationBean<FirstFilter> firstFilter() {
    FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new FirstFilter());
    registrationBean.setOrder(1); // 가장 먼저 실행
    return registrationBean;
}

@Bean
public FilterRegistrationBean<SecondFilter> secondFilter() {
    FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new SecondFilter());
    registrationBean.setOrder(2); // 두 번째 실행
    return registrationBean;
}

📌 낮은 숫자의 필터가 먼저 실행됨!
📌 인터셉터보다 필터가 먼저 실행됨.


5. 필터 실행 흐름

[클라이언트 요청] 
    → 필터(Filter) 실행 (서블릿 컨텍스트)
    → DispatcherServlet 실행 (Spring 컨텍스트)
    → 인터셉터(Interceptor) 실행
    → 컨트롤러(Controller) 실행
    → 인터셉터(Interceptor) 실행 (afterCompletion)
    → DispatcherServlet 반환
    → 필터(Filter) 실행
    → [클라이언트 응답]

필터는 서블릿 컨텍스트에서 실행되므로, DispatcherServlet보다 먼저 실행됨.
인터셉터는 DispatcherServlet 이후에 실행됨.


6. 결론

🚀 필터는 Spring IoC 컨텍스트와 무관하지만, FilterRegistrationBean을 사용하면 Spring 빈을 사용할 수 있음.
🔥 필터를 사용하려면 FilterRegistrationBean을 추천! (@Component도 가능하지만 실행 순서 조정이 어려움.)
💡 서블릿 컨텍스트에서 실행되는 필터와 Spring 컨텍스트의 차이를 이해하면 필터를 더 효과적으로 활용할 수 있음! 🚀


필터 종류 및 CORS, XSS 예시


1. Spring에서 제공하는 주요 필터 종류

Spring은 javax.servlet.Filter뿐만 아니라, 몇 가지 유용한 필터 클래스를 제공합니다.

1.1. OncePerRequestFilter (한 요청당 한 번만 실행)

  • 한 요청에 대해 필터가 여러 번 실행되지 않도록 보장하는 Spring 제공 필터.
  • 기본 필터(Filter 인터페이스)를 사용하면 같은 요청이 여러 번 필터링될 수도 있음.
  • OncePerRequestFilter를 확장하면 동일한 요청에 대해 한 번만 실행되도록 보장됨.

📌 예제: 요청 로깅 필터

import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        System.out.println("LoggingFilter: " + request.getRequestURI());
        filterChain.doFilter(request, response);
    }
}

1.2. CharacterEncodingFilter (문자 인코딩 설정)

  • 요청과 응답의 문자 인코딩(UTF-8 등)을 설정하는 필터.
  • Spring Boot에서는 자동으로 등록되지만, 설정을 커스터마이징할 수도 있음.
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public CharacterEncodingFilter encodingFilter() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        return filter;
    }
}

📌 요청과 응답의 인코딩을 UTF-8로 설정하여 한글 깨짐 방지.


1.3. CorsFilter (CORS 처리)

  • CORS(Cross-Origin Resource Sharing) 정책을 적용하는 필터.
  • 외부 도메인에서 API를 호출할 때 브라우저에서 차단되는 문제를 해결함.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*"); // 모든 도메인 허용
        config.addAllowedMethod("*"); // 모든 HTTP 메서드 허용
        config.addAllowedHeader("*"); // 모든 헤더 허용
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

📌 모든 도메인에서 API를 호출할 수 있도록 CORS 설정 적용.


1.4. HiddenHttpMethodFilter (RESTful API 지원)

  • HTML <form> 태그는 GETPOST만 지원하지만, PUT, DELETE 등의 메서드를 사용할 수 있도록 변환해줌.
  • PUT, DELETE 요청을 POST 요청으로 보내면서 _method 파라미터를 포함하면 변환됨.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

@Configuration
public class FilterConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new HiddenHttpMethodFilter();
    }
}

📌 HTML 폼에서 _method=PUT을 보내면, 실제로는 PUT 요청으로 처리됨.


2. CORS 처리 예제 (CorsFilter)

📌 CORS(Cross-Origin Resource Sharing) 문제 해결을 위한 필터를 적용하는 방법

✅ 2.1. CorsFilter를 사용한 CORS 처리

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*"); // 모든 도메인 허용
        config.addAllowedMethod("*"); // 모든 HTTP 메서드 허용
        config.addAllowedHeader("*"); // 모든 헤더 허용
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

📌 모든 도메인, 메서드, 헤더를 허용하여 CORS 문제 해결.


✅ 2.2. WebMvcConfigurer를 사용한 CORS 설정

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

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") // `/api/` 경로에만 적용
                .allowedOrigins("https://example.com") // 특정 도메인 허용
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

📌 특정 도메인에서만 API를 호출할 수 있도록 제한 가능.


3. XSS(Cross-Site Scripting) 방어 필터

📌 XSS 공격을 방어하는 방법

  • 사용자가 입력한 악성 JavaScript 코드가 실행되지 않도록 방지.
  • HttpServletRequestWrapper를 사용하여 입력 값을 필터링함.

✅ XSS 방어 필터

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

public class XssFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        XssRequestWrapper wrappedRequest = new XssRequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrappedRequest, response);
    }
}

HttpServletRequestWrapper를 사용하여 입력 값 필터링

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class XssRequestWrapper extends HttpServletRequestWrapper {

    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return sanitize(value);
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) return null;

        for (int i = 0; i < values.length; i++) {
            values[i] = sanitize(values[i]);
        }
        return values;
    }

    private String sanitize(String input) {
        return input == null ? null : input.replaceAll("<", "&lt;")
                                           .replaceAll(">", "&gt;")
                                           .replaceAll("\"", "&quot;")
                                           .replaceAll("'", "&#x27;")
                                           .replaceAll("&", "&amp;");
    }
}

📌 사용자가 입력한 값에서 <script> 태그와 같은 악성 코드가 실행되지 않도록 변환.


✅ XSS 필터를 Spring에 등록

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

@Configuration
public class XssFilterConfig {

    @Bean
    public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
        FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new XssFilter());
        registrationBean.addUrlPatterns("/*"); // 모든 요청에 적용
        registrationBean.setOrder(1); // 필터 실행 순서 지정
        return registrationBean;
    }
}

📌 모든 요청에서 XSS 공격을 자동으로 방어.


📌 정리

  • OncePerRequestFilter: 한 요청당 한 번만 실행되는 필터.
  • CorsFilter: CORS 문제 해결.
  • CharacterEncodingFilter: UTF-8 인코딩 적용.
  • HiddenHttpMethodFilter: RESTful API 지원.
  • XSS 필터: 사용자의 입력 값에서 <script>와 같은 악성 코드 차단.

🚀 필터를 적절히 사용하면 보안과 성능을 향상시킬 수 있음!


© 2023 Lee. All rights reserved.