hmac.New第二个参数必须是[]byte,不能传string或未经解码的base64/hex密钥;签名原文需严格统一method、path、timestamp、nonce、body_hash的拼接顺序与编码方式;X-Timestamp和X-Nonce须校验防重放;中间件应使用context注入用户信息。

hmac.New 传错密钥类型导致签名永远不一致
Go 的 hmac.New 第二个参数必须是 []byte,不是 string。哪怕你写 []byte("secret") 都行,但绝不能用 string([]byte("x")) 或隐式转换。常见错误包括:
- 从环境变量读取密钥后直接
[]byte(os.Getenv("API_SECRET"))—— 看似正确,但如果环境变量末尾有换行或空格,len(key)就会多出 1 字节 - 密钥是 base64 编码字符串(如
"aGVsbG8="),却没调base64.StdEncoding.DecodeString(),而是直接转[]byte - 密钥是 hex 字符串(如
"deadbeef"),没走hex.DecodeString(),导致实际用了 ASCII 字符而非原始字节
实操建议:服务启动时一次性解码并缓存为 []byte;调试时打印 fmt.Printf("%q", key) 和 len(key),确认无不可见字符。
签名原文拼接规则不统一,hmac.Equal 总返回 false
客户端和服务端算出的签名不一致,90% 是因为待签名字符串内容不一致。不是“差不多就行”,而是每个字符、大小写、URL 编码、空格、换行都必须严丝合缝。
- method 必须全大写(
"GET"不是"get") - path 用
req.URL.EscapedPath(),不是req.URL.Path(后者不编码斜杠) - query 参数不能用
req.URL.RawQuery(顺序不定),得手动url.ParseQuery()→sort.Strings(keys)→ 每个key和value都用url.PathEscape()(不是url.QueryEscape()) - body 哈希用原始字节流:
sha256.Sum256(bodyBytes)[:],不是解析后的结构体或表单值 - 时间戳统一用秒级:
time.Now().Unix(),别用UnixMilli()
实操建议:校验失败时,先打印双方生成的原始待签字符串(fmt.Sprintf 拼成单行),比对差异再调算法。
立即学习“go语言免费学习笔记(深入)”;
X-Timestamp 和 X-Nonce 校验不到位,防重放形同虚设
只校验签名正确性不够。攻击者截获合法请求后改参数重发,只要签名还有效就可能绕过业务逻辑。
-
X-Timestamp必须是纯数字字符串,且与当前时间偏差 ≤ 300 秒:abs(timestamp - time.Now().Unix()) > 300直接拒绝 -
X-Nonce至少 16 字节随机字符串(推荐crypto/rand生成),避免被爆破 - Redis 存指纹用
SetNX,key 为req:%s:%d:%s(userID、timestamp、nonce),TTL 设为窗口时长 + 几秒冗余 - 绝对不要先
GET再SET,否则有竞态;也别把jti当唯一标识塞进 JWT payload —— 它得绑定用户+短窗口,不是全局去重
实操建议:时间戳校验必须在签名验证前完成;nonce 若由客户端生成,需强制其长度和字符集(如只允许 a-z0-9)。
中间件里注入用户信息用 context.Value 而不是全局变量
很多初学者把 user_id 存进包级 map 或全局 struct,结果并发请求下数据错乱、鉴权失效甚至 panic。
-
context.Context是 Go 原生支持的请求生命周期载体,天然线程安全,且可随请求取消 - 中间件中用
r = r.WithContext(context.WithValue(r.Context(), userKey, userID))注入,下游 handler 用r.Context().Value(userKey)取 - 别用 Gin 的
c.Keys(本质是map[string]interface{}),易拼错 key 名;也不要用c.Set()后在 handler 里c.MustGet(),因为中间件未执行完时取不到 - 如果用 JWT,解析后应只取
sub和exp,别信任其他字段(如role)—— 它们可能被伪造
实操建议:定义 typed key(如 type userKey struct{}),避免不同中间件用相同字符串 key 冲突;密钥硬编码在二进制里极易被 strings 提取,生产必须从 Vault 或环境变量动态加载。


















