Part3 - CH6. 스프링 기능 활용하기(4) Filter
Filter
Filter : Web Application에서 관리되는 영역, Spring Boot Framework에서 Client로 오는 요청/ 응답에 대하여 최초/최종단계의 위치에 존재하며, 이를 통해서 요청/응답의 정보를 변경하거나 Spring에 의해 데이터가 변환되기 전의 순수한 Client의 요청/응답 값을 확인 할 수 있다.
유일하게 ServletRequest, ServletResponse의 객체를 변환 할 수 있다.
주로 request/response의 Loggin용도로 활용하거나, 인증과 관련된 Logic들을 해당 Filter에서 처리한다.
Filter의 전역적 사용
Filter 인터페이스를 상속받은 GlobalFilter를 하나 만들자.
@Slf4j
@Component
public class GlobalFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//전처리
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String url = httpServletRequest.getRequestURI();
BufferedReader br = httpServletRequest.getReader();
br.lines().forEach(line -> {
log.info("url : {} , line : {}", url, line);
});
chain.doFilter(request, response); // 기준
//후처리
}
}
chain.doFilter()
를 기준으로 그 이전에 작성한 코드는 전처리, 그 이후에 작성한 코드는 후처리로 적용
된다. 이전에 배웠던 AOP의 @Arround를 활용할때와 비슷한 것 같다.
일단 전처리쪽 코드만 작성해서 실행하고 요청을 보내면, 일단 에러가 발생한다…!
일단 로그찍힌것을 보면 json이 어떻게 들어오는지 확인할 수 있다.
그 이유는 BufferedReader의 특성때문인데, 가운데 있는 br.lines()를 돌면서 전부 읽어버려서 커서가 맨 끝으로 가 있는데 그상태에서 스프링 body를 읽으려고 했지만 읽을 수 없는 상태가 되어 버렸기 때문이다.
이를 방지하기 위해서 다양한 방법이 있었는데, 스프링에서는 다음과 같은 방법을 제공한다.
@Slf4j
@Component
public class GlobalFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//전처리
ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper ((HttpServletResponse) response);
String url = httpServletRequest.getRequestURI();
/** 기준 */
chain.doFilter(request, response);
//후처리
//req
String reqContent = String.valueOf(httpServletRequest.getContentAsByteArray());
log.info("request url : {}, request body : {}", url, reqContent);
//res
String resContent = String.valueOf(httpServletResponse.getContentAsByteArray());
int httpStatusCode = httpServletResponse.getStatus();
httpServletResponse.copyBodyToResponse(); // body를 한번 읽어버렸기 때문에 복사해서 넘겨줘야한다.
log.info("response status : {}, response body : {}", httpStatusCode, resContent)
}
}
ContentCachingRequestWrapper, ContentCachingResponseWrapper라는 타입형을 제공하여 요청의 내용을 모두 읽어도 계속해서 읽을 수 있도록 해준다. 원리는 요청내용을 cache하여 다시 읽을 수 있도록 한다.
ContentCachingWrapper 특성상 chain.doFilter 후에 내용이 적히고 그 전에는 길이만 설정되기 때문에 chain.doFilter 후에 값을 받아서 로그를 찍어야한다.
그리고 마지막에 response를 주기 전에도 body를 이미 읽어버려서, copyBodyToResponse메소드를 통해 body를 다시 복사해서 응답을 내려줘야 한다. 그렇지 않으면 클라이언트에서 빈 body를 받게된다.
Filter의 지역적 사용
특정 리퀘스트에 대해서만 필터를 걸려고 할 때에, 두가지 정도의 방법이 있는데 그 중 쉬운 방법을 소개하고 있 다.
일단 springBootApplication클래스에 가서 @ServletComponentScan 이라는 어노테이션을 붙여준다.
@SpringBootApplication
@ServletComponentScan //필터 지역적 사용을 위해 붙여줌
public class FilterAndInterceptorApplication {
public static void main(String[] args) {
SpringApplication.run(FilterAndInterceptorApplication.class, args);
}
}
그리고 GlobalFilter를 스프링이 관리하기 위해 붙여주었던 어노테이션인 @Component를 지우고, @WebFilter라는 어노테이션으로 바꿔준다.
@Slf4j
//@Component
@WebFilter(urlPatterns = "/api/user/**")
public class GlobalFilter implements Filter {
//생략
}
그러면 WebFilter어노테이션에서 지정한 urlPattern 에 해당하는 url에 대해서만 필터를 적용하게 된다.