必须用HMAC而非SHA256直接拼接,因后者易受长度扩展攻击;需严格校验时间窗口并去重;Body需缓存或流式计算签名;Header字段名须统一小写约定。

签名验证该用 hmac 还是 sha256 直接拼接?
必须用 hmac,别手撸拼接。直接拼 sha256(secret + data) 是典型错误——攻击者能利用长度扩展攻击伪造签名,尤其当 secret 在前、data 在后时。hmac 内部做了密钥混淆和两次哈希,标准且安全。
Go 标准库的 crypto/hmac 就够用,别引入第三方哈希包装器。示例关键行:
h := hmac.New(sha256.New, []byte(secret)) h.Write([]byte(payload)) signature := hex.EncodeToString(h.Sum(nil))
- secret 必须是服务端存储的固定密钥,不能从请求里读取
- payload 要严格按约定顺序拼接字段(如
method|path|timestamp|body_md5),空值也不能省略 - 别用
fmt.Sprintf拼 payload —— 容易漏转义、多空格、字段顺序错
时间戳怎么校验才防重放?
只比对 timestamp 字段是否“不超时”远远不够。必须同时做两件事:校验时间窗口 + 拒绝已见过的 timestamp。
常见错误是只做 abs(now.Unix() - req.Timestamp) ,这会导致同一合法请求在 5 分钟内重复提交成功。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用 Redis 或本地 LRU cache(如
golang-lru)存最近 5 分钟用过的timestamp(建议加用户 ID 前缀防跨账号碰撞) - cache key 示例:
"sig:ts:" + userID + ":" + strconv.FormatInt(req.Timestamp, 10) - 时间戳单位统一用秒,服务端用
time.Now().Unix(),别用毫秒——客户端时钟精度不一致容易误判 - 拒绝所有
req.Timestamp > time.Now().Add(2 * time.Minute).Unix()的请求(防客户端时间快太多)
Body 签名要不要读两次?怎么避免阻塞?
要读两次,但不能用 io.ReadAll(r.Body) 后再塞回 r.Body —— 默认 http.Request.Body 是单次读取流,二次读会返回空。
正确做法是提前把 body 缓存到内存或临时 buffer,再分别用于签名计算和后续解析:
b, _ := io.ReadAll(r.Body) r.Body = io.NopCloser(bytes.NewReader(b)) // 接着算 signature,再 json.Unmarshal(b, &req)
- 务必限制 body 大小(如
http.MaxBytesReader),否则恶意大 payload 会吃光内存 - 如果 body 可能很大(>1MB),改用 streaming HMAC:用
hmac.Hash的Write接口边读边算,但需确保 body 不被中间件提前消费(比如某些日志中间件会偷偷读) - 别在签名逻辑里调
json.Decode(r.Body)—— 解码失败时 body 已不可逆消耗,后续业务拿不到原始数据
签名头字段名和服务端解析不一致怎么办?
最常踩的坑是前端传 X-Signature,后端却去取 req.Header.Get("x-signature") —— Go 的 http.Header 是 case-insensitive,但 key 存储时首字母自动大写,实际用 Get 没问题;真正崩的是前端发了 X-SIGNATURE,而你代码里写 req.Header.Get("X-Signature") 却没报错,只是返回空字符串。
- 统一约定小写横线命名,比如
x-api-signature、x-api-timestamp,并在文档里明确写出全小写形式 - 取值一律用
req.Header.Get("x-api-signature"),Go 会自动匹配 - 签名验证失败时,别只返回 401 —— 加个明确错误字段,比如
{"error": "invalid_signature", "detail": "timestamp_expired"},方便前端快速定位是时间错还是密钥错 - 测试时用
curl -H "X-Api-Timestamp: 1717023456" -H "X-Api-Signature: abc..." ...,别依赖 Postman 自动生成 header 的大小写逻辑
签名验证真正难的不是算法,是字段拼接顺序、时间同步粒度、body 生命周期管理这三个点,任何一个松动都会让整个机制形同虚设。

















