You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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应该验证失败")
    }
}

最后再划重点:

  1. 绝对不要用ParseUnverified做生产环境的验证;
  2. 验证时必须检查err和token.Valid,还要验证签名算法;
  3. Claims必须用强类型结构体,不能用索引访问;
  4. JWT里绝对不能存敏感信息(比如密码),它只是身份标识,不是存储工具!

内容的提问来源于stack exchange,提问作者user3162553

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:27:40