[ 스프링부트 / Spring Boot ] Filter
백엔드/SpringBoot

[ 스프링부트 / Spring Boot ] Filter

#시작하기 전..

현재 온라인 강의와 토비의 스프링 책를 통해 스프링 기초에 대해 공부를 시작하고 있다.

역시 내가 모르는 것들이 너무 많았고, 그 중 Spring MVC 라이프 사이클을 모르는 내 자신에 부끄러웠다. 

Filter, Interceptor, AOP를 이해하려면 Spring MVC 라이프 사이클이 너무 너무 중요하다.

그것을 이제서야 깨닫고 있으며, 공부하는 중이다...


1. Filter란 무엇인가?

필터를 설명하기 전에 스프링 MVC가 작동하는 원리에 대한 사전 지식이 필요하다. 

 

내가 아는 얕은 지식으로 예로 든다면,

 1. 사용자는 웹사이트에 접속을 한다. 

 2. 특정 버튼, 등을 통해 서버에 정보를 조회하거나, 등록, 수정, 등을 요청한다. (Request)

 3. 서버는 요청을 받는다. 일반적으론, 비즈니스로직을 통해  데이터베이스에 조회, 등록, 수정 등을 수행한다.

 4. 처리가 완료되면, 사용자에게 응답을 준다. (Response)

 

아래의 사진을 보면 맨 앞에 Filter가 존재한다.

예시 2번과 같이 사용자가 요청을 보낸것이다. (Request)

사용자가 입력한 정보들이 넘어오며, 스프링의 기능을 사용하기 전에 받을 수 있는 것이 Filter이다.

예시 4번과 같이 마지막으로 사용자에게 응답할 때 (Response) 도 거치게 된다.

 

2. Filter를 어떤 용도로 사용할 수 있을까?

경험이 다양하진 않지만, 지금까지 사용했던 방식은 3가지가 있었다.

 

1. 크로스 도메인(CORS) 해결하기

  -> 다른 도메인에서 AJAX로 접근하면 Access-Control-Allow-Origin 에러가 발생

2. Request, Response의 값을 custom하기 

 -> 날짜를 특정 값으로 변경, 암/복호화, 등이 있는 것 같다.

3. Request, Response의 값을 log에 표시하기

 

3. Filter를 사용하는 방법

 - 필터 생성

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //filter 생성 시 처리
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //다음 Filter 실행 전 처리 (preHandle)
        
        //다음 filter-chain에 대한 실행 (filter-chain의 마지막에는 Dispatcher servlet실행)
        filterChain.doFilter(servletRequest, servletResponse);
        
        //다음 Filter 실행 후 처리 (postHandle)
    }

    @Override
    public void destroy() {
        //filter 제거 시 처리 (보통 자원의 해제처리를 한다.)
    }
}

 1. web.xml로 등록하는 방법

<filter>
    <filter-name>testFilter</filter-name>
    <filter-class>com.example.springstudy.filter.TestFilter</filter-class>
</filter>
<filter>
    <filter-name>testFilter2</filter-name>
    <filter-class>com.example.springstudy.filter.TestFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <!-- url-pattern 대신 Servlet을 지정할 수도 있다. -->
    <servlet-name>testServlet</servlet-name> 
</filter-mapping>
<filter-mapping>
    <filter-name>testFilter2</filter-name>
    <url-pattern>/*</url-pattern>
    <!-- url-pattern 대신 Servlet을 지정할 수도 있다. -->
    <servlet-name>testServlet</servlet-name> 
</filter-mapping>

 

 2. FilterRegistration Bean을 정의하여 추가할 Filter를 정의

@Configuration
public class WebApplicationFilterConfig {

    @Bean
    public FilterRegistrationBean testFilterRegistration() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new TestFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setName("Test-Filter");
        filterRegistrationBean.setOrder(1);

        return filterRegistrationBean;
    }
    
    @Bean
    public FilterRegistrationBean testFilter2Registration() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new TestFilter2());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setName("Test-Filter2");
        filterRegistrationBean.setOrder(2);

        return filterRegistrationBean;
    }
}

 

 3. 스프링 부트로 설정하는 방법

  1. @ServletComponentScan을 등록한다. ( @WebFilter(urlPatterns = "/api/user/*") 을 지정하기 위해 )
  2. @WebFilter는 해당 필터가 어떤 urlpattern에서 지정 될 것인지 설정해 준다.
  3. doFilter를 override하여 재정의한다.
    -> 아래의 소스는 예제를 실습한 코드다. 요청(Request), 응답(Response)를 로그에 표시해주는 소스다.
@SpringBootApplication
@ServletComponentScan
public class FilterApplication {

    public static void main(String[] args) {
        SpringApplication.run(FilterApplication.class, args);
    }

}
@Slf4j
@WebFilter(urlPatterns = "/api/user/*")
public class TestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // -------------------전 처리-------------------
        //ContentCachingRequestWrapper, ContentCachingResponseWrapper
        //캐싱을 함으로써, chain.doFilter 값을 사용해도 결과 값으로 출력이 가능하다.
        //캐싱하지 않으면 chain.doFilter 후 request, response 값이 없다.
        ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);
        // -------------------전 처리-------------------

        chain.doFilter(httpServletRequest, httpServletResponse);

        // -------------------후 처리-------------------
        //request info log 표시
        String url = httpServletRequest.getRequestURI();
        String reqContent = new String(httpServletRequest.getContentAsByteArray());
        log.info("request url: {}, reqContent: {}", url, reqContent);

        //response info log 표시
        String resContent = new String(httpServletResponse.getContentAsByteArray());
        int httpStatus = httpServletResponse.getStatus();
        httpServletResponse.copyBodyToResponse(); //이 메소드를 안쓰면 response 값이 찍히지 않는다.
        log.info("response status: {}, responseBody: {}", httpStatus, resContent);
        // -------------------후 처리-------------------
    }
}

#마무리하며..

Filter에 대해 지식이 부족해 잘못된 정보가 존재할 것 같다.

그리고 다른 분에 글을 참조해서 쓴 것들이 많다. 그 만큼 내가 아는건 일부분이라는 의미 같다.

좀 더 경험해보고 찾아봐서 글을 수정보완이 필요해 보인다. 

 

앞으로 사이드프로젝트에서 Filter를 사용해 볼 방안은 많겠지만 우선, 로그와 암복호화 적용은 필요할 것 같다.

 

출처: https://jaehun2841.github.io/2018/08/25/2018-08-18-spring-filter-interceptor/#filter-%EC%83%9D%EC%84%B1