验签中间件必须置于路由匹配之后、controller之前,按路径注册或前置路径判断,安全读取并重置原始Body,失败时调用ctx.StopExecution()终止执行,不可塞入handler内。

验签中间件必须放在路由匹配之后、controller之前
验签不是业务逻辑,是请求准入控制。Iris 中 app.Use() 注册的中间件会作用于所有路由(包括 /health),但签名验证通常只对 API 路径生效;若混用 app.Use(verifySignature) 全局注册,又没在中间件里手动跳过免验签路径,会导致健康检查失败、监控告警误报。
正确做法是按路径注册:app.Post("/api/*", verifySignature, orderHandler),或在中间件开头加判断:
- 用
ctx.Path()检查是否匹配/api/前缀 - 用
ctx.Method()排除GET类无参请求(除非你真要验 GET 查询参数) - 避免对
/favicon.ico、/robots.txt等静态资源验签,否则可能触发重复 Body 读取 panic
签名验证前必须安全读取并重置原始 Body
Iris 的 ctx.ReadBody() 或 ctx.FormValue() 会触发内部解析,破坏原始字节流——而签名是基于原始 payload 计算的(比如 JSON 字符串未格式化、form data 未 URL 解码)。直接拿解析后的 map 或 struct 去哈希,必然验不过。
必须用底层 io.ReadAll() + io.NopCloser() 方式:
body, _ := io.ReadAll(ctx.Request().Body)ctx.Request().Body = io.NopCloser(bytes.NewReader(body))- 千万别用
ctx.GetBody():它返回的是 Iris 内部缓存副本,且已做编码转换,无法还原原始签名输入 - 如果项目启用了
app.UseRouter或 JSON 自动绑定中间件,Body 很可能已被提前消费,此时需确保验签中间件注册顺序在它们之前
验签失败必须立即终止,不能调 ctx.Next()
验签属于「链路守门员」,失败意味着请求不可信。常见错误是在中间件里写:
if !valid {
ctx.StatusCode(401)
ctx.WriteString("invalid signature")
}
ctx.Next() // 错!这里 controller 仍会执行
这会导致副作用:订单创建、数据库写入、日志落盘等操作照常发生。正确姿势是:
- 调
ctx.StopExecution()强制中断 - 再调
ctx.StatusCode()和ctx.WriteString()返回错误 - 切记:
StopExecution()后不能再调ctx.Next(),否则 Iris panic 报错next called after stop execution
别把验签逻辑塞进某个 handler 里
把签名验证写在 POST /api/order 的 handler 函数内部,看似能跑通,实则埋下严重隐患:
- 新增接口(如
/api/refund)默认不验签,攻击者可绕过所有防护直击业务层 - 同一个 handler 多处复用时,容易漏掉某次调用的校验分支
- 无法统一审计验签失败日志,排查入侵行为时缺少关键上下文(如来源 IP、原始签名头)
- 后续加 JWT 校验、IP 白名单等策略时,代码结构会迅速变得不可维护
真正难的不是写对一次验签,而是让所有 API 请求都经过同一道门——这道门的位置、输入保真度、失败处置方式,三者缺一不可。中间件不是语法糖,是安全边界的物理存在。











