对话 LLM:JWT 原理

序言

『对话 LLM』是指在与 LLM 聊天工具(如 DeepSeek、ChatGPT)的对话中学习知识。通过 Q&A 的方式学习,效率更高。『对话 LLM』的博文内容主要源自于我与 LLM 聊天工具的对话。

本文讲述了 JWT 的原理、工作流程、优缺点。

Q1. JWT 原理

Q:

介绍一下 JWT 的原理

A:

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络应用间安全地传输信息。其核心原理是通过数字签名或加密确保信息的完整性和可信性,常用于身份验证和授权。以下是JWT的详细原理和工作流程:


1. JWT的结构 JWT由三部分组成,用点(.)分隔,形式为:Header.Payload.Signature 。示例:xxxxx.yyyyy.zzzzz

1.1 Header(头部)

  • 作用:声明令牌类型(JWT)和签名算法(如HMAC SHA256或RSA)。
  • 格式:JSON对象经过Base64Url编码。
  • 示例:
    1
    2
    3
    4
    {
    "alg": "HS256", // 签名算法
    "typ": "JWT" // 令牌类型
    }

1.2 Payload(负载)

  • 作用:携带实际需要传递的数据(称为“声明”,Claims)。
  • 格式:JSON对象经过Base64Url编码。
  • 声明类型
    • 预注册声明(Registered Claims):标准字段,如iss(签发者)、exp(过期时间)、sub(主题)等。
    • 公共声明(Public Claims):可自定义,但需避免冲突。
    • 私有声明(Private Claims):双方协商的字段。
  • 示例:
    1
    2
    3
    4
    5
    {
    "sub": "user123",
    "name": "Alice",
    "exp": 1620000000 // 过期时间戳
    }

1.3 Signature(签名)

  • 作用:验证消息未被篡改,确保发送方的可信性。
  • 生成方式:将Base64Url编码后的Header和Payload用.拼接,再通过Header中指定的算法和密钥(Secret)生成签名。
  • 示例(使用HS256算法):
    1
    2
    3
    4
    HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secretKey
    )

2. JWT的工作流程

  1. 用户登录:客户端提交身份信息(如用户名密码)到服务器。

  2. 生成JWT:服务器验证身份后,生成JWT:

    • 构造Header和Payload。
    • 用密钥生成签名,组合三者形成完整的JWT。
  3. 返回JWT给客户端:客户端将JWT存储在本地(如LocalStorage或Cookie)。

  4. 客户端携带JWT请求资源 :后续请求在Authorization头中以Bearer模式携带JWT:

    1
    Authorization: Bearer <JWT>
  5. 服务器验证JWT

    • 解码Header和Payload,检查签名是否有效(防止篡改)。
    • 验证声明(如exp是否过期、iss是否合法)。
    • 验证通过后,返回请求的资源。

3. JWT的安全性

  • 信息暴露:Header和Payload仅经过Base64编码,未加密,敏感信息(如密码)不应放入。
  • 传输安全:必须通过HTTPS传输,防止中间人攻击。
  • 密钥管理
    • 对称算法(如HS256):需严格保护密钥。
    • 非对称算法(如RS256):用私钥签名,公钥验证,更安全。
  • 防篡改:签名确保数据完整性,但需防止密钥泄露。

4. JWT的优缺点

优点

  • 无状态:服务端无需存储会话信息,适合分布式系统。
  • 跨域支持:可轻松用于跨域场景(如微服务、单点登录)。
  • 灵活性:Payload可自定义扩展数据。

缺点

  • 无法废止:签发后有效期结束前无法强制失效(需结合Token黑名单或短有效期)。
  • 存储风险:客户端存储可能被XSS攻击窃取(建议用HttpOnly Cookie)。

5. 典型应用场景

  1. 用户身份认证(如OAuth 2.0的Bearer Token)。
  2. 服务间API鉴权(微服务架构)。
  3. 单点登录(SSO)。

通过上述原理,JWT以紧凑、自包含的方式实现了安全的信息传输,成为现代Web开发中广泛使用的身份验证方案。

Q2. 哪些部分是明文?

Q:

三部分中哪些部分对于用户来说相当于是明文?

A:

在 JWT 的三部分(Header、Payload、Signature)中,Header 和 Payload 对用户来说是明文,而 Signature 是加密签名,无法直接解读。以下是详细解释:


1. Header 和 Payload 是明文

  • 原因:Header 和 Payload 仅经过 Base64Url 编码(一种 URL 安全的编码方式),并未加密

  • 特点

    • 可逆性:任何人均可通过 Base64Url 解码工具(如 jwt.io)直接解码,得到原始的 JSON 数据。
    • 透明性:解码后的内容可被直接查看,例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // Header 解码后:
      {
      "alg": "HS256",
      "typ": "JWT"
      }

      // Payload 解码后:
      {
      "sub": "user123",
      "name": "Alice",
      "exp": 1620000000
      }
  • 风险

    • 敏感信息泄露:如果在 Payload 中存储了敏感数据(如用户 ID、邮箱、权限),攻击者可轻易读取。
    • 篡改风险:虽然可被解码和修改,但无法伪造有效签名(需依赖密钥)。

2. Signature 是密文

  • 原因:Signature 是 Header 和 Payload 的加密签名,由密钥和指定算法(如 HS256、RS256)生成。
  • 特点
    • 不可逆性:无法通过解码直接获取原始信息,其本质是哈希值或加密后的二进制数据。
    • 防篡改作用:服务器通过验证 Signature 确保 Header 和 Payload 未被篡改。

3. 用户视角的示例

假设一个 JWT 如下:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6IkFsaWNlIiwiZXhwIjoxNjIwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • HeadereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 。解码后:

    1
    { "alg": "HS256", "typ": "JWT" }
  • PayloadeyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6IkFsaWNlIiwiZXhwIjoxNjIwMDAwMDAwfQ 。解码后:

    1
    { "sub": "user123", "name": "Alice", "exp": 1620000000 }
  • SignatureSflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 。无法直接解码,需用密钥验证。


4. 安全建议

  1. 避免敏感数据:不要在 Header 或 Payload 中存储密码、密钥等敏感信息。
  2. 使用 HTTPS:防止 JWT 在传输中被窃取。
  3. 短有效期:通过 exp 字段设置合理过期时间,减少泄露风险。
  4. 加密敏感字段:如需存储敏感数据,可对 Payload 进行额外加密(如 JWE)。

总结

  • 明文部分:Header 和 Payload(Base64Url 编码,可逆)。
  • 密文部分:Signature(加密签名,不可逆)。
  • 核心安全机制:依赖 Signature 防止数据篡改,而非隐藏数据本身。

Q3. 哈希与加密

Q:

所以 Signature 会将 Header + Payload先哈希,再对哈希值进行加密? 服务端收到 Signature 后,先解密,再验证哈希值是否正确?

A:

你的问题涉及 JWT 签名生成和验证的核心机制,但需要明确区分 哈希(Hash)加密(Encryption) 的概念。不同签名算法(如 HS256RS256)的实现方式不同,具体流程如下:


1. JWT 签名的生成过程

(1) HMAC 算法(如 HS256)

  • 核心机制:基于密钥的哈希(无需加密)。
  • 步骤
    1. 将 Header 和 Payload 进行 Base64Url 编码,并用 . 拼接(得到 header_b64.payload_b64)。
    2. 对拼接后的字符串计算哈希:使用 HMAC + 哈希算法(如 SHA256)和密钥生成消息认证码(MAC)。
    3. 生成签名:将哈希结果进行 Base64Url 编码,得到 Signature。
  • 公式
    1
    Signature = Base64UrlEncode(HMAC_SHA256(header_b64.payload_b64, secret_key))

(2) RSA 算法(如 RS256)

  • 核心机制:基于非对称加密的签名(涉及加密)。
  • 步骤
    1. 将 Header 和 Payload 进行 Base64Url 编码并拼接(header_b64.payload_b64)。
    2. 对拼接后的字符串计算哈希:使用哈希算法(如 SHA256)生成哈希值。
    3. 加密哈希值:用私钥对哈希值进行加密(即数字签名)。
    4. 生成签名:将加密后的结果进行 Base64Url 编码,得到 Signature。
  • 公式
    1
    Signature = Base64UrlEncode(RSA_Encrypt(SHA256(header_b64.payload_b64), private_key))

2. 服务端的验证过程

服务端收到 JWT 后,验证签名的逻辑如下:

(1) HMAC 算法(HS256)

  • 无需解密:因为 HMAC 是密钥哈希,不是加密。
  • 验证步骤
    1. 重新计算 header_b64.payload_b64 的 HMAC 哈希(使用相同的密钥)。
    2. 将计算结果与客户端传来的 Signature 解码后的值对比。
    3. 若一致,则数据未被篡改。

(2) RSA 算法(RS256)

  • 需要“解密”:这里的“解密”实质是验证签名。
  • 验证步骤
    1. 用公钥对 Signature 解码后的密文进行解密,得到原始哈希值。
    2. 重新计算 header_b64.payload_b64 的哈希值(如 SHA256)。
    3. 对比解密后的哈希值与重新计算的哈希值。
    4. 若一致,则数据未被篡改。

3. 关键区别总结

算法类型 哈希(HMAC) 非对称加密(RSA)
签名生成 基于密钥的哈希,无需加密 私钥加密哈希值
签名验证 重新计算哈希对比 公钥解密签名,对比哈希值
密钥管理 对称密钥(服务端保管) 非对称密钥(私钥签名,公钥验证)
安全性 依赖密钥保密性 依赖私钥安全性

4. 回答你的问题

  • 问题 1“Signature 会将 Header + Payload 先哈希,再对哈希值进行加密?”

    • 答案
      • 对于 HMAC(如 HS256)仅哈希,不加密(但哈希过程依赖密钥)。
      • 对于 RSA(如 RS256)先哈希,再加密哈希值(用私钥加密)。
  • 问题 2“服务端收到 Signature 后,先解密,再验证哈希值是否正确?”

    • 答案
      • 对于 HMAC无需解密,直接重新计算哈希对比。
      • 对于 RSA需要解密(用公钥解密签名,得到哈希值后再对比)。

5. 技术误区澄清

  • 哈希 ≠ 加密
    • 哈希是单向不可逆的(如 SHA256),用于确保数据完整性。
    • 加密是双向可逆的(如 RSA),用于保护数据机密性。
  • JWT 签名的本质
    • HMAC 是 密钥化哈希(完整性 + 身份验证)。
    • RSA 是 加密哈希值(完整性 + 非抵赖性)。

6. 实际代码示例(伪代码)

HS256 签名验证

1
2
3
4
5
def verify_hmac(jwt, secret_key):
header_b64, payload_b64, signature_b64 = jwt.split('.')
data = header_b64 + "." + payload_b64
expected_signature = HMAC_SHA256(data, secret_key)
return expected_signature == base64url_decode(signature_b64)

RS256 签名验证

1
2
3
4
5
6
7
def verify_rsa(jwt, public_key):
header_b64, payload_b64, signature_b64 = jwt.split('.')
data = header_b64 + "." + payload_b64
decoded_signature = base64url_decode(signature_b64)
decrypted_hash = RSA_Decrypt(decoded_signature, public_key) # 用公钥“解密”
actual_hash = SHA256(data)
return decrypted_hash == actual_hash

总结

  • JWT 的签名机制根据算法不同,流程有所差异:
    • HMAC:依赖密钥哈希,验证时直接对比哈希值。
    • RSA:用私钥加密哈希值,验证时用公钥解密并对比。
  • 核心目标:确保 Header 和 Payload 未被篡改,而非加密数据本身。
  • 安全建议:根据场景选择算法(HS256 简单高效,RS256 更适合分布式系统)。

Q4. 前向保密

Q:

按照你的说法,无论 HMAC 或是 RSA,JWT 都不支持前向保密

A:

是的,你的理解是正确的。JWT(无论是使用 HMAC 还是 RSA 签名算法)本身并不支持前向保密(Forward Secrecy, FS)。以下是详细解释:


1. 前向保密(Forward Secrecy)的核心概念

前向保密是一种安全特性,要求即使长期密钥(如服务器的私钥或 HMAC 的共享密钥)在未来被泄露,攻击者也无法解密过去的通信数据或伪造历史会话。
实现前向保密的关键:每次会话使用临时生成的密钥(如 TLS 中的 ECDHE 密钥交换),会话结束后密钥立即销毁,不依赖长期密钥。


2. JWT 的签名机制为何不支持前向保密

(1) HMAC(对称签名算法,如 HS256)

  • 依赖长期密钥:HMAC 的签名和验证使用同一个共享密钥(secret_key),且该密钥通常长期有效。
  • 风险:一旦 secret_key 泄露,攻击者可伪造任意历史或未来的 JWT。
  • 无临时密钥机制:HMAC 的密钥不随会话变化,无法实现前向保密。

(2) RSA(非对称签名算法,如 RS256)

  • 依赖长期私钥:签名使用固定的私钥,公钥用于验证。
  • 风险:私钥泄露后,攻击者可伪造历史或未来的 JWT。
  • 密钥固定性:RSA 私钥通常长期不变,签名过程不涉及临时密钥。

3. 为什么 JWT 不原生支持前向保密?

  • 设计目标不同:JWT 的核心目标是实现无状态、自包含的令牌机制,而非动态会话管理。
  • 静态令牌特性:JWT 一旦签发,其有效性完全依赖签名和声明(如 exp),无法动态更新密钥。
  • 场景限制:JWT 常用于身份认证和授权,而非端到端加密通信,因此未引入复杂的密钥协商机制。

4. 如何缓解 JWT 缺乏前向保密的风险?

虽然 JWT 本身不支持前向保密,但可通过以下策略降低风险:

(1) 短期令牌有效期

  • 设置较短的 exp(过期时间),例如 15 分钟到 1 小时,减少密钥泄露后的攻击窗口。
  • 配合刷新令牌(Refresh Token)机制,动态更新访问令牌。

(2) 密钥轮换(Key Rotation)

  • 定期更换 HMAC 的共享密钥或 RSA 的私钥。
  • 对于已签发的 JWT,需在密钥轮换后强制失效(例如结合黑名单或短有效期)。

(3) 结合 TLS 的前向保密

  • 在传输层使用 TLS 1.3,启用支持前向保密的密码套件(如 ECDHE-ECDSA-AES128-GCM-SHA256)。
  • 确保 JWT 在传输过程中通过安全通道(HTTPS),防止窃听和中间人攻击。

(4) 使用无状态短期令牌

  • 例如 OAuth 2.0 的 access_token(通常为 JWT)设计为短期有效,结合服务端状态管理(如数据库记录令牌状态)。

(5) 敏感数据加密

  • 对 JWT 的 Payload 敏感字段额外加密(如使用 JWE)。
  • 使用混合方案:JWT 签名确保完整性,加密确保机密性。

5. 替代方案:支持前向保密的令牌机制

如果业务场景对前向保密有严格要求,可考虑其他方案:

  • 动态会话密钥:在服务端为每个会话生成临时密钥,签名后传递给客户端(但违背 JWT 无状态的设计理念)。
  • 基于会话的令牌:改用有状态的会话 Cookie,结合服务端存储的临时密钥(牺牲无状态性,换取前向保密)。
  • 端到端加密协议:在应用层实现前向保密(如 Signal 协议),但复杂度较高。

6. 总结

  • JWT 不支持前向保密:因其依赖长期有效的密钥(HMAC 的共享密钥或 RSA 的私钥)。
  • 缓解措施:通过短期令牌、密钥轮换、TLS 前向保密等方式降低风险。
  • 权衡设计:JWT 的简洁性和无状态特性与安全性需结合业务场景权衡。

若业务对前向保密有强需求,建议在传输层(TLS)或应用层协议(如 OAuth 2.0 的短期令牌)中实现,而非依赖 JWT 自身机制。

Q5. JWT 的另一种表述

Q:

在登录场景中,我是否可以将 JWT 技术理解为:服务端不使用 session,完全使用 cookie 存放一些用户相关的信息,同时该 cookie 无法被用户篡改。

A:

在登录场景中,你的理解基本正确。

Q6. JWT 头部的作用

Q:

为啥要在 Header(头部)中声明令牌类型(JWT)和签名算法(如HMAC SHA256或RSA)?

验证 payload 是否被篡改完全是服务端的工作,我的理解正确吗?那这样的话,为什么要把这个信息在客户端、服务端之间传输呢?

这是为了让服务端兼容多种验证方式吗?

A:

1. Header 中声明令牌类型和算法的原因

JWT 的 Header 中必须包含 alg(签名算法)和 typ(令牌类型)字段,这是由 JWT 标准(RFC 7519) 明确规定的。其必要性体现在以下几个方面:


(1) 服务端需要动态选择验证方式

  • 场景:服务端可能支持多种签名算法(如同时兼容 HS256RS256)。
  • 问题:如果 Header 不声明算法,服务端无法知道该用哪种算法和密钥去验证签名。
  • 示例
    1
    2
    3
    4
    5
    // Header
    {
    "alg": "RS256", // 服务端看到此字段后,选择公钥验证 RSA 签名
    "typ": "JWT"
    }

(2) 防止算法混淆攻击(Algorithm Confusion Attack)

  • 背景:攻击者可能篡改 Header 中的 alg 字段,诱导服务端使用错误的算法验证签名(例如将 RS256 改为 HS256)。
  • 防御措施
    • 服务端应严格校验 alg 字段是否为预期值(如只允许 RS256)。
    • 绝不依赖客户端声明的算法,而是通过配置固定服务端支持的算法。

(3) 明确令牌类型

  • 作用typ: "JWT" 表明这是一个标准的 JWT 令牌,避免与其他类型令牌(如加密的 JWE)混淆。
  • 扩展性:如果未来需要支持新类型的令牌(如 JWT+XML),可通过此字段区分。

2. 为什么需要将 Header 传输到客户端?

  • 核心逻辑:JWT 是 自包含的令牌,服务端无需存储额外信息即可验证它。
    • 验证签名:服务端需要 Header 中的 alg 字段知道如何验证签名。
    • 解码 Payload:服务端需要 Header 中的声明(如 cty 自定义内容类型)来正确解析 Payload。
  • 无状态设计:若 Header 不包含算法信息,服务端需额外存储每个令牌的元数据,违背 JWT 的无状态原则。

3. 验证篡改是否完全是服务端的工作?

  • 正确性:是的,客户端无法验证 JWT 的合法性,这是服务端的职责。
  • 客户端角色
    • 仅存储和传输 JWT(如通过 Cookie)。
    • 不参与签名验证(即使客户端解码 Header/Payload,也无法确保签名有效)。

4. 为什么 Header 信息需要传输?

(1) 技术必要性

  • 签名验证依赖 Header:签名的生成基于完整的 Header 和 Payload,服务端必须获取原始的 Header 和 Payload 才能重新计算签名进行对比。

(2) 安全风险与误区

  • 误区:“Header 传输算法信息会导致安全隐患”。
    • 事实:攻击者无法通过篡改 alg 字段绕过验证,因为服务端会强制校验签名是否与声明的算法一致。
    • 安全实践:服务端应配置允许的算法白名单(如只允许 RS256),忽略客户端声明的其他算法。

5. 是否为了兼容多种验证方式?

  • 部分正确:Header 中的 alg 允许服务端支持多算法,但这不是主要目的。
  • 核心目的
    1. 无状态验证:服务端无需预先知道令牌的签名方式。
    2. 算法声明:确保签名生成和验证过程的一致性。
    3. 防止算法混淆:明确约束签名机制。

6. 实际场景示例

(1) 多租户系统

  • 不同租户使用不同签名算法(如租户 A 用 HS256,租户 B 用 RS256)。
  • 服务端根据 Header 中的 alg 动态选择对应的验证方式。

(2) 算法升级

  • 旧系统使用 HS256,新系统升级到 RS256
  • 服务端通过 Header 中的 alg 兼容新旧令牌,逐步淘汰旧算法。

总结

  • Header 中的 algtyp 是必要的:服务端需要这些信息正确验证签名和解析令牌。
  • 安全责任在服务端:必须严格校验算法、签名和声明,而非依赖客户端传递的信息。
  • 传输 Header 无额外风险:攻击者无法通过篡改 Header 绕过验证(只要服务端实现正确)。

Q7. 可以去掉 JWT 头部吗?

Q:

如果服务端只支持 JWT,且只有一种加密算法。不考虑 JWT 规范,我们是否可以不要传递 header?

A:

分步解析与建议

1. 技术可行性分析

如果服务端仅支持一种固定算法(如 HS256)且不遵循 JWT 标准,理论上可以移除 Header 字段,但需重新设计令牌结构。以下是两种方案的对比:

方案 标准 JWT(带 Header) 无 Header 的自定义令牌
结构 Header.Payload.Signature Payload.Signature
签名生成方式 HMAC(Header_b64.Payload_b64) HMAC(Payload_b64)
服务端验证 读取 Header 中的算法,动态验证 固定算法(如 HS256)直接验证
兼容性 兼容 JWT 生态工具(如库、调试器) 需完全自定义实现

2. 潜在风险与限制

风险/限制 说明
签名范围不一致 签名未包含 Header,若未来需扩展元数据(如令牌版本),需重新设计签名逻辑,可能导致兼容性问题。
算法升级困难 硬编码算法后,若需更换算法(如迁移到 RS256),需强制所有客户端升级,无法平滑过渡。
生态工具不兼容 无法使用标准 JWT 库(如 jsonwebtoken),需自行实现编解码和签名验证,增加开发成本。
安全审计复杂性 自定义方案需独立安全评估,而标准 JWT 的安全性已被广泛验证。

总结

  • 可以移除 Header:在服务端严格固定算法的场景下,技术上可行。
  • 代价:牺牲标准化、灵活性和工具兼容性,需自行承担安全风险。
  • 推荐场景:仅适用于封闭、小规模系统,且需长期维护自定义实现。

其他

常见的两种解决集群部署 web 服务器时 session 共享问题的方案:

  • 一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
  • 另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

参考