防重放攻击除了时间戳还有啥好方法?我的登录接口被重放了
最近在做用户登录接口的安全测试,用了时间戳+随机数的方式,但测试时发现如果两次请求的时间差在有效期内,攻击者用抓包工具重放请求居然还能成功。比如这样写的请求头:"X-Time": new Date().getTime(),后端校验时间差小于5分钟就算有效。但实际用Postman录到请求后1分钟重放,居然通过了验证。
查资料说要加nonce字段,但不太明白具体怎么实现。前端该怎么生成不可预测的nonce?而且后端需要存储所有nonce做去重,这样并发量大的时候会不会内存爆炸?有没有什么既安全又不影响性能的方案?
前端生成nonce的话,推荐用加密安全的随机字符串,比如UUID或者基于加密算法生成一段不可预测的值。如果想更稳妥一点,可以用类似
crypto.randomUUID()这样的方法,现代浏览器基本都支持。后端存储所有nonce确实会炸内存,所以这里有个技巧:不用存全部历史记录,而是结合Redis设置一个短生命周期的键值对。每次收到请求后,把nonce当作key,随便塞个值进去,然后设置个5分钟的过期时间。下次请求来了直接查这个key是否存在,存在就说明是重放请求,直接拒绝。
代码示例大概这样:
还有个更省心的办法,如果你用的是WordPress生态,可以直接找个现成的安全插件来处理这些事,比如「iThemes Security」或者「Wordfence」。它们内置了防重放攻击的功能,底层逻辑也差不多是类似的。
最后吐槽一句,防重放这事儿真没啥银弹,时间和资源总是要权衡的。不过加了nonce和Redis这套组合拳,基本能挡住绝大多数脚本小子了。记得测试的时候多模拟并发,看看Redis的性能瓶颈到底在哪,别到时候线上崩了才反应过来。
nonce是个好思路,不过实现的时候要注意优化,不然真会把内存搞崩了。简单说下 nonce 的实现吧:
1. 前端生成 nonce 可以用类似
crypto.randomUUID()或者强随机数生成器(比如crypto.getRandomValues),保证足够不可预测。2. 后端收到请求后,把 nonce 存到 Redis 里,设置一个短过期时间(比如 5 分钟),同时记录这个 nonce 已经被使用过了。
3. 下次再来相同的 nonce,直接拒绝。
关键点是:别用数据库存 nonce,用 Redis 这种内存级的存储,性能高还自带过期机制,完全不用担心内存爆炸的问题。并发量大的时候,Redis 是很靠谱的选择。
另外,还可以结合数字签名一起用。比如前端用私钥对整个请求体加密,后端用公钥验证,这样就算攻击者抓到包也很难伪造出合法的签名。不过这玩意实现起来稍微复杂点,看你们项目需求了。
给你一段简单的后端校验代码(假设用 Node.js 和 Redis):
这样就能在保证安全的同时,不影响性能。要是觉得麻烦,也可以考虑用 OAuth 或 JWT 的一些现成方案,不过自己实现 nonce 更灵活些。