필터는 'http 요청과 응답을 변경할 수 있는 재사용 가능한 클래스'이다. 객체의 형태로 존재하는 클라이언트에서 오는 요청(request)과 최종자원(JSP, 서블릿, 기타 자원)T 사이에 위치하여 클라이언트의 요청 정보를 알맞게 변경할 수 있다. 또한, 최종 자원과 클라이언트로 가는 응답(response) 사이에 위치하여 최종 자원의 요청 결과를 알맞게 변경할 수 있다.
https://o7planning.org/10395/java-servlet-filter
필터를 추가하기 위해서는 javax.servlet의 Filter 인터페이스를 구현(implements)해야 하며 이는 다음의 3가지 메소드를 가지고 있다.
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 ==============
@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);
}
}
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는 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();
}
}