🟧 01. 필터(Filter) 란?
필터(Filter)는 디스패처 서블릿에 요쳥이 전달되기 전/후로 작업을 처리해주는 기능이다.
그림을 보면 알 수 있듯이 스프링 컨테이너가 필터를 관리하지 않고 웹 컨테이너가 관리하고 있다.
jakarta.servlet.Filter가 들어있는 것을 확인할 수 있다.
사실 필터를 봤을 때 궁금한 점이 많았다. 🤔
- 필터는 어디서 어떻게 사용하고 있는걸까 ?
- 인터셉터와 차이가 무엇이 있을까 ?
이제부터 하나하나 알아가보자.
🟧 02. 필터로 전/후로 작업을 처리하는게 뭐가 있을까 ?
먼저 필터가 연쇄적으로 동작할 수 있게 이루어져 있다는 것을 알고 진행하는 편이 좋을거 같다.
이 개념은 필터 체인(FilterChain)을 통해서 알 수 있고 간단하게 그림만 보고 넘어가자.
필터는 위 그림과 같이 체인 방식으로 서블릿 실행 전/후에 작업을 진행할 때 사용된다.
필터는 요청/응답 객체를 조작하여 넘길 수 있기 때문에 여러가지 방식으로 응용해서 사용하기 좋다.
- 인증/인가 작업
- 로깅 작업
- 이미지/데이터 압축 및 문자열 인코딩 작업
- ...
🟢 인증/인가 필터
대표적으로 "인증/인가"에서 필터들이 어떻게 되어 있는지 그림만 간단하게 봐보자.
스프링 시큐리티(Spring Security)는 인증, 권한부여, 공격 보호 기능 등을 제공하는 프레임워크이다.
스프링 시큐리티에서 보안 관련 기능은 필터(Servlet Filter)를 기반으로 하고 있다.
아래 그림에서 인증/인가에 관련된 필터들이 연쇄적으로 동작한다는 정도만 알아두자.
🟧 03. Filter 인터페이스
필터에 대해서 알아보는데 시큐리티 보다는 로깅이 쉬울것 같아서 로깅 예제를 아래에 작성했다.
로깅 필터를 만들기 위해서는 Filter 인터페이스를 구현해야 한다.
그러니 먼저 Filter 인터페이스에 대해서 얘기해보자.
이제부터 Filter 코드를 보면서 어떤 메서드가 있는지 보자.
🟢 03-01. Filter 인터페이스
Filter 인터페이스에는 세 가지 메서드가 존재한다.
- init()
- doFilter()
- destroy()
package jakarta.servlet;
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {}
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default void destroy() {}
}
init() 메서드
웹 컨테이너가 필터를 서비스에 추가하기 위해서 필터 객체를 초기화할 때 단 한번만 호출 메서드이다.
init() 메서드는 필터링 작업을 하기 전에 성공적으로 처리된다.
doFilter() 메서드
doFilter() 메서드는 요청/응답이 체인을 통과될 때마다 컨테이너에 의해 호출된다.
파라미터에 FilterChain이 있는 것을 확인할 수 있다.
FilterChain은 doFilter() 메서드를 호출할 때 다음 필터에게 요청/응답이 넘어가게 된다.
package jakarta.servlet;
public interface FilterChain {
void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
destroy() 메서드
웹 컨테이너에 의해서 서비스에서 데이터를 꺼내기 위해서이다. destroy() 메서드가 호출됐다면 doFilter() 메서드는 더 이상 호출되지 않는다.
🟧 04. 예제) 필터를 이용한 요청 정보 로깅
다음과 같이 요구사항이 나왔다고 하자.
클라이언트 요청 정보 로깅 요구사항
- HttpMethod가 POST
- payload 로깅
컨트롤러가 호출되기 전/후 중에 하나의 요청을 잡아 로깅하면 된다.
이때 인터셉터(interceptor)를 이용해도 된다고 생각할 수 있다.
클라이언트 요청의 InputStream은 한 번 읽으면 Stream이 닫히기 때문에 null이 출력되거나 예외가 발생할 수 있다.
우리는 payload를 여러번 사용하기 위해서는 필터를 이용해서 InputStream을 캐싱하여 사용하는 방식을 이용해야한다.
인터셉터를 이용한다면 요청 객체를 새로운 객체로 조작해서 다음 인터셉터에 넘길 수 없다.
그러나 필터는 요청 객체를 조작하여 새로운 객체를 만들어 다음 필터로 넘길 수 있다.
필터를 이용하면 다음과 같이 코드를 작성할 수 있다.
/**
* 인터셉터가 아닌 필터를 이용해야 하는 이유
* - 필터는 Request를 조작 가능하기 때문이다.
*/
@Slf4j
@Component
public class LogFilter extends AbstractRequestLoggingFilter {
@Override
protected void beforeRequest(final HttpServletRequest request, final String message) {
/* */
}
@Override
protected void afterRequest(final HttpServletRequest request, final String message) {
// POST일 때 payload를 출력한다.
if (POST.matches(request.getMethod())) {
setIncludePayload(true);
log.info("{}", message);
}
}
}
요청을 보낸 결과 payload 로깅이 잘 됐다 짝짝 👏
🟢 AbstractRequestLoggingFilter는 내부에서 InputStream을 캐싱하는 코드가 있다.
스프링에서 제공하고 있는 요청 로깅 필터이다.
LogFilter 클래스를 보면 알 수 있듯이 beforeRequest(), afterRequest() 두 메서드를 오버라이딩해서 사용하면 된다.
내부에 요청을 ContentCachingRequestWrapper를 이용해서 캐싱하고 있는 것을 볼 수 있다.
그리고 doFilter()를 호출하여 다음 필터로 조작한 요청 객체를 넘기고 있다.
public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean isFirstRequest = !isAsyncDispatch(request);
HttpServletRequest requestToUse = request;
if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
// ContentCachingRequestWrapper를 이용하여 캐싱한다.
requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength());
}
boolean shouldLog = shouldLog(requestToUse);
if (shouldLog && isFirstRequest) {
beforeRequest(requestToUse, getBeforeMessage(requestToUse));
}
try {
filterChain.doFilter(requestToUse, response);
}
finally {
if (shouldLog && !isAsyncStarted(requestToUse)) {
afterRequest(requestToUse, getAfterMessage(requestToUse));
}
}
}
}
👊 정리하자면
필터 특징
- 인터셉터와 다르게 조작한 요청/응답을 다음 필터로 보낼 수 있다.
- 체이닝 방식으로 이용할 수 있다.
- 스프링부트가 등장하면서 자동으로 빈으로 등록된다.
'🍃 스프링' 카테고리의 다른 글
[Spring] 필터와 인터셉터의 차이 (Filter, Interceptor) (0) | 2023.05.07 |
---|---|
[Spring MVC] 디스패처 서블릿(DispatcherServlet) 둘러보기 (0) | 2023.04.24 |
[Spring MVC - Exception] @ExceptionHandler와 ExceptionHandlerExceptionResolver로 예외 처리하기 (1/2) (0) | 2023.04.17 |
[Spring MVC - ArgumentResolver] ArgumentResolver를 이용해서 컨트롤러 메서드의 파라미터 받기 (6) | 2023.04.13 |