jwt.parse 默认不验证签名,必须显式提供keyfunc且返回非nil密钥;校验exp需手动启用jwt.withexpiration(true),否则过期token仍解析成功。

为什么直接用 jwt.Parse 容易漏掉签名验证?
很多人以为调用 jwt.Parse 就自动验签了,其实不然——它默认只解析结构、不校验签名,除非你显式传入 keyFunc 并返回非 nil 的密钥。没配 keyFunc 或返回 nil,Parse 会跳过签名检查,直接返回 payload,这在生产环境极其危险。
正确做法是:始终提供 keyFunc,且确保它对有效 token 返回密钥、对无效/不可信 issuer 返回错误(不能返回 nil)。
-
keyFunc必须返回interface{}类型密钥(如[]byte或*rsa.PublicKey),不能返回nil - 如果使用 HS256,密钥必须是
[]byte;RS256 则需*rsa.PublicKey - 建议在
keyFunc中做 issuer / kid 校验,避免无条件返回固定密钥
如何让 Parse 同时校验签名和过期时间?
JWT 库本身支持自动校验 exp(过期)、iat(签发)、nbf(生效)等标准声明,但前提是:1)签名已通过;2)启用 VerifyExp 等选项。默认情况下,Parse 不强制校验 exp,哪怕 token 已过期也会成功解析。
必须手动启用验证逻辑,最稳妥的方式是用 jwt.WithValidMethods + jwt.WithExpiration 配合 ParseWithClaims。
- 用
jwt.ParseWithClaims替代Parse,并传入自定义jwt.MapClaims或结构体 - 传入
jwt.WithExpiration(true)才会触发exp校验(注意:不是默认行为) - 推荐搭配
jwt.WithValidMethods([]string{"HS256"})显式限定算法,防 alg=none 攻击 - 校验失败时,错误类型通常是
*jwt.ValidationError,可判断Errors字段区分原因
一个安全可用的解析函数长什么样?
下面是一个最小可行、兼顾签名与有效期校验的函数,适用于 HS256 场景:
func ParseAndValidateToken(tokenString string, secret []byte) (jwt.MapClaims, error) {
token, err := jwt.ParseWithClaims(
tokenString,
jwt.MapClaims{},
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return secret, nil
},
jwt.WithExpiration(true),
jwt.WithValidMethods([]string{"HS256"}),
)
if err != nil {
return nil, err
}
if !token.Valid {
return nil, fmt.Errorf("token is invalid")
}
return token.Claims.(jwt.MapClaims), nil
}
关键点:它拒绝非 HS256 算法、强制验 exp、且不接受 token.Valid == false 的结果。别省略最后那个 !token.Valid 判断——ParseWithClaims 即使校验失败也可能返回非 nil token 对象。
常见错误现象和调试提示
遇到 “token parsed but expired” 却没报错?大概率是忘了 jwt.WithExpiration(true)。遇到 “signature is invalid” 却返回了 claims?说明 keyFunc 返回了 nil 或密钥不匹配。还有几个典型陷阱:
- 时间偏差:服务器时间不准会导致
exp校验失败,建议用 NTP 同步或设宽松的Leeway(如jwt.WithLeeway(60)) - base64 编码问题:token 中的点号(
.)被 URL 转义(如%2E),需先解码再传入Parse - secret 类型错误:HS256 要
[]byte,误传string会导致验签失败(Go 不自动转换) - claims 类型断言失败:用
jwt.MapClaims解析后,务必用token.Claims.(jwt.MapClaims)断言,而非直接当map[string]interface{}用
真正麻烦的从来不是写几行解析代码,而是确保每一步校验都按预期触发——尤其是签名和 exp 这两个环节,任何一个被绕过,整个认证就形同虚设。











