Kratos中请求体解密必须在首个中间件完整读取Body并重置,置于所有中间件最前;需校验Content-Type与长度,验签失败应手动返回400及明确错误码,密钥须动态加载且匹配算法要求。

Kratos 中请求体解密必须绕过默认 Body 读取逻辑
Kratos 的 ctx.Request().Body 默认只可读一次,且中间件链中若存在日志、限流、JSON 解析等前置处理,Body 很可能已被提前消费。直接在自定义中间件里调 io.ReadAll(ctx.Request().Body) 会返回空字节,后续 Controller 拿不到数据。正确做法是:在第一个需要原始 Body 的中间件(通常是解密验签中间件)开头就完整读取,再用 io.NopCloser(bytes.NewReader(body)) 重置 Body 流。
别依赖 ctx.GetBody() ——它返回的是 Kratos 内部缓存的副本,但这个副本可能已被 URL 解码或 JSON 预解析污染,导致解密前的字节与原始请求不一致,验签必然失败。
- 务必把解密验签中间件放在所有其他中间件之前(比如在
http.Middleware()列表最前面) - 如果同时支持
application/json和application/x-www-form-urlencoded,建议强制统一为 JSON 格式,避免表单解析时自动转义/解码引入不可控差异 - 读取 Body 后要立即检查
Content-Type和长度,防止空请求或超大 payload 耗尽内存
验签失败时不能简单 return error
Kratos 的中间件错误处理机制默认把 error 当作 HTTP 500 返回,但验签失败属于客户端问题,应返回 401 或 400,并附带明确错误码(如 "invalid_signature")。直接 panic 或 return 错误会让上层无法区分是服务崩溃还是非法请求。
更关键的是:验签和登录态校验必须分离。Sa-Token 或 JWT 登录验证失败是“你没登录”,验签失败是“你发的数据不可信”——两者触发条件、响应头(如是否清 Cookie)、监控指标都不同,混在一起会导致风控策略失效。
- 验签失败时手动写响应:
w.WriteHeader(http.StatusBadRequest)+json.NewEncoder(w).Encode(map[string]string{"code": "invalid_signature", "msg": "sign mismatch"}) - 不要调用
ctx.Error(),它会走 Kratos 全局错误格式化逻辑,可能掩盖原始错误类型 - 记录验签失败日志时,至少带上
request_id、client_ip、sign_timestamp(用于排查重放攻击)
解密密钥必须从 config 动态加载,禁止硬编码
Kratos 的 config 模块支持多源配置(文件、环境变量、远程 etcd),密钥必须通过 c.Value("crypto.aes_key").String() 这类方式获取,而不是写死在代码里。硬编码密钥一旦泄露,整个加解密体系即告崩溃。
还要注意密钥长度匹配算法要求:AES-128 需 16 字节密钥,AES-256 需 32 字节。用错长度会在 cipher.NewCipher() 阶段 panic,错误信息是 "invalid key size",但这个 panic 发生在中间件初始化时,不是请求处理时,容易被忽略。
- 密钥建议用 base64 编码后存入配置,读取后用
base64.StdEncoding.DecodeString()解码 - 生产环境优先选 GCM 模式(AEAD),它同时提供加密和认证,比先 AES-CBC 再 HMAC-SHA256 更安全简洁
- 如果用 GCM,IV(nonce)必须随每次请求随机生成并透传,不能复用,否则会破坏安全性
调试阶段最容易漏掉的两个点
一是前端传来的加密字符串常带首尾双引号(比如 JSON 字符串里嵌套了一层字符串),解密前不 strings.Trim(encrypted, `"`) 会导致解密失败;二是 Kratos 默认不设置 Content-Type 响应头,但部分 Android 端 SDK 严格校验返回头,解密后的响应若没设 application/json,会被当成解析错误。
- 加密字符串预处理:去除首尾引号、空白符,再 Base64 解码
- 解密成功后,务必在写响应前调
w.Header().Set("Content-Type", "application/json; charset=utf-8") - 本地调试时用
curl -v抓包确认请求体是否真为密文、响应体是否真为明文,别只信日志输出


















