request body数据多次读取解决方案及通用签名校验方式

当用请求体里面的参体的参数用以签名的时候,就会遇到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";
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值