OAuth 2.0 详解:从授权码到 PKCE(完整图景)

发布: (2026年3月28日 GMT+8 14:28)
8 分钟阅读
原文: Dev.to

Source: Dev.to

OAuth 无处不在,且大多数开发者在并未真正了解其内部工作原理的情况下使用它。你点击“Sign in with Google”,神奇的事情发生了,你就登录了。但当出现问题时——令牌过期、重定向失败、作用域错误——你会突然需要调试一个从未学过的协议。

四个角色

OAuth 有四个参与者,混淆它们是大多数困惑的根源:

  • 资源所有者 — 那就是你,用户。你拥有数据。
  • 客户端 — 请求访问你数据的应用程序。可以是网页应用、移动应用或 CLI 工具。
  • 授权服务器 — 在你授予权限后颁发令牌。Google、GitHub、Auth0——这些都运行授权服务器。
  • 资源服务器 — 保存你实际数据的 API。有时它与授权服务器是同一家公司,有时不是。

OAuth 的核心目的:客户端在 永不看到你的密码 的情况下,获取对资源服务器的有限访问权限。

授权码流程(逐步说明)

这是最常见的 OAuth 流程,也是几乎所有场景都应使用的方式:

  1. 应用将你重定向到授权服务器。
    URL 中包含客户端 ID、请求的作用域(它想要的权限)、重定向 URI(返回你的地址)以及 state 参数(防止 CSRF)。

  2. 你登录并授权。
    授权服务器会显示应用请求的内容——“Acme App 想读取你的电子邮件和个人资料”。你可以选择同意或拒绝。

  3. 授权服务器带着代码重定向回来。
    这里返回的不是令牌,而是一个短期有效的 授权码。单独使用该代码是没有意义的。

  4. 应用使用代码换取令牌。
    这一步在服务器之间完成(后端对授权服务器),使用授权码加上客户端密钥。响应中会包含访问令牌,通常还有刷新令牌。

  5. 应用使用访问令牌调用 API。
    令牌放在 Authorization 头部。资源服务器验证令牌后返回数据。

为什么不直接在第 3 步返回令牌? 因为重定向发生在浏览器中——令牌会出现在地址栏、浏览器历史和服务器日志里。代码换令牌的过程在服务器端进行,才是真正安全的。

PKCE:修复公共客户端的漏洞

授权码流程存在一个弱点:如果客户端无法保守密钥怎么办?移动应用和单页应用会把全部源代码交付给用户,因此没有安全的地方来存放客户端密钥。

PKCE(发音为 “pixie”,全称 Proof Key for Code Exchange)解决了这个问题。 其工作原理如下:

  1. 在发起授权请求之前,客户端生成一个随机字符串,称为 code_verifier,并计算其 SHA‑256 哈希,得到 code_challenge

  2. 在授权请求期间,客户端将 code_challenge 发送给授权服务器。

  3. 在令牌交换期间,客户端发送原始的 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 构建东西吗?在评论区找我——很乐意帮你调试。

0 浏览
Back to Blog

相关文章

阅读更多 »

忽视代码可维护性的痛苦

我最近花了好几个小时调试代码库中一个特别棘手的问题,当时我为了赶紧的截止日期而偷工减料。本该是一个简单的修复,却……