- 问题背景 :HTTP 请求的
InputStream
是单向的,默认只能被读取一次。一旦调用request.getInputStream()
或request.getReader()
读取数据后,后续再尝试读取会抛出IllegalStateException
。 - 场景需求:在微信支付回调场景中,需要先验证签名(读取请求体),再解密数据(需要再次读取请求体)。若直接使用原始请求对象,第二次读取会失败。
- 解决方案:自定义一个包装器,缓存request中输入流的数据,后续可以从包装器中直接获取输入流的数据,而不是直接读取原始流,避免产生二次读取导致抛出异常。
java
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class VerdureRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
private byte[] bytes;
private WrappedServletInputStream wrappedServletInputStream;
public VerdureRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.orgRequest = request;
//读取输入流的请求参数,保存到bytes中
bytes = IOUtils.toByteArray(request.getInputStream());
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
this.wrappedServletInputStream = new WrappedServletInputStream(byteArrayInputStream);
//把post参数重新写入请求流
reWriteInputStream();
}
public void setRequestParams(String json) {
wrappedServletInputStream.setStream(new ByteArrayInputStream(json.getBytes()));
}
/**
* 把参数重新写进请求里
*/
public void reWriteInputStream() {
wrappedServletInputStream.setStream(new ByteArrayInputStream(bytes != null ? bytes : new byte[0]));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return wrappedServletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(wrappedServletInputStream));
}
/**
* 获取post参数
*/
public String getRequestParams() throws IOException {
return new String(bytes, this.getCharacterEncoding());
}
//清洗参数,防止xss注入
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = xssEncode(values[i]);
}
return encodedValues;
}
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
private static String xssEncode(String s) {
return s;
}
public HttpServletRequest getOrgRequest() {
return this.orgRequest;
}
private class WrappedServletInputStream extends ServletInputStream {
public void setStream(InputStream stream) {
this.stream = stream;
}
private InputStream stream;
public WrappedServletInputStream(InputStream stream) {
this.stream = stream;
}
public int read() throws IOException {
return stream.read();
}
public boolean isFinished() {
return true;
}
public boolean isReady() {
return true;
}
public void setReadListener(ReadListener readListener) {
}
}
}
- 使用示例:
java
private JSONObject wxCallbackDecrypt(HttpServletRequest request) throws IOException {
// 微信支付配置信息
String apiV3Key = wxPayConfig.getApiV3Key();
log.info("apiV3Key:=======" + apiV3Key);
AesUtil.checkKey(apiV3Key.getBytes());
VerdureRequestWrapper requestWrapper = new VerdureRequestWrapper(request);
log.info(requestWrapper.getRequestParams());
PayCallbackWx callbackWx = JSONObject.parseObject(requestWrapper.getRequestParams(), PayCallbackWx.class);
try {
if (verifySignature(request, requestWrapper)) {
PayCallbackWxResource resource = callbackWx.getResource();
String associatedData = resource.getAssociated_data();
String nonce = resource.getNonce();
String ciphertext = resource.getCiphertext();
String decrypt = AesUtil.decryptToString(apiV3Key.getBytes(), associatedData.getBytes(), nonce.getBytes(), ciphertext);
JSONObject result = JSONObject.parseObject(decrypt);
return result;
}
} catch (Exception e) {
log.info("微信回调信息解密失败:{}", e.getMessage());
}
return null;
}
如此便可有效解决Spring Boot框架中的HttpServeletRequest的原始输入流只能读取一次问题。