必须放middleware,且要在路由匹配之后、controller执行之前;iris中验签属请求预处理,失败须立即中断链路,避免副作用,应通过app.use或按路径注册,注意body只能读一次并重置。

签名验证该放在 middleware 还是 controller?
必须放 middleware,且要在路由匹配之后、controller 执行之前。Iris 的 ctx.Next() 机制决定了签名校验属于「请求预处理」,一旦失败应直接中断链路并返回错误,不能拖到业务逻辑里再判断——否则可能已触发副作用(如数据库查询、日志写入)。
常见错误是把验签逻辑塞进某个 POST /api/order 的 handler 里,结果其他接口全绕过,安全策略形同虚设。
- 注册方式:用
app.Use(verifySignature)全局生效,或app.Post("/api/*", verifySignature, handler)按路径启用 - 注意顺序:
Use()注册的中间件会作用于所有路由;若需排除健康检查等免验签路径,得在中间件内部用ctx.Path()和ctx.Method()显式跳过 - 别在中间件里调
ctx.StopExecution()后还执行ctx.Next(),会导致 panic
怎么从 Iris context 安全读取原始请求体?
Iris 默认对 ctx.ReadBody() 和 ctx.FormValue() 做了缓冲和解析,但签名验证必须基于原始字节流(比如 application/json 或 application/x-www-form-urlencoded 的原始 payload),否则哈希值对不上。
正确做法是用 ctx.Request().Body 直接读,但要注意两点:一是 Body 只能读一次,二是 Iris 的 ctx.Request().Body 在部分场景下已被提前消费(如启用了自动 JSON 解析中间件)。
- 推荐方案:在验签中间件开头调
body, _ := io.ReadAll(ctx.Request().Body),然后用ctx.Request().Body = io.NopCloser(bytes.NewReader(body))重置 Body,保证后续 handler 仍能正常读取 - 别用
ctx.GetBody()——它只返回已缓存的副本,且不保证原始编码格式(比如 URL 解码可能已发生) - 如果接口同时支持 JSON 和表单,建议统一约定为只接受
application/json,避免解析歧义导致签名失效
签名算法怎么跟 Iris 的参数解析解耦?
签名字段(如 sign、timestamp、nonce)通常和业务参数混在 query、header 或 body 里。Iris 的 ctx.URLParam()、ctx.Header()、ctx.ReadJSON() 是各自独立的解析路径,不能指望一次解析覆盖全部来源。
验签前必须手动拼出待签名字符串,顺序、编码、空值处理都必须严格对齐客户端规则(比如 PHP 的 http_build_query 或 Java 的 URLEncodedUtils 行为)。
- 典型拼接逻辑:
method + path + sorted_query_string + sorted_header_string + raw_body_bytes,其中 query 和 header 要按 key 字典序排列,value 必须做 RFC 3986 编码(不是 JS 的encodeURIComponent) - 别直接用
ctx.FormValues()拼接——它会自动解码,而签名原文需要编码后的字符串 - 时间戳校验必须做时钟偏移容忍(比如
abs(server_time - timestamp) ),否则 NTP 不准的客户端会频繁失败
密钥管理与 HMAC 验证的实际坑点
用 hmac.New() 算签名本身很简单,但线上环境容易栽在密钥分发和时效控制上。Iris 本身不提供密钥轮换机制,得自己设计。
最常被忽略的是:签名密钥不能硬编码在代码里,也不能从环境变量直读(易泄露),更不能每个请求都查 DB(性能崩)。一个折中方案是启动时加载密钥到内存 map,按 app_id + version 索引,并定期 reload。
- HMAC 计算必须用
sha256或更强(sha512),md5和sha1已不安全,Iris 不拦截也不警告 - 客户端传来的
sign字段要先做 base64 或 hex 解码,再跟计算结果比对;别用字符串比较,要用hmac.Equal()防时序攻击 - 如果支持多租户,
app_id必须参与签名拼接,且验签前先查该 app_id 是否启用、密钥是否过期——否则攻击者可伪造 app_id 绕过校验
签名验证真正的复杂点不在代码几行,而在客户端和服务端对「原始数据」的定义是否完全一致,以及密钥生命周期如何跟业务发布节奏对齐。很多问题上线后才暴露,因为本地调试时双方都用同一套测试密钥、同一台机器时钟、同一个 Postman 请求——真实环境里,这些全是变量。











