必须显式校验exp、iat、iss三要素,需配合withexpirationrequired()、withissuedat()、withissuer()等选项,否则过期token仍可通过验证;同时须用withvalidmethods限定算法防alg篡改。

JWT校验前必须验证的三个签名要素
Go里用 github.com/golang-jwt/jwt/v5 做校验,光调 Parse 不行——它默认不校验 exp、iat 和 iss,得手动配 WithExpirationRequired()、WithIssuedAt() 等选项。漏掉任意一个,token过期后还能通过校验。
- 必须显式传入
jwt.WithValidMethods([]string{"RS256"}),否则攻击者可篡改 header 的alg字段为none绕过签名验证 - 公钥加载要提前完成,别在每次请求里读文件或远程拉取——
rsa.PublicKey可复用,反复解析 PEM 会拖慢 10ms+(实测) - 校验失败时,
err.Error()返回的字符串可能含敏感信息(如密钥路径),生产环境需统一返回"invalid token"
如何安全提取并验证 claims 中的 user_id
别直接用 token.Claims["user_id"]——这是 map[string]interface{},类型断言容易 panic。得先定义结构体实现 jwt.Claims 接口,再用 token.Claims.(*MyClaims).UserID。
- 结构体字段必须是导出的(首字母大写),否则 JSON 反序列化失败,
UserID int64 `json:"user_id"` -
Valid()方法里要覆盖校验逻辑:比如检查UserID > 0,防止伪造负数 ID - 如果业务要求多租户,
aud字段必须匹配当前服务名,用jwt.WithAudience("api.example.com")配合VerifyAudience
中间件里怎么避免重复解析 JWT
HTTP handler 中每次调 jwt.Parse 是最常见性能坑。正确做法是在中间件里解析一次,把 *jwt.Token 或提取后的 userID 存进 context.Context,后续 handler 直接取。
- 用
ctx = context.WithValue(r.Context(), "user_id", claims.UserID),但注意 key 必须是自定义类型(如type ctxKey string),避免和其他中间件冲突 - 别把整个
*jwt.Token放 context——它包含原始 token 字符串和未验证的 claims,有安全风险 - 如果用了 Gin,用
c.Set("user_id", claims.UserID)更简洁,但记得在中间件末尾加c.Next()
测试 JWT 校验模块绕不开的三个边界 case
本地跑单元测试时,只测 “正常 token 能过” 不够。这三个 case 一漏,上线后就出 500。
- 构造过期 token:
time.Now().Add(-time.Hour).Unix()当exp,确认返回jwt.ErrTokenExpired - 伪造 signature:用正牌私钥签一个 token,再手动改最后 10 字节,确保触发
jwt.ErrSignatureInvalid - 空 token 或 Bearer 后无内容:
Authorization: Bearer(结尾空格),此时strings.TrimSpace()后长度为 0,应直接拒绝
真正麻烦的是密钥轮换——旧公钥还没下线,新公钥已生效,这时得支持多公钥并行校验,但 Parse 不支持数组。得自己写个循环,逐个尝试 ParseWithClaims,直到成功或全部失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











