はじめに
Spring Securityの認可処理(投票クラスなど)において、
RequestのBodyを参照しなければならないケースが存在すると思います。
(例 POSTやPUTでDBを更新する場合など
しかし、HttpServletRequestクラスにおいて、
Bodyはストリームであり、読み取りは一度きりとなります。
そこで今回はBodyを複数回読み取る方法を紹介していきます。
やりたいこと
①Bodyから読み取った情報を保持
②Bodyの取得元を①の情報へと差替え
差替え用クラスの準備
Streamクラス
HttpServletRequest.getInputStream()コール時の戻り値の型とし、
初期化にはbyte配列を使用
BufferedServletInputStream.java
public class BufferedServletInputStream extends ServletInputStream {
private ByteArrayInputStream inputStream;
// byte配列で初期化
public BufferedServletInputStream(byte[] buffer) {
this.inputStream = new ByteArrayInputStream(buffer);
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
}
HttpServletRequestのラッパークラス
BufferedServletRequestWrapper.java
public class BufferedServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] buffer;
public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// Request BodyからStreamを取得
InputStream is = request.getInputStream();
// Streamをbyte配列に変換し、インスタンス変数に保持
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buff[] = new byte[1024];
int read;
while ((read = is.read(buff)) > 0) {
baos.write(buff, 0, read);
}
this.buffer = baos.toByteArray();
}
// Bodyの取得元をこのメソッドに差替え
@Override
public ServletInputStream getInputStream() throws IOException {
// Streamクラスを初期化して返却
return new BufferedServletInputStream(this.buffer);
}
}
実際に差替える
Filterクラス
OncePerRequestFilterにより、リクエスト単位に差替えを実施
MultipleReadEnableFilter.java
@Component
public class MultipleReadEnableFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// HttpServletRequestからラッパークラスを初期化
HttpServletRequest wrappedRequest = new BufferedServletRequestWrapper((HttpServletRequest) request);
filterChain.doFilter(wrappedRequest, response);
}
}
Spring Securityの設定クラスで、作成したFilterを設定
// inputStreamを複数回読み込めるようラップ
http.addFilter(multipleReadEnableFilter);