摘要

  • draft-ietf-oauth-status-list-21 让 Referenced Token 通过 URI 和索引指向压缩状态列表。一个受签名或 MAC 保护的 Status List Token 可以同时承载大量令牌的 VALID、INVALID、SUSPENDED 或应用自定义状态。第 21 版已获 IESG 批准并进入 RFC Editor 队列,但仍是 Internet-Draft,不是 RFC。
  • 默认情况下,被选中的值只说明 Status List Token 签发时所断言的状态。iat、exp 和 ttl 分别约束签发、有效窗口与缓存;它们不会自动记录状态实际变更的时刻、批准者、原因、首次分发时刻或 relying party 的最终决定。
  • Daniel Kade 提议在协议外保存一份最小化的“状态决策回执”:令牌与列表指纹、URI/索引、签发者验证、四只分开的时钟、解码语义、本地新鲜度规则、执行结果与后续更正。它不是 IETF 要求,也不应演化成长期跟踪凭证出示行为的数据库。

状态列表最有价值的地方,恰恰在于它没有为每张凭证制作一份厚重档案。验证方取得一个包含大量条目的受保护对象,按凭证给出的地址和索引找到几位数据,就能得到状态断言。它不必在每次出示时向签发者单独询问某一张可识别凭证,网络成本和隐私暴露都可以下降。

治理问题不是“位太少”,更不是协议承诺落空。问题发生在组织把这份紧凑断言向后扩写:既然它足以影响准入,就被想象成足以说明变更的全部来龙去脉。到了复核时,人们才发现状态源、列表签发、内容分发和业务决定属于不同主体,也发生在不同时间。

已批准并不等于已经成为 RFC

Datatracker 页面把 2026 年 6 月 21 日的第 21 版列为 OAuth Working Group 的 Standards Track Internet-Draft。文档历史显示,IESG 在 6 月 4 日批准了第 20 版,文档随后进入 RFC Editor 队列;第 21 版在 6 月 21 日提交,RFC Production Center 又在 8 月 13 日把状态记为 Awaiting First editor。这是一份已经通过 IESG、进入编辑生产阶段的成熟草案,但截至研究截点仍不是 RFC。

第 21 版原文写明将于 2026 年 12 月 23 日到期;20→21 官方差异界定了当前文本。标准化进度不能用来推断实际部署率,更不能证明任何实现都按文本运行。OAuth Working Group负责标准文本,不是本文假设场景中的服务运营者。

草案面向由 JOSE 或 COSE 保护的多类令牌,包括 JWT、SD-JWT、CWT 与 ISO mdoc 场景。RFC 7515和 RFC 7519分别给出 JWS 与 JWT 基础;RFC 8392和 RFC 9052覆盖 CWT 与 COSE。状态列表不是原凭证的副本,而是另一份受保护的数据,说明凭证签发后可能变化的状态。

URI 和索引只负责找到那一格

使用列表机制时,Referenced Token 的 status 中包含 status_list,里面必须有 uri 和非负整数 idx。前者指向 Status List Token,后者指出要读取的位置。这对坐标能定位断言,却没有包含撤销理由、批准人或源系统事件。

Status Issuer 为每个条目选择一、二、四或八位,分配互不相同的索引,把状态从字节的最低有效位向最高有效位排列,再用 DEFLATE 与 ZLIB 格式压缩。RFC 1951和 RFC 1950定义了相应压缩层。压缩数组最终进入 JWT 或 CWT,并由数字签名或 MAC 保护。

这里至少有三种角色。Issuer 签发 Referenced Token;Status Issuer 接收状态信息并制作受保护列表;Status Provider 对外提供列表。三项职能可以由同一实体承担,也可以拆开。第三方托管或 CDN 可以分发一份已签名列表,而不因此获得修改受保护内容的权力。

正因为可以拆分,证据也不能混写。Status Issuer 的签名能说明列表字节来自谁、是否被篡改;Provider 的响应能说明某个时点交付了什么。两者都不会凭空证明上游账户何时关闭、谁批准暂停或源事件何时进入状态系统。即使三种角色位于同一机构,职能边界仍然存在。

状态语义由注册表约束:0x00 是 VALID,0x01 是 INVALID,0x02 是 SUSPENDED,另有应用专用范围及未来注册空间。若每项使用多位,整组位共同表示一个状态,而不是同时发生的多项事件。

草案还明确,Referenced Token 自身的处理规则优先。已经过期的令牌不会因为列表显示 VALID 而重新有效;其格式、签名、必要 claims 和其他限制都要先检查。状态读取完成后,relying party 仍须按应用策略作出决定。因此,“凭证验证通过”“列表验证通过”“状态为 VALID”“应用允许操作”是四个命题,不能压成同一盏绿灯。

四只时钟测量四件事

第一只是状态源时钟:现实或管理状态究竟何时改变?也许账户在审批后撤销,也许风险系统先暂停,也许复核后恢复。第 21 版并不定义这个上游流程;紧凑条目也没有强制承载操作者、原因或事件时间。

第二只是列表签发时钟。Status List Token 的 iat 是必需项,表明受保护列表何时签发;建议使用的 exp 表明 Status Issuer 认为列表何时过期。它们限定断言的时间范围,却不等于某个条目的状态变更时间。

第三只是分发与获取时钟。列表可能经过独立 Status Provider 或 CDN。relying party 在某个时间点取得一份早先签发的对象。获取时间能证明观察,不能替代签发时间,更不能替代上游变更时间。

第四只是决策时钟。请求可能即时处理,也可能排队、批处理或等待人工复核。本地政策在此刻判断列表够不够新,并将状态与凭证其他条件组合成允许、拒绝或升级审查。

ttl 只在这些时间之间建立缓存约束。草案给出两种实践:从实际获取时刻加上 ttl 后再检查更新,有助于分散 Provider 负载;对更重视新鲜度的场景,可在 iat + ttl 附近检查,并为签发和分发留出小幅偏移。若 HTTP 缓存头与受保护 claims 冲突,Status List Token 内的 exp 与 ttl 优先。RFC 9110提供 HTTP 语义,但可接受的新鲜度仍由生态和 relying party 决定。

过长的缓存可能放大状态陈旧窗口,过短的缓存又可能制造不合理请求量。安全章节因此要求客户端为 exp 和 ttl 设合理上下界,并警告不当数值可能让 Provider URL 承受意外或恶意的巨大流量。把“遵守 ttl”写进日志仍不够;还要说明采用哪一种计时方式、适用什么本地上限,以及作决定时缓存多旧。

历史查询给出快照,不给出因果记录

默认机制提供最近状态。草案另定义可选历史解析:请求中加入 time=<timestamp>。支持该功能的服务器可以返回在指定时点有效的 Status List Token,也可以报错。若静态托管忽略查询参数、仍返回当前列表,只有当请求时间落在返回对象受保护的 iat/exp 窗口内时,客户端才可接受。

多份历史快照可以把变化夹在一个区间里。例如 10:00 的列表为 VALID,10:15 的列表为 INVALID,说明变化最迟已反映在第二份列表中。但这仍不能证明指令在 10:02 作出、源系统在 10:07 更新或 Provider 在 10:14 首次可见。原因与授权者也未必在列表中。快照序列不是因果事件日志。

隐私章节对历史功能保持克制:没有强理由、没有充分理解后果时,建议不要支持。验证方长期保存 URI/索引并反复查询,可以描绘凭证的状态轨迹;外部观察者归档列表,还可能估算签发量、撤销量或内部活动。审计需要某一决定的证据,并不必然需要所有人的永久历史。

“群体隐私”不是匿名保证

许多令牌共享一份列表,使 Issuer 看到一次列表请求,却未必知道验证方正在检查哪一张凭证。草案称之为 herd privacy。列表越大,匿名集合通常越大,但传输成本也上升。

这一边界可以被元数据削弱:每个令牌使用唯一 URI、每张凭证单独成表、列表异常小或尺寸独特,都会缩小群体;HTTP 请求仍可能暴露验证方地址。URI/索引组合本身也是可关联数据,多个验证方串通比较后,可能识别同一 Referenced Token 的多次出示。RFC 9901提供 SD-JWT 的选择性披露语境;RFC 9458定义的 Oblivious HTTP 是草案引用的一种隐藏请求来源技术。

草案列出的是工具箱,而非万能方案:第三方托管、随机或伪随机索引、诱饵条目、多份并行列表、一次性批量凭证、重新签发时使用新条目。状态语义也会泄露信息;SUSPENDED 比简单的有效/无效透露更多业务或管理阶段,应用自定义状态可能更敏感。

因此,决策回执必须做数据最小化。它不应保存完整凭证、原始 IP 或所有出示历史,只因这些数据“以后可能有用”。历史查询只在实际发生并与决定有关时记录,且需要访问控制和删除期限。

解码不是无关紧要的管道步骤

状态只有几位,并不代表不会读错。草案特别说明:位顺序在每个字节内从最低有效位走向最高有效位,字节则按自然次序前进。若实现对错误的数据解压、算错位或字节索引,就会给凭证读出错误状态。文档提供测试向量,并建议实现验证。

审计记录应保留实际执行顺序:先验证 Referenced Token;再解析 URI;检查 Status List Token 的类型、签名或 MAC 与 claims;核对其 subject 和 URI;应用 iat、exp、ttl 与本地新鲜度规则;解压;读取索引;解释注册语义;最后执行应用政策。

若索引越界,草案说无法对状态作出陈述,并要求拒绝 Referenced Token。若列表检查失败,同样无法作出状态陈述,并建议拒绝。这里的“无法陈述”不是 INVALID 的同义词:一个是明确状态,另一个是证据取得或验证失败。运营记录把两者混在一起,会掩盖完全不同的修复责任。

保存决定,而不是复制一个监控系统

Daniel Kade 提议的最小回执先保存两枚指纹:Referenced Token 的最小标识和实际使用的 Status List Token 字节哈希。随后记录 URI/索引、Status Issuer、观察到的 Provider、密钥解析、签名或 MAC 结果,以及与解码相关的实现版本或测试向量证明。

回执再把四只时钟分列:上游变更时间若确知则记录,否则明确写“未知”;列表 iat/exp;ttl、获取时刻和使用时的缓存年龄;业务决策时刻。它注明当前或历史请求、所请求时间、数值状态、注册语义、本地新鲜度上限、业务规则、实际动作与后续撤回或更正。

这不是修改 Token Status List 的建议,而是对长期后果的协议外控制。它应优先使用哈希、最小引用和有限保留。The Policy Mirror提供比较规则与可见决定的纪律;Running-Code Primacy要求核对真正运行的路径;Reality, Not Advocacy要求未知项保持未知。这些文章是治理方法,不是任何 OAuth 事故的事实证据。

资料来源