惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 司徒正美
宝玉的分享
宝玉的分享
美团技术团队
量子位
The Cloudflare Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
IT之家
IT之家
爱范儿
爱范儿
J
Java Code Geeks
博客园 - Franky
Last Week in AI
Last Week in AI
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
InfoQ
GbyAI
GbyAI
Recent Announcements
Recent Announcements
小众软件
小众软件
H
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog

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

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计
【身份与访问控制工程】JWT、JWS、JWE、JWKS 一次讲透
Liao Tonglang · 2026-04-21 · via 土法炼钢兴趣小组的算法知识备份

在 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 语义(isssubexpiataud 等)。
  • 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 中(签名+加密)。

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

选型建议

  • 新系统默认选择 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 库不会阻止你,但它可以被暴力破解。

工程陷阱 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"]
  • 密钥加密层:用接收方的公钥(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 字段指定内容加密算法——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"
}

字段说明:

  • kty(Key Type):ECRSAoct(对称密钥)。
  • 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 不可用时导致认证全挂。正确的做法是设立本地缓存:

  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 的实现差异。