必须先用io.readall读取并重置r.body,再校验签名;对multipart/form-data等大body应跳过或改用参数级签名,避免中间件全量读取导致后续handler无法解析。

中间件里怎么取到原始请求体做签名验证
直接读 r.Body 会破坏流,后续 handler 拿不到数据。必须用 io.TeeReader 或提前缓存——但更稳妥的是用 r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) 重置 Body。
常见错误:用 ioutil.ReadAll(r.Body) 后没重置,导致下游解析 JSON 失败,报错 invalid character '' looking for beginning of value。
- 先调用
bodyBytes, _ := io.ReadAll(r.Body) - 校验签名(比如 HMAC-SHA256)
- 再用
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))恢复 Body 流 - 注意:若请求含文件上传(
multipart/form-data),不建议在中间件里全量读 body,应改用参数级签名或跳过该类型请求
签名头字段名和算法怎么统一约定
别硬编码 X-Signature 或 Signature,得跟客户端对齐。常见组合是 X-Signature + X-Timestamp + X-Nonce,缺一不可。
验证逻辑必须包含时间窗口检查(比如 ±5 分钟),否则重放攻击防不住。单纯比对签名值没意义。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 从 header 取
r.Header.Get("X-Signature")、r.Header.Get("X-Timestamp") - 用
time.Now().Unix()和X-Timestamp做差值判断是否超时 - 签名原文拼接顺序要严格一致:通常是
timestamp + nonce + raw_body(或按文档约定) - 密钥不能写死在代码里,走环境变量或配置中心,例如
os.Getenv("API_SECRET")
怎么在 Echo 中注册并拦截非 POST/PUT 请求
签名验证只对数据修改类请求有意义,GET/HEAD/OPTIONS 不需要校验,否则影响缓存和调试。中间件里第一句就该做 method 过滤。
别把中间件挂到全局 e.Use() 下——那样连健康检查接口都强制验签,容易引发误拦。
- 用
echo.HTTPMethodPost和echo.HTTPMethodPut显式判断r.Method - 对路径做白名单控制,比如只校验
/api/v1/order开头的路由 - 推荐注册方式:
e.POST("/api/v1/order", orderHandler, signatureMiddleware),而非全局 use - 校验失败时,直接
c.AbortWithStatusJSON(http.StatusUnauthorized, map[string]string{"error": "invalid signature"})
为什么 HMAC 校验总失败?几个关键细节
最常踩的坑是 body 编码和换行处理:JSON 序列化后可能带空格/换行,而客户端用紧凑格式生成签名;或者前端发的是 application/json;charset=UTF-8,但服务端按默认 UTF-8 解析时忽略 BOM 导致字节差异。
还有就是签名密钥用了 base64 解码却没对应处理——客户端传的是明文密钥,服务端却去 decode,结果密钥错位。
- 打印
len(bodyBytes)和 hex dump 对比客户端发来的原始字节长度 - 确认双方 JSON 序列化是否都禁用了缩进(
json.Marshalvsjson.Compact) - 签名计算前,对 body 做
bytes.TrimSpace清掉首尾空白(尤其防 curl 自动加的换行) - 用
hmac.Equal而不是==比较签名结果,防时序攻击
hmac.Equal 返回 false。调试时优先打原始 body hex 和签名原文字符串,而不是只看最终 signature 字段。










