题记与说明
主题研究 | 精读日期:2026-11-01 | 精读员:#0 | 前缀:knowledge-qyxuo8eu-0
交付物:七维提取 + 个人应用接入清单(第八节)
0. 来源清单与检索方式(网络可用,全部在线精读)
| # | 来源 | 版本/日期 | 作用 |
|---|---|---|---|
| 1 | The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1-17 | Internet-Draft v17,2026-10-31,expires 2027-05-04 | OAuth 2.1 主体规范;§10 与 2.0 的差异清单 |
| 2 | RFC 9700 — Best Current Practice for OAuth 2.0 Security (BCP 240) | 2025-01,Lodderstedt/Bradley/Labunets/Fatt | 安全强制项:PKCE、刷新令牌保护、scope 最小化、客户端认证 |
| 3 | OpenID Connect Core 1.0 | 最终版 | 身份层:openid scope、ID Token、nonce/state |
| 4 | RFC 8252 — OAuth 2.0 for Native Apps | RFC 8252 | 个人桌面/CLI/本地应用接入的关键规范 |
| 5 | Aaron Parecki, "OAuth 2.1" | 2019-12-12 | 草案编辑对 OAuth 2.1 定位的解读 |
| 补 | RFC 6749 §4.4/§6、OIDC Discovery 1.0 | — | client credentials 原文、refresh 请求参数、discovery 端点 |
检索执行记录(本会话实际运行):
- 连通性:
curl -s -o /dev/null -w "%{http_code}" https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1→200 - WebFetch 精读 ×5(来源 1–5,带定向提问提取引语)
- 因页面截断,用 curl + sed/grep 对纯文本逐字核对关键条文:
curl -s https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-17.txt+sed -n '/^10\./,/^11\./p'→ §10 全文(注意:draft-ietf-oauth-v2-17.txt是 2011 年老草案,文件名少一个连字符会抓错文档)curl -s https://www.rfc-editor.org/rfc/rfc9700.txt+sed -n '1930,2020p'→ §4.14 刷新令牌保护全文curl -s https://www.rfc-editor.org/rfc/rfc6749.txt+ sed → §4.4、§6 原文curl -s https://openid.net/specs/openid-connect-core-1_0.html+ 标签剥离 + grep → §5.4 scope→claims 映射curl -s https://openid.net/specs/openid-connect-discovery-1_0.html+ grep →/.well-known/openid-configuration确认存在
一、核心问题
- 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。
- OAuth 只有授权、没有身份:OAuth 2.0 core "is incapable of providing information about the authentication of an End-User"(OIDC Core §1),需要 OIDC 补"我是谁"这一层。
- 不同形态的应用(Web 后端 / 桌面 CLI / SPA / 服务账号)各自该怎么接,没有一个"直接照抄"的默认答案——这正是本篇要产出的清单要回答的。
二、主要主张
- 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"。
- PKCE 对所有客户端成为默认要求(不再区分 public/confidential),且 plain 方法被移除,只剩 S256。
- Implicit 与 Resource Owner Password Credentials 两个 grant 被整体移除。
- 刷新令牌是最大攻击面之一:public client 的 refresh token 必须 sender-constrained(mTLS/DPoP)或轮换(rotation),轮换配合重放检测。
- 权限最小化是 BCP 级要求:access token 权限"SHOULD be restricted to the minimum required"。
- OIDC 以
openidscope 挂在授权码流程上,身份信息通过签名过的 ID Token(JWT)传递,RP 必须校验。 - 原生/个人应用:外置浏览器 + 127.0.0.1 loopback 动态端口 + PKCE,内嵌 webview 被禁止,client secret 对原生应用无意义。
三、方法
- 文献结构:一层"合并性规范"(OAuth 2.1 草案,把 8252/7636/9700/6750/10017 并进来),一层"强制安全要求"(RFC 9700),一层"身份扩展"(OIDC Core),一层"应用形态细则"(RFC 8252),加一篇草案编辑的动机解读。五份材料互相印证:草案 §10 的每条差异都回指 RFC 9700 的具体章节,可用引语交叉验证。
- 提取方法:对每份来源定向提问抓取规范级引语(MUST/SHOULD/RECOMMENDED 措辞);截断处用 curl 拉纯文本 + sed/grep 按行号逐字补齐;RFC 编号 + 章节号随引语落盘,保证清单里每条建议可溯源。
四、数据与证据(关键原文引述)
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)
- §2.1.1 PKCE:"Public clients MUST use PKCE [RFC7636]";"For confidential clients, the use of PKCE [RFC7636] is RECOMMENDED, as it provides strong protection against misuse and injection of authorization codes";"Authorization servers MUST support PKCE [RFC7636]."
- §4.5.3.1 PKCE 防什么:"PKCE ensures that an attacker cannot redeem a stolen authorization code at the token endpoint of the authorization server without knowledge of the code_verifier."
- §2.2.2 刷新令牌:"Refresh tokens for public clients MUST be sender-constrained or use refresh token rotation as described in Section 4.14."
- §4.14.1(curl+sed 逐字提取):"Refresh tokens are an attractive target for attackers because they represent the full scope of access granted to a certain client, and they are not further constrained to a specific resource."
- §4.14.2 轮换与重放检测(逐字):
"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"
- §2.3 最小权限:"The privileges associated with an access token SHOULD be restricted to the minimum required for the particular application or use case."(可用
scope/resource〔RFC 8707〕/authorization_details〔RFC 9396〕收窄) - §2.1/§4.1.3 redirect:"authorization servers MUST utilize exact string matching except for port numbers in localhost redirection URIs of native apps"
- §2.5 客户端认证:"It is RECOMMENDED to use asymmetric cryptography for client authentication, such as mutual TLS for OAuth 2.0 [RFC8705] or signed JWTs"
4.3 OpenID Connect Core 1.0
- §1 定位:"a simple identity layer on top of the OAuth 2.0 protocol";"OpenID Connect implements authentication as an extension to the OAuth 2.0 authorization process."
- §3.1.2.1 openid scope:"OpenID Connect requests MUST contain the openid scope value. If the openid scope value is not present, the behavior is entirely unspecified."
- §2 ID Token 必含 claims(逐字提取):
iss"Issuer Identifier";sub"Subject Identifier"(≤255 ASCII);aud"MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value";exp/iatREQUIRED。"ID Tokens MUST be signed using JWS";"ID Tokens MUST NOT use none as the alg value"。 - §3.1 授权码流程价值:"This provides the benefit of not exposing any tokens to the User Agent."
- §3.1.2.1:nonce "String value used to associate a Client session with an ID Token, and to mitigate replay attacks";state "Opaque value used to maintain state between the request and the callback",CSRF 防护靠 "cryptographically binding the value of this parameter with a browser cookie"。
- §5.4 scope→claims(curl 标签剥离后逐行提取):
profilescope "requests access to the End-User's default profile Claims, which are: name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at";emailscope "requests access to the email and email_verified Claims"。
4.4 RFC 8252(原生应用,个人桌面/CLI 工具的依据)
- 摘要:"OAuth 2.0 authorization requests from native apps should only be made through external user-agents, primarily the user's browser."
- §8.12:"native apps MUST NOT use embedded user-agents to perform authorization requests"——原因:宿主 app "can access the user's full authentication credential, not just the OAuth authorization grant"。
- §7.3 loopback redirect:URI 形如
http://127.0.0.1:{port}/{path}(IPv6 为http://[::1]:{port}/{path}),"The authorization server MUST allow any port to be specified at the time of the request for loopback IP redirect URIs"。 - §8.3:用
localhost字面量 "NOT RECOMMENDED",应使用 loopback IP 字面量127.0.0.1。 - §8.1 为什么要 PKCE:"code interception by a different native app running on the same device may be possible";"The PKCE [RFC7636] protocol was created specifically to mitigate this attack."
- §8.5 client secret:"it is NOT RECOMMENDED for authorization servers to require client authentication of public native apps clients using a shared secret";Appendix A:"Not assume that native app clients can keep a secret."
- §7.2:claimed
httpsscheme(自定义域名反向注册)"SHOULD use them over the other options where possible"。
4.5 RFC 6749 补充(服务账号与刷新请求)
- §4.4(curl+sed 逐字):"The client can request an access token using only its client credentials … when the client is requesting access to the protected resources under its control, or those of another resource owner that have been previously arranged with the authorization server";"The client credentials grant type MUST only be used by confidential clients."
- §6 刷新请求参数:
grant_type=refresh_token(REQUIRED)、refresh_token(REQUIRED)、scope(OPTIONAL)——"The requested scope MUST NOT include any scope not originally granted by the resource owner, and if omitted is treated as equal to the scope originally granted by the resource owner."
4.6 OIDC Discovery 1.0
- "the configuration information for that OP is retrieved from a well-known location as a JSON document, including its OAuth 2.0 endpoint locations"——端点即
/.well-known/openid-configuration(grep 确认该字符串存在于规范)。
五、结论
- OAuth 2.1 把"正确姿势"从"读十份 RFC"变成"一份文档 + 一份差异清单":授权码 + PKCE(S256) 是唯一推荐的用户侧流程;implicit、password、plain PKCE、query-string bearer、通配 redirect 全部出局。
- 个人应用(无服务器、无 secret)走 public client 路线:外置浏览器 + loopback redirect + PKCE + 刷新令牌轮换;安全模型不依赖"app 能保密"。
- 服务账号是另一条轨道:client credentials,仅 confidential client,无用户同意、无 refresh token,认证方式推荐非对称(私钥 JWT/mTLS)。
- OIDC = OAuth 2.1 +
openidscope + ID Token 校验;要做登录就必须验 iss/aud/exp/签名/nonce,不能拿 access token 当登录凭证。 - 刷新令牌与 scope 是两条纵深防御:轮换 + 重放检测兜住令牌泄露,最小 scope 兜住单次泄露的影响半径。
六、局限
- OAuth 2.1 仍是 Internet-Draft(v17,2026-10-31 发布,2027-05-04 过期;"replaces and obsoletes 6749/6750 (if approved)"),条文与章节号可能变动,落地时以最新版本为准。
- RFC 10017(Browser-Based Apps)与 DPoP/mTLS 仅引用未精读:SPA 的刷新令牌生命周期限制(§6.3.2.3)、sender-constrained 的实现细节未展开;清单中相应条目标注为"依据引用而非精读"。
- 抓取截断与引语精度:WebFetch 对超长页面会截断(OIDC §5.4 完整 claims 表、RFC 9700 §4.14 首轮均被截断),已用 rfc-editor/ietf 纯文本逐字补齐关键条文;但小模型摘要的引语可能与原文有细微出入,已尽量以 curl 直取文本为准。
- 未覆盖实现生态:各厂商(Google/微软/GitHub/Auth0/Keycloak)对刷新令牌策略、aud 约束、discovery 支持的差异未调研;本笔记是规范视角,不是集成 troubleshooting 手册。
- 清单中的"个人经验判断"条目(如 PAT 与 client credentials 的取舍、keychain 存储)是推断与工程惯例,规范依据较弱,已单独标注。
七、对我的启发
- 个人工具/脚本接入第三方 API 的默认模板就此定型:authorization code + PKCE +
http://127.0.0.1:<随机端口>回调——不用申请 webhook、不用固定端口,重启即换端口也合规(RFC 8252 §7.3 明文要求 AS 允许任意端口)。 - 批量数据任务别模拟浏览器登录:那是 password grant 的个人版复刻,2.1 已把这条路标为非法;要么 client credentials(服务账号),要么用户侧授权一次 + 刷新令牌轮换长期续命。
invalid_grant报错不要无脑重试:按 RFC 9700 §4.14.2,它可能意味着授权已因重放检测被整体吊销,正确动作是重新走一次授权,而不是拿旧 refresh token 疯狂重试。- 写技术文章/做分享的好素材:草案 §10 的 10 条差异清单本身就是一篇"OAuth 2.0 → 2.1 迁移指南"的骨架,且每条都带 RFC 9700 回指,可溯源。
- 最小 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 credentials | RFC 6749 §4.4 |
| 一句话同步的小工具 | 直接 PAT / 环境变量注入,别上 OAuth〔经验〕 | — |
| 任何形态 | 永远不用 implicit、password grant | 草案 §10.1、RFC 9700 §2.1.2/§2.4 |
A. Web 应用(有后端)
- 从
https://<issuer>/.well-known/openid-configuration拉 endpoints 与 issuer(Discovery 1.0)。 - 注册 redirect URI 用精确字符串(协议、域名、路径全匹配),不做通配(RFC 9700 §4.1.3;草案 §10)。
- 生成随机
state(绑定服务端 session,防 CSRF)与高熵nonce;一次性使用(OIDC Core §3.1.2.1)。 - 授权请求:
response_type=code+scope=openid <最小业务scope>+code_challenge(S256)+code_challenge_method=S256+state+nonce(草案:PKCE 参数 REQUIRED;OIDC:openidscope MUST)。 - 回调校验
state后,用code+code_verifierPOST token endpoint;2.1 下不再带redirect_uri参数(草案 §10)。 - 校验 ID Token:签名(JWS,拒绝
none)→iss精确匹配 →aud含你的 client_id →exp/iat→nonce回读(OIDC Core §2、§3.1.3.7)。 - access token 存服务端(内存/加密存储),不放 URL、不下发到前端不需要的位置(草案 §10:bearer 不走 query string)。
- 刷新:
grant_type=refresh_token,scope只能收缩不能扩(RFC 6749 §6);轮换模式下收到新 refresh token 就替换旧的。
B. 桌面 / CLI / 本地个人工具
- 注册 redirect URI 为
http://127.0.0.1(路径可自定义),不要写死端口、不要用localhost字面量(RFC 8252 §7.3/§8.3)。 - 启动时绑定任意可用端口监听回调用,回调 URI 形如
http://127.0.0.1:49152/callback(AS 被要求允许任意端口)。 - 用系统默认浏览器打开授权页;绝不内嵌 webview(RFC 8252 §8.12;有条件时用应用内浏览器标签提升体验)。
- PKCE 必须:每个授权请求生成 43–128 字符随机 verifier,S256 方式;plain 已被 2.1 移除(草案 §10)。
- 无 client secret(native app 假定无法保密,RFC 8252 §8.5 / Appendix A)。
- 刷新令牌按 public client 标准:轮换 or sender-constrained(RFC 9700 §2.2.2);本地实现轮换——每次刷新保存新 token、丢弃旧的。
- 收到
invalid_grant:停止重试,当作授权已整体吊销,重新走一遍授权流程(RFC 9700 §4.14.2 重放检测语义)。 - refresh token 存 OS keychain(Windows Credential Manager / macOS Keychain / Secret Service),不进明文配置文件、不进 git〔经验〕。
C. 服务账号(脚本 / CI / 无人值守)
- 用 client credentials grant:
grant_type=client_credentials+ 客户端认证;仅 confidential client(RFC 6749 §4.4 MUST)。 - 认证方式优先非对称:私钥 JWT(private_key_jwt)或 mTLS,其次才是 client secret;推荐"AS 不存对称密钥"的方案(RFC 9700 §2.5 RECOMMENDED)。
- 只申请访问"自己控制的资源"或"预先约定的资源";
scope/resource参数显式收窄,期望 token 的aud受限到目标 RS(RFC 9700 §2.3)。 - 不要指望 refresh token:client credentials 响应 "SHOULD NOT" 含 refresh token(RFC 6749 §4.4.3)——需要长期凭证就把密钥本身管理好,或到期前重新申请〔含经验判断〕。
- 密钥管理:环境变量或密钥管理器注入,
.env进.gitignore;个人项目最起码做到 secret 不进仓库、不进日志(RFC 9700 §4.15 类要求:不泄日志)〔经验〕。 - 记得给脚本处理 401 → 重新认证的路径,而不是缓存过期 token 后无限重试〔经验〕。
D. 通用校验与坑位自查(所有形态)
- [ ] 没有用
response_type=token(implicit)或 password grant(草案 §10.1) - [ ] PKCE 用 S256,没用 plain(草案 §10)
- [ ] redirect URI 精确匹配,本地用
127.0.0.1而非localhost(RFC 9700 §4.1.3;RFC 8252 §8.3) - [ ] 没把 access token / refresh token 放进 URL query(草案 §10)
- [ ] refresh token 只发给 authorization server,从不发给资源服务器(草案正文)
- [ ] ID Token 验了 iss/aud/exp/签名/nonce(OIDC Core §2/§3.1.3.7)
- [ ] scope 每次申请都最小化,刷新不扩权(RFC 9700 §2.3;RFC 6749 §6)
- [ ] client secret(若有)不在前端、不在原生 app 内、不在日志里(RFC 8252 §8.5;RFC 9700 §2.5)
- [ ] 服务账号没用在 public client 场景(RFC 6749 §4.4 MUST)
- [ ]
invalid_grant的处理是"重新授权"而非"重试刷新"(RFC 9700 §4.14.2)
E. 已知实现提示(未逐一验证,标注为推断)
- 主流 AS(Google、Microsoft Entra、GitHub、Auth0、Keycloak)均已强制/默认 PKCE,部分已拒绝 implicit——与 2.1 方向一致,但各家对刷新令牌轮换、
aud、idle 过期的默认值差异大,接入前查各自文档〔推断,未核实〕。 - 浏览器端 localStorage 存 token 的风险主要来自 XSS;SPA 的 token 存储建议以 RFC 10017 为准(本篇未精读,仅引用其存在与草案 §10 的生命周期限制条款)。
附:一句话总结
OAuth 2.1 没有发明新东西,它把十年来 OAuth 2.0 社区用血泪换来的安全共识(PKCE、精确 redirect、刷新令牌轮换、移除 implicit/password)写进了一份文档;个人应用接入的答案因此变得极简:外置浏览器 + 授权码 + PKCE(S256) + 127.0.0.1 回调 + 轮换的刷新令牌 + 最小 scope;服务账号单独走 client credentials + 非对称认证。
(检索与核验方式:本篇全部来源在线抓取并精读,关键条文经 curl + sed/grep 对 RFC/草案纯文本逐字核对;无网络降级情况未发生。)