프로젝트 진행중 LoginFilter에서 http request body에 담긴 id,password를 꺼내서 로그인 처리를 하게 구현했었다.
하지만 필터에서 request.getInputStream()
으로 꺼내버렸을때 문제가 생겼다.. 이것에 대해 정리해보고자 글을 작성하였다.
Java를 공부하는 사람들이라면 Stream 많이 들어보고 사용도 해봤을것이다.
당장 알고리즘 풀때만해도 이렇게 입력을 받으니까..
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
그럼 InputStream이 뭘까?
java 11 공식문서의 설명을 보면 다음과 같다.
This abstract class is the superclass of all classes representing an input stream of bytes.
input(들어오는) stream(통로) of bytes(파일)
한마디로 데이터가 들어오는 통로가 InputStream 이다.
데이터가 오는 방법은 다양하다. ( 네트워크, 파일, 키보드 ...)
InputStream이 데이터가 들어오는 통로 라는것은 이해했다.
그러면 무슨 특징이 있을까?
1. 단방향으로 흘러간다.
-> InputStream은 입력방향으로만 진행하고( 수신한 데이터만 처리)
출력방향은 OutPutStream으로 진행한다.
2. 한번만 읽을 수 있다.
-> InputStream은 통로 이므로 데이터를 꺼내면 소비가 되어 삭제된다.
이러한 특징을 기억하고 문제상황을 살펴보자.
LoginFilter에서 사용자의 요청으로부터 Json Body를 가지고 와서 로그인 처리를 하는 상황
Request에서 Body를 가지고 오기위해 request.getInputStream()
를 사용하고 컨트롤러에서 RequestBody 값을 받으려고 할 때 오류가 발생했다.
위에 Stream의 특징에서도 알아봤듯이 한번 소모해버리면 읽을 수 없기 때문에 filter에서 소모해버려서 Controller 에서 사용할 수 없던 것이다.
그러면 filter에서 로그를 찍거나 로직을 처리할때 body값을 사용할 방법은 없는지 찾아보다가 RequestWrapper 를 알게 되었다.
HttpServletRequestWrapper 클래스를 확장하여 HTTP 요청 객체를 래핑하며, 필요한 메서드를 재정의하여 요청을 수정하거나 보강하기 위해 사용
주로 사용목적은 다음과 같다.
요청 데이터 수정: HTTP 요청에서 받은 데이터를 변경하려는 경우, ServletRequestWrapper를 사용하여 요청 데이터를 수정할 수 있다. 예를 들어, 요청 매개변수(parameter)를 추가, 제거 또는 수정할 수 있다.
보안 필터 적용: 요청 데이터를 보안 필터를 사용하여 처리하려는 경우 ServletRequestWrapper를 사용하여 보안 관련 작업을 추가할 수 있다. 예를 들어, 입력 데이터의 유효성을 검사하거나 권한 검사를 수행할 수 있다.
따라서 나는 Wrapper를 사용하여 Request 를 복사해놓고, 실제 Request 객체가 아닌 Wrapper 객체를 사용하기로 하였다.
HttpServletRequest requestWrapper = new CustomHttpServletRequestWrapper(request, messageBody);
filterChain.doFilter(requestWrapper, response)
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String requestBody;
public CustomHttpServletRequestWrapper(HttpServletRequest request, String requestBody) {
super(request);
this.requestBody = requestBody;
}
@Override
public ServletInputStream getInputStream() {
byte[] bytes = requestBody.getBytes(StandardCharsets.UTF_8);
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public int read() {
return inputStream.read();
}
};
}
}
이렇게 하여 Controller에도 원하는 Request의 body값을 그대로 얻을 수 있었다.