4. JWT令牌原理与生成

JWT(JSON Web Token)是权限审计网关中最常用的令牌格式。它通过自包含的声明结构,在客户端与服务端之间安全地传递用户身份与权限信息。本章将深入解析JWT的结构、签名算法,并演示如何在Go中生成与验证JWT。

4.1 JWT结构:Header / Payload / Signature

一个JWT由三个部分组成,以点号(.)分隔:Header.Payload.Signature。每个部分都是Base64URL编码的JSON字符串。

部分 名称 内容 是否加密
Header 头部 令牌类型(typ)和签名算法(alg) 仅Base64编码,不加密
Payload 载荷 声明(Claims):用户ID、角色、过期时间等 仅Base64编码,不加密
Signature 签名 对Header和Payload的签名,防止篡改 由密钥或私钥生成
重要提示:JWT默认只签名不加密。Payload中的敏感信息(如密码、身份证号)不应直接放入JWT,除非使用JWE(JSON Web Encryption)进行加密。

4.1.1 Header(头部)

Header通常包含两个字段:

  • alg:签名算法,如 HS256、RS256、ES256
  • typ:令牌类型,固定为 JWT
{
  "alg": "RS256",
  "typ": "JWT"
}

4.1.2 Payload(载荷)

Payload包含声明(Claims),分为三类:

  • 注册声明(Registered Claims):预定义的标准字段,如 iss(签发者)、sub(主题)、exp(过期时间)、iat(签发时间)
  • 公共声明(Public Claims):自定义字段,需在IANA注册或使用命名空间避免冲突
  • 私有声明(Private Claims):双方约定的自定义字段,如 role、permissions
{
  "sub": "1234567890",
  "name": "张三",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

4.1.3 Signature(签名)

签名由以下公式生成:

signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

对于RS256算法,签名使用私钥进行RSA签名,验证时使用公钥。

JWT令牌结构示意图 Header alg: "RS256" typ: "JWT" Payload sub: "用户ID" exp: 过期时间 Signature HMACSHA256 或 RSASHA256 JWT 最终格式 Base64URL(Header) . Base64URL(Payload) . Base64URL(Signature) 示例:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.XxYz...签名

4.2 RS256 与 HS256 算法对比

HS256(HMAC with SHA-256)和 RS256(RSA with SHA-256)是最常用的两种JWT签名算法。它们在密钥管理、安全性和性能上有显著差异。

对比维度 HS256(对称) RS256(非对称)
密钥类型 单一共享密钥 私钥签名 + 公钥验证
密钥分发 需安全通道共享密钥 公钥可公开分发
签名速度 快 慢(私钥签名)
验证速度 快 快(公钥验证)
安全性风险 密钥泄露可伪造任意令牌 私钥泄露风险低,公钥泄露不影响
适用场景 单体应用、内部服务 微服务、第三方认证、分布式系统
安全警告:在微服务架构中,强烈建议使用RS256。每个服务只需持有公钥即可验证令牌,无需共享密钥。如果使用HS256,任何一个服务被攻破,整个系统的令牌信任体系都会崩溃。

4.3 Go中生成JWT

Go生态中最流行的JWT库是 github.com/golang-jwt/jwt/v5。下面分别演示HS256和RS256的令牌生成。

4.3.1 使用HS256生成JWT

package main

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

func main() {
    // 1. 定义共享密钥
    secret := []byte("your-256-bit-secret-key-here")

    // 2. 创建声明(Claims)
    claims := jwt.MapClaims{
        "sub":  "user-001",
        "name": "张三",
        "role": "admin",
        "iat":  time.Now().Unix(),
        "exp":  time.Now().Add(2 * time.Hour).Unix(),
    }

    // 3. 创建令牌
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // 4. 签名生成完整JWT字符串
    tokenString, err := token.SignedString(secret)
    if err != nil {
        panic(err)
    }

    fmt.Println("HS256 JWT:", tokenString)
}

4.3.2 使用RS256生成JWT

RS256需要先生成RSA密钥对。可以使用OpenSSL生成,也可以在Go中动态生成。

package main

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

func main() {
    // 1. 生成RSA密钥对(生产环境应使用持久化密钥)
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        panic(err)
    }

    // 2. 创建声明
    claims := jwt.MapClaims{
        "sub":  "user-001",
        "name": "张三",
        "role": "admin",
        "iat":  time.Now().Unix(),
        "exp":  time.Now().Add(2 * time.Hour).Unix(),
    }

    // 3. 使用RS256签名方法创建令牌
    token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

    // 4. 使用私钥签名
    tokenString, err := token.SignedString(privateKey)
    if err != nil {
        panic(err)
    }

    fmt.Println("RS256 JWT:", tokenString)

    // 5. 公钥用于验证(可安全分发给其他服务)
    publicKey := &privateKey.PublicKey
    _ = publicKey // 实际使用中传递给验证函数
}

4.3.3 验证JWT令牌

func verifyJWT(tokenString string, key interface{}) (*jwt.Token, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // 验证签名算法
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
            // HS256:返回共享密钥
            return []byte("your-256-bit-secret-key-here"), nil
        }
        if _, ok := token.Method.(*jwt.SigningMethodRSA); ok {
            // RS256:返回公钥
            return publicKey, nil
        }
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    })

    if err != nil {
        return nil, err
    }

    if !token.Valid {
        return nil, fmt.Errorf("invalid token")
    }

    return token, nil
}
最佳实践:
  • 始终在验证函数中检查 token.Method,防止算法混淆攻击(攻击者将RS256改为HS256并用公钥签名)
  • 设置合理的 exp(过期时间),建议15分钟到2小时
  • 使用 jwt.NewParser() 并设置 WithValidMethods() 明确指定允许的算法

4.4 令牌生命周期与安全策略

阶段 操作 说明
签发 用户登录成功后生成JWT 包含用户ID、角色、过期时间
传输 通过HTTP Header: Authorization: Bearer <token> 必须使用HTTPS防止中间人攻击
验证 网关或服务端解析并验证签名 检查签名、过期时间、签发者
刷新 使用Refresh Token获取新的Access Token 避免频繁登录,同时限制Access Token有效期
撤销 加入黑名单或使用短有效期 JWT本身无状态,撤销需额外机制
JWT令牌生命周期 ① 签发 用户登录成功 ② 传输 Bearer Token ③ 验证 签名+过期检查 ④ 刷新 Refresh Token 撤销机制 JWT无状态,撤销需使用黑名单或短有效期+刷新令牌 推荐:Access Token 15分钟 + Refresh Token 7天

4.5 本章小结

  • JWT由Header、Payload、Signature三部分组成,以点号分隔
  • HS256使用共享密钥,适合单体应用;RS256使用公私钥对,适合分布式系统
  • Go中使用 golang-jwt/jwt/v5 库可以方便地生成和验证JWT
  • 验证时必须检查签名算法,防止算法混淆攻击
  • JWT本身无状态,撤销需要额外的黑名单或短有效期策略