필터

gisung2215·2022년 7월 9일
0

SpringBoot

목록 보기
3/4

Filter란?

필터는 'http 요청과 응답을 변경할 수 있는 재사용 가능한 클래스'이다. 객체의 형태로 존재하는 클라이언트에서 오는 요청(request)과 최종자원(JSP, 서블릿, 기타 자원)T 사이에 위치하여 클라이언트의 요청 정보를 알맞게 변경할 수 있다. 또한, 최종 자원과 클라이언트로 가는 응답(response) 사이에 위치하여 최종 자원의 요청 결과를 알맞게 변경할 수 있다.

https://o7planning.org/10395/java-servlet-filter

SpringBoot에서 Filter 설정법

1. FilterRegistrationBean을 통한 Filter 등록

필터를 추가하기 위해서는 javax.servlet의 Filter 인터페이스를 구현(implements)해야 하며 이는 다음의 3가지 메소드를 가지고 있다.

  • init()
  • doFilter()
  • destroy()
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;


public class FirstFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        /*
        	필터 객체를 초기화
            웹 컨테이너각 1회 init 메서드를 호출
        */
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        /*
            필터 기능을 수행
            filterChain 을 이용해서 다음 필터로 처리를 전달
         */
        System.out.println("01.FirstFilter start ==============");

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        filterChain.doFilter(servletRequest, servletResponse);

        System.out.println("01.FirstFilter end ==============");
    }

    @Override
    public void destroy() {
        /*
            필터가 웹 컨테이너에서 삭제될 때 호출
            주로, 필터가 사용한 자원을 반납
         */

        System.out.println("1 필터 destory() ===============");

    }
}

위와 같이 생성한 Filter를 아래와 같이 bean으로 등록한다.

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

import java.util.Arrays;

@Configuration
public class FilterConfiguration implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean setFirstFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new FirstFilter());
        registrationBean.setOrder(1);
        registrationBean.setUrlPatterns(Arrays.asList("/boards/*"));
        return registrationBean;
    }


    @Bean
    public FilterRegistrationBean setSecondFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new SecondFilter());
        registrationBean.setOrder(2);
        registrationBean.setUrlPatterns(Arrays.asList("/boards/*"));
        return registrationBean;
    }
}
01.FirstFilter start ==============
02.SecondFilter start ==============
null
02. SecondFilter end ==============
01.FirstFilter end ==============

2. @WebFilter + @ServletComponentScan

@WebFilter(urlPatterns= "/boards/*")
public class FirstFilter implements Filter {

....

 }
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class BoardApplication {

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

}

Filter로 request Body 가공

POST 방식으로 전달된 "application/json" 타입의 데이터를 Servlet의 Filter나 Spring의 Interceptor에서 모종의 처리를 하기 위해서는 HttpServletRequest의 InputStream을 읽어들여야 합니다. 그러나 이러한 방법에서 마주할 수 있는 문제는 HttpServletRequest의 InputStream은 한 번 읽으면 다시 읽을 수 없다는 것입니다. 만약 Interceptor나 Filter에서 InputStream을 읽게되면, 이후 Spring이 Converter를 이용해 Json 데이터를 바인딩 처리할 때 아래와 같은 에러를 만날 수 있습니다

java.lang.IllegalStateException: getReader() has already been called for this request
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Stream closed; nested exception is java.io.IOException: Stream closed

이미 읽어버릴 InputStream을 다시 읽으려고 시도했기 때문에 발생하는 에러입니다. 이 문제를 해결하기 위해서는 InputStream을 읽어 작업 후, 새로운 InputStream을 생성하여 반환해주는 방법이 있습니다.

HttpServletRequestWrapper

HttpServletRequestWrapper는 ServletRequestWrapper를 상속한 래퍼 클래스로 ServletRequestWrapper는 필터가 요청을 변경한 결과를 저장하는 래퍼클래스다.

import org.json.JSONObject;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;


public class ReadableRequestWrapper extends HttpServletRequestWrapper {

    private byte[] rawData;
    private byte[] newData;
    private String prefix;

    public ReadableRequestWrapper(HttpServletRequest request, String prefix) {
        super(request);
        this.prefix = prefix;

        try {
            ServletInputStream inputStream = request.getInputStream();
            this.rawData = inputStream.readAllBytes();

        } catch (Exception e) {
            e.printStackTrace();
        }


        try {
            JSONObject jsonObject = new JSONObject(new String(rawData));
            System.out.println("jsonObject : " + jsonObject.toString());

            jsonObject.put("userId", "changed userId");
            jsonObject.put("password", "changed password");

            /*
            *   request로부터 읽은 데이터를 바탕으로
            *   다음 Filter로 전달할 InputStream을 생성한다.
            */
            newData = jsonObject.toString().getBytes(StandardCharsets.UTF_8);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.newData);
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    @Override
    public ServletRequest getRequest() {
        return super.getRequest();
    }
}


0개의 댓글