OAuth 2.0 详解:从授权码到 PKCE(完整图景)
Source: Dev.to
OAuth 无处不在,且大多数开发者在并未真正了解其内部工作原理的情况下使用它。你点击“Sign in with Google”,神奇的事情发生了,你就登录了。但当出现问题时——令牌过期、重定向失败、作用域错误——你会突然需要调试一个从未学过的协议。
四个角色
OAuth 有四个参与者,混淆它们是大多数困惑的根源:
- 资源所有者 — 那就是你,用户。你拥有数据。
- 客户端 — 请求访问你数据的应用程序。可以是网页应用、移动应用或 CLI 工具。
- 授权服务器 — 在你授予权限后颁发令牌。Google、GitHub、Auth0——这些都运行授权服务器。
- 资源服务器 — 保存你实际数据的 API。有时它与授权服务器是同一家公司,有时不是。
OAuth 的核心目的:客户端在 永不看到你的密码 的情况下,获取对资源服务器的有限访问权限。
授权码流程(逐步说明)
这是最常见的 OAuth 流程,也是几乎所有场景都应使用的方式:
应用将你重定向到授权服务器。
URL 中包含客户端 ID、请求的作用域(它想要的权限)、重定向 URI(返回你的地址)以及state参数(防止 CSRF)。你登录并授权。
授权服务器会显示应用请求的内容——“Acme App 想读取你的电子邮件和个人资料”。你可以选择同意或拒绝。授权服务器带着代码重定向回来。
这里返回的不是令牌,而是一个短期有效的 授权码。单独使用该代码是没有意义的。应用使用代码换取令牌。
这一步在服务器之间完成(后端对授权服务器),使用授权码加上客户端密钥。响应中会包含访问令牌,通常还有刷新令牌。应用使用访问令牌调用 API。
令牌放在Authorization头部。资源服务器验证令牌后返回数据。
为什么不直接在第 3 步返回令牌? 因为重定向发生在浏览器中——令牌会出现在地址栏、浏览器历史和服务器日志里。代码换令牌的过程在服务器端进行,才是真正安全的。
PKCE:修复公共客户端的漏洞
授权码流程存在一个弱点:如果客户端无法保守密钥怎么办?移动应用和单页应用会把全部源代码交付给用户,因此没有安全的地方来存放客户端密钥。
PKCE(发音为 “pixie”,全称 Proof Key for Code Exchange)解决了这个问题。 其工作原理如下:
在发起授权请求之前,客户端生成一个随机字符串,称为
code_verifier,并计算其 SHA‑256 哈希,得到code_challenge。在授权请求期间,客户端将
code_challenge发送给授权服务器。在令牌交换期间,客户端发送原始的
code_verifier。
授权服务器对其进行哈希运算,并检查是否与之前的 challenge 相匹配。
拦截到授权码的攻击者无法使用它——因为他们没有原始的 code_verifier。这其中的数学运算是单向的:可以从 verifier 生成 challenge,但无法逆向得到 verifier。
OAuth 2.1 将 PKCE 设为所有客户端的强制要求,而不仅仅是公共客户端。这足以说明安全社区对其重要性的认知。
访问令牌 vs 刷新令牌
- 访问令牌 是短期(分钟到小时)并用于 API 调用。
- 刷新令牌 是长期且功能强大——丢失它就相当于丢失会话。
最佳实践:在每次使用时轮换刷新令牌(一次性使用令牌),并安全存储它们。
OAuth vs OpenID Connect
- OAuth 处理 授权 — “这个应用可以访问我的照片吗?”
- OpenID Connect (OIDC) 在此基础上添加 身份验证 — “这个用户是谁?”
OIDC 引入了 ID token,其中包含用户身份声明(姓名、电子邮件等),采用 JWT 格式。
- 如果你需要 “使用 X 登录” —— 那就是 OIDC。
- 如果你需要 “让此应用读取我的日历” —— 那就是 OAuth。
实际上,大多数实现会同时使用两者。
我见过的常见错误
- 将令牌存储在
localStorage中。 任何页面上的 JavaScript 都可以访问,包括 XSS 攻击。请改用 HttpOnly Cookie 或内存存储。 - 范围过于宽泛。 只请求你实际需要的最小权限。如果你仅读取邮件,请不要请求写入权限。
- 未验证
state参数。 这是你的 CSRF 防护。跳过它会让攻击者欺骗用户将其账户链接到攻击者的 OAuth 会话。 - 将访问令牌当作身份凭证。 访问令牌仅表示“此请求已授权”。它并不能可靠地告诉你是谁发起的请求。请使用 OIDC ID 令牌来进行身份验证。
底线
OAuth 看起来很复杂,因为它解决了一个真正困难的问题:在不共享凭证的情况下进行委托访问,跨不可信网络,适用于多种客户端类型。一旦你了解每一步背后的原理——为什么要进行代码交换,为什么会有 PKCE,为什么令牌被分为访问令牌和刷新令牌——这个协议就会变得非常合理。
上面的视频以可视化方式演示了每一步。如果你打算从头实现 OAuth,我建议你在观看视频的同时参考官方 RFC 6749和 Auth0 的文档。理论加上实现示例是最快让你弄懂这些内容的方法。
现在正在用 OAuth 构建东西吗?在评论区找我——很乐意帮你调试。