必须显式判断token.valid,因为parsewithclaims成功仅表示语法解析和算法匹配,不校验过期、密钥错误、alg篡改等;token.valid为false时err仍可能为nil,漏判将导致非法token被误用。

校验 JWT 合法性 ≠ 解析成功,漏判 token.Valid 就等于放行过期、签名错、alg篡改的 token。
为什么 token.Valid 必须显式判断
Go 的 github.com/golang-jwt/jwt/v5 把解析和校验拆开:ParseWithClaims 成功只说明 header/payload 格式对、算法没被改成完全不支持的类型;但以下全部不在此范围内:
-
ExpiresAt已过期 →token.Valid为false,但err仍可能为nil - 密钥错误或长度不足(HS256 要求 ≥32 字节)→
token.Valid为false - header 中
alg被篡改为"none"→ 若未限制ValidMethods,验签被跳过,token.Valid可能为true
所以必须写:if !token.Valid { return errors.New("invalid or expired token") },不能只靠 if err != nil。
keyFunc 怎么写才防攻击又不出 panic
keyFunc 不是“返回密钥”那么简单,它要同时做三件事:校验算法、加载密钥、检查密钥强度。硬编码或忽略类型断言,等于主动开后门。
- 先断言算法:
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid signing method") } - 密钥必须从环境变量读:
secret := os.Getenv("JWT_SECRET"),禁止"my-secret"字面量 - HS256 要求密钥 ≥32 字节:
if len(secret) - 返回值必须是
[]byte,不是string(否则编译报错) - 务必加
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),否则攻击者可改header.alg绕过验签
自定义 Claims 结构体为什么必须嵌入 jwt.RegisteredClaims
不嵌入,exp、iat、nbf 这些字段就只是普通字段,不会触发任何自动校验逻辑 —— token.Claims.(MyClaims).ExpiresAt 可能有值,但 token.Valid() 永远返回 true,过期检查彻底失效。
- 正确写法:
type UserClaims struct { UserID string; jwt.RegisteredClaims } - 字段名必须严格匹配:
ExpiresAt(不是exp或expire_at) - 时间字段必须用
jwt.NewNumericDate()包装:ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)) - 解析时传指针:
&UserClaims{},传值会导致字段赋值失败
解析后怎么安全取用户 ID
token.Claims 是 interface{},直接访问 claims.UserID 会 panic。
- 解析后先做类型断言:
claims, ok := token.Claims.(*UserClaims);!ok表示结构体定义或传参方式错误(比如传了值而非指针) - 断言成功后,仍要检查
token.Valid和字段有效性:claims.UserID != "",因为 payload 是 Base64 编码,前端可任意篡改 - 别用
map[string]interface{}解析,v5 对标准字段识别不完整,且无类型安全 - 如果需要更细粒度错误定位,做
*jwt.ValidationError类型断言:ve.Errors & jwt.ValidationErrorExpired != 0表示过期
最常被忽略的是:时间戳单位错(用了 UnixMilli() 当秒级时间)、密钥长度不足、keyFunc 返回 nil 却不报错 —— 这些都会让 token.Valid 静默为 false,而日志里只看到 “invalid token”,查不出原因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











