Kratos的JWT中间件仅解析校验access_token,不处理refresh_token刷新逻辑;刷新必须显式实现独立接口,区分密钥与校验方式,并在业务层实现防重放、分布式锁及令牌轮换。

Kratos 本身不处理 Token 刷新逻辑,它只负责解析和校验 JWT,刷新必须由你自行实现——框架不会自动调用 refresh 接口、不会维护 refresh_token 生命周期、也不会拦截 401 后重放请求。
为什么 Kratos 的 jwt 中间件对刷新无感
Kratos 的 jwt.New() 中间件只做三件事:从 Authorization: Bearer <token></token> 提取 token、用 signing key 校验签名与过期时间、把解析后的 *jwt.Token 存入 ctx.Value。它完全不关心这个 token 是 access 还是 refresh,也不检查 payload 里有没有 refresh_token 字段,更不会在鉴权失败时触发刷新流程。
常见误操作包括:
- 以为加了
jwt.New()就能自动刷新,结果所有接口在 access_token 过期后直接返回 401 - 把 refresh_token 也塞进同一个 JWT 并交给该中间件校验,导致 refresh_token 被当成普通 access_token 校验(而它通常应走独立 endpoint + 更严的存储/传输要求)
- 没区分 access_token 和 refresh_token 的签发方式(比如 access_token 用
HS256,refresh_token 却用RS256或存 DB),但中间件配置统一用jwt.WithSigningMethod(jwt.SigningMethodHS256),导致 refresh_token 校验失败
如何在 Kratos 中正确暴露 refresh 接口
刷新必须作为独立 HTTP 接口显式实现,不能依赖中间件自动触发。典型做法是:
- 定义一个专用 endpoint,如
POST /v1/auth/refresh,接收refresh_token(建议从请求体或X-Refresh-Tokenheader 传,避免混用Authorization) - 在 handler 中手动解析该 token:
jwt.ParseWithClaims(refreshToken, &claims, keyFunc),注意使用与签发时一致的SigningMethod和密钥(refresh_token建议用不同密钥或更长密钥) - 校验 claims 中的
scope、aud、是否被吊销(需查 Redis 黑名单或 DB 记录)、是否被重复使用(可记录jti防重放) - 成功后签发新的
access_token(短时效)和新的refresh_token(旧的立即失效),并返回给前端
不要复用 jwt.New() 中间件来保护这个接口——它默认校验的是 access_token,而你这里要处理的是 refresh_token,逻辑完全不同。
前端并发刷新时,Kratos 后端怎么防重放和死循环
Kratos 不提供刷新锁或队列机制,这部分必须你在业务层补全。关键点在于:
- 每次成功刷新后,旧
refresh_token必须立即标记为已使用(例如写入 Redis,key 为rt:<jti></jti>,TTL 设为 1 小时),并在解析时先查黑名单 - 前端多个请求同时触发刷新时,后端不能让它们都去 DB 查、都去签新 token。建议在 refresh handler 开头加分布式锁(如 Redis SETNX),只有拿到锁的请求执行完整刷新流程,其余请求等待几秒后重试或直接返回 429
- 避免“刷新接口自己也需要鉴权”导致死循环:不要给
/v1/auth/refresh加jwt.New()中间件;若需基础校验(如 client_id 是否合法),应走独立轻量逻辑,而非 JWT 解析 - refresh_token 的有效期建议设为滑动过期(每次刷新重置 TTL),而不是固定过期,否则用户长期不操作就会被强制登出
最易被忽略的一点:Kratos 的 jwt.FromContext(r.Context()) 只能拿到中间件解析出的 access_token,它和 refresh_token 完全是两套生命周期、两套校验逻辑、两套存储方式。混淆这两者,是绝大多数刷新失败问题的根源。


















