在线工具集

OAuth 2.0 完全指南:4 种 grant flow 实战

OAuth 2.0 是当今互联网最重要的授权协议,从微信登录到 GitHub 第三方应用接入,从企业 SSO 到 API 平台,几乎所有需要委托授权的场景都在用它。但 OAuth 2.0 的复杂之处也在于它不是单一协议而是一个框架——四种主要的 grant flow、PKCE 扩展、refresh token 机制、scope 设计、与 OpenID Connect 的关系,每一项都有大量细节与安全陷阱。本文将逐一拆解四种 grant flow 的应用场景与流程,配合 PKCE、OIDC、refresh token、scope 设计等关键扩展,帮你建立完整的 OAuth 2.0 知识体系,避免落入常见安全陷阱。

一、OAuth 解决什么问题

设想这样的场景:你想用第三方应用(如打印照片的网站)访问你的 Google Photos。最朴素的做法是把 Google 账号密码交给第三方,但这意味着第三方拥有你账号的全部权限,可以随时改密码、删邮件、读取你的银行邮件。这显然不能接受。

OAuth 2.0 提供了优雅的解法:用户去 Google(资源服务器与授权服务器)登录,授权第三方应用一个有限制的 access token;第三方应用拿这个 token 去访问 Google Photos API;token 有过期时间、有限定的 scope(只能读照片不能改密码)、可以随时撤销。整个过程第三方从来不接触用户密码。OAuth 把"授权"与"认证"解耦,把"访问凭证"与"用户密码"解耦,是现代委托授权的基础设施。

二、四个核心角色与基本概念

OAuth 2.0 定义了四个角色:Resource Owner(资源所有者,通常是用户)、Client(客户端,第三方应用)、Resource Server(资源服务器,存储被保护资源的服务)、Authorization Server(授权服务器,颁发 token 的服务)。在小型部署里,Resource Server 与 Authorization Server 可以是同一个进程,但概念上必须分开,因为大公司里两者通常由不同团队维护。

核心凭证有三种:authorization code(短期、一次性的中间凭证)、access token(短期访问凭证,通常 5 分钟到 1 小时)、refresh token(长期凭证,用于换新的 access token)。客户端分两类:confidential client(能保护 client_secret,如后端服务)、public client(无法保护 secret,如手机 App、SPA)。这些概念是理解后续四种 flow 的基础。

三、Authorization Code Flow(最常用)

授权码流程是 OAuth 2.0 最重要、最推荐的 flow,适用于带后端的 Web 应用。流程如下:用户点击"用 GitHub 登录",浏览器跳转到 GitHub 授权页(带 client_id、redirect_uri、scope、state 参数);用户登录并同意授权,GitHub 重定向回客户端 redirect_uri(带 code 参数);客户端后端用 code + client_id + client_secret 向 GitHub token endpoint 换取 access_token;后端拿 access_token 调用 GitHub API。

这个流程的精妙之处是 access_token 永远不会出现在浏览器 URL 或前端 JS 里。中间的 code 即使泄露也只能用一次、且必须配合 client_secret 才能兑换。state 参数用于防御 CSRF:客户端在跳转前生成随机 state,回调时校验是否匹配。这是任何 OAuth 实现的最低安全要求。本站的 UUID 生成工具可以用来生成 state 与 nonce 参数。

四、Authorization Code with PKCE(移动端 / SPA)

朴素的 authorization code flow 假设客户端能保密 client_secret,这对后端服务成立,对手机 App 和单页应用却不成立——APK 可以反编译,SPA 的 JS 任何人都能读。如果攻击者拿到 client_secret 与截获的 code,就能假冒客户端换 token。

PKCE(Proof Key for Code Exchange,发音 pixy)解决了这个问题。客户端在跳转授权前生成一个随机字符串 code_verifier,对它做 SHA256 哈希得到 code_challenge,把 challenge 发给授权服务器;后续用 code 换 token 时再附上 verifier;服务器对 verifier 做同样的哈希,校验是否等于先前的 challenge。即使攻击者截获 code,没有 verifier 也无法换 token。OAuth 2.1 已经把 PKCE 列为所有客户端的强制要求,不论 confidential 还是 public。可以用本站的 SHA256 哈希工具理解 challenge 计算过程。

五、Client Credentials Flow(机器对机器)

当没有用户参与,是两个后端服务之间的相互调用时,使用 client credentials flow。客户端直接用 client_id + client_secret 向 token endpoint 换 access token,跳过用户授权环节。典型场景:A 服务调用 B 服务的内部 API、定时任务调用第三方 API、CI 流水线发布制品到对象存储。

由于没有用户,scope 通常是基于服务身份预先配置的固定权限。client_secret 必须通过环境变量、KMS、Vault 等机制安全存储,绝对不能写死在代码里、提交到 Git。token 缓存策略也很重要:access token 有效期通常 1 小时,频繁调用的服务应该缓存 token 直到接近过期再续,否则会把授权服务器打挂。监控 token endpoint 的 QPS 是常规运维项。

六、Resource Owner Password Credentials(已弃用)

Password grant 让客户端直接收集用户密码,再用密码 + client_id 换 token。它存在的初衷是为同公司的官方 App 提供"原生登录体验"——用户在 App 内输密码而不必跳转浏览器。

但这种 flow 严重违背了 OAuth 的核心原则:客户端不应该接触用户密码。一旦官方 App 被仿冒、或 App 内嵌 WebView 被注入恶意脚本、或第三方 SDK 偷偷上报,密码就会泄露。OAuth 2.1 已经正式弃用 password grant,所有新项目都不应该使用。即便是官方 App,也应该走 authorization code + PKCE,配合 system browser 或 ASWebAuthenticationSession 提供原生体验。仅在迁移老系统的过渡阶段、且客户端与授权服务器属于同一家公司、且能强制更新客户端时,才作为权宜之计使用。

七、Refresh Token 与会话管理

access token 通常很短(5 分钟到 1 小时),目的是限制泄露后的损失。但用户不能每小时重新登录一次,所以授权服务器同时颁发 refresh token——一个长期凭证(数周到数月),客户端用它换新的 access token,无需用户参与。

refresh token 是高价值凭证,存储要求严格:后端服务存数据库并加密;移动端存系统 KeyStore;浏览器 SPA 推荐 BFF 模式(refresh token 留在后端 cookie,前端只拿 access token)。Refresh token rotation 是另一个重要实践:每次使用 refresh token 都换发新的 refresh token 并使旧的失效,配合家族追踪可以检测到 token 泄露——如果一个旧 token 被使用,整个家族立即吊销。授权服务器应该提供撤销端点(RFC 7009)让客户端主动登出。本站的 JWT 最佳实践详细讨论了 token 存储与轮换。

八、OpenID Connect 与身份认证

OAuth 2.0 解决的是授权(你能不能干某事),不是认证(你是谁)。但很多场景需要的恰恰是认证——"用 GitHub 登录"本质上是想知道这个用户是谁。OpenID Connect(OIDC)在 OAuth 2.0 之上加了认证层:在 access_token 之外多颁发一个 id_token(一个签名的 JWT),里面包含 sub(用户唯一 ID)、email、name 等标准 claim。

OIDC 的好处是标准化:客户端只需要解析 id_token 就能拿到用户身份,不必再调用各家专有的 user_info API。Discovery endpoint(/.well-known/openid-configuration)让客户端自动发现授权服务器的端点与公钥,简化了集成。Auth0、Okta、Keycloak、AWS Cognito、Azure AD 都是 OIDC 的实现。如果你只需要登录,用 OIDC;如果需要委托访问 API,用 OAuth 2.0;通常两者一起用,OIDC 颁发 id_token + access_token。常见安全陷阱有:未校验 id_token 签名、未校验 aud(受众)、未校验 nonce、未防范替换攻击、混用 access_token 当作身份凭证。每一项都可能导致严重漏洞,必须严格按照 OIDC 规范实现。

常见问题

OAuth 和 OpenID Connect 有什么区别?

OAuth 2.0 解决的是授权问题,回答的是这个客户端能不能代表用户访问某资源。OpenID Connect 在 OAuth 2.0 基础上加了一层认证(Authentication),通过 id_token 把用户的身份信息带给客户端。简单说,OAuth 是授权协议,OIDC 是建立在它之上的身份协议。绝大多数实际系统两者一起使用。

PKCE 是什么?什么场景需要?

PKCE 是 Proof Key for Code Exchange 的缩写。客户端生成随机 code_verifier 与其哈希 code_challenge,授权时发送 challenge,换 token 时发送 verifier,服务端校验两者匹配。专为公共客户端(移动 App、SPA)设计,防止授权码被恶意 App 截获后盗用。2026 年所有公共客户端都强制要求 PKCE,OAuth 2.1 进一步把它推广到所有客户端。

Password Grant 还能用吗?

强烈不推荐。OAuth 2.1 已正式弃用 Resource Owner Password Credentials Grant,理由是它要求客户端直接接触用户密码,违背了 OAuth 的核心安全假设。仅在迁移老系统、且客户端与授权服务器同属一家公司的极少数场景作为过渡使用。新项目一律使用 authorization code + PKCE,原生体验通过 ASWebAuthenticationSession 等系统组件实现。

Refresh Token 应该存哪里?

后端服务(confidential client)存数据库,并加密;移动 App 存系统 KeyStore(iOS Keychain、Android KeyStore);浏览器 SPA 不应该长期持有 refresh token,推荐使用 BFF 模式或 refresh token rotation + httpOnly cookie。绝对不要存 localStorage,会被 XSS 一锅端。配合 refresh token rotation 与家族追踪,可以最大化降低泄露风险。

Scope 应该如何设计?

遵循最小权限原则,按资源加动作组合。例如 read:user、write:user、delete:user,而不是粗粒度的 admin。提供常见 scope 组合的快捷别名(profile、email、openid)方便客户端使用。授权页面要清楚展示每个 scope 的实际含义,让用户知情同意。Scope 一旦发布很难收回,初期设计要慎重,建议参考 GitHub、Google 等成熟平台的 scope 体系。

相关工具