使用jwt-go库时,错误参数下JWT为何始终验证通过?
解决jwt-go验证任意Token都通过的问题 + 新版Claims使用指南
兄弟,我之前也踩过jwt-go的坑!你遇到的“不管输入什么Token都验证通过”的问题,大概率是验证逻辑漏了核心校验步骤,再加上新版本库的Claims结构改了,老文档确实坑人,我给你一步步捋清楚:
一、为什么任意Token都能通过验证?
最常见的几个坑:
- 用了
ParseUnverified而不是正经的解析方法:这个方法是官方给调试用的,直接跳过了签名验证,所以不管是啥Token都能解析成功,绝对不能在生产代码里用! - 没检查解析错误和Token有效性:调用
Parse/ParseWithClaims之后,直接忽略返回的err,也不判断token.Valid,相当于默认所有Token都合法; - 签名验证逻辑缺失:比如解析时没传入密钥,或者没验证签名算法是否符合预期(防止攻击者篡改算法为无签名的
none); - 错误地把密码放进了Claims:先插一句,JWT是Base64编码的,不是加密的!任何人都能解码看到里面的内容,密码绝对不能存在Claims里!你应该在登录时验证密码,通过后签发包含用户标识(比如邮箱、ID)的Token,后续用Token识别用户,而不是在Token里存密码再验证。
二、新版jwt-go的Claims怎么用?
新版本(v4+)已经废弃了老版本的Claims索引操作(比如claims["email"]),必须用强类型结构体来定义自定义Claims,嵌套官方的标准Claims:
// 定义你的自定义Claims结构体,嵌套标准RegisteredClaims(v4用这个,v3是StandardClaims) type CustomClaims struct { Email string `json:"email"` // 你的自定义字段,比如用户邮箱、ID等,别放敏感信息! jwt.RegisteredClaims }
三、正确的签发+验证代码示例
1. 生成Token
func GenerateToken(email string) (string, error) { claims := CustomClaims{ Email: email, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 设置过期时间 Issuer: "your-app-name", // 签发者,验证时可以校验这个字段 }, } // 使用HS256算法,传入你的密钥(要和验证时完全一致) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte("your-strong-secret-key")) // 密钥要足够复杂,别用明文! }
2. 验证Token(核心!)
func ValidateToken(tokenString string) (*CustomClaims, error) { // 解析时传入自定义Claims的指针,以及签名验证回调函数 token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, 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 []byte("your-strong-secret-key"), nil }) // 第二步:检查解析和签名验证的错误 if err != nil { return nil, err } // 第三步:检查Token是否有效(包括过期时间、签发者等标准Claims的校验) if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { // 这里还可以加自定义字段的校验,比如邮箱是否存在于你的数据库? return claims, nil } else { return nil, fmt.Errorf("invalid token") } }
四、测试用例怎么写?
你之前的测试用例之所以失败,就是因为验证逻辑没做全校验,现在用上面的代码写测试:
func TestValidateToken(t *testing.T) { // 测试合法Token validToken, err := GenerateToken("test@example.com") if err != nil { t.Fatalf("生成合法Token失败: %v", err) } claims, err := ValidateToken(validToken) if err != nil { t.Errorf("合法Token应该通过验证: %v", err) } if claims.Email != "test@example.com" { t.Errorf("预期邮箱是test@example.com,实际是%s", claims.Email) } // 测试伪造的Token(随便编的无效签名) fakeToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZha2VAZXhhbXBsZS5jb20ifQ.InvalidSignature" _, err = ValidateToken(fakeToken) if err == nil { t.Error("伪造的Token应该验证失败") } // 测试过期的Token expiredClaims := CustomClaims{ Email: "test@example.com", RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(-24 * time.Hour)), // 已过期 Issuer: "your-app-name", }, } expiredToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, expiredClaims).SignedString([]byte("your-strong-secret-key")) if err != nil { t.Fatalf("生成过期Token失败: %v", err) } _, err = ValidateToken(expiredToken) if err == nil { t.Error("过期的Token应该验证失败") } }
最后再划重点:
- 绝对不要用
ParseUnverified做生产环境的验证; - 验证时必须检查
err和token.Valid,还要验证签名算法; - Claims必须用强类型结构体,不能用索引访问;
- JWT里绝对不能存敏感信息(比如密码),它只是身份标识,不是存储工具!
内容的提问来源于stack exchange,提问作者user3162553
相关产品推荐
相关产品推荐

