当用请求体里面的参体的参数用以签名的时候,就会遇到request body里面数据只能读取一次,再次读取就null的情况。故这里将解决多次读取request body的解决方案。
扩展HttpServletRequestWrapper,使用请求InputStream和基本缓存字节。并将过滤的优先级设为最前的优先级,是后续再请求体获取数据不至于为空。
@Configuration
public class RequestBodyConfig {
@Bean
public FilterRegistrationBean requestBodyFilterRegistration(){
FilterRegistrationBean registration = new FilterRegistrationBean();
//添加过滤器
registration.setFilter(new RequestBodyFilter());
//设置过滤路径,/*所有路径
registration.addUrlPatterns("/*");
registration.setName("requestBodyFilter");
//设置优先级
registration.setOrder(0);
return registration;
}
public class RequestBodyFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//防止读流一次之后就没有了,所以将流继续写出去
BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
/**
* request.getInputStream();
* request.getReader();
* 和request.getParameter("key");
* 保存流
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
String bodyString = StreamUtil.getBodyString(request);
body = bodyString.getBytes(Charset.forName("UTF-8"));
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
}
这里将完整获取请求体里面的工具类附上
public class StreamUtil {
private static final Integer BUFFER_SIZE = 128;
private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtil.class);
public static String getBodyString(HttpServletRequest request) {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
ServletInputStream inputStream = request.getInputStream();
if (!Objects.isNull(inputStream)) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
int bytesRead = 0;
char[] charBuffer = new char[BUFFER_SIZE];
while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException e) {
LOGGER.error("get body fail,{}", e.getMessage());
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return stringBuilder.toString();
}
}
这里将完整获取请求体里面的工具类附上
public class RequestBodyUtils {
private static final int BUFFER_SIZE = 1024 * 8;
/**
* read string.
*
* @param reader Reader instance.
* @return String.
* @throws IOException
*/
public static String read(Reader reader) throws IOException
{
StringWriter writer = new StringWriter();
try
{
write(reader, writer);
return writer.getBuffer().toString();
}
finally{ writer.close(); }
}
/**
* write.
*
* @param reader Reader.
* @param writer Writer.
* @return count.
* @throws IOException
*/
public static long write(Reader reader, Writer writer) throws IOException
{
return write(reader, writer, BUFFER_SIZE);
}
/**
* write.
*
* @param reader Reader.
* @param writer Writer.
* @param bufferSize buffer size.
* @return count.
* @throws IOException
*/
public static long write(Reader reader, Writer writer, int bufferSize) throws IOException
{
int read;
long total = 0;
char[] buf = new char[BUFFER_SIZE];
while( ( read = reader.read(buf) ) != -1 )
{
writer.write(buf, 0, read);
total += read;
}
return total;
}
}
这是通用的签名校验的通用方式,也是因为这个签名校验校验了请求体里面内容。故需要保存请求体里面的内容用于多次读取
@Component
@WebFilter(filterName = "signFilter", urlPatterns = "/*")
public class SignFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(SignFilter.class);
/**
* 正常情况是通过配置文件获取
*/
private static final String key = "ABCD";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String token = request.getHeader("token");
String ts = request.getHeader("ts");
String appId = request.getHeader("appId");
String sign = request.getHeader("sign");
String bodyString = StreamUtil.getBodyString(request);
String newSign = SignUtils.getMD5(bodyString + ts + appId + token + key);
JsonVO jsonVO = null;
boolean pass = true;
if (StringUtils.isEmpty(sign) || !sign.equals(newSign)) {
LOGGER.error("鉴权失败,newSign:{},sign:{},token:{},ts:{},appId:{}", newSign, sign, token, ts, appId);
jsonVO = new JsonVO(500, "鉴权失败");
pass = false;
} else if (System.currentTimeMillis() - SignUtils.parseLong(ts) > 600000) {
jsonVO = new JsonVO(500, "时间超时");
pass = false;
}else {
filterChain.doFilter(request, response);
}
if (pass) {
return;
}
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = null;
try {
out = response.getWriter();
out.append(JSON.toJSONString(jsonVO));
} catch (Exception e) {
LOGGER.error("sgin out fail:{}", e.getMessage());
} finally {
if (!Objects.isNull(out)) {
out.close();
}
}
}
@Override
public void destroy() {
}
}
签名校验的工具类
public class SignUtils {
private static final String MD_STR = "MD5";
private static final String CODING_UTF8 = "UTF-8";
public static String getMD5(String info) {
try {
//获取 messageDigst 对象,参数为 MD5 字符,
// 表示这是MD5 算法(其他还有 SHA1 算法)
MessageDigest md5 = MessageDigest.getInstance(MD_STR);
//update(byte[]) ,输入原数据
//类似stringBuilder 对象的append() 方法,追加模式,属于一个累计更改的过程
md5.update(info.getBytes(CODING_UTF8));
//disgest() 被调用,MessageDigest对象被重置,既不能联系再次调用该方法计算原数据的MD5
byte[] md5Array = md5.digest();
return bytesToHex1(md5Array);
} catch (UnsupportedEncodingException e) {
return "";
} catch (NoSuchAlgorithmException e) {
return "";
}
}
private static String bytesToHex1(byte[] md5Array) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < md5Array.length; i++) {
//防止转变int的前24位防止 负号反码,让24高位为0
int temp = 0xff & md5Array[i];
String hexString = Integer.toHexString(temp);
//如果是16进制的of,默认只显示f,此时要补0
if (hexString.length() == 1) {
stringBuilder.append("0").append(hexString);
} else {
stringBuilder.append(hexString);
}
}
return stringBuilder.toString();
}
//通过java提供的BigInteger 完成byte->HexString
private static String bytesToHex2(byte[] md5Array) {
BigInteger bigInt = new BigInteger(1, md5Array);
return bigInt.toString(16);
}
//通过为运算,将字节数据到16进制的转化
public static String bytesToHex3(byte[] byteArray) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] resultArray = new char[byteArray.length * 2];
int index = 0;
for (byte b : byteArray) {
resultArray[index++] = hexDigits[b >> 4 & 0xf];
resultArray[index++] = hexDigits[b & 0xf];
}
return new String(resultArray);
}
public static Long parseLong(String numStr) {
Pattern compile = Pattern.compile("^[1-9][0-9]*$");
if(compile.matcher(numStr).find()) {
return Long.parseLong(numStr);
}
return 0L;
}
}
Controller用于验证是否能够多次获取请求体里面内容
@RestController
@RequestMapping("/filter")
public class FilterController {
@PostMapping("/getRequest")
public String testRequestBody(HttpServletRequest request, HttpServletResponse response) throws IOException {
String token = request.getHeader("token");
System.out.println("token:" + token);
String str = request.getQueryString();
BufferedReader bufferedReader = request.getReader();
String read = RequestBodyUtils.read(bufferedReader);
System.out.println("read:" + read);
System.out.println("---------------------");;
BufferedReader bufferedReader2 = request.getReader();
String read2 = RequestBodyUtils.read(bufferedReader2);
System.out.println("read:" + read2);
return "success";
}
}