对话 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):双方协商的字段。
- 预注册声明(Registered Claims):标准字段,如
- 示例:
1
2
3
4
5{
"sub": "user123",
"name": "Alice",
"exp": 1620000000 // 过期时间戳
}
1.3 Signature(签名)
- 作用:验证消息未被篡改,确保发送方的可信性。
- 生成方式:将Base64Url编码后的Header和Payload用
.
拼接,再通过Header中指定的算法和密钥(Secret)生成签名。 - 示例(使用HS256算法):
1
2
3
4HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)
2. JWT的工作流程
-
用户登录:客户端提交身份信息(如用户名密码)到服务器。
-
生成JWT:服务器验证身份后,生成JWT:
- 构造Header和Payload。
- 用密钥生成签名,组合三者形成完整的JWT。
-
返回JWT给客户端:客户端将JWT存储在本地(如LocalStorage或Cookie)。
-
客户端携带JWT请求资源 :后续请求在
Authorization
头中以Bearer
模式携带JWT:1
Authorization: Bearer <JWT>
-
服务器验证JWT
- 解码Header和Payload,检查签名是否有效(防止篡改)。
- 验证声明(如
exp
是否过期、iss
是否合法)。 - 验证通过后,返回请求的资源。
3. JWT的安全性
- 信息暴露:Header和Payload仅经过Base64编码,未加密,敏感信息(如密码)不应放入。
- 传输安全:必须通过HTTPS传输,防止中间人攻击。
- 密钥管理:
- 对称算法(如HS256):需严格保护密钥。
- 非对称算法(如RS256):用私钥签名,公钥验证,更安全。
- 防篡改:签名确保数据完整性,但需防止密钥泄露。
4. JWT的优缺点
优点
- 无状态:服务端无需存储会话信息,适合分布式系统。
- 跨域支持:可轻松用于跨域场景(如微服务、单点登录)。
- 灵活性:Payload可自定义扩展数据。
缺点
- 无法废止:签发后有效期结束前无法强制失效(需结合Token黑名单或短有效期)。
- 存储风险:客户端存储可能被XSS攻击窃取(建议用HttpOnly Cookie)。
5. 典型应用场景
- 用户身份认证(如OAuth 2.0的Bearer Token)。
- 服务间API鉴权(微服务架构)。
- 单点登录(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 |
-
Header:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
。解码后:1
{ "alg": "HS256", "typ": "JWT" }
-
Payload:
eyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6IkFsaWNlIiwiZXhwIjoxNjIwMDAwMDAwfQ
。解码后:1
{ "sub": "user123", "name": "Alice", "exp": 1620000000 }
-
Signature:
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
。无法直接解码,需用密钥验证。
4. 安全建议
- 避免敏感数据:不要在 Header 或 Payload 中存储密码、密钥等敏感信息。
- 使用 HTTPS:防止 JWT 在传输中被窃取。
- 短有效期:通过
exp
字段设置合理过期时间,减少泄露风险。 - 加密敏感字段:如需存储敏感数据,可对 Payload 进行额外加密(如 JWE)。
总结
- 明文部分:Header 和 Payload(Base64Url 编码,可逆)。
- 密文部分:Signature(加密签名,不可逆)。
- 核心安全机制:依赖 Signature 防止数据篡改,而非隐藏数据本身。
Q3. 哈希与加密
Q:
所以 Signature 会将 Header + Payload先哈希,再对哈希值进行加密? 服务端收到 Signature 后,先解密,再验证哈希值是否正确?
A:
你的问题涉及 JWT 签名生成和验证的核心机制,但需要明确区分 哈希(Hash) 和 加密(Encryption) 的概念。不同签名算法(如 HS256
和 RS256
)的实现方式不同,具体流程如下:
1. JWT 签名的生成过程
(1) HMAC 算法(如 HS256)
- 核心机制:基于密钥的哈希(无需加密)。
- 步骤:
- 将 Header 和 Payload 进行 Base64Url 编码,并用
.
拼接(得到header_b64.payload_b64
)。 - 对拼接后的字符串计算哈希:使用 HMAC + 哈希算法(如 SHA256)和密钥生成消息认证码(MAC)。
- 生成签名:将哈希结果进行 Base64Url 编码,得到 Signature。
- 将 Header 和 Payload 进行 Base64Url 编码,并用
- 公式:
1
Signature = Base64UrlEncode(HMAC_SHA256(header_b64.payload_b64, secret_key))
(2) RSA 算法(如 RS256)
- 核心机制:基于非对称加密的签名(涉及加密)。
- 步骤:
- 将 Header 和 Payload 进行 Base64Url 编码并拼接(
header_b64.payload_b64
)。 - 对拼接后的字符串计算哈希:使用哈希算法(如 SHA256)生成哈希值。
- 加密哈希值:用私钥对哈希值进行加密(即数字签名)。
- 生成签名:将加密后的结果进行 Base64Url 编码,得到 Signature。
- 将 Header 和 Payload 进行 Base64Url 编码并拼接(
- 公式:
1
Signature = Base64UrlEncode(RSA_Encrypt(SHA256(header_b64.payload_b64), private_key))
2. 服务端的验证过程
服务端收到 JWT 后,验证签名的逻辑如下:
(1) HMAC 算法(HS256)
- 无需解密:因为 HMAC 是密钥哈希,不是加密。
- 验证步骤:
- 重新计算
header_b64.payload_b64
的 HMAC 哈希(使用相同的密钥)。 - 将计算结果与客户端传来的 Signature 解码后的值对比。
- 若一致,则数据未被篡改。
- 重新计算
(2) RSA 算法(RS256)
- 需要“解密”:这里的“解密”实质是验证签名。
- 验证步骤:
- 用公钥对 Signature 解码后的密文进行解密,得到原始哈希值。
- 重新计算
header_b64.payload_b64
的哈希值(如 SHA256)。 - 对比解密后的哈希值与重新计算的哈希值。
- 若一致,则数据未被篡改。
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 | def verify_hmac(jwt, secret_key): |
RS256 签名验证
1 | def verify_rsa(jwt, public_key): |
总结
- 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) 服务端需要动态选择验证方式
- 场景:服务端可能支持多种签名算法(如同时兼容
HS256
和RS256
)。 - 问题:如果 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 中的
- 无状态设计:若 Header 不包含算法信息,服务端需额外存储每个令牌的元数据,违背 JWT 的无状态原则。
3. 验证篡改是否完全是服务端的工作?
- 正确性:是的,客户端无法验证 JWT 的合法性,这是服务端的职责。
- 客户端角色:
- 仅存储和传输 JWT(如通过 Cookie)。
- 不参与签名验证(即使客户端解码 Header/Payload,也无法确保签名有效)。
4. 为什么 Header 信息需要传输?
(1) 技术必要性
- 签名验证依赖 Header:签名的生成基于完整的 Header 和 Payload,服务端必须获取原始的 Header 和 Payload 才能重新计算签名进行对比。
(2) 安全风险与误区
- 误区:“Header 传输算法信息会导致安全隐患”。
- 事实:攻击者无法通过篡改
alg
字段绕过验证,因为服务端会强制校验签名是否与声明的算法一致。 - 安全实践:服务端应配置允许的算法白名单(如只允许
RS256
),忽略客户端声明的其他算法。
- 事实:攻击者无法通过篡改
5. 是否为了兼容多种验证方式?
- 部分正确:Header 中的
alg
允许服务端支持多算法,但这不是主要目的。 - 核心目的:
- 无状态验证:服务端无需预先知道令牌的签名方式。
- 算法声明:确保签名生成和验证过程的一致性。
- 防止算法混淆:明确约束签名机制。
6. 实际场景示例
(1) 多租户系统
- 不同租户使用不同签名算法(如租户 A 用
HS256
,租户 B 用RS256
)。 - 服务端根据 Header 中的
alg
动态选择对应的验证方式。
(2) 算法升级
- 旧系统使用
HS256
,新系统升级到RS256
。 - 服务端通过 Header 中的
alg
兼容新旧令牌,逐步淘汰旧算法。
总结
- Header 中的
alg
和typ
是必要的:服务端需要这些信息正确验证签名和解析令牌。 - 安全责任在服务端:必须严格校验算法、签名和声明,而非依赖客户端传递的信息。
- 传输 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 就是这种方案的一个代表。