이전 포스팅인 웹 애플리케이션 인증 방식 1 : 쿠키-세션, 웹 애플리케이션 인증 방식 2 : JWT에 이어집니다.
서블릿 필터(Servlet Filter) : Java 웹 애플리케이션에서 클라이언트의 요청(request)과 서버의 응답(response)을 가로채서 처리할 수 있는 도구이다. 필터는 요청이 서블릿(Servlet)에 도달하기 전 또는 응답이 클라이언트에게 전달되기 직전에 실행된다. 이를 통해 요청/응답 데이터를 변환하거나, 인증/인가를 확인하여 접근을 제어하고, 로깅을 수행하는 등의 작업을 수행한다.
스프링 내부에는 jakarta.servlet.Filter
인터페이스를 통해 구현된 서블릿 필터가 존재한다. 해당 인터페이스는 클라이언트의 요청을 가로채어 처리할 수 있도록 하는 여러 메서드들을 제공한다.
[서블릿 필터 예시]
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
import java.util.logging.Logger;
@WebFilter(urlPatterns = "/*") // 모든 요청에 대해 필터 적용
public class ExampleFilter implements Filter {
private final Logger logger = Logger.getLogger(ExampleFilter.class.getName());
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 필터 초기화 작업 (필요시)
logger.info("ExampleFilter initialized");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 요청 처리 전
logger.info("ExampleFilter: Request processing started");
// 다음 필터로 요청을 전달하거나 서블릿에 전달
chain.doFilter(request, response);
// 응답 처리 후
logger.info("ExampleFilter: Response processing completed");
}
@Override
public void destroy() {
// 필터 종료 작업 (필요시)
logger.info("ExampleFilter destroyed");
}
}
@WebFilter(urlPatterns = "/*")
: 필터가 적용될 URL 패턴을 지정한다. "/*"을 지정하면 모든 요청에 대해 필터가 적용된다.init(FilterConfig filterConfig)
: 필터의 인스턴스가 생성된 후 처음에 딱 한 번 호출되는 서블릿 필터의 초기화 메서드이다. FilterConfig 객체를 통해 필터 이름, 초기화 매개변수, 서블릿 컨텍스트같은 필터 초기화 매개변수를 읽어올 수 있다. doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
: 각 요청이나 응답이 들어올 때마다 호출되는 필터의 핵심 메서드로 ServletRequest
와 ServletResponse
객체를 통하여 요청과 응답을 처리한 후 다음 필터나 최종 대상(서블릿)으로 요청을 전달한다.
(필터는 여러 필터가 순서대로 엮여 있는 사슬(Chain)모양을 하고 있다.)
chain.doFilter()
:
요청 전 처리
: doFilter()
메서드의 chain.doFilter()
호출 이전은 요청을 처리하거나 가로채는 로직이다.요청 전달
: chain.doFilter(request, response)
호출은 요청을 다음 필터로 전달한다. 만약 다음 필터가 없다면 서블릿으로 요청이 전달된다.응답 후 처리
: chain.doFilter()
호출 이후는 요청이 처리되어 다시 돌아온 응답을 처리하거나 수정하는 로직이다.destroy()
: 필터가 더 이상 사용되지 않을 때 마지막으로 호출되는 메소드이다. 이 메소드는 필터가 리소스를 정리하고, 열린 연결을 닫거나, 메모리에서 할당된 객체를 해제하는 등의 마무리 작업을 수행하는 데 사용된다.inti() 메서드로 필터를 초기화하여 설정하고 doFilter()로 요청을 검사, 처리하고 다음 필터나 서블릿으로 넘겨준 후 요청이 처리되어 돌아온 응답을 처리하거나 수정한 후 destory() 메서드로 마무리 작업을 수행한 후 소멸된다.
FilterChain
은 Java Servlet API에서 서블릿 필터의 동작을 관리하고 필터 간의 요청과 응답을 연결하는 역할을 하는 인터페이스이다. 필터 체인은 여러 필터가 순차적으로 요청을 처리할 수 있도록 도와준다.
여러 필터의 연결
: FilterChain은 여러 필터를 연결하여 요청이 필터 체인을 통해 순차적으로 처리되도록 한다. 각 필터는 다음 필터 또는 최종 서블릿으로 요청을 전달할 수 있다.
요청과 응답 전달
: 필터는 FilterChain을 사용하여 chain.doFilter(ServletRequest request, ServletResponse response)
요청과 응답 객체를 다음 필터 또는 서블릿으로 전달한다. 이 메소드가 호출되지 않으면, 요청은 필터 체인을 통과하지 않고 종료된다. 해당 메커니즘을 통해 필터 간에 데이터를 주고받을 수 있다.
체인 흐름 제어
: 각 필터는 FilterChain을 통해 자신의 처리 후 다음 필터로 제어를 넘길 수 있으며, 필요할 경우 추가적인 후처리를 수행할 수 있다.
[필터 클래스 구현]
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 필터 초기화 로직 (필요하다면)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("Request URI is: " + req.getRequestURI());
// 다음 필터로 요청을 전달
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 필터 종료 시의 로직 (필요하다면)
}
}
@Bean
을 사용한 필터 등록
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.servlet.Filter;
@Configuration
public class FilterConfig {
@Bean
public Filter customFilter() {
return new CustomFilter();
}
}
설정을 나타내는 @Configuration 클래스를 생성하고 필터를 @Bean을 통해 수동으로 필터를 빈으로 등록한다.
서블릿 필터를 등록하고 설정하는 데 사용되는 클래스인 FilterRegistrationBean
을 사용하면 필터를 특정 URL 패턴에만 적용하거나 필터의 순서를 지정할 수 있기 때문에 필터를 더 세밀하게 제어할 수 있다는 장점이 있다.
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<CustomFilter> loggingFilter() {
FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CustomFilter());
registrationBean.addUrlPatterns("/api/*"); // 특정 URL 패턴에만 필터 적용
registrationBean.setOrder(1); // 필터의 순서 지정
return registrationBean;
}
}
@WebFilter
를 통한 필터 등록과 @ServletComponentScan
을 통한 필터 활성화[@WebFIlter()
로 필터 클래스 구현 및 등록]
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/api/*")
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 초기화 로직 (필요하다면)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("Request URI is: " + req.getRequestURI());
// 다음 필터로 요청을 전달
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 필터 종료 로직 (필요하다면)
}
}
[@ServletComponentScan
으로 필터 활성화]
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Application에 @ServletComponentScan
를 붙여주면 Spring Boot가 자동으로 @WebFilter로 등록된 필터를 검색하고 등록해준다.