在 OAuth 2.0 和 OIDC 的工程讨论中,“JWT”这个词被严重过载了。人们用它指代 JSON Web Token(RFC 7519)、JSON Web Signature(RFC 7515)、JSON Web Encryption(RFC 7516)三个不同的东西——有时候连什么签什么都搞不清楚。本系列第 02 篇(OIDC)中已经提到 ID Token 是一个 JWT,但 ID Token 的格式依赖于 JWS 的签名模型和 JWKS 的密钥分发模型。本文回到 JOSE(JSON Object Signing and Encryption)体系的基础层,把 JWT / JWS / JWE / JWK / JWKS 的关系一次讲清楚。
一、JOSE 体系全景
JOSE 体系由五个 RFC 构成,它们之间的关系是:
flowchart TD
JWK["JWK (RFC 7517)<br/>JSON Web Key<br/>密钥的 JSON 表示"] --> JWS["JWS (RFC 7515)<br/>JSON Web Signature<br/>签名+payload → 三段式结构"]
JWK --> JWE["JWE (RFC 7516)<br/>JSON Web Encryption<br/>加密 payload → 五段式结构"]
JWS --> JWT["JWT (RFC 7519)<br/>JSON Web Token<br/>定义 claims 语义<br/>body 可以是 JWS 或 JWE"]
JWE --> JWT
JWK --> JWKS["JWKS<br/>JSON Web Key Set<br/>密钥集合 + kid 索引"]
- JWT(RFC 7519)定义了 token 的 claims
语义(
iss、sub、exp、iat、aud等)。 - JWS(RFC 7515)定义了”如何签名 JWT
或其他
payload”——把一段内容加上数字签名,形成三段式结构:
header.payload.signature。 - JWE(RFC 7516)定义了”如何加密 JWT
或其他
payload”——把一段内容加密,形成五段式结构:
header.encrypted_key.iv.ciphertext.tag。 - JWK(RFC 7517)定义了密钥的 JSON 表示格式。
- JWKS 是一组 JWK 的集合,用
kid字段做索引。
所以准确的说法是:ID Token 是一个 JWT,JWT 的实现形式通常是 JWS(签名)或嵌套 JWS 在 JWE 中(签名+加密)。
二、JWS:签名层的完整拆解
2.1 三段式结构
一个 JWS 的结构是
BASE64URL(Header).BASE64URL(Payload).BASE64URL(Signature):
// Header (解码后)
{
"alg": "RS256",
"typ": "JWT",
"kid": "2026-06-key-01"
}
// Payload (解码后 — JWT Claims Set)
{
"iss": "https://idp.example.com",
"sub": "user-12345",
"aud": "my-app",
"exp": 1718400000,
"iat": 1718396400,
"jti": "unique-token-id-abc123"
}
// Signature (二进制)
// = RSASSA-PKCS1-v1_5-SHA-256(
// BASE64URL(Header) + "." + BASE64URL(Payload),
// PrivateKey
// )签名字段覆盖 Header 和 Payload
两部分(包括中间的点号),所以 Header 中的 alg
字段本身也被签名了——如果攻击者修改 alg 为
none,签名就会变化,验签失败。
2.2 签名算法选择
| 算法族 | alg 值 |
密钥类型 | 签名大小 | 使用场景 |
|---|---|---|---|---|
| HMAC | HS256, HS384,
HS512 |
对称密钥(共享 secret) | 32-64 bytes | 内部服务间(密钥分发简单) |
| RSA | RS256, RS384,
RS512 |
非对称密钥 | 256-512 bytes | 传统 PKI 部署(兼容性最好) |
| ECDSA | ES256, ES384,
ES512 |
非对称密钥(ECC) | 64-132 bytes | 签名体积小,验证速度快 |
| EdDSA | EdDSA |
非对称密钥(Ed25519) | 64 bytes | 现代选择,安全性好,签名/验证都极快 |
| None | none |
无 | 0 bytes | 仅在测试中使用,生产环境必须拒绝 |
选型建议:
- 新系统默认选择
ES256(ECDSA P-256 + SHA-256):签名体积是RS256的 1/4,验证速度快约 3 倍(移动端和 IoT 设备上差距更大),安全性等价。 - 对”最新最佳”有追求的团队选择
EdDSA:使用 Ed25519 曲线,签名和验证都极快,没有 ECDSA 的 nonce 复用风险(nonce 复用会导致私钥泄露)。但截至 2026 年,并非所有 JWT 库和 IdP 都支持EdDSA。 HS256只在内部服务间使用:共享密钥意味着任何持有密钥的方都可以签发有效 token——在微服务架构中这意味着”密钥泄露 = 全局失陷”。HS256的密钥必须至少有 256 位(32 字节随机值)。一个常见的安全漏洞是用弱密码短语(如 “my-secret”)作为HS256的密钥——JWT 库不会阻止你,但它可以被暴力破解。
工程陷阱 1:
alg: "none"攻击。如果 JWT 验证库没有显式限制允许的算法列表,攻击者可以构造{"alg": "none", "typ": "JWT"}的 Header,使其接受未签名的 token。验证库的正确配置是:显式指定允许的算法列表,默认为空,不信任 token 自身的alg声明。
2.3 kid(Key
ID)的作用
当签名方有多个密钥(如正在进行密钥轮换),kid
让验证方知道用哪个公钥来验证签名。kid
是一个任意字符串——在 JWKS 端点中,它与 JWK
对象的 kid 字段对应:
{
"keys": [
{"kty": "EC", "kid": "2026-06-key-01", "crv": "P-256", "x": "...", "y": "..."},
{"kty": "EC", "kid": "2026-03-key-01", "crv": "P-256", "x": "...", "y": "..."}
]
}验证方从 JWS Header 取 kid,在 JWKS 中找对应
key,用该 key 验证签名。
三、JWE:加密令牌的内部结构
JWE 解决的是”如果有人截获了 token,能不能读到里面的内容”。JWS 只提供完整性和来源认证——签名不隐藏 payload,任何人 Base64 解码就能读到 all claims。
JWE 用双层加密模型解决这个问题:
flowchart LR
CEK["Content Encryption Key (CEK)<br/>随机生成"] -->|"RSA/ECDH 加密"| EK["Encrypted Key"]
Plaintext -->|"AES-GCM 用 CEK 加密"| CT["Ciphertext + Auth Tag"]
- 密钥加密层:用接收方的公钥(RSA/EC)加密
CEK,产生
encrypted_key。 - 内容加密层:用 CEK(AES-GCM 或
AES-CBC+HMAC)加密 payload,产生
ciphertext+authentication tag。
五段式结构:BASE64URL(Header).BASE64URL(EncryptedKey).BASE64URL(IV).BASE64URL(Ciphertext).BASE64URL(AuthTag)
Header 中的 enc
字段指定内容加密算法——A128GCM、A256GCM、A128CBC-HS256
等。alg
字段指定密钥加密算法——RSA-OAEP-256、ECDH-ES+A256KW
等。
工程陷阱 2:JWE 的加密不替代 TLS。如果你在 HTTPS 连接中传输 JWE,看起来是”双重加密”,但 JWE 的真实价值在于端到端加密——token 经过多个中间节点(API Gateway、负载均衡器、日志系统)时,JWE 的加密保持闭态,而 HTTPS 在每个中间节点终止。如果你的服务不在高安全需求行业(如金融、医疗等法规明确要求端到端加密的领域),优先使用 JWS + HTTPS 的组合,复杂度更低。
四、JWKS 的密钥管理与轮换
4.1 JWK 密钥表示
一个 EC P-256 公钥的 JWK 表示:
{
"kty": "EC",
"kid": "2026-06-key-01",
"use": "sig",
"alg": "ES256",
"crv": "P-256",
"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}字段说明:
kty(Key Type):EC、RSA、oct(对称密钥)。use(Public Key Use):sig(签名)或enc(加密)。一个 key 不应同时用于两者。alg:此 key 适用的算法。kid:密钥标识符——JWKS 中的索引键。
4.2 密钥轮换策略
密钥轮换是令牌安全中最容易被工程团队搁置的问题,但当密钥泄露时,轮换窗口的长度直接决定了受影响的令牌数量。
典型轮换模式:
时间线:
T+0: JWKS 包含 [key-01] (active)
T+30d: JWKS 包含 [key-01, key-02] (transition)
→ 用 key-02 签发新 token
→ 继续用 key-01 验证旧 token
T+31d: JWKS 包含 [key-02] (key-01 移除)
→ 所有 key-01 签发的 token 失效
轮换的核心策略:
- 轮换周期:生产环境建议 30 天(Google 和 Auth0 的默认策略),高安全环境 7-14 天。
- 过渡期:新旧 key 同时在 JWKS 中出现,过渡期长度 = 最长 token 有效期 × 安全系数(通常取 2-3x)。过渡期内用新 key 签发,用新 key 或旧 key 验证均可。
- 紧急轮换:如果私钥泄露,立即从 JWKS 中移除泄露的 key,所有由该 key 签发的 token 立即失效,所有持有这些 token 的用户必须重新登录。这叫”破坏性轮换”。
4.3 JWKS 缓存
RP 不应该每次验证 token 都去拉一次 JWKS——这会引入不必要的延迟、增加 OP 的负载、在 OP 不可用时导致认证全挂。正确的做法是设立本地缓存:
- 首次拉取 JWKS,记录
Cache-Control和ExpiresHeader(如果 OP 提供)。 - 缓存有效期默认 1 小时,可配置。
- 如果使用缓存中的 key 验签失败(
kid不在缓存中或签名不匹配),重新拉取 JWKS,只重试一次。 - 如果 OP 的 JWKS 端点不可用,暂时使用缓存中的 key(降级模式),同时触发告警。
工程陷阱 3:很多 JWT 库(包括早期版本的
jsonwebtokenfor Node.js、PyJWTfor Python)默认不验证kid的存在——它们尝试用 JWKS 中每一个 key 去验证签名。这在 key 数量少时能跑,但有两个问题:a) O(n) 的验证复杂度在 key 数量增长时变成性能问题,b) 攻击者可以构造不带kid的 token 来探测服务的行为。
五、小结
JOSE 体系的核心不是格式本身,而是它在分布式身份系统中的角色分工:
- JWT 定义了 claims 语义——这是”令牌上写了什么”。
- JWS 提供了完整性和来源认证——这是”令牌是不是真的,有没有被改过”。
- JWE 提供了机密性——这是”令牌里的内容不能被中间节点看到”。
- JWK + JWKS 提供了密钥分发机制——这是”我该用哪个公钥来验证或解密”。
生产环境的 JWT 使用建议: - 签名算法选
ES256(兼容性最好)或
EdDSA(安全性最好)。 - kid
必须有——它是密钥轮换的基础。 - exp 加
iat,Access Token 不超过 15 分钟。 -
验证时显式限定允许的 alg 列表(默认拒绝
none)。 - JWKS 缓存,带失效和重试策略。
上一篇:SCIM 与账号生命周期:开通、变更、离职自动化 下一篇:Session、Refresh Token 与吊销体系
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【身份与访问控制工程】IAM 全景:为什么这是高价值赛道
从 2020 年 SolarWinds 到 2024 年 Okta 支持系统泄露,身份基础设施的安全失败反复证明一件事:IAM 不是 IT 支撑系统,而是安全架构的承重墙。本文建立现代 IAM 的全景地图——从认证协议、令牌体系、权限模型到身份治理与平台选型,给出 5 个贯穿全系列的核心问题。
【身份与访问控制工程】企业单点登录:OIDC 与现代 SSO
OIDC 是当下企业 SSO 的事实标准,但大多数实现只用了它 20% 的规范。本文从 OIDC 核心规范出发,拆解 Authorization Code Flow + PKCE 的完整交互、ID Token 的验证规则、Discovery 与 Dynamic Registration 的互操作性机制,以及 RP-Initiated Logout 和 Session Management 的工程实现细节。
【身份与访问控制工程】Session、Refresh Token 与吊销体系
JWT 的无状态设计带来了可扩展性,但让令牌吊销变成了系统性问题——签出去的 JWT 在到期之前全是活令牌。Refresh Token Rotation、Token Introspection、基于事件的吊销通知、撤销列表——这些机制构成了身份系统的'紧急刹车',各自的成本、延迟和覆盖范围完全不同。本文拆解四种吊销机制的工程权衡。
【零信任安全架构】身份感知代理:Google IAP、Pomerium 与零信任的入口
身份感知代理(IAP)是零信任架构中用户进入企业资源的唯一入口——它取代了 VPN 的'拨入内网'模型,把每个 HTTP 请求的认证和授权检查放在资源前面。本文拆解 IAP 协议层的完整请求流、JWT 验证的严格性要求、Header Injection 的安全陷阱,以及 Pomerium/oauth2-proxy/Cloudflare Access 的实现差异。