一 防篡改是什么?

防篡改(英语:Tamper resistance)是指通过包装、系统或其他物理措施抗击产品正常用户的篡改(tamper,故意引发故障或造成破坏)的行为。

二 防重放是什么?

入侵者 C 可以从网络上截获 A 发给 B 的报文。C 并不需要破译这个报文(因为这
可能很花很多时间)而可以直接把这个由 A 加密的报文发送给 B,使 B 误认为 C 就是 A。然后
B 就向伪装是 A 的 C 发送许多本来应当发送给 A 的报文

三 防篡改和防重放的解决方式

    可以通过时间戳,将时间戳放在header头中进行处理

    通过时间戳 + sign签名处理,通过将报文参数进行相应的md5进行签名处理,同时将时间戳和sign放在header中,网关进行相应的验签证明请求的合法性

    通过时间戳+随机数(norce)+sign签名的方式进行处理

    流程如下:

   

防篡改 java 防篡改包装_java

四 代码实现

1.Filter实现

/**
 * 安全基线拦截器:
 * 防重放、防篡改
 */
@Component
public class CosSecurityFilter extends ZuulFilter {

    private final Logger log = LoggerFactory.getLogger(CosSecurityFilter.class);


    @Resource
    private CosSecurityProperties cosSecurityProperties;

    @Resource
    private CosSecurityUtil cosSecurityUtil;

    @Resource
    private AntPathMatcher antPathMatcher;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 拦截顺序,越小越优先
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return -9;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        if (BooleanUtil.isTrue(cosSecurityProperties.getEnable())&& !ctx.getBoolean("transmit")) {
            return true;
        } else {
            log.debug("【安全基线】未开启");
            return false;
        }
    }

    /**
     * 如果安全校验不通过,请求上下文中会有isSecurityPass;
     * isSecurityPass为true,代表安全校验通过
     * isSecurityPass为false,代表安全校验不通过
     */
    @Override
    public Object run() {
        log.info("---CosSecurityFilter---");
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String uri = request.getRequestURI();

        String sign = request.getHeader(CosSecurityConstants.HEADER_SIGN);
        String timestamp = request.getHeader(CosSecurityConstants.HEADER_TIMESTAMP);
        String nonce = request.getHeader(CosSecurityConstants.HEADER_NONCE);
        String clientVersion = request.getHeader(CosSecurityConstants.HEADER_VERSION);
        String method = request.getMethod();
        String contentType = request.getContentType();
        try {
            //url白名单
            List<String> ignoreUrlList = cosSecurityProperties.getIgnoreUrlList();
            if (CollectionUtil.isNotEmpty(ignoreUrlList)) {
                for (String ignoreUrl : ignoreUrlList) {
                    if (antPathMatcher.match(uri, ignoreUrl)) {
                        return null;
                    }
                }
            }
            switch (method) {
                case ServletUtil.METHOD_POST:

                    //application/json才校验签名
                    if (contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
                        // 版本号不符合条件
                        if (StrUtil.isNotEmpty(clientVersion) && (!Pattern.matches(CosSecurityConstants.PATTERN_VERSION, clientVersion)
                                || AppUtil.compareVersion(clientVersion, CosSecurityConstants.CONFIG_BASE_VERSION_VALUE) < 0)) {
                            if (cosSecurityProperties.getIsValidVersion()){
                                //如果开启版本号校验并且版本号为空或者不符合条件
                                throw new BusinessException(ExceptionEnum.VERSION_LOW.getCode(), ExceptionEnum.VERSION_LOW.getErrMsg());
                            } else {
                                //如果不开启版本号校验,并且版本在11以下 不验证签名
                                return null;
                            }
                        }
                        BodyReaderHttpServletRequestWrapper httpServletRequestWrapper = new BodyReaderHttpServletRequestWrapper(request);
                        //校验签名
                        cosSecurityUtil.validPostSign(sign, Long.valueOf(timestamp), nonce, httpServletRequestWrapper);
                        ctx.setRequest(httpServletRequestWrapper);
                        //校验超时时间
                        cosSecurityUtil.validTimestamp(timestamp);
                        //校验随机数
                        cosSecurityUtil.validAndSaveNonce(nonce, request);
                    }
                    break;
                case ServletUtil.METHOD_GET:
                    // GET请求不作处理
                    break;
                default:
                    break;
            }
            ctx.set(CosSecurityConstants.KEY_IS_SECURITY_PASS, true);
        } catch (BusinessException e) {
            log.error("【安全请求校验】校验不通过,uri=[{}],timestamp=[{}],nonce=[{}],sign=[{}],errorMessage=[{}]", uri, timestamp, nonce, sign, e.getMessage());
            log.error("error:",e);
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            ResultMessage result = new ResultMessage(false, e.getCode(), e.getErrMsg());
            ctx.getResponse().setCharacterEncoding("UTF-8");
            ctx.getResponse().setContentType("application/json; charset=utf-8");
            ctx.setResponseBody(JSON.toJSONString(result, SerializerFeature.BrowserCompatible));
            ctx.set(CosSecurityConstants.KEY_IS_SECURITY_PASS, false);
        } catch (Exception e) {
            log.error("【安全请求校验】校验失败,uri=[{}],timestamp=[{}],nonce=[{}],sign=[{}],errorMessage=[{}]", uri, timestamp, nonce, sign, e.getMessage());
            log.error("error:",e);
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            ResultMessage result = new ResultMessage(false, ExceptionEnum.SIGN_INVALID.getCode(), ExceptionEnum.SIGN_INVALID.getErrMsg());
            ctx.getResponse().setCharacterEncoding("UTF-8");
            ctx.getResponse().setContentType("application/json; charset=utf-8");
            ctx.setResponseBody(JSON.toJSONString(result, SerializerFeature.BrowserCompatible));
            ctx.set(CosSecurityConstants.KEY_IS_SECURITY_PASS, false);
        }
        return null;
    }

}

2.工具类实现

@Component
public class CosSecurityUtil {

    private final Logger log = LoggerFactory.getLogger(CosSecurityUtil.class);

    @Resource
    private CosSecurityProperties cosSecurityProperties;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 校验请求有效时间
     *
     * @param time
     * @return
     */
    public void validTimestamp(String time) throws Exception {
        if (StrUtil.isEmpty(time)) {
            throw new BusinessException(ExceptionEnum.TIMESTAMP_INVALID.getCode(), ExceptionEnum.TIMESTAMP_INVALID.getErrMsg());
        }

        if (!Pattern.matches(CosSecurityConstants.PATTERN_TIMESTAMP, time)) {
            throw new BusinessException(ExceptionEnum.TIMESTAMP_INVALID.getCode(), ExceptionEnum.TIMESTAMP_INVALID.getErrMsg());
        }


        Long timestamp = Long.valueOf(time);
        //服务器UTC时间
        LocalDateTime utcLocalDateTime = Instant.now().atZone(ZoneId.of("UTC")).toLocalDateTime();

        LocalDateTime severalMinutesBefore = LocalDateTimeUtil.offset(utcLocalDateTime, -10L, ChronoUnit.MINUTES);
        LocalDateTime severalMinutesAfter = LocalDateTimeUtil.offset(utcLocalDateTime, 10L, ChronoUnit.MINUTES);

        if (timestamp > LocalDateTimeUtil.toEpochMilli(severalMinutesBefore) && timestamp < LocalDateTimeUtil.toEpochMilli(severalMinutesAfter)) {
            //do nothing
        } else {
            //签名超时
            throw new BusinessException(ExceptionEnum.SIGN_TIMEOUT.getCode(), ExceptionEnum.SIGN_TIMEOUT.getErrMsg());
        }
    }


    /**
     * 校验nonce,并保存至redis中
     *
     * @param nonce
     * @param request
     * @return
     */
    public void validAndSaveNonce(String nonce, HttpServletRequest request) throws Exception {
        if (StrUtil.isEmpty(nonce)) {
            throw new BusinessException(ExceptionEnum.NONCE_INVALID.getCode(), ExceptionEnum.NONCE_INVALID.getErrMsg());
        }

        if (!Pattern.matches(CosSecurityConstants.PATTERN_NONCE, nonce)) {
            throw new BusinessException(ExceptionEnum.NONCE_INVALID.getCode(), ExceptionEnum.NONCE_INVALID.getErrMsg());
        }

        String remoteHost = request.getRemoteHost();
        String method = request.getMethod();
        String requestUrl = request.getRequestURL().toString();
        JSONObject nonceRedisValue = new JSONObject();
        nonceRedisValue.put("remoteHost", remoteHost);
        nonceRedisValue.put("method", method);
        nonceRedisValue.put("requestUrl", requestUrl);

        String nonceRedisKey = CosSecurityConstants.PREFIX_NONCE_KEY + nonce;
        Boolean hasKey = stringRedisTemplate.hasKey(nonceRedisKey);
        if (null != hasKey && hasKey) {
            throw new BusinessException(ExceptionEnum.REQUEST_REPEAT.getCode(), ExceptionEnum.REQUEST_REPEAT.getErrMsg());
        }
        RBucket<String> nonceBucket = redissonClient.getBucket(nonceRedisKey);
        boolean isSuccess = nonceBucket.trySet(nonceRedisValue.toJSONString(), 10L, TimeUnit.MINUTES);
        if (!isSuccess) {
            throw new BusinessException(ExceptionEnum.REQUEST_REPEAT.getCode(), ExceptionEnum.REQUEST_REPEAT.getErrMsg());
        }
    }

    /**
     * 校验POST请求的sign
     *
     * @param sign
     * @param timestamp
     * @param nonce
     * @throws Exception
     */
    public void validPostSign(String sign, Long timestamp, String nonce, BodyReaderHttpServletRequestWrapper request) throws Exception {
        if (StrUtil.isEmpty(sign)) {
            throw new BusinessException(ExceptionEnum.SIGN_INVALID.getCode(), ExceptionEnum.SIGN_INVALID.getErrMsg());
        }

        if (!Pattern.matches(CosSecurityConstants.PATTERN_SIGN, sign)) {
            throw new BusinessException(ExceptionEnum.SIGN_INVALID.getCode(), ExceptionEnum.SIGN_INVALID.getErrMsg());
        }

        //取请求body体的md5摘要
        String reqBody = request.getBodyString();
        log.info("【安全基线-POST请求签名校验】requestBody=[{}]", reqBody);
        String body = DigestUtils.md5Hex(reqBody);
        //计算规则:sign=md5(timestamp+nonce+body+key)
        String signString = "timestamp=" + timestamp + "&nonce=" + nonce + "&body=" + body +"&key=" + cosSecurityProperties.getSecurityCode();
        log.info("验签字符串拼接结果为:{}",signString);
        String serverSign = DigestUtils.md5Hex(signString);
        log.info("【安全基线-POST请求签名校验】前端sign=[{}],后端计算body=[{}],后端sign=[{}]", sign, body, serverSign);
        if (!StrUtil.equals(sign.toLowerCase(), serverSign.toLowerCase())) {
            throw new BusinessException(ExceptionEnum.SIGN_INVALID.getCode(), ExceptionEnum.SIGN_INVALID.getErrMsg());
        }
    }
    public static void main(String[] args){
        String reqBody = "";
        String body = DigestUtils.md5Hex(reqBody);
        LocalDateTime utcLocalDateTime = Instant.now().atZone(ZoneId.of("UTC")).toLocalDateTime();
        Long timestamp = LocalDateTimeUtil.toEpochMilli(utcLocalDateTime);
        System.out.println(timestamp);
        String nonce = "eb8f198d36b3edcb913ade2506707631";
        //计算规则:sign=md5(timestamp+nonce+body+key)
        String signString = "timestamp=" + timestamp + "&nonce=" + nonce + "&body=" + body +"&key=" + "RETAILCLOUD@HUA123";
        String serverSign = DigestUtils.md5Hex(signString);
        System.out.println(serverSign);
    }
}