前后端传输,利用哈希算法确保报文完整性

245 阅读4分钟

哈希算法确保报文完整性

一、哈希函数的定义

哈希函数是一种将任意长度的输入数据映射到固定长度的输出数据的函数。

二、常见的应用场景

  1. 数据存储和检索--HashMap:哈希函数可以将大数据集映射到较小的存储空间中,从而提高数据存储效率。在检索数据时,通过计算哈希值可以快速定位到对应的存储位置。2
  2. 密码学--sha系列、MD5:哈希函数在密码学中用于消息摘要和数字签名。它可以将明文消息转换为固定长度的哈希值,用于验证消息的完整性和一致性。通过计算数据的哈希值并与原始哈希值进行比较,可以判断数据是否被修改。
  3. 负载均衡--一致性哈希:在分布式系统中,哈希函数可以将请求均匀分配到不同的服务器上,从而实现负载均衡。
  4. 数据去重--布隆过滤器:哈希函数可以用于去除数据集中的重复项。通过计算数据的哈希值,可以快速识别重复的数据。
  5. 判断数据是否存在--布隆过滤器:布隆过滤器由 N 个哈希函数组成,解决高并发下缓存穿透问题,过滤掉要访问的数据既不在 Redis 缓存中,也不在数据库中的数据。 img-blog.csdnimg.cn/img_convert…

三、数据完整性验证实战

数据完整性验证的一般过程:

  1. 计算原始数据的哈希值:使用哈希函数对原始数据进行计算,得到原始数据的哈希值。
  2. 传输或存储数据和哈希值:将原始数据和哈希值一起传输或存储。可以将它们一起存储在数据库中,或者将哈希值附加在数据文件中。
  3. 接收或获取数据和哈希值:接收方或数据使用者从传输方或存储设备中获取数据和哈希值。
  4. 计算接收数据的哈希值:使用相同的哈希函数对接收的数据进行计算,得到接收数据的哈希值。
  5. 比较哈希值:将接收数据的哈希值与传输或存储的哈希值进行比较。如果两个哈希值相等,则表示数据在传输或存储过程中未被篡改;如果哈希值不相等,则表示数据可能被篡改或损坏。

服务端作为接收端的处理方式可以增加一个签名过滤器,主要实现方式如下:

// 签名校验过滤器,继承OncePerRequestFilter,每个请求都会经过
public class SignFilter extends OncePerRequestFilter {

    @Autowired
    private SignService signService;

    // 判断哪些请求是不需要过滤的,请求头中没有XSignBody的、url与配置的需要完整性校验的url不匹配的、分流未开的
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String sign = request.getHeader(SecurityProperties.XSignBody);
        if (!StringUtils.hasText(sign)) {
            return true;
        }
        if (!signService.urlMatch(request.getServletPath())) {
            return true;
        }
        if (!signService.rateMatch()) {
            return true;
        }
        return  false;
    }
    // 1、取出body
    // 2、取出请求头中的签名
    // 3、针对body进行sha256算法加密,将加密后得到的签名与请求头中的签名进行比对,一致即视为通过
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        try {
            String requestBody = getBodyMsg(request);
            if (!StringUtils.hasText(requestBody)) {
                RequestParameterWrapper requestWrapper = new RequestParameterWrapper(request, requestBody);
                filterChain.doFilter(requestWrapper, response);
                return;
            }
            String xSign = request.getHeader(SecurityProperties.XSignBody);
            String sign = SHAUtil.encryptSHA256(requestBody);

            boolean accept = Objects.equals(sign, xSign);
            if (!accept) {
                response.setHeader("Content-Type", "application/json;charset=utf-8");
                // 返回值一般都是封装的统一的ReturnNodel
                response.getOutputStream()
                    .write(JSONObject.toJSONString("签名异常").getBytes("utf-8"));
                return;
            }
            RequestParameterWrapper requestWrapper = new RequestParameterWrapper(request, requestBody);
            filterChain.doFilter(requestWrapper, response);
        } catch (Exception exception) {
            response.setHeader("Content-Type", "application/json;charset=utf-8");
            response.getOutputStream()
                .write(JSONObject.toJSONString("签名校验异常").getBytes("utf-8"));

        }
    }

    /**
     * 获取httpbody内容
     * @param request
     * @return
     */
    public String getBodyMsg(HttpServletRequest request) {
        InputStream is = null;
        StringBuilder sb = new StringBuilder();
        try {
            is = request.getInputStream();
            byte[] b = new byte[4096];
            for (int n; (n = is.read(b)) != -1;) {
                sb.append(new String(b, 0, n));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

    protected class RequestParameterWrapper extends HttpServletRequestWrapper {
        private String body = "";

        RequestParameterWrapper(HttpServletRequest request, String body) {
            super(request);
            this.body = body;
        }

        @Override
        public ServletInputStream getInputStream() {
            final byte[] bodybyte = body.getBytes();
            ServletInputStream servletInputStream = new ServletInputStream() {
                private int lastIndexRetrieved = -1;
                private ReadListener readListener = null;

                @Override
                public boolean isFinished() {
                    return (lastIndexRetrieved == bodybyte.length - 1);
                }

                @Override
                public boolean isReady() {
                    return isFinished();
                }

                @Override
                public void setReadListener(ReadListener readListener) {
                    this.readListener = readListener;
                    if (!isFinished()) {
                        try {
                            readListener.onDataAvailable();
                        } catch (IOException e) {
                            readListener.onError(e);
                        }
                    } else {
                        try {
                            readListener.onAllDataRead();
                        } catch (IOException e) {
                            readListener.onError(e);
                        }
                    }
                }

                @Override
                public int read() throws IOException {
                    int i;
                    if (!isFinished()) {
                        i = bodybyte[lastIndexRetrieved + 1];
                        lastIndexRetrieved++;
                        if (isFinished() && (readListener != null)) {
                            try {
                                readListener.onAllDataRead();
                            } catch (IOException ex) {
                                readListener.onError(ex);
                                throw ex;
                            }
                        }
                        return i;
                    } else {
                        return -1;
                    }
                }
            };
            return servletInputStream;
        }

    }
}
// 配置类
@Configuration
@ConditionalOnProperty(name = "api.security.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityConfig {
   @Bean
   @Order(0)
   public Filter signFilter() {
      return new SignFilter();
   }
}