API接口如何防止参数被篡改和重放攻击?

Posted androidstarjack

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了API接口如何防止参数被篡改和重放攻击?相关的知识,希望对你有一定的参考价值。

点击上方关注 “终端研发部

设为“星标”,和你一起掌握更多数据库知识

作者:巨人大哥

cnblogs.com/jurendage/p/12886352.html

说明:目前所有的系统架构都是采用前后端分离的系统架构,那么就不可能避免的需要服务对外提供API,那么如何保证对外的API的安全呢?

生鲜电商中API接口防止参数篡改和重放攻击

目录

1. 什么是API参数篡改?

说明:API参数篡改就是恶意人通过抓包的方式获取到请求的接口的参数,通过修改相关的参数,达到欺骗服务器的目的,常用的防止篡改的方式是用签名以及加密的方式。关注公众号码猿技术专栏获取更多面试资源

2. 什么是API重发攻击?

说明:API重放攻击: 就是把之前窃听到的数据原封不动的重新发送给接收方.

3,常用的解决的方案

常用的其他业务场景还有:

  • 发送短信接口

  • 支付接口

基于timestamp和nonce的方案

微信支付的接口就是这样做的

timestamp的作用

每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。HTTP请求从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间相比较,是否超过了60s,如果超过了则认为是非法的请求。

一般情况下,从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了,如果修改timestamp参数为当前的时间戳,则signature参数对应的数字签名就会失效,因为不知道签名秘钥,没有办法生成新的数字签名。

但这种方式的漏洞也是显而易见的,如果在60s之后进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效

nonce的作用

nonce的意思是仅一次有效的随机字符串,要求每次请求时,该参数要保证不同。我们将每次请求的nonce参数存储到一个“集合”中,每次处理HTTP请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。

nonce参数在首次请求时,已经被存储到了服务器上的“集合”中,再次发送请求会被识别并拒绝。

nonce参数作为数字签名的一部分,是无法篡改的,因为不知道签名秘钥,没有办法生成新的数字签名。

这种方式也有很大的问题,那就是存储nonce参数的“集合”会越来越大。

nonce的一次性可以解决timestamp参数60s(防止重放攻击)的问题,timestamp可以解决nonce参数“集合”越来越大的问题。

防篡改、防重放攻击 拦截器(用到了redis)

public class SignAuthInterceptor implements HandlerInterceptor {  
  
    private RedisTemplate<String, String> redisTemplate;  
  
    private String key;  
  
    public SignAuthInterceptor(RedisTemplate<String, String> redisTemplate, String key) {  
        this.redisTemplate = redisTemplate;  
        this.key = key;  
    }  
  
    @Override  
    public boolean preHandle(HttpServletRequest request,  
                             HttpServletResponse response, Object handler) throws Exception {  
        // 获取时间戳  
        String timestamp = request.getHeader("timestamp");  
        // 获取随机字符串  
        String nonceStr = request.getHeader("nonceStr");  
        // 获取签名  
        String signature = request.getHeader("signature");  
  
        // 判断时间是否大于xx秒(防止重放攻击)  
        long NONCE_STR_TIMEOUT_SECONDS = 60L;  
        if (StrUtil.isEmpty(timestamp) || DateUtil.between(DateUtil.date(Long.parseLong(timestamp) * 1000), DateUtil.date(), DateUnit.SECOND) > NONCE_STR_TIMEOUT_SECONDS) {  
            throw new BusinessException("invalid  timestamp");  
        }  
  
        // 判断该用户的nonceStr参数是否已经在redis中(防止短时间内的重放攻击)  
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);  
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {  
            throw new BusinessException("invalid nonceStr");  
        }  
  
        // 对请求头参数进行签名  
        if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {  
            throw new BusinessException("invalid signature");  
        }  
  
        // 将本次用户请求的nonceStr参数存到redis中设置xx秒后自动删除  
        redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);  
  
        return true;  
    }  
  
    private String signature(String timestamp, String nonceStr, HttpServletRequest request) throws UnsupportedEncodingException {  
        Map<String, Object> params = new HashMap<>(16);  
        Enumeration<String> enumeration = request.getParameterNames();  
        if (enumeration.hasMoreElements()) {  
            String name = enumeration.nextElement();  
            String value = request.getParameter(name);  
            params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));  
        }  
        String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);  
        log.info("qs:{}", qs);  
        String sign = SecureUtil.md5(qs).toLowerCase();  
        log.info("sign:{}", sign);  
        return sign;  
    }  
  
    /**  
     * 按照字母顺序进行升序排序  
     *  
     * @param params 请求参数 。注意请求参数中不能包含key  
     * @return 排序后结果  
     */  
    private String sortQueryParamString(Map<String, Object> params) {  
        List<String> listKeys = Lists.newArrayList(params.keySet());  
        Collections.sort(listKeys);  
        StrBuilder content = StrBuilder.create();  
        for (String param : listKeys) {  
            content.append(param).append("=").append(params.get(param).toString()).append("&");  
        }  
        if (content.length() > 0) {  
            return content.subString(0, content.length() - 1);  
        }  
        return content.toString();  
    }  
}  

总结:做互联网应用,无论是生鲜小程序还是APP,安全永远都是第一位,安全做好了,其他的才能做得更好,当然,信息安全是一个永久的话题,并非通过本文就能够说得清楚的

希望本文可以给大家一点思考与建议。

另外,作者已经完成了两个专栏的文章Mybatis进阶Spring Boot 进阶 ,已经将专栏文章整理成书,有需要的公众号回复关键词Mybatis 进阶Spring Boot 进阶免费获取。

BAT等大厂Java面试经验总结 想获取 Java大厂面试题学习资料扫下方二维码回复「BAT」就好了回复 【加群】获取github掘金交流群回复 【电子书】获取2020电子书教程回复 【C】获取全套C语言学习知识手册回复 【Java】获取java相关的视频教程和资料回复 【爬虫】获取SpringCloud相关多的学习资料回复 【Python】即可获得Python基础到进阶的学习教程回复 【idea破解】即可获得intellij idea相关的破解教程关注我gitHub掘金,每天发掘一篇好项目,学习技术不迷路!



回复 【idea激活】即可获得idea的激活方式
回复 【Java】获取java相关的视频教程和资料
回复 【SpringCloud】获取SpringCloud相关多的学习资料
回复 【python】获取全套0基础Python知识手册
回复 【2020】获取2020java相关面试题教程
回复 【加群】即可加入终端研发部相关的技术交流群
阅读更多
为什么HTTPS是安全的
因为BitMap,白白搭进去8台服务器...
《某厂内部SQL大全 》.PDF
字节跳动一面:i++ 是线程安全的吗?
大家好,欢迎加我微信,很高兴认识你!
在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!

相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术!



如果喜欢就给个“在看”

以上是关于API接口如何防止参数被篡改和重放攻击?的主要内容,如果未能解决你的问题,请参考以下文章

Java编程:API接口防止重放攻击(重复攻击)

HTTPS如何防止重放攻击?

如何确保API接口安全呢?

api-gateway实践(19)HTTP请求防篡改

JWT权限控制

API设计中防重放攻击