必须显式检查 token.valid,否则过期、签名错误等都会被当成合法 token;err == nil 仅表示格式正确、可解码、算法匹配,不保证未过期、已生效、密钥正确或 alg 未被篡改为 "none"。

必须显式检查 token.Valid,否则过期、签名错误等都会被当成合法 token —— 这是最常踩的坑。
为什么 err == nil 不代表 token 合法?
jwt.ParseWithClaims 返回 err == nil 只说明 token 格式正确、能解码、签名算法匹配,但不保证:过期时间未到、签发时间已生效、签名密钥正确、alg 未被篡改为 "none"。这些全靠后续校验逻辑兜底。
常见错误是直接用 if err != nil { ... } 就放行 claims,结果 claims.ExpiresAt 已过期,token.Valid 却是 false,但代码没判就往下走了。
-
token.Valid是最终合法性开关,必须显式判断 - 即使
err == nil,也要做if !token.Valid { return err } - 不要依赖
err捕获所有问题;*jwt.ValidationError类型错误需单独处理
ParseWithClaims 的 keyfunc 怎么写才安全?
keyfunc 不只是返回密钥,它承担算法校验、密钥加载、长度验证三重责任。硬编码或忽略类型检查会引入 alg=none 攻击或静默 panic。
- 必须先断言
key.Method类型,比如_, ok := t.Method.(*jwt.SigningMethodHMAC),拒绝非预期算法 - 密钥必须从环境变量读取(如
os.Getenv("JWT_SECRET")),禁止字面量字符串 - HS256 要求密钥长度 ≥ 32 字节,
if len(secret) 必须报错,否则 crypto/hmac 会 panic - 返回值必须是
[]byte,不能是string(编译不通过)
Claims 结构体嵌入 jwt.RegisteredClaims 为什么不能省?
漏掉这一步,exp、iat、nbf 等标准字段不会被自动解析和校验,claims.Valid() 永远返回 true,过期检查彻底失效。
- 必须用
jwt.RegisteredClaims(v5)或jwt.StandardClaims(v4),二者混用直接编译失败 - 自定义结构体要内嵌,不是“包含字段”,例如:
type MyClaims struct { UserID string; jwt.RegisteredClaims } - 字段名必须严格匹配(
ExpiresAt,不是exp或expire_at) - 若用
jwt.MapClaims,必须手动设"exp": jwt.NewNumericDate(t).Unix(),不能传time.Time
解析后怎么安全取用户字段?
Claims 是 interface{},直接访问 claims.UserID 会 panic。类型断言失败很常见,尤其生成和解析用的结构体不一致时。
- 必须用
claims, ok := token.Claims.(*MyClaims),ok == false就立即返回错误 - 断言成功后,仍要检查关键字段是否为空(如
claims.UserID == "") - 别跳过
token.Valid判断——断言成功但token.Valid == false仍属非法 - 不要用
jwt.MapClaims存用户数据,易因 key 名大小写/拼写错误导致取不到值
最复杂的点其实是组合校验顺序:keyfunc 做算法+密钥检查 → ParseWithClaims 解析并初步验证 → 显式判断 token.Valid → 类型断言 → 字段有效性检查。少一环,token 就可能在过期或伪造状态下被当作合法凭证使用。











