签名算法必须包含时间戳和随机nonce以防止重放攻击;核心是使每个请求唯一且有时效,仅参数排序哈希无效,须将timestamp和nonce纳入签名输入并服务端校验时间差。

签名算法必须包含时间戳和随机 nonce
防重放的核心是让每个请求具备唯一性且有时效性。只对参数排序哈希(比如 sha256(body + secret))完全无效——攻击者截获后可无限次重放。必须把 timestamp 和 nonce 作为签名输入的一部分,并在服务端校验:abs(now - timestamp) (单位秒),且 <code>nonce 需存入 Redis 或数据库做去重(TTL 设为略大于窗口期,如 360 秒)。
常见错误:把 timestamp 放在请求头但不参与签名计算;或用 UUID 作 nonce 却未做全局查重;或校验时忽略时区,导致服务端用 time.Now().Unix() 而客户端用本地时间造成偏差。
-
timestamp必须是 Unix 秒级整数(非毫秒),避免浮点或字符串格式引发解析歧义 -
nonce建议用 16 字节随机字节转 hex(hex.EncodeToString(randBytes)),长度固定便于索引 - 签名原文拼接顺序要严格约定,例如:
method + \n + path + \n + timestamp + \n + nonce + \n + body(body 为空时也保留换行)
Go 中用 hmac-sha256 实现签名与验签
Go 标准库的 crypto/hmac 足够安全,无需引入第三方。关键在于密钥管理——不能硬编码,应通过环境变量或 Secret Manager 注入;签名函数返回的应是 hex 编码字符串(不是 raw bytes),验签时也需统一解码比对。
示例签名逻辑:
func signRequest(method, path, body string, timestamp, nonce string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
io.WriteString(h, method+"\n")
io.WriteString(h, path+"\n")
io.WriteString(h, timestamp+"\n")
io.WriteString(h, nonce+"\n")
io.WriteString(h, body)
return hex.EncodeToString(h.Sum(nil))
}
注意:HTTP 方法名必须大写("POST"),path 必须是原始路径(不含 query string),body 必须是原始字节(不经过 gzip 或其他编码);若 body 是 JSON,客户端和服务端都应禁用 json.Marshal 的空格缩进,否则哈希不一致。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
中间件中校验签名和防重放必须原子执行
验签、时间戳检查、nonce 去重这三步不能拆成独立 handler,否则存在竞态:两个相同 nonce 请求可能同时通过“是否已存在”查询,再同时写入,导致漏检。必须用 Redis 的 SET key value EX 360 NX(或数据库的 INSERT ... ON CONFLICT DO NOTHING)保证原子性。
典型错误场景:
- 先查 Redis 是否存在
nonce,再 set —— 中间有间隙 - 用
time.Since()校验时间戳但传入的是字符串,未转int64导致永远为 0 - 签名头字段名大小写不一致(如客户端发
X-Signature,服务端读x-signature,在某些代理下被转小写)
推荐 header 命名统一用 X-Api-Signature、X-Api-Timestamp、X-Api-Nonce,全部小写处理读取(r.Header.Get("x-api-signature"))。
测试时容易忽略的边界情况
本地调试常因系统时间不同步、body 含不可见字符、URL 编码差异导致验签失败。真实环境还要考虑反向代理(如 Nginx)可能篡改 Content-Length 或自动解压 body。
- 用
httptest.NewRequest构造测试请求时,显式设置r.Body = io.NopCloser(strings.NewReader(body)),避免 body 被多次读取为空 - 签名前对 body 做
bytes.TrimSpace,消除 curl 自动添加的换行 - 如果 API 允许 form-data,签名应基于解析后的规范 form 字符串(而非原始 multipart body),否则无法跨语言对齐
- 生产环境开启日志时,切勿打印完整
secret或原始body(尤其含敏感字段)
最麻烦的其实是 clock skew:K8s Pod 之间、客户端与服务器之间的时间差超过 5 分钟就会频繁触发时间戳拒绝。务必确保所有节点启用 NTP 同步,并在验签前加一个容忍阈值日志告警,而不是直接拒掉。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










