kratos 不提供内置接口签名校验,需手动实现中间件;其设计遵循职责分离,签名规则高度定制化,jwt 与 sign 功能独立不可替代,校验须严格复现客户端流程并注意 nonce、timestamp、body 原始性等细节。

Kratos 本身不提供内置的接口签名(Sign)校验能力,必须手动实现中间件或在业务层完成 HMAC/SHA256 等对称签名验证逻辑。 它和 JWT 鉴权是两套完全独立的机制:JWT 中间件只处理 token 解析与基础校验,而 Sign 是应用层对整个请求参数+body 的完整性保护,框架不会自动帮你拼参、哈希、比对。
为什么 Kratos 没有开箱即用的 Sign 中间件
Kratos 的设计哲学是“职责分离”:transport 层只负责协议转换(HTTP/gRPC),biz 层专注业务逻辑,安全策略(如签名、权限、限流)由你显式编排。官方未封装 Sign 中间件,是因为签名规则高度定制化——不同合作方要求的参数排序方式、是否包含 body、是否 URL Decode、nonce 存储位置(Redis?DB?)、时间戳偏差容忍值(30s?5min?)全都不一样。
常见错误现象:sign invalid 或 timestamp expired 报错后死查中间件配置,结果发现压根没加 Sign 校验逻辑,只加了 JWT。
- JWT 中间件和 Sign 校验不能互相替代:JWT 保证“你是谁”,Sign 保证“这个请求没被改过、没被重放”
- 如果你用的是微信/支付宝/银行类对接场景,
sign字段通常放在X-Signature或Authorization自定义头里,不是 JWT 的 Bearer Token - Kratos 的
http.Server默认不解析 request body,若签名依赖body,需提前用http.MaxBytesReader或自定义 middleware 读取并缓存
手写 Sign 校验中间件的关键四步
以 HMAC-SHA256 为例,服务端校验必须严格复现客户端签名流程。漏掉任意一环,hmac.Equal 就会返回 false。
-
提取原始参数:从
r.URL.Query()取 query 参数,用io.ReadAll(r.Body)读 body(注意后续要重置r.Body),合并为 map[string]string;排除sign、signature等签名字段本身 -
标准化拼接:按 key 字典序排序 → 所有 value 进行
url.PathEscape(不是url.QueryEscape!路径参数和查询参数编码规则不同)→ 用\n换行连接(非 & 或 =) -
计算签名:用配置的
signing_key(如conf.GetSign().GetKey())调用hmac.New(sha256.New, []byte(key)),写入拼接字符串,再hex.EncodeToString(sum[:]) -
恒定时间比对:必须用
hmac.Equal([]byte(clientSign), []byte(serverSign)),严禁用==字符串比较(防时序攻击)
容易被忽略的三个生产级细节
这些点不写进代码,上线后大概率被重放或绕过:
-
nonce必须全局唯一且单次有效:建议用 Redis SETNX + EXPIRE(如SET nonce:abc123 "1" EX 300 NX),不能只存在内存 map 里——多实例部署时失效 -
timestamp校验必须用服务端当前时间,而非客户端传的时间做减法:防止客户端系统时间被恶意调慢,应取time.Now().Unix()与参数中timestamp做差,偏差 > 300 秒直接拒绝 - body 内容参与签名时,若前端发的是
application/json,服务端必须原样读取字节流,不能先json.Unmarshal再拼——浮点数精度、空格、字段顺序差异都会导致哈希不一致
真正卡住人的从来不是算法,而是参数标准化那一步:URL Decode 还是 PathEscape?换行符用 \n 还是 \r\n?大小写敏感吗?这些细节必须和对接方逐条对齐,文档里没写的,就抓包看对方真实请求。











