JWT 看似简单,实则暗藏玄机。从 JWS/JWE 标准族到密钥管理陷阱,从 Token 生命周期设计到 JWT 伪造攻击,这篇小课堂带你深入 JWT 的每一个角落。
一、JWT 到底是什么?
JWT(JSON Web Token)是一种紧凑、自包含的方式,用于在各方之间安全地传输信息。它由 IETF 在 2015 年通过 RFC 7519 标准化,但核心思想可追溯到更早的 Token 认证机制。它并非为替代 Session 而生,而是为了解决无状态分布式系统中的身份传递问题。
核心定位
| 维度 | 说明 |
|---|---|
| 设计哲学 | 自包含原则:Token 自身携带全部必要信息,服务端无需额外查询 |
| 数据模型 | 三段式结构:Header.Payload.Signature,Base64Url 编码 |
| 类型系统 | Payload 为 JSON 对象,支持字符串、数字、布尔、数组、对象 |
| 编码规范 | 必须使用 Base64Url 编码(非标准 Base64,去除了 = 填充和 +/ 字符) |
| 标准族 | RFC 7515(JWS)、7516(JWE)、7517(JWK)、7518(JWA)、7519(JWT) |
| 元数据 | Header 声明算法与类型,Payload 声明声明(claims),Signature 保证完整性 |
核心特点深度解读
| 特点 | 表面理解 | 深层陷阱 |
|---|---|---|
| 自包含 | Token 携带全部信息,服务端无状态 | Payload 明文可见,敏感信息直接暴露 |
| 跨域友好 | 天然适合分布式、微服务、前后端分离 | Token 一旦签发,在过期前无法主动撤销 |
| 标准化 | IETF 标准,多语言库支持完善 | 算法声明在 Header 中,存在算法混淆攻击面 |
| 易于传输 | 字符串格式,可放 Header/URL/Cookie | 体积通常比 Session ID 大 5-10 倍,高频请求带宽压力大 |
| 人类可读 | Base64 解码即可查看内容 | 可读≠可信任,客户端可随意篡改 Payload 内容 |
| 广泛支持 | 几乎所有编程语言都有成熟库 | 库的实现质量参差不齐,默认配置常存在安全隐患 |
二、JWT 格式规范:三段式结构深度解析
2.1 完整结构
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. ← Header(蓝色)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. ← Payload(绿色)
SflKxwRJSMeKKF2QT4fwpMe ← Signature(红色)
2.2 各段详解
| 段 | 内容 | 编码方式 | 核心作用 | 安全警示 |
|---|---|---|---|---|
| Header | JSON 对象,声明 alg(算法) 和 typ(类型) |
Base64Url | 告诉验证方用什么算法验证签名 | ⚠️ alg 可被篡改,必须白名单校验 |
| Payload | JSON 对象,携带声明(claims) | Base64Url | 携带用户身份、权限、过期时间等业务数据 | ⚠️ 仅 Base64 编码,任何人可解码查看 |
| Signature | 签名值 | Base64Url | 防止 Header 和 Payload 被篡改 | ✅ 唯一不可伪造的部分,依赖密钥安全 |
签名公式:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)
2.3 标准声明(Registered Claims)
| 声明 | 全称 | 说明 |
|---|---|---|
iss |
Issuer | 签发者,标识 Token 来源 |
sub |
Subject | 主题,通常为用户唯一标识 |
aud |
Audience | 受众,标识 Token 的目标接收方 |
exp |
Expiration Time | 过期时间戳(秒级 Unix 时间)⭐ 必须设置 |
nbf |
Not Before | 生效时间,在此之前 Token 无效 |
iat |
Issued At | 签发时间 |
jti |
JWT ID | 唯一标识,用于防止 Token 重放 |
2.4 关键规则
| 规则编号 | 规则内容 | 违反后果 |
|---|---|---|
| R1 | 必须使用 Base64Url 编码,禁止标准 Base64 的 = 填充 |
URL 中 = 会被转义,破坏 Token 格式 |
| R2 | Header 中的 alg 不可为 none(生产环境) |
算法置空后签名验证被跳过,攻击者可任意伪造 Token |
| R3 | exp 必须设置且为未来的时间戳 |
永不过期的 Token 一旦泄露,风险永久存在 |
| R4 | 敏感数据不得放入 Payload(仅 JWE 可加密) | Payload 仅 Base64 编码,任何人可解码查看 |
| R5 | 密钥长度必须符合算法要求(如 HS256 需 ≥256 位) | 短密钥可被暴力破解或彩虹表攻击 |
| R6 | 验证时必须同时校验签名、过期时间、受众 | 漏检任一环节都可能被绕过 |
三、签名算法:六种核心方案
3.1 算法对照表
| 算法 | 类型 | 密钥要求 | 特点 | 适用场景 |
|---|---|---|---|---|
| HS256 | 对称 (HMAC-SHA256) | 单一共享密钥 | 速度快,密钥分发困难 | 内部服务、微服务间通信 |
| HS384/512 | 对称 | 单一共享密钥 | 更高安全级别 | 高安全要求的内部系统 |
| RS256 | 非对称 (RSA-SHA256) | 私钥签 / 公钥验 | 公钥可公开分发,私钥保密 | 公开 API、OAuth2 |
| ES256 | 非对称 (ECDSA-P256) | 私钥签 / 公钥验 | 签名更短,性能更好 | 移动端、带宽敏感场景 |
| PS256 | 非对称 (RSA-PSS) | 私钥签 / 公钥验 | 更安全的 RSA 填充方案 | 新标准推荐替代 RS |
| none | 禁用 | 无 | 不签名,仅 Base64 编码 | 生产环境绝对禁用 |
3.2 对称 vs 非对称选型
| 维度 | 对称 (HMAC) | 非对称 (RSA/ECDSA) |
|---|---|---|
| 签名速度 | 快 | 慢(RSA 尤其明显) |
| 验证速度 | 快 | 快(仅需公钥) |
| 密钥管理 | 困难(多方共享同一密钥) | 简单(公钥可公开) |
| Token 体积 | 小 | 大(RSA 签名 256-512 字节) |
| 适用架构 | 内部服务、单体应用 | 开放平台、第三方集成 |
| 密钥泄露风险 | 高(任何持有方都可伪造) | 低(仅私钥持有方可伪造) |
四、JWT 安全:被低估的攻击面
4.1 算法混淆攻击(Algorithm Confusion / alg: none 攻击)
攻击者修改 Header 中的 alg 为 none,并删除 Signature 段,服务端若未严格校验算法,会跳过签名验证直接信任 Payload。
🔴 攻击示例:
原始 Token: eyJhbGciOiJIUzI1NiJ9.xxx.yyy
伪造 Token: eyJhbGciOiJub25lIn0.xxx. ← alg 改为 none,去掉签名
🛡️ 防御方案:
- 服务端硬编码允许的算法白名单,拒绝
none - 验证时从可信来源读取
alg,不信任 Header 中的声明 - 对称与非对称算法使用完全独立的密钥
4.2 密钥混淆攻击(Key Confusion)
攻击者将 alg 从 RS256 改为 HS256,然后用公钥作为 HMAC 密钥签名,服务端若用同一公钥验证 HMAC,会导致验证通过。
🛡️ 防御方案:
- 对称与非对称密钥物理隔离
- 验证前检查密钥类型与算法是否匹配
- 不允许客户端指定算法
4.3 暴力破解与密钥泄露
| 攻击类型 | 描述 | 防御方案 |
|---|---|---|
| 密钥暴力破解 | 短密钥或弱密钥被枚举破解 | 密码学安全随机数生成器,密钥长度 ≥256 位 |
| 密钥硬编码 | 密钥写在代码/配置中泄露到 GitHub | 使用 KMS/HSM 管理,环境变量注入,定期轮换 |
| 密钥泄露传播 | 共享密钥被多方持有后扩散 | 非对称算法优先,对称密钥最小权限分发 |
| 日志泄露 | Token 被打印到日志中 | 禁止日志输出完整 Token,脱敏处理 |
4.4 Token 生命周期风险
| 风险 | 描述 | 防御方案 |
|---|---|---|
| 永不过期 | 无 exp 或 exp 极远 |
强制设置合理过期时间(通常 15 分钟 ~ 2 小时) |
| 无法撤销 | 签发后服务端无法主动失效 | 结合黑名单/Redis 存储 Token 状态,或使用短 Token + Refresh Token |
| 重放攻击 | 截获的 Token 被重复使用 | 结合 jti + 服务端存储已用 ID,或使用一次性 Token |
| 令牌窃取 | XSS 窃取 Token,CSRF 伪造请求 | HttpOnly Cookie 存储(防 XSS),SameSite 属性(防 CSRF) |
五、JWT 与 Session 深度对比
| 特性 | JWT | Session |
|---|---|---|
| 存储位置 | 客户端(Cookie/LocalStorage/Header) | 服务端(内存/Redis/数据库) |
| 服务端状态 | 无状态 | 有状态 |
| 跨域支持 | 天然支持 | 需 CORS + Cookie 配置 |
| 分布式扩展 | 天然支持 | 需共享 Session 存储(Redis) |
| 撤销能力 | 困难(需黑名单机制) | 简单(服务端直接删除) |
| 信息暴露 | Payload 明文可见 | 仅 Session ID 暴露 |
| Token 体积 | 较大(500B ~ 2KB) | 极小(~32B) |
| 过期控制 | 依赖 exp,签发后不可改 |
服务端随时可调整 |
| 适用场景 | 分布式系统、微服务、开放 API | 单体应用、需要即时撤销的场景 |
六、Refresh Token 机制
6.1 为什么需要双 Token?
- Access Token:有效期短(15 分钟),减少泄露风险
- Refresh Token:有效期长(7-30 天),用于在 Access Token 过期后无感知续期
6.2 双 Token 流程
用户登录 → 服务端返回 Access Token + Refresh Token
↓
客户端用 Access Token 访问 API
↓
Access Token 过期(401)
↓
客户端用 Refresh Token 请求 /refresh
↓
服务端验证 Refresh Token,返回新的 Access Token + Refresh Token
↓
Refresh Token 也过期 → 要求用户重新登录
6.3 Refresh Token 安全要点
| 要点 | 说明 |
|---|---|
| 存储方式 | 必须 HttpOnly Cookie,禁止前端 JavaScript 访问 |
| 轮换机制 | 每次刷新后同时更新 Access Token 和 Refresh Token,旧 Refresh Token 立即失效 |
| 绑定设备 | Refresh Token 与设备指纹/IP 绑定,异常环境拒绝刷新 |
| 撤销能力 | Refresh Token 必须在服务端持久化存储,支持随时撤销 |
七、JWT 最佳实践清单
✅ 生成 JWT 时
- 始终设置合理的
exp(建议 15 分钟 ~ 2 小时) - 敏感数据绝不放入 Payload,需要加密时使用 JWE
- 使用密码学安全随机数生成器生成密钥
- 对称密钥长度 ≥256 位 ,非对称密钥 RSA ≥2048 位
- 设置
iss、aud、jti等声明,增强 Token 上下文 - 使用成熟的库(
jsonwebtoken、PyJWT、jjwt),不手写签名逻辑
✅ 验证 JWT 时
- 硬编码允许的算法白名单,拒绝
none和意外算法 - 严格验证签名、过期时间、签发者、受众
- 对称与非对称密钥物理隔离,防止密钥混淆攻击
- 设置 Token 最大长度限制,防止畸形数据攻击
- 验证失败时返回统一错误,不暴露具体失败原因(防止信息泄露)
❌ 避免这样做
- 用
alg: none或允许客户端指定算法 - 将密钥硬编码在代码仓库中
- 在 Payload 中存储密码、密钥等敏感信息
- 设置永不过期的 Token
- 用 JWT 替代所有 Session 场景(需要即时撤销时选 Session)
- 在 URL 参数中传递 JWT(泄露风险高)
- 忽略 Token 体积对带宽的影响
八、什么时候选择 JWT?
| 场景 | 推荐方案 | 核心理由 |
|---|---|---|
| 分布式微服务间身份传递 | JWT | 无状态,天然适合跨服务调用 |
| 开放 API / 第三方集成 | JWT (RS256) | 公钥可公开分发,私钥保密 |
| 单页应用(SPA)身份认证 | JWT + Refresh Token | 跨域友好,支持无感知续期 |
| 移动端 App 认证 | JWT (ES256) | 签名短,节省带宽 |
| 需要即时撤销权限 | Session + Redis | JWT 撤销困难,Session 可即时删除 |
| 内部高安全要求系统 | Session | 服务端完全控制,可随时干预 |
| 一次性密码/邀请链接 | 带 jti 的短效 JWT |
自包含,无需服务端存储状态 |
九、在线体验
欢迎体验 BugCome JWT 解码/验证工具,支持:
- 🔓 一键解码 JWT Header 和 Payload
- ✅ 验证签名有效性(支持 HS256/RS256/ES256)
- ⏰ 自动解析过期时间、签发时间等声明
- 📋 格式化复制 与导出
