读书笔记 · 约 17,077 字 · 预计推送 2 部分

OAuth 2.1 与 OIDC 实践精读:授权码、PKCE、刷新令牌、Scope 与服务账号 —— 附个人应用接入清单 未开始

目录

题记与说明

主题研究 | 精读日期:2026-11-01 | 精读员:#0 | 前缀:knowledge-qyxuo8eu-0
交付物:七维提取 + 个人应用接入清单(第八节)

0. 来源清单与检索方式(网络可用,全部在线精读)

#来源版本/日期作用
1The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1-17Internet-Draft v17,2026-10-31,expires 2027-05-04OAuth 2.1 主体规范;§10 与 2.0 的差异清单
2RFC 9700 — Best Current Practice for OAuth 2.0 Security (BCP 240)2025-01,Lodderstedt/Bradley/Labunets/Fatt安全强制项:PKCE、刷新令牌保护、scope 最小化、客户端认证
3OpenID Connect Core 1.0最终版身份层:openid scope、ID Token、nonce/state
4RFC 8252 — OAuth 2.0 for Native AppsRFC 8252个人桌面/CLI/本地应用接入的关键规范
5Aaron Parecki, "OAuth 2.1"2019-12-12草案编辑对 OAuth 2.1 定位的解读
补RFC 6749 §4.4/§6、OIDC Discovery 1.0—client credentials 原文、refresh 请求参数、discovery 端点

检索执行记录(本会话实际运行):


一、核心问题

  1. OAuth 2.0(RFC 6749,2012)已落后于安全实践:它允许 implicit flow、密码模式、通配 redirect 匹配、不强制 PKCE——这些现在都是已知攻击面。开发者面对"读一堆 RFC 才能安全实现"的困境,Parecki 原话:"If you want to implement a secure OAuth solution today, it requires reading"多份 RFC。
  2. OAuth 只有授权、没有身份:OAuth 2.0 core "is incapable of providing information about the authentication of an End-User"(OIDC Core §1),需要 OIDC 补"我是谁"这一层。
  3. 不同形态的应用(Web 后端 / 桌面 CLI / SPA / 服务账号)各自该怎么接,没有一个"直接照抄"的默认答案——这正是本篇要产出的清单要回答的。

二、主要主张

  1. OAuth 2.1 = 2.0 + 已确立的扩展与安全 BCP 的合并,本身不发明新东西。Parecki:"My main goal with OAuth 2.1 is to capture the current best practices in OAuth 2.0 as well as its well-established extensions";"it will not define any new behavior itself, it is just to capture behavior defined in other specs"。
  2. PKCE 对所有客户端成为默认要求(不再区分 public/confidential),且 plain 方法被移除,只剩 S256。
  3. Implicit 与 Resource Owner Password Credentials 两个 grant 被整体移除。
  4. 刷新令牌是最大攻击面之一:public client 的 refresh token 必须 sender-constrained(mTLS/DPoP)或轮换(rotation),轮换配合重放检测。
  5. 权限最小化是 BCP 级要求:access token 权限"SHOULD be restricted to the minimum required"。
  6. OIDC 以 openid scope 挂在授权码流程上,身份信息通过签名过的 ID Token(JWT)传递,RP 必须校验。
  7. 原生/个人应用:外置浏览器 + 127.0.0.1 loopback 动态端口 + PKCE,内嵌 webview 被禁止,client secret 对原生应用无意义。

三、方法

四、数据与证据(关键原文引述)

4.1 OAuth 2.1 草案(draft-ietf-oauth-v2-1-17,2026-10-31)

摘要(§Abstract):

"The OAuth 2.1 authorization framework enables an application to obtain limited access to a protected resource, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and an authorization service, or by allowing the application to obtain access on its own behalf. This specification replaces and obsoletes the OAuth 2.0 Authorization Framework described in RFC 6749 and the Bearer Token Usage in RFC 6750."

§10 "Differences from OAuth 2.0" 非规范性差异清单(curl+sed 逐字提取,全文 10 条):

- The authorization code grant is extended with the functionality from PKCE [RFC7636] such that the default method of using the authorization code grant according to this specification requires the addition of the PKCE parameters
- Redirect URIs must be compared using exact string matching as per Section 4.1.3 of [RFC9700]
- The Implicit grant (response_type=token) is omitted from this specification as per Section 2.1.2 of [RFC9700]
- The Resource Owner Password Credentials grant is omitted from this specification as per Section 2.4 of [RFC9700]
- Bearer token usage omits the use of bearer tokens in the query string of URIs as per Section 4.3.2 of [RFC9700]
- Refresh tokens for public clients must either be sender-constrained or one-time-use as per Section 4.14.2 of [RFC9700]
- Refresh tokens issued to browser-based applications have additional lifetime constraints as per Section 6.3.2.3 of [RFC10017]
- The token endpoint request containing an authorization code no longer contains the redirect_uri parameter
- Authorization servers must support client credentials in the request content
- The PKCE plain method is removed

§10.1 移除 Implicit 的理由:

"The intent of removing the Implicit grant is to no longer issue access tokens in the authorization response, as such tokens are vulnerable to leakage and injection and cannot be sender-constrained to a client."

授权请求参数(首次 WebFetch 提取):code_challenge "REQUIRED unless the specific requirements of Section 7.5.1 are met";code verifier 是 "a unique, high-entropy, cryptographically random string generated for each authorization request"(43–128 字符)。

刷新令牌边界(草案正文):refresh tokens "intended for use only with authorization servers and are never sent to resource servers";签发时 "MUST be bound to the scope and resource servers as consented by the resource owner"。

4.2 RFC 9700(BCP 240,2025-01)

"the authorization server issues a new refresh token with every access token refresh response. The previous refresh token is invalidated, but information about the relationship is retained by the authorization server. If a refresh token is compromised and subsequently used by both the attacker and the legitimate client, one of them will present an invalidated refresh token, which will inform the authorization server of the breach. The authorization server cannot determine which party submitted the invalid refresh token, but it will revoke the active refresh token."
"Refresh tokens SHOULD expire if the client has been inactive for some time"

4.3 OpenID Connect Core 1.0

4.4 RFC 8252(原生应用,个人桌面/CLI 工具的依据)

4.5 RFC 6749 补充(服务账号与刷新请求)

4.6 OIDC Discovery 1.0

五、结论

  1. OAuth 2.1 把"正确姿势"从"读十份 RFC"变成"一份文档 + 一份差异清单":授权码 + PKCE(S256) 是唯一推荐的用户侧流程;implicit、password、plain PKCE、query-string bearer、通配 redirect 全部出局。
  2. 个人应用(无服务器、无 secret)走 public client 路线:外置浏览器 + loopback redirect + PKCE + 刷新令牌轮换;安全模型不依赖"app 能保密"。
  3. 服务账号是另一条轨道:client credentials,仅 confidential client,无用户同意、无 refresh token,认证方式推荐非对称(私钥 JWT/mTLS)。
  4. OIDC = OAuth 2.1 + openid scope + ID Token 校验;要做登录就必须验 iss/aud/exp/签名/nonce,不能拿 access token 当登录凭证。
  5. 刷新令牌与 scope 是两条纵深防御:轮换 + 重放检测兜住令牌泄露,最小 scope 兜住单次泄露的影响半径。

六、局限

  1. OAuth 2.1 仍是 Internet-Draft(v17,2026-10-31 发布,2027-05-04 过期;"replaces and obsoletes 6749/6750 (if approved)"),条文与章节号可能变动,落地时以最新版本为准。
  2. RFC 10017(Browser-Based Apps)与 DPoP/mTLS 仅引用未精读:SPA 的刷新令牌生命周期限制(§6.3.2.3)、sender-constrained 的实现细节未展开;清单中相应条目标注为"依据引用而非精读"。
  3. 抓取截断与引语精度:WebFetch 对超长页面会截断(OIDC §5.4 完整 claims 表、RFC 9700 §4.14 首轮均被截断),已用 rfc-editor/ietf 纯文本逐字补齐关键条文;但小模型摘要的引语可能与原文有细微出入,已尽量以 curl 直取文本为准。
  4. 未覆盖实现生态:各厂商(Google/微软/GitHub/Auth0/Keycloak)对刷新令牌策略、aud 约束、discovery 支持的差异未调研;本笔记是规范视角,不是集成 troubleshooting 手册。
  5. 清单中的"个人经验判断"条目(如 PAT 与 client credentials 的取舍、keychain 存储)是推断与工程惯例,规范依据较弱,已单独标注。

七、对我的启发

  1. 个人工具/脚本接入第三方 API 的默认模板就此定型:authorization code + PKCE + http://127.0.0.1:<随机端口> 回调——不用申请 webhook、不用固定端口,重启即换端口也合规(RFC 8252 §7.3 明文要求 AS 允许任意端口)。
  2. 批量数据任务别模拟浏览器登录:那是 password grant 的个人版复刻,2.1 已把这条路标为非法;要么 client credentials(服务账号),要么用户侧授权一次 + 刷新令牌轮换长期续命。
  3. invalid_grant 报错不要无脑重试:按 RFC 9700 §4.14.2,它可能意味着授权已因重放检测被整体吊销,正确动作是重新走一次授权,而不是拿旧 refresh token 疯狂重试。
  4. 写技术文章/做分享的好素材:草案 §10 的 10 条差异清单本身就是一篇"OAuth 2.0 → 2.1 迁移指南"的骨架,且每条都带 RFC 9700 回指,可溯源。
  5. 最小 scope 是纪律不是洁癖:个人应用常常图省事全要 profile email read write;每次只请求当下需要的 scope,权限升级走二次授权——这既是规范 SHOULD,也是降低"token 泄露半径"的实际手段。

八、产出:个人应用接入清单(核心交付物)

用法:按应用形态选 A 或 B 或 C,然后按序执行。每条末尾括号标注依据;标注〔经验〕的为规范之外的工程判断。

A0. 选型决策(先回答一个问题:token 给谁用?)

你的形态走哪条关键依据
有后端的 Web 应用,用户在浏览器登录A. 授权码 + PKCE(+ OIDC)草案 §10;RFC 9700 §2.1.1
桌面程序 / CLI / 本地脚本,调用户自己的云端数据B. 授权码 + PKCE + loopback 回调RFC 8252
纯前端 SPA(无后端)B + 浏览器应用 BCP(RFC 10017,刷新令牌有额外生命周期限制)〔依据引用,未精读〕草案 §10
无人值守脚本 / CI / 定时任务,只访问"自己的"资源C. 服务账号 client credentialsRFC 6749 §4.4
一句话同步的小工具直接 PAT / 环境变量注入,别上 OAuth〔经验〕—
任何形态永远不用 implicit、password grant草案 §10.1、RFC 9700 §2.1.2/§2.4

A. Web 应用(有后端)

  1. 从 https://<issuer>/.well-known/openid-configuration 拉 endpoints 与 issuer(Discovery 1.0)。
  2. 注册 redirect URI 用精确字符串(协议、域名、路径全匹配),不做通配(RFC 9700 §4.1.3;草案 §10)。
  3. 生成随机 state(绑定服务端 session,防 CSRF)与高熵 nonce;一次性使用(OIDC Core §3.1.2.1)。
  4. 授权请求:response_type=code + scope=openid <最小业务scope> + code_challenge(S256)+ code_challenge_method=S256 + state + nonce(草案:PKCE 参数 REQUIRED;OIDC:openid scope MUST)。
  5. 回调校验 state 后,用 code + code_verifier POST token endpoint;2.1 下不再带 redirect_uri 参数(草案 §10)。
  6. 校验 ID Token:签名(JWS,拒绝 none)→ iss 精确匹配 → aud 含你的 client_id → exp/iat → nonce 回读(OIDC Core §2、§3.1.3.7)。
  7. access token 存服务端(内存/加密存储),不放 URL、不下发到前端不需要的位置(草案 §10:bearer 不走 query string)。
  8. 刷新:grant_type=refresh_token,scope 只能收缩不能扩(RFC 6749 §6);轮换模式下收到新 refresh token 就替换旧的。

B. 桌面 / CLI / 本地个人工具

  1. 注册 redirect URI 为 http://127.0.0.1(路径可自定义),不要写死端口、不要用 localhost 字面量(RFC 8252 §7.3/§8.3)。
  2. 启动时绑定任意可用端口监听回调用,回调 URI 形如 http://127.0.0.1:49152/callback(AS 被要求允许任意端口)。
  3. 用系统默认浏览器打开授权页;绝不内嵌 webview(RFC 8252 §8.12;有条件时用应用内浏览器标签提升体验)。
  4. PKCE 必须:每个授权请求生成 43–128 字符随机 verifier,S256 方式;plain 已被 2.1 移除(草案 §10)。
  5. 无 client secret(native app 假定无法保密,RFC 8252 §8.5 / Appendix A)。
  6. 刷新令牌按 public client 标准:轮换 or sender-constrained(RFC 9700 §2.2.2);本地实现轮换——每次刷新保存新 token、丢弃旧的。
  7. 收到 invalid_grant:停止重试,当作授权已整体吊销,重新走一遍授权流程(RFC 9700 §4.14.2 重放检测语义)。
  8. refresh token 存 OS keychain(Windows Credential Manager / macOS Keychain / Secret Service),不进明文配置文件、不进 git〔经验〕。

C. 服务账号(脚本 / CI / 无人值守)

  1. 用 client credentials grant:grant_type=client_credentials + 客户端认证;仅 confidential client(RFC 6749 §4.4 MUST)。
  2. 认证方式优先非对称:私钥 JWT(private_key_jwt)或 mTLS,其次才是 client secret;推荐"AS 不存对称密钥"的方案(RFC 9700 §2.5 RECOMMENDED)。
  3. 只申请访问"自己控制的资源"或"预先约定的资源";scope/resource 参数显式收窄,期望 token 的 aud 受限到目标 RS(RFC 9700 §2.3)。
  4. 不要指望 refresh token:client credentials 响应 "SHOULD NOT" 含 refresh token(RFC 6749 §4.4.3)——需要长期凭证就把密钥本身管理好,或到期前重新申请〔含经验判断〕。
  5. 密钥管理:环境变量或密钥管理器注入,.env 进 .gitignore;个人项目最起码做到 secret 不进仓库、不进日志(RFC 9700 §4.15 类要求:不泄日志)〔经验〕。
  6. 记得给脚本处理 401 → 重新认证的路径,而不是缓存过期 token 后无限重试〔经验〕。

D. 通用校验与坑位自查(所有形态)

E. 已知实现提示(未逐一验证,标注为推断)


附:一句话总结

OAuth 2.1 没有发明新东西,它把十年来 OAuth 2.0 社区用血泪换来的安全共识(PKCE、精确 redirect、刷新令牌轮换、移除 implicit/password)写进了一份文档;个人应用接入的答案因此变得极简:外置浏览器 + 授权码 + PKCE(S256) + 127.0.0.1 回调 + 轮换的刷新令牌 + 最小 scope;服务账号单独走 client credentials + 非对称认证。

(检索与核验方式:本篇全部来源在线抓取并精读,关键条文经 curl + sed/grep 对 RFC/草案纯文本逐字核对;无网络降级情况未发生。)

← 资料库今日播报 →