Kratos中JWT认证核心实现在自定义transport/http/middleware,需手动解析Authorization头、验证签名及exp/iss等字段,并将Claims注入context;官方不内置JWT中间件,须基于golang-jwt/jwt/v5封装,且密钥须配置化、禁止硬编码。

JWT 认证在 Kratos 中的核心实现位置是 middleware
Kratos 的 JWT 认证不是靠某个“开箱即用的 Auth 模块”自动完成的,而是通过自定义 transport/http/middleware 实现的。你必须手动解析请求头中的 Authorization: Bearer ,验证签名、过期时间、issuer 等,并把解析出的用户 ID 或 Claims 注入到 context.Context 中供后续 handler 使用。
典型流程是:HTTP 请求 → JWT middleware → 验证 token → ctx = context.WithValue(ctx, key, claims) → 传给 biz logic。Kratos 官方示例里没有内置 JWT 中间件,得自己写或基于 github.com/golang-jwt/jwt/v5 封装。
如何写一个安全可用的 JWT middleware
关键点不在“能不能解”,而在“验什么”和“怎么传”。常见疏漏是只校验签名,忽略 exp、nbf、iss、aud,或直接把原始 jwt.Token 塞进 context 而不提取结构化字段。
- 使用
jwt.ParseWithClaims+ 自定义jwt.Claims结构体(推荐嵌入jwt.RegisteredClaims) - 密钥必须从配置加载(如
conf.JWT.Secret),禁止硬编码;对称密钥用jwt.SigningMethodHS256,非对称则用jwt.SigningMethodRS256+rsa.PrivateKey/rsa.PublicKey - 验证失败时统一返回
http.StatusUnauthorized和标准错误 JSON,不要泄露内部细节(比如别返回"token expired",而用"invalid credentials") - 解析成功后,建议用自定义 key(如
type ctxKey string; const userCtxKey ctxKey = "user")存入 context,避免和其它中间件冲突
为什么不能直接在 service 层做 JWT 解析
Kratos 的分层设计中,service 层只处理业务逻辑,不感知 transport 细节。如果把 token 解析逻辑写在 service.Login 里,会导致:
- 同一个 service 方法无法复用于 gRPC 场景(gRPC 没有
Authorizationheader) - 违反依赖倒置:transport 层本该负责协议适配,不该把解析责任推给业务层
- 难以统一拦截未授权请求(比如所有 /api/** 都要鉴权,但每个 service 方法都重复写校验?)
正确做法是:HTTP transport 加 middleware,gRPC transport 加 grpc.UnaryServerInterceptor,两者各自完成 token 提取与校验,再以统一方式注入 context —— 这样 service 层只需从 context 里取 userID 即可。
常见报错和调试线索
实际跑不通时,优先查这几点:
-
token is invalid:多数是密钥不一致(环境变量没生效、配置文件加载顺序错)、算法不匹配(前端用 HS256 签,后端用 RS256 验) -
token is expired:注意服务器时间是否准确,Docker 容器内时区是否同步(date命令对比宿主机) - context 取不到用户信息:检查 middleware 是否被正确注册(
srv.Use(yourJWTMiddleware)),以及是否在 handler 执行前调用(顺序很重要) - gRPC 请求始终 401:确认客户端是否在 metadata 里设置了
authorization键(小写),且值为"Bearer xxx",Kratos 默认只读这个 key
最易被忽略的是:JWT middleware 必须放在所有依赖用户身份的中间件之前,比如日志中间件如果先于鉴权执行,就可能记录空用户 ID —— 这类顺序问题不会报错,但会导致线上行为异常。











