土法炼钢兴趣小组的算法知识备份

【身份与访问控制工程】JWT、JWS、JWE、JWKS 一次讲透

文章导航

分类入口
architecturesecurity
标签入口
#jwt#jws#jwe#jwk#jwks#jose#token#signature#encryption#key-rotation

目录

在 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 索引"]

所以准确的说法是: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 字段本身也被签名了——如果攻击者修改 algnone,签名就会变化,验签失败。

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 仅在测试中使用,生产环境必须拒绝

选型建议

工程陷阱 1alg: "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"]

五段式结构:BASE64URL(Header).BASE64URL(EncryptedKey).BASE64URL(IV).BASE64URL(Ciphertext).BASE64URL(AuthTag)

Header 中的 enc 字段指定内容加密算法——A128GCMA256GCMA128CBC-HS256 等。alg 字段指定密钥加密算法——RSA-OAEP-256ECDH-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"
}

字段说明:

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 失效

轮换的核心策略:

4.3 JWKS 缓存

RP 不应该每次验证 token 都去拉一次 JWKS——这会引入不必要的延迟、增加 OP 的负载、在 OP 不可用时导致认证全挂。正确的做法是设立本地缓存:

  1. 首次拉取 JWKS,记录 Cache-ControlExpires Header(如果 OP 提供)。
  2. 缓存有效期默认 1 小时,可配置。
  3. 如果使用缓存中的 key 验签失败(kid 不在缓存中或签名不匹配),重新拉取 JWKS,只重试一次。
  4. 如果 OP 的 JWKS 端点不可用,暂时使用缓存中的 key(降级模式),同时触发告警。

工程陷阱 3:很多 JWT 库(包括早期版本的 jsonwebtoken for Node.js、PyJWT for Python)默认不验证 kid 的存在——它们尝试用 JWKS 中每一个 key 去验证签名。这在 key 数量少时能跑,但有两个问题:a) O(n) 的验证复杂度在 key 数量增长时变成性能问题,b) 攻击者可以构造不带 kid 的 token 来探测服务的行为。

五、小结

JOSE 体系的核心不是格式本身,而是它在分布式身份系统中的角色分工:

  1. JWT 定义了 claims 语义——这是”令牌上写了什么”。
  2. JWS 提供了完整性和来源认证——这是”令牌是不是真的,有没有被改过”。
  3. JWE 提供了机密性——这是”令牌里的内容不能被中间节点看到”。
  4. JWK + JWKS 提供了密钥分发机制——这是”我该用哪个公钥来验证或解密”。

生产环境的 JWT 使用建议: - 签名算法选 ES256(兼容性最好)或 EdDSA(安全性最好)。 - kid 必须有——它是密钥轮换的基础。 - expiat,Access Token 不超过 15 分钟。 - 验证时显式限定允许的 alg 列表(默认拒绝 none)。 - JWKS 缓存,带失效和重试策略。


上一篇SCIM 与账号生命周期:开通、变更、离职自动化 下一篇Session、Refresh Token 与吊销体系

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-13 · architecture / security

【身份与访问控制工程】IAM 全景:为什么这是高价值赛道

从 2020 年 SolarWinds 到 2024 年 Okta 支持系统泄露,身份基础设施的安全失败反复证明一件事:IAM 不是 IT 支撑系统,而是安全架构的承重墙。本文建立现代 IAM 的全景地图——从认证协议、令牌体系、权限模型到身份治理与平台选型,给出 5 个贯穿全系列的核心问题。

2026-06-13 · architecture / security

【身份与访问控制工程】企业单点登录:OIDC 与现代 SSO

OIDC 是当下企业 SSO 的事实标准,但大多数实现只用了它 20% 的规范。本文从 OIDC 核心规范出发,拆解 Authorization Code Flow + PKCE 的完整交互、ID Token 的验证规则、Discovery 与 Dynamic Registration 的互操作性机制,以及 RP-Initiated Logout 和 Session Management 的工程实现细节。

2026-06-15 · architecture / security

【身份与访问控制工程】Session、Refresh Token 与吊销体系

JWT 的无状态设计带来了可扩展性,但让令牌吊销变成了系统性问题——签出去的 JWT 在到期之前全是活令牌。Refresh Token Rotation、Token Introspection、基于事件的吊销通知、撤销列表——这些机制构成了身份系统的'紧急刹车',各自的成本、延迟和覆盖范围完全不同。本文拆解四种吊销机制的工程权衡。

2026-06-12 · architecture / security

【零信任安全架构】身份感知代理:Google IAP、Pomerium 与零信任的入口

身份感知代理(IAP)是零信任架构中用户进入企业资源的唯一入口——它取代了 VPN 的'拨入内网'模型,把每个 HTTP 请求的认证和授权检查放在资源前面。本文拆解 IAP 协议层的完整请求流、JWT 验证的严格性要求、Header Injection 的安全陷阱,以及 Pomerium/oauth2-proxy/Cloudflare Access 的实现差异。


By .