JWT合法性需同时满足语法解析、签名有效、算法匹配、时间有效及结构体一致,仅err==nil不保证合法,必须显式校验token.Valid及具体错误类型。

验证 JWT Token 合法性 ≠ 解析成功,必须显式检查 token.Valid 并处理具体错误类型。
为什么 jwt.ParseWithClaims 返回 err == nil 却仍不合法?
常见误解是“没报错就代表 token 可用”,其实 ParseWithClaims 只负责语法解析和签名解密;过期、未生效、算法不匹配等校验失败时,err 可能为 nil,但 token.Valid 为 false。
-
token.Valid == false时,err可能是*jwt.ValidationError,需用位运算判断具体原因:ve.Errors & jwt.ValidationErrorExpired != 0 - 若只检查
err != nil就放行,会导致过期 token 被当作有效用户处理 - 标准字段(如
exp、iat、nbf)是否生效,取决于 Claims 是否嵌入jwt.RegisteredClaims,否则这些字段被忽略
如何安全提取 UserID 等自定义字段?
不能直接从 token.Claims 断言 map[string]interface{} 或裸结构体——类型不匹配会 panic 或静默失败。
- 生成时用了
type UserClaims struct { UserID string; jwt.RegisteredClaims },解析时必须传&UserClaims{}指针 - 解析后先做类型断言:
claims, ok := token.Claims.(*UserClaims),!ok表示结构体定义或传参方式错误(比如传了值而非指针) - 断言成功后,仍要检查
token.Valid和字段有效性:claims.UserID != "",因为 payload 是可篡改的 Base64,不能信前端传来的任意值
keyFunc 和算法校验最容易踩哪些坑?
密钥和算法不匹配是验签失败最常见原因,且错误信息往往模糊。
-
keyFunc必须返回[]byte,且长度 ≥32 字节(HS256 要求),硬编码字符串字面量会编译报错:cannot use string as jwt.Keyfunc - 必须校验
token.Method类型,防止 header 中alg被篡改为none或HS384:if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok - 务必传入
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),否则攻击者可修改 header alg 绕过验签 - 旧版
github.com/dgrijalva/jwt-go有alg=none漏洞,必须用github.com/golang-jwt/jwt/v5
时间校验失效的典型表现和修复
token 总是报 token expired,但实际时间明显没超——大概率是时间戳单位错了。
-
ExpiresAt必须是jwt.NumericDate类型,底层是int64秒级 Unix 时间戳 - 错用
time.Now().Add(1 * time.Hour).UnixMilli()→ 值是毫秒(如1740825123456),验签器按秒解析,当成公元 5 万多年,立刻判定过期 - 正确写法:
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)) - 多服务部署时,机器间时钟偏移超过 1 分钟就会导致
nbf/exp校验失败,上线前必须用chrony或ntpd对齐时间
最麻烦的不是代码写错,而是把 token.Valid 当作可选判断,或者以为 err == nil 就万事大吉。JWT 的“合法性”是多个条件叠加的结果:语法正确 + 签名有效 + 算法可信 + 时间窗口内 + 结构体匹配。漏掉任一环,都可能让伪造 token 浑水摸鱼。


















