JWT 并不安全——除非你了解 JWS 和 JWE
抱歉,我没有看到需要翻译的正文内容。请您提供要翻译的文本(除代码块和 URL 之外的部分),我会按照要求将其翻译成简体中文并保留原有的格式。
Introduction
JWT 默认并不安全。它们快速、无状态且功能强大,但如果不了解实际操作,就很容易被破坏。大多数开发者只是复制粘贴 jwt.verify(token, secret) 然后继续——问题就在这里。
JWT 基础
JWT 代表 JSON Web Token(JSON 网络令牌)。它是 JOSE(JSON 对象签名与加密)的一部分。两个概念很重要:
- JWS(JSON Web Signature) – 对数据进行签名。任何人都可以读取,但篡改会被检测到。
- JWE(JSON Web Encryption) – 对数据进行加密。只有预期的接收者才能读取。
大多数 JWT 是 JWS,因为通常不需要隐藏负载,只需要证明它没有被修改。
令牌结构
eyJhbGci... . eyJzdWIi... . 2Xh3n...
│ header │ │ payload │ │ signature │
每一部分都是 base64url 编码的 JSON:
| 部分 | 内容 |
|---|---|
| HEADER | { "alg": "HS256" } |
| PAYLOAD | { "sub": "user_123" } |
| SIGNATURE | HMAC(header + payload) |
可视化分解:
┌─────────────┐
│ HEADER │ → 算法 + 元数据
├─────────────┤
│ PAYLOAD │ → 你的声明(可读!)
├─────────────┤
│ SIGNATURE │ → 完整性证明
└─────────────┘
负载 未加密。Base64 是一种编码方式,而不是加密;任何人都可以解码:
atob("eyJzdWIiOiJ1c2VyXzEyMyJ9"); // {"sub":"user_123"}
标头详情
{
"alg": "HS256",
"kid": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
alg– 用于签名的算法(例如 HS256、RS256、ES256)。kid– 对签署令牌的密钥的标识符(仅限 UUID;绝不使用文件路径或 URL)。jku– 获取密钥的 URL(永远不要信任令牌中的此值)。
规则: 不要基于标头值做安全决策;攻击者可以控制这些值。
负载声明
{
"sub": "user_5839",
"exp": 1640995200,
"iss": "https://auth.yourapp.com",
"aud": "https://api.yourapp.com",
"jti": "a1b2c3d4"
}
sub– 主体(用户标识)。exp– 过期时间(必填)。iss– 发行者。aud– 受众。jti– 唯一令牌 ID(用于注销/列入黑名单)。
永不在 JWT 中存储机密信息(密码、API 密钥、社会安全号码)。
JWE(加密)
当需要保密时,JWE 会加密载荷:
header.encrypted_key.iv.ciphertext.tag
何时使用 JWE
- 令牌会经过不受信任的中间方。
- 合规要求对静止数据进行加密。
何时省略 JWE
- 通信已使用 HTTPS(在传输中已加密)。
- 发行者和验证者均在您的控制之下。
大多数系统不需要 JWE。
常见陷阱
算法混淆
// Vulnerable: algorithm not enforced
const decoded = jwt.verify(token, publicKey);
如果令牌声明 "alg": "HS256",但服务器期望使用 RS256,攻击者就可以伪造令牌。
none 算法
"alg": "none" 的令牌没有签名。一些库错误地接受它:
eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9.
未验证的 kid
{ "kid": "../../etc/passwd" }
{ "kid": "http://attacker.com/keys" }
直接根据 kid 加载密钥而不进行验证,可能导致任意文件读取或远程密钥注入。
不可信的 jku
如果服务器根据令牌中提供的 URL(jku)去获取密钥,攻击者可以将其指向恶意来源。
验证最佳实践
jwt.verify(token, key, {
algorithms: ['RS256'], // Hard‑code allowed algorithms
issuer: 'https://auth.yourapp.com', // Hard‑code expected issuer
audience: 'https://api.yourapp.com', // Hard‑code expected audience
maxAge: '30m' // Enforce token age
});
- 显式设置允许的算法。
- 验证
iss(发行者)和aud(受众)。 - 绝不要信任令牌中的
jku;在服务器端配置 JWKS URL。 - 使用 UUID 作为
kid,而不是文件路径或 URL。 - 保持访问令牌的有效期短(15–30 分钟)。
- 使用带有撤销支持的更长刷新令牌。
摘要
JWT 是一个 签名声明。签名保证了完整性和真实性,但它 并不 保证该令牌应被接受。是否接受取决于对声明(exp、iss、aud 等)的正确验证。
大多数 JWT 漏洞来源于:
- 信任本应不可信的输入(例如,头部字段)。
- 跳过关键的验证步骤。
严格配置你的验证,验证所有内容,切勿假设库会为你强制安全。了解签名实际能证明的内容,是安全实现与破碎实现之间的区别。
参考文献
- RFC 7515 – JSON Web Signature (JWS)
- RFC 7516 – JSON Web Encryption (JWE)
- RFC 7519 – JSON Web Token (JWT)