Go 认证与 JWT
Go 服务中使用 JWT 作为短期 Access Token 的实现要点:Claims、验签、中间件身份注入,以及与 Session/Refresh 生命周期的边界。
[!info] related notes
- 所属 MOC:服务工程 · Web 与后端 · 学习路线
- 通用认证模型:Stateful、Stateless 与 Hybrid 认证架构
- 凭证概念:Access Token · Refresh Token · JWT · Server-side Session 鉴权
- Go 前置:Context · HTTP 中间件 · net/http
Go 认证与 JWT
一句话定义
在 Go Web 服务中,JWT 常被用作短生命周期 Access Token:Middleware 验证 Token 后,把已经验证的 principal 写进 context.Context,业务层再执行资源级 Authorization。
这里的关键是:
JWT
= Token 格式
Access Token
= 凭证角色
Session / Grant
= 可选的长期登录状态
所以“Go 使用 JWT”不等于“整个认证系统必然无状态”。
一个典型 Hybrid 结构
Login
→ verify password
→ create Session / Grant
→ bind Refresh Token
→ issue short-lived Access JWT
Normal API
→ Go Middleware local verify Access JWT
→ principal into context
→ Service authorization
Refresh / Logout / Revoke
→ Auth service
→ check Session / Grant
如果系统是普通 Browser Web App,也可以直接使用 opaque Session Cookie,而完全不需要 JWT。
Access JWT 的最小 Claims
常见 Registered Claims:
sub → subject / user identity
exp → expiry
iat → issued at
iss → issuer
aud → audience
业务 claims 应保持最小。Role/scope 可以作为短期授权上下文,但它们仍然只是签发时的快照,不能替代当前资源 ownership / policy 查询。
Go 中的验签边界
使用 github.com/golang-jwt/jwt/v5 等成熟库时,中间件通常负责:
- 从
Authorization: Bearer ...提取 Access Token; - 限制允许的签名算法;
- 验证签名;
- 验证
exp、iss、aud等必要 claims; - 把
sub等已验证身份放入 request context; - 调用后续 handler。
不要让 JWT Middleware 承担所有业务 Authorization。
Context 注入
推荐使用私有类型作为 key:
type ctxKey string
const userIDKey ctxKey = "user_id"
ctx := context.WithValue(r.Context(), userIDKey, claims.Subject)
next.ServeHTTP(w, r.WithContext(ctx))
下游只能把这份值当作“已经通过认证得到的 principal”,而不是客户端可自由提交的 user_id。
JWT 安全边界
Payload 不是秘密
JWT Payload 通常只经过 Base64URL 编码,可以被读取;密码、密钥等敏感秘密不要放进去。
必须限制算法
不要盲信 Token Header 中的 alg。服务端要显式限制允许的算法,避免算法混淆或错误配置。
Access Token 应控制有效窗口
长寿命 Access Token 会让账户封禁、权限变化和 Session 撤销更晚生效。短 TTL 用来限制 stale authorization window;具体是 5、15 或 60 分钟取决于威胁模型和刷新成本,没有固定标准。
Refresh Token
Refresh Token 不需要是 JWT。更常见的安全模型可以是:
opaque random Refresh Token
→ hash lookup
→ Session / Grant
→ rotate
→ issue new Access JWT
Refresh Token 的 rotation、replay detection 和撤销属于长期登录生命周期,不应塞进普通 JWT Middleware。
Pure Stateful 与 Hybrid
Pure Session
request
→ opaque Session Token
→ shared Session Store
即时撤销强,但每请求查询共享状态。
Hybrid
normal request
→ local Access JWT verify
refresh/logout/revoke
→ server-side Session / Grant
高频路径减少共享状态查询,但接受 Access Token 在 exp 前存在有限滞后窗口。
Authentication 与 Authorization
JWT 验证成功只建立身份上下文:
sub = user-42
scope = assessment:write
业务层仍要判断:
user-42 是否拥有 Assessment 789?
当前 tenant membership 是否有效?
资源是否处于允许修改的状态?
因此不要把 role 塞进 JWT 后就认为 Authorization 问题已经解决。
常见误区
- “JWT 认证天然无状态” → 取决于系统是否还有 Session/Grant/revocation 状态。
- “JWT 是微服务默认最佳方案” → 要看服务边界和撤销需求。
- “Refresh Token 必须 JWT” → 不需要。
- “JWT 验证成功就可以操作任意资源” → 仍需资源级授权。
- “退出登录只要客户端删除 JWT” → Hybrid/Session 架构还应撤销服务端长期状态。
Go 工程实践
- 密钥从 Secret Manager / 环境注入,不硬编码;
- 支持密钥轮换,非对称场景可使用
kid; - 不记录完整 bearer token;
- 给过期、错误算法、错误 audience/issuer 写测试;
- Middleware 只建立 principal,业务 Policy 放 Service/Authorization 层;
- 多实例系统明确 Session/Refresh authority 放在哪里。
最短记忆
Go 的 JWT Middleware 负责验证短期凭证并注入身份;长期登录生命周期是否有状态、如何 Refresh 和 Revoke,是更上一层的认证架构决策。