token 单独使用无法防重放,必须配合时间戳或nonce并由服务端校验是否已处理该组合;Redis应设带TTL的前缀key,数据库唯一索引需覆盖操作最小标识集并在业务执行前插入校验。

为什么 token 单独用不能防重放
很多人以为只要每次请求带个随机 token,服务端校验一次就完事——其实根本没防住。因为攻击者截获一次合法请求后,可以反复重放这个 token,只要服务端没记它用过、也没设过期,就一直能过。
-
token必须配合时间戳或单调递增序号(如nonce),且服务端要校验「是否已处理过该token+timestamp组合」 - 单纯用
uuid4()生成的token没有业务上下文约束,无法绑定用户、操作类型、有效期 - Redis 中存
token的 key 建议带前缀和 TTL,比如f"req:{user_id}:{token}",TTL 设为 5 分钟,避免长期占用内存
数据库唯一索引怎么设才真起作用
加个 UNIQUE INDEX (user_id, order_no) 看似稳妥,但容易漏掉两个关键点:字段组合是否覆盖重放本质、写入时机是否在幂等判断之后。
- 索引字段必须包含「能标识一次不可重复操作」的最小集合,比如支付场景是
(user_id, out_trade_no),不是(order_id)(因为订单可能还没创建) - 必须在业务逻辑真正执行前就尝试
INSERT或REPLACE INTO,靠数据库报IntegrityError回滚,而不是先查再插——否则查插之间存在竞态窗口 - 如果用
Django,别依赖get_or_create默认行为,要显式传defaults=...并捕获IntegrityError,否则可能误更新已有记录
Python 里怎么安全校验 timestamp 和 nonce
客户端传来的 timestamp 不可信,服务端必须做严格校验;nonce 如果只是简单字符串,也容易被绕过。
-
timestamp要和服务端当前时间比对,偏差超过 300 秒(5 分钟)直接拒绝,别用abs()算差值后比较,要算服务端时间减客户端时间的绝对值 -
nonce建议和user_id、timestamp拼起来做一次HMAC-SHA256签名,服务端重新算一遍,不一致就拒掉——防止篡改或复用 - 别把
nonce存成明文日志,尤其不要打到error.log里,否则可能泄露用户行为指纹
并发下唯一索引和 Redis 校验谁更可靠
两者不是二选一,而是分层兜底:Redis 做快速初筛,数据库唯一索引做最终仲裁。单靠哪一层都有死角。
立即学习“Python免费学习笔记(深入)”;
- Redis 可能因网络分区或超时失败,导致「本该拦截的请求漏过去了」;此时唯一索引就是最后一道防线
- 数据库唯一索引生效前提是事务隔离级别够高(至少
READ COMMITTED),且应用没绕过 ORM 直接用raw SQL插入 - 如果用
asyncio+aiomysql,注意连接池大小和超时设置,否则高并发下 Redis 检查通过了,但数据库插入卡住,会堆积大量半开请求
最麻烦的是跨服务调用场景:A 服务生成 token,B 服务校验,中间经过网关、负载均衡、消息队列……每个环节都可能引入时钟漂移或重试,这时候光靠单点校验很容易失效。










