3、Token基础:JWT结构解析(Header/Payload/Signature)、HS256与RS256算法对比

JSON Web Token(JWT)是当前 Web 授权中最常用的自包含令牌格式。理解 JWT 的三段式结构以及签名算法的差异,是进行 Token 自省与撤销端点测试的前提。本节将深入解析 JWT 的 Header、Payload、Signature 三个组成部分,并对比对称算法 HS256 与非对称算法 RS256 在安全模型、性能与适用场景上的核心区别。

3.1 JWT 三段式结构

一个标准的 JWT 由三个 Base64URL 编码的片段组成,以点号(.)分隔:Header.Payload.Signature。解码后,每一部分都是一个 JSON 对象。

部分 名称 内容说明 是否加密
第一部分 Header(头部) 令牌类型(typ)和签名算法(alg) 仅 Base64 编码,明文可读
第二部分 Payload(载荷) 声明(Claims),如用户 ID、角色、过期时间 仅 Base64 编码,明文可读
第三部分 Signature(签名) 对前两部分的签名,用于防篡改 二进制签名数据,不可逆
安全提示:Payload 中的敏感信息(如密码、信用卡号)不应以明文存放。JWT 的 Payload 仅经过 Base64 编码,并非加密。如需保护载荷内容,应使用 JWE(JSON Web Encryption)。

3.2 Header 与 Payload 详解

Header(头部)

Header 通常包含两个字段:alg(签名算法)和 typ(令牌类型)。示例:

{
  "alg": "RS256",
  "typ": "JWT"
}
  • alg:指定签名算法,常见值有 HS256、RS256、ES256。
  • typ:固定为 JWT,表示这是一个 JWT 令牌。

Payload(载荷)

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

  • 注册声明(Registered Claims):预定义的标准字段,如 iss(签发者)、sub(主题)、aud(受众)、exp(过期时间)、iat(签发时间)。
  • 公共声明(Public Claims):由使用方自定义,需在 IANA JSON Web Token Claims 注册表中定义,或使用命名空间避免冲突。
  • 私有声明(Private Claims):双方协商的字段,如 user_id、role。

示例 Payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "admin"
}

3.3 Signature 生成原理

Signature 是对 Header 和 Payload 的完整性保护。生成过程如下:

  1. 将 Header 和 Payload 分别进行 Base64URL 编码。
  2. 将编码后的字符串用点号连接:base64UrlEncode(header).base64UrlEncode(payload)。
  3. 使用指定的签名算法(HS256 或 RS256)和密钥对上述字符串进行签名。
  4. 将签名结果进行 Base64URL 编码,作为 JWT 的第三部分。
验证要点:接收方使用相同的算法和密钥重新计算签名,并与 JWT 中的签名比对。若不一致,则令牌无效。
JWT 三段式结构解析 JWT 三段式结构解析 Header(头部) alg: RS256 typ: JWT Payload(载荷) sub: 1234567890 exp: 1516242622 Signature(签名) HMACSHA256( base64(header).base64(payload) JWT 令牌格式 Header.Payload.Signature eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature

3.4 HS256 与 RS256 算法对比

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

对比维度 HS256(对称) RS256(非对称)
密钥类型 单一共享密钥(对称密钥) 私钥签名 + 公钥验证(密钥对)
密钥分发 需安全共享给所有验证方 公钥可公开分发,私钥仅签发方持有
安全性风险 密钥泄露后,攻击者可伪造任意令牌 私钥泄露风险低,公钥泄露不影响安全
性能 签名和验证速度极快(微秒级) 签名较慢(毫秒级),验证较快
适用场景 单一服务内部通信、微服务间信任 多服务、跨组织、第三方验证
密钥轮换 轮换时需同步所有服务 仅需更新公钥,支持动态轮换
常见攻击 密钥暴力破解、算法混淆攻击 公钥注入、弱密钥攻击
安全警告:在 Go 语言实现中,使用 HS256 时务必确保密钥长度 ≥ 256 位(32 字节),且使用 crypto/rand 生成。使用 RS256 时,私钥必须妥善保管,建议使用硬件安全模块(HSM)或密钥管理服务(KMS)。

3.5 算法选择决策树

在实际的 Go Web 授权测试中,选择哪种算法取决于你的架构和安全需求。以下决策树可帮助快速判断:

  • 所有验证方都在同一信任域内? → 选择 HS256(性能优先)
  • 需要第三方或外部服务验证令牌? → 选择 RS256(安全优先)
  • 需要支持密钥动态轮换? → 选择 RS256
  • 令牌签发频率极高,对延迟敏感? → 选择 HS256
  • 合规要求私钥不可泄露? → 选择 RS256
HS256 与 RS256 算法对比架构 HS256 vs RS256 算法对比架构 HS256(对称算法) 签发方 使用共享密钥签名 验证方 使用相同密钥验证 共享密钥(Secret) 需安全分发 RS256(非对称算法) 签发方 使用私钥签名 验证方 使用公钥验证 私钥 仅签发方持有 公钥 可公开分发 RS256 支持公钥分发,适合微服务与第三方验证

3.6 Go 语言中的 JWT 实现要点

在 Go 中,github.com/golang-jwt/jwt/v5 是最主流的 JWT 库。以下是使用 HS256 和 RS256 的核心代码片段:

HS256 签名与验证

import "github.com/golang-jwt/jwt/v5"

// 签名
claims := jwt.MapClaims{
    "sub": "1234567890",
    "exp": time.Now().Add(time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString([]byte("your-256-bit-secret"))

// 验证
parsedToken, err := jwt.Parse(signedToken, 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-256-bit-secret"), nil
})

RS256 签名与验证

import "github.com/golang-jwt/jwt/v5"

// 加载私钥
privateKeyBytes, _ := os.ReadFile("private.pem")
privateKey, _ := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)

// 签名
claims := jwt.MapClaims{
    "sub": "1234567890",
    "exp": time.Now().Add(time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
signedToken, err := token.SignedString(privateKey)

// 加载公钥
publicKeyBytes, _ := os.ReadFile("public.pem")
publicKey, _ := jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)

// 验证
parsedToken, err := jwt.Parse(signedToken, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return publicKey, nil
})
测试要点:在授权测试中,应重点验证以下边界情况:
1. 修改 Header 中的 alg 为 "none" 或 "HS256"(当服务端使用 RS256 时)。
2. 篡改 Payload 中的 sub、exp、role 等关键声明。
3. 使用空密钥或弱密钥进行签名尝试。
4. 验证过期令牌是否被正确拒绝。

3.7 常见攻击与防御

攻击类型 描述 防御措施
算法混淆攻击 将 RS256 令牌的 alg 改为 HS256,使用公钥作为 HMAC 密钥验证 在验证时强制检查算法类型,拒绝非预期算法
none 算法攻击 将 alg 改为 "none",绕过签名验证 禁用 "none" 算法,验证时要求签名非空
密钥暴力破解 对 HS256 令牌进行离线字典攻击 使用强随机密钥(≥256 位),定期轮换
公钥注入 攻击者替换 RS256 公钥为自签名公钥 公钥通过 HTTPS 安全分发,使用固定公钥指纹
重放攻击 捕获有效令牌后重复使用 使用 jti(JWT ID)声明 + 短过期时间 + 令牌撤销列表