Go语言中使用JWT

  • ~5.71K 字
  1. 1. JWT介绍
    1. 1.1. 定义
    2. 1.2. 结构
    3. 1.3. 使用场景
    4. 1.4. 工作原理
  2. 2. 在Go语言中使用JWT

JWT介绍

定义

JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。由于该信息经过数字签名,因此可以被验证和信任。

结构

JWT的紧凑形式由三部分组成,每部分经过 Base64Url 编码后以点号( . )连接,分别是:

  • Header( 头部 )
    包含令牌类型 ( typ ) 和签名算法 ( alg ) ,例如:

    1
    2
    3
    4
    {
    "alg": "HS256",
    "typ": "JWT"
    }

    该JSON经过Base64Url编码,形成 JWT 的第一部分( 如 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 )。

  • Payload( 载荷 )
    包含声明 ( claims ) ,即需要传递的数据。声明分为三类:

    • ​​Registered Claims(注册声明)​​(非强制但推荐使用):
    • Public Claims(公共声明)​​:可自定义,但需避免冲突(建议使用 IANA 注册或 URI 命名空间)。
    • Private Claims(私有声明)​​:双方协商的自定义数据。

    示例:

    1
    2
    3
    4
    5
    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }

    该 JSON 会经过 ​​Base64Url​​ 编码,形成 JWT 的第二部分( 如 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ )。

    签名仅防篡改,​​不加密​​!头部和载荷信息可被任何人解码查看,敏感数据需额外加密。

  • Signature( 签名 )​
    用于验证令牌完整性和真实性。生成方式依赖头部指定的算法:

    • ​​HMAC SHA256​​ 示例:
    1
    2
    3
    4
    HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secretKey
    )
    • ​​RSA​​ 示例(使用私钥签名,公钥验证):
    1
    2
    3
    4
    RSASHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    privateKey
    )

    签名结果经过 ​​Base64Url​​ 编码,形成 JWT 的第三部分(如 SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c)。

完整JWT示例

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

解码后

  • Header: {“alg”:”HS256”,”typ”:”JWT”}
  • Payload: {“sub”:”1234567890”,”name”:”John Doe”,”iat”:1516239022}
  • Signature: 使用密钥验证前两部分是否被篡改

使用场景

  • Authorization ( 授权 )
    JWT 最常见的用途。用户登录后,每个后续请求都会携带 JWT,允许用户访问与该令牌匹配的路由、服务和资源。如今,​​单点登录(Single Sign-On, SSO)​​ 广泛使用 JWT,因为它开销小,且能轻松跨域使用。

  • Information Exchange ( 信息交换 )
    JWT 是在各方之间安全传输信息的一种优秀方式。由于 JWT 可以签名(例如使用公私钥对),因此可以确保发送者的身份真实可信。此外,由于签名是基于头部(header)和载荷(payload)计算的,还能验证内容是否被篡改。

工作原理

在身份验证过程中,当用户使用凭据成功登录后,系统会返回一个 JSON Web Token(JWT)。由于令牌本身就是凭证,必须格外注意防范安全问题。一般来说,令牌的保存时间不应超过必要期限。同时,由于浏览器存储缺乏安全性,不应在其中保存敏感的会话数据。

当用户需要访问受保护的路由或资源时,用户代理(如浏览器)应当发送 JWT,通常是通过 Authorization 头部使用 Bearer 模式进行传输。头部内容格式如下:

1
Authorization: Bearer <token>

在某些情况下,这可以成为一种无状态的授权机制。服务器的受保护路由会检查 Authorization 头部中的有效 JWT,如果存在有效令牌,则允许用户访问受保护资源。如果JWT包含必要数据,可能会减少某些操作对数据库查询的需求,不过实际情况可能有所不同。

参考: Introduction to JSON Web Tokens

在Go语言中使用JWT

  • 添加 jwt-go 作为依赖项

    1
    go get -u github.com/golang-jwt/jwt/v5
  • 导入至代码中

    1
    import "github.com/golang-jwt/jwt/v5"
  • 定义安全密钥以及 Claims 结构体

    1
    2
    3
    4
    5
    6
    7
    8
    var secret = []byte("your secret here")  // 从安全配置中读取

    // 自定义JWT Claims结构体, 嵌入了标准Claims
    type MyClaims struct {
    UserID uint64 `json:"user_id"`
    UserName string `json:"user_name"`
    jwt.StandardClaims
    }
  • 生成 Token 的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    func GenToken(user_id uint64, user_name string) (string, error) {
    c := MyClaims{
    UserID: user_id,
    UserName: user_name,
    StandardClaims: jwt.StandardClaims{
    ExpiresAt: ***, // 过期时间,强制 Token 在一段时间后失效,减少泄露后的风险窗口
    Issuer: ***, // 签发人,标识 Token 的签发主体(通常是服务端应用或授权服务器)
    },
    }
    // 使用指定的签名方法创建签名对象
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
    // 使用指定的secret签名并获得完整的编码后的字符串token
    return token.SignedString(secret)
    }
  • 解析 Token 的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func ParseToken(tokenString string) (*MyClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(t *jwt.Token) (interface{}, error) {
    return secret, nil
    })
    if err != nil {
    return nil, err
    }
    if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
    return claims, nil
    }
    return nil, errors.New("invalid token")
    }
  • 实现基于 JWT 的 Gin 中间件,验证请求中的 Token 并提取用户信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    type contextKey string

    const (
    ContextUserIDKey contextKey = "userID"
    )

    func JWTAuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
    // 1. 从多种位置获取 Token
    tokenString := extractToken(c)
    if tokenString == "" {
    c.JSON(http.StatusUnauthorized, gin.H{
    "code": 401,
    "message": "Authorization token is required",
    })
    c.Abort()
    return
    }

    // 2. 解析并验证 Token
    claims, err := jwt.ParseToken(tokenString)
    if err != nil {
    log.Printf("JWT validation failed: %v", err)
    c.JSON(http.StatusUnauthorized, gin.H{
    "code": 401,
    "message": "Invalid or expired token",
    })
    c.Abort()
    return
    }

    // 3. 存储用户信息到上下文
    c.Set(string(ContextUserIDKey), claims.UserID)
    c.Next()
    }
    }

    // 支持从 Header/Cookie/Query 提取 Token
    func extractToken(c *gin.Context) string {
    // 1. 从 Authorization Header 获取
    authHeader := strings.TrimSpace(c.GetHeader("Authorization"))
    if authHeader != "" {
    parts := strings.SplitN(authHeader, " ", 2)
    if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
    return parts[1]
    }
    }

    // 2. 从 Query 参数获取
    if token := c.Query("token"); token != "" {
    return token
    }

    // 3. 从 Cookie 获取
    if token, err := c.Cookie("token"); err == nil {
    return token
    }

    return ""
    }
赞助喵
非常感谢您的喜欢!
赞助喵
分享这一刻
让朋友们也来瞅瞅!