单纯校验签名无法防重放,因签名仅保证请求未被篡改,不保证“新鲜性”;必须结合X-Timestamp(双向时间窗口校验)与Redis原子化nonce去重(SET key EX NX),缺一不可。

为什么单纯校验签名无法防重放
签名值比对通过,只说明请求体没被篡改,但不保证是“新鲜”的。攻击者截获一次合法请求后反复重发,服务端照样验签通过——这就是重放攻击。关键缺失的是时间维度控制。
必须校验 X-Timestamp 和时间窗口
从请求头取 X-Timestamp,转成 int64 后与 time.Now().Unix() 比较差值:
- 差值绝对值超过 300 秒(5 分钟)直接拒绝
- 建议用
time.Unix(timestamp, 0).Before(time.Now().Add(-5 * time.Minute))判断是否过期,避免负数时间戳绕过 - 不要只做“小于当前时间”,要双向检查:既不能太旧,也不能未来时间(防客户端时钟漂移过大)
Nonce 必须配合存储和去重验证
X-Nonce 不是随便生成的随机字符串,它需要被短时缓存并判重:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 推荐用 Redis 存储
nonce+timestamp,TTL 设为略大于时间窗口(如 360 秒) - 每次请求先查 Redis 是否已存在该
nonce,存在则拒绝(http.StatusForbidden) - 校验通过后再写入 Redis,注意原子性:用
SET key value EX 360 NX - 跳过
GET/HEAD等幂等方法,只对POST/PUT/PATCH校验
签名原文拼接顺序和 body 处理陷阱
签名原文必须严格一致,否则客户端和服务端算出的 HMAC 值永远不匹配:
- 典型拼接格式:
timestamp + "\n" + nonce + "\n" + raw_body(注意换行符) - 如果请求是
multipart/form-data,别在中间件里调用io.ReadAll(r.Body)——会导致后续c.MultipartForm()解析失败 - 正确做法:对非 JSON/URL-encoded 类型直接跳过签名校验,或改用参数级签名(如对 query + form values 排序后拼接)
- body 读取后必须重置:
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)),否则下游 handler 拿不到数据,报错invalid character '' looking for beginning of value
时间戳校验和 nonce 去重缺一不可,而 body 处理方式会直接决定中间件能否和 Echo 的原生解析逻辑共存——这两点最容易在压测或上线后暴露。










