报文防止篡改加签名

思路:
为了防止报文中途被拦截修改,需要在请求头中加入签名字段信息,值为请求连接请求方式params参数post参数认证信息等字符串的加密字符串。
加密方式采用非对称加密(不可逆)。 后端获取到这些数据后通过同样的加密方式对上述字符串进行加密,然后与签名字段做对比,如果不一致则认为报文信息被修改过,则拦截此次请求。

此种方式需要加密前的字符串需要保持一致,且加密方式保持一致。
请求params参数保持一致
参数转化为字符串前要先排序:
前端排序方法:

const sortJSON = (old) => {
  const type = Object.prototype.toString.call(old)
  const res = {}
  // 如果是对象就对key排序,生成一个新的对象返回
  if (type === '[object Object]') {
    const keyArray = []
    for (const key in old) {
      keyArray.push(key)
    }
    keyArray.sort()
    for (let key in keyArray) {
      key = keyArray[key]
      const value = '' + old[key]
      res[key] = sortJSON(value)
    }
    return res
  }
  if (type === '[object Array]') {
    const type = Object.prototype.toString.call(old[0])
    // 如果数组里嵌套字符串和数字,直接对数组排序
    if (type === '[object String]' || type === '[object Number]') {
      old.sort()
      return old
    }
    // 如果数组里嵌套对象,不改变对象顺序,只改变对象内属性顺序
    const newArray = []
    for (let i = 0; i < old.length; i++) {
      newArray.push(sortJSON(old[i]))
    }
    return newArray
  }
  // 对对象里的value排序,但不是对象活数组,就原样返回
  return old
}

使用:

params = JSON.stringify(sortJSON(params))

后端通过httpservletrequest获得参数放入map中,然后根据key排序,

post报文保持一致:
post报文是通过数据流传输的,因此不用排序。后端在获取到数据后,转化为字符串即可(此处要排除二进制文件,以及乱码问题)。
因为流数据只能读取一次,所以需要继承HttpServletRequestWrapper重写方法。(百度,多次读取httpservletrequest流数据)

@Slf4j
public class KCHttpRequestWrapper extends HttpServletRequestWrapper {

    private byte[] requestBody = null;

    public KCHttpRequestWrapper(HttpServletRequest request) {
        super(request);
        try {
            requestBody = StreamUtils.copyToByteArray(request.getInputStream());
        } catch (IOException e) {
            log.error("Wrap requestBody failed");
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (requestBody == null) {
            requestBody = new byte[0];
        }
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public void setReadListener(ReadListener listener) {
                // do nothing
            }

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

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

    @Override//对外提供读取流的方法
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(),"UTF-8"));
    }
}
public class ChannelFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (servletRequest instanceof HttpServletRequest) {
            String contentType = servletRequest.getContentType();
            String method = "multipart/form-data";
            if (contentType != null && contentType.contains(method)) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
            requestWrapper = new KCHttpRequestWrapper((HttpServletRequest) servletRequest);
        }
        if (requestWrapper == null) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
@Slf4j
public class SignatureInterceptor extends HandlerInterceptorAdapter {

    private final static String SALT = "testtest";

    @Override
    public boolean preHandle(HttpServletRequest servletRequest, HttpServletResponse response, Object handler) throws IOException {
        String contentType = servletRequest.getContentType();
        if (null != contentType && contentType.contains("multipart/form-data")) {
            return true;
        }
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
            if (jwtIgnore != null) {
                // 忽略带JwtIgnore注解的请求
                return true;
            }
        }
        HttpServletRequest request = new KCHttpRequestWrapper(servletRequest);
        if (request == null) {
            return true;
        }
        String method = request.getMethod().toLowerCase();
        if ("options".equals(method)) {
            return true;
        }
        String path = request.getServletPath();
        if (!path.contains("api")) {
            return true;
        }
        String frontSignature = request.getHeader("Signature");
//        if (null == frontSignature) {
//            return true;
//        }
        StringBuffer newSignatureBuffer = new StringBuffer(SALT);
        newSignatureBuffer.append(request.getRequestURI());
        newSignatureBuffer.append(method);
        Map<String, String[]> parameterMap = request.getParameterMap();
        newSignatureBuffer.append(dealParams(parameterMap));
        String data = "";
        StringBuffer sb = new StringBuffer();
        BufferedReader isr = request.getReader();
        try {
            char[] charBuffer = new char[2048];
            int readCount = 0;
            while ((readCount = isr.read(charBuffer)) != -1) {
                sb.append(charBuffer, 0, readCount);
            }
        } catch (IOException e) {
            throw e;
        }
        data = sb.toString();
        newSignatureBuffer.append(data);
        String authorization = request.getHeader("Authorization");
        if (!StringUtils.hasLength(authorization)) {
            authorization = "";
        }
        newSignatureBuffer.append(authorization);
        String decryptData = DigestUtils.sha256Hex(newSignatureBuffer.toString());
        if (!StringUtils.hasLength(frontSignature)) {
            log.info("报文头没有Signature,请求不被允许!");
            sendError(response, "没有Signature,请求不被允许");
        } else if (frontSignature.equals(decryptData)) {
            return true;
        } else {
            log.info("报文有被修改,请求不被允许!请求信息:{}。原始报文密文:{}", newSignatureBuffer, frontSignature);
            sendError(response, "报文修改,请求不被允许");
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 放置tl内存泄漏,手动回收
        WebContext.getInstance().removeCurrentUser();
    }

    private void sendError(HttpServletResponse response, String errMsg) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_OK);
        ResultVO<String> vo = ResultUtil.error(HttpServletResponse.SC_UNAUTHORIZED, errMsg);
        response.getWriter().println(JSON.toJSONString(vo));
    }

    /**
     * 处理params参数,与前端保持一致
     *
     * @param parameterMap
     * @return
     */
    private String dealParams(Map<String, String[]> parameterMap) {
        String params = "";
        if (CollectionUtils.isEmpty(parameterMap)) {
            return params;
        }
        Map<String, Object> strMap = new HashMap<>();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] value = entry.getValue();
            Object strValue = "";
            if (null != value && value.length > 0) {
                strValue = value[0];
            }
            strMap.put(key, strValue);
        }
        params = JSON.toJSONString(sortMapByKey(strMap));
        return params;
    }

    private static Map<String, Object> sortMapByKey(Map<String, Object> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }
        Map<String, Object> sortMap = new TreeMap<String, Object>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return ((String) o1).compareTo((String) o2);
            }
        });
        sortMap.putAll(map);
        return sortMap;
    }
}

加密方式保持一致:

前端引入js-sha256

import { sha256 } from 'js-sha256'
config.headers.Signature = sha256(sign)

后端:

String decryptData = DigestUtils.sha256Hex(newSignatureBuffer.toString());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值