好的!我将以更细致的步骤和详细解释,手把手带你掌握这个库的用法。以下是完全从零开始的完整指南:


1. JWT 基础概念

什么是JWT?

  • 全称:JSON Web Token
  • 用途:安全地在两个系统之间传输信息(常用于身份验证)
  • 结构:Header.Payload.Signature(用点分隔的三部分)

示例Token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG5fZG9lIiwiaXNzIjoibXktYXBwIiwiZXhwIjoxNjkzMjYwMDAwfQ.6Q9k_2XnRU0JzrqC7Z4Xqo4T7d8tVv6l1y3w0qWY3M4
  • Header:描述算法和类型(如HS256)
  • Payload:携带的数据(如用户ID、过期时间)
  • Signature:前两部分的签名,防止篡改

2. 安装库

在终端执行:

go get github.com/golang-jwt/jwt/v5

3. 创建第一个JWT

步骤1:导入包

import (
    "fmt"
    "time"
    "github.com/golang-jwt/jwt/v5"
)

步骤2:定义Claims结构

// 自定义Claims结构
type MyClaims struct {
    UserID int `json:"user_id"` // 自定义字段
    jwt.RegisteredClaims        // 内嵌标准字段(如过期时间、签发者等)
}
  • RegisteredClaims 包含标准字段:

    • ExpiresAt:过期时间
    • Issuer:签发者
    • Subject:主题
    • 等等

步骤3:生成Token

func generateToken() string {
    // 1. 准备密钥(重要!实际使用要保密)
    secretKey := []byte("your-256-bit-secret")

    // 2. 创建Claims(数据载体)
    claims := MyClaims{
        UserID: 123, // 自定义数据
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), // 1小时后过期
            Issuer:    "my-server", // 签发者标识
        },
    }

    // 3. 创建Token对象
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // 4. 生成签名字符串
    signedToken, err := token.SignedString(secretKey)
    if err != nil {
        panic(err) // 实际应返回错误
    }
    return signedToken
}

4. 解析并验证JWT

步骤1:解析函数

func parseToken(tokenString string) (*MyClaims, error) {
    // 1. 定义用于接收数据的Claims对象
    claims := &MyClaims{}

    // 2. 解析Token
    parsedToken, err := jwt.ParseWithClaims(
        tokenString,
        claims,
        func(t *jwt.Token) (interface{}, error) {
            // 验证签名算法是否正确
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
            }
            return []byte("your-256-bit-secret"), nil
        },
        jwt.WithLeeway(5*time.Second), // 允许5秒时间误差
    )

    // 3. 处理错误
    if err != nil {
        return nil, err
    }

    // 4. 验证Claims是否有效
    if !parsedToken.Valid {
        return nil, fmt.Errorf("invalid token")
    }

    return claims, nil
}

步骤2:使用解析函数

func main() {
    // 生成Token
    token := generateToken()
    fmt.Println("Generated Token:", token)

    // 解析Token
    claims, err := parseToken(token)
    if err != nil {
        fmt.Println("解析失败:", err)
        return
    }

    fmt.Printf("用户ID: %d, 签发者: %s\n", claims.UserID, claims.Issuer)
}

5. 关键点详解

签名方法选择

  • HS256(常用):对称加密,服务端用同一个密钥签名和验证
  • RS256:非对称加密,用私钥签名,公钥验证(更安全)
  • 其他支持的方法:ES256, EdDSA等

Claims验证细节

  • 自动验证的标准字段:

    • ExpiresAt:检查是否过期
    • NotBefore:是否已生效
    • Issuer:签发者是否匹配(需配合jwt.WithIssuer()选项)
  • 自定义验证:通过jwt.WithValidator()添加

错误处理示例

claims, err := parseToken(invalidToken)
if err != nil {
    switch {
    case errors.Is(err, jwt.ErrTokenMalformed):
        fmt.Println("根本不是有效的JWT格式")
    case errors.Is(err, jwt.ErrTokenExpired):
        fmt.Println("Token已过期")
    case errors.Is(err, jwt.ErrTokenSignatureInvalid):
        fmt.Println("签名不匹配,可能被篡改")
    default:
        fmt.Println("其他错误:", err)
    }
    return
}

6. 完整可运行示例

package main

import (
    "errors"
    "fmt"
    "time"
    "github.com/golang-jwt/jwt/v5"
)

type MyClaims struct {
    UserID int `json:"user_id"`
    jwt.RegisteredClaims
}

func main() {
    // 生成Token
    secret := []byte("your-secret-key-here")
    tokenString, err := createToken(secret)
    if err != nil {
        panic(err)
    }
    fmt.Println("生成Token:", tokenString)

    // 解析验证
    claims, err := parseAndValidate(tokenString, secret)
    if err != nil {
        fmt.Println("验证失败:", err)
        return
    }
    fmt.Printf("验证成功!用户ID: %d, 签发者: %s\n", claims.UserID, claims.Issuer)
}

func createToken(secret []byte) (string, error) {
    claims := MyClaims{
        UserID: 1001,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
            Issuer:    "my-auth-server",
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(secret)
}

func parseAndValidate(tokenString string, secret []byte) (*MyClaims, error) {
    claims := &MyClaims{}
    token, err := jwt.ParseWithClaims(
        tokenString,
        claims,
        func(t *jwt.Token) (interface{}, error) {
            if t.Method.Alg() != jwt.SigningMethodHS256.Name {
                return nil, errors.New("invalid signing algorithm")
            }
            return secret, nil
        },
        jwt.WithIssuer("my-auth-server"),
    )
    if err != nil {
        return nil, err
    }
    if !token.Valid {
        return nil, errors.New("invalid token")
    }
    return claims, nil
}

7. 常见问题解决

Q1:如何添加自定义字段?

在自定义Claims结构体中添加字段即可:

type MyClaims struct {
    Username string `json:"username"`
    Role     string `json:"role"`
    jwt.RegisteredClaims
}

Q2:如何处理过期时间?

  • 生成时设置ExpiresAt
  • 解析时自动验证过期,如果过期会返回jwt.ErrTokenExpired

Q3:密钥泄露怎么办?

  • 立即更换新密钥
  • 使所有基于旧密钥的Token失效
  • 推荐使用环境变量存储密钥,不要硬编码在代码中

8. 安全最佳实践

  1. 永远不要在客户端存储敏感数据(如密码)
  2. 使用足够强度的密钥(HS256至少32字节随机字符串)
  3. 设置合理的过期时间(如15分钟-1小时)
  4. 启用HTTPS防止Token被窃听
  5. 考虑使用刷新令牌(Refresh Token)机制

通过这个详细指南,你应该能够:

  1. 理解JWT的基本工作原理
  2. 使用golang-jwt/jwt/v5生成安全的Token
  3. 正确解析和验证收到的Token
  4. 处理各种常见错误情况

实际开发时,建议将密钥管理、错误处理等封装成独立的安全模块。

每日更新-免费小火箭账号
不要错过任何机会,探索最新的应用和游戏,就在我们的平台。
立即访问
最后修改:2025 年 03 月 30 日
如果觉得我的文章对你有用,请随意赞赏