我终于理解了 OAuth 🤯🤯🤯

2025-06-05

我终于理解了 OAuth 🤯🤯🤯

说实话,OAuth 感觉就像一个谜中之谜。它的大体思路很简单:“验证用户身份,一切顺利,皆大欢喜。” 但当你仔细研究细节时,突然间你就被一堆看似随机的参数淹没了。网上的每个教程都说“就用这个”,但没有人真正解释过这些东西存在的意义!🤔

我长期处于这种困境——直到我开始构建自己的身份验证库。那时我不得不深入研究细节,阅读数百页的 OAuth 规范(是的,这听起来很有趣😅)。但你知道吗?经历了这一切之后,我意识到 OAuth 实际上非常合乎逻辑。每个参数都只是为了防止一种或多种类型的攻击。🛡️

在本文中,我将从您能想到的最简单的“身份验证”流程开始——共享您的密码。从这里开始,我们将逐一解决每个漏洞,逐步进行保护,直到达到您熟悉和喜爱(或者可能只是可以忍受)的完整、安全的 OAuth 流程。

Stack Auth:开源 Auth0/Clerk 替代方案

在深入探讨之前,我想快速介绍一下我们正在构建的开源身份验证库Stack Auth。它设计得非常易于设置,并提供了一套精美的开箱即用的 UI 组件!无论您是在构建 SaaS 产品还是您的下一个业余项目,Stack Auth 都能在不牺牲灵活性的情况下简化身份验证。

堆栈认证

没有 OAuth 的世界

Big Head 想节省 Hooli 云盘的存储空间。他找到了 Pied Piper,一款承诺可以压缩文件的应用。但 Pied Piper 要想发挥它的魔力,需要访问他的 Hooli 云盘。

Pied Piper 获取访问权限最简单的方法就是向 Big Head 索要他的 Hooli 用户名和密码。有了这些,Pied Piper 就可以代表 Big Head 登录 Hooli 并访问他的文件。

具体过程如下:

流程 1

大头在他的浏览器中看到的屏幕可能看起来像这样:

流程 2

攻击#1:大头的凭据被曝光

我们有一个问题

通过交出用户名和密码,大头魔将赋予 Pied Piper 对其整个 Hooli 帐户的完全访问权限。这包括 Hooli 邮件、Hooli 聊天,甚至包括更改密码!此外,如果有人入侵了 Pied Piper,他们就能以纯文本形式看到大头魔的密码。

于是,他们想出了一个新计划:生成一个访问令牌。这个令牌可以让 Pied Piper 在有限的权限下访问 Big Head 在 Hooli 上的数据。

流程 3

在大头看来,情况是这样的:

流程 4

虽然这种方法很安全,但用户使用体验并不好。大头可不想每次压缩文件或登录服务时都手动生成访问令牌。他希望 Pied Piper 能帮他完成这件事。

自动化

Big Head 无需手动生成访问令牌并将其复制到 Pied Piper,Pied Piper 可以要求 Hooli 代表他生成访问令牌。

流程 5

如果这个世界上没有坏人,这该有多棒啊!大头只需要点击一个按钮,一切就都自动完成了。但是,这里有一个明显的问题。

攻击 #2:任何人都可以声称自己是别人

我是你妈妈

这种方法绝对没有任何安全性!Pied Piper 可以假装代表任何人行事,而 Hooli 会很乐意生成访问令牌。

因此,为了确保该请求确实来自大头,Hooli 需要向他确认。

流动

在广阔的网络世界中,第三步通常是通过将“大头”重定向到某个hooli.com域名下的页面来完成的。因此,他的浏览器会显示以下内容:

流动

Hooli 现在可以验证坐在浏览器前的人是否是大头。在第三步中,Hooli 可以自由设计确认流程。Hooli 可以要求大头确认其电子邮件/密码登录信息、进行双重身份验证 (2FA) 和/或征求同意。

给它起个名字:隐式流

OAuth 的早期实现看起来就是这样的;我们称之为隐式流程。不过,这篇文章还没到一半,所以你可能已经猜到你不应该在生产应用中使用它。

但是,让我们开始根据 OAuth 的称呼来为事物命名:

流动

URL 中的查询参数为:

  • client_id=piedpiper:这会告诉 Hooli 哪个应用程序正在请求访问权限(在本例中为 Pied Piper)。
  • redirect_uri=https://piedpiper.com/callback:当 Hooli 批准连接后,他应该将 Big Head 送回这里。
  • scope=drive:Pied Piper 需要的权限。
  • response_type=token:告诉 Hooli 我们正在使用隐式流。

到目前为止,一切都很好!

攻击#3:重定向URI操纵

Endframe 是 Pied Piper 的恶意竞争对手,想要窃取 Big Head 的 Hooli 驱动器数据。

  1. Endframe 向 Big Head 发送了一封电子邮件,主题为“将您的 Hooli 帐户与 Pied Piper 连接起来”。
    • 它们包含一个指向 的链接https://hooli.com/authorize?...,其中包含相同的内容client_id,以及scope来自 Pied Piper 的真实请求。
    • 但是,他们将redirect_uri参数更改为https://endframe.com
  2. 大头检查域名,发现是hooli.com,于是点击了链接。
  3. Hooli 的同意界面弹出。Big Head 确认这是 Pied Piper 的客户端 ID,于是他登录并确认访问。
  4. 然而,在 Big Head 点击“允许”后,Hooli 会将他重定向到 而https://endframe.com不是https://piedpiper.com/callback,并将访问令牌发送给 Endframe。
  5. 现在,Endframe 可以访问 Big Head 的 Hooli 驱动器!

问题是 Hooli 没有验证client_idredirect_uri是否匹配。

解决方案是要求 Pied Piper 首先注册所有可能的重定向 URI。然后,Hooli 应该拒绝重定向到任何其他域名。

攻击#4:跨站请求伪造(CSRF)

然而,还有一些更高级的攻击。Endframe 仍然可以诱骗 Big Head 使用恶意的 Hooli 帐户登录:

  1. Endframe 在 Pied Piper 注册了一个账户。
  2. 他们登录 Pied Piper 并access_token_456为自己的 Hooli 驱动器生成访问令牌。
  3. 他们向大头发送了一封电子邮件,标题为“查看我们的 Bachmanity 相册”并附带一个链接https://piedpiper.com/callback?access_token=access_token_456
  4. 大头检查域名,发现是piedpiper.com,就点击了它。
  5. Pied Piper 网站打开回调并将 Big Head 登录到 Endframe 的 Hooli 驱动器,因此他上传的任何文件都将转到 Endframe 的 Hooli 驱动器。

我们如何才能避免这种情况?我们需要确保只有在我们启动 OAuth 流程时才完成它。

为此,Pied Piper 可以生成一个随机字符串(我们称之为state),将其存储在 Cookie 中,然后发送给 Hooli。Hooli 会state在将 Big Head 重定向回时,将其发送回 Pied Piper。Pied Piper 随后会检查收到的 是否state与 Cookie 中的匹配。

流动

  • state=random_string_123:随机生成的字符串,用于防止CSRF攻击。

攻击#5:窃听访问令牌

Endframe 不会放弃。他们又想出了另一个计划:

  1. 他们与一位名叫杨建的天才开发者合作,开发了一款名为“热狗与否”的工具,用于检测热狗。此外,该工具还会恶意地将整个浏览器历史记录发送给 Endframe。
    • (还有许多其他历史嗅探或 HTTP 降级攻击也可以实现这一点。)
  2. 大头觉得好笑,就装了起来。
  3. Endframe 现在可以通过搜索类似于的 URL 来查看 Big Head 曾经登录过的所有服务的所有访问令牌https://piedpiper.com/callback?access_token=access_token_123

这个问题有一个解决办法。我们需要确保访问令牌永远不会在浏览器 URL 中交换。

Hooli 会生成一个短暂有效的“授权码”,并使用此码重定向回 Pied Piper。Pied Piper 随后会向另一个端点发出 POST 请求,该端点会将授权码作废,并将其交换为访问令牌。

这样,访问令牌就不会出现在浏览器历史记录中。我们称之为授权码流程。它比隐式流程更安全,但我们还没有完成。

流动

  • response_type=code:告诉 Hooli 我们正在使用授权码流,而不是隐式流。
  • code=authorization_code_123:Hooli 为 Pied Piper 生成的授权码。
  • grant_type=authorization_code:对于授权码流程,这始终是authorization_code(我们不会介绍其他授权类型)。

攻击#6:窃听授权码

如果 Endframe 实时窃听授权码,他们就能在 Big Head 浏览器之前非常快速地将其交换为访问令牌。

  1. 大头仍然安装了“热狗与否”工具。
  2. 一旦大头连接到他的 Hooli 驱动器帐户,“Hot dog or not”就会从大头的浏览器历史记录中获取授权码。
  3. 实时情况下,Endframe 的速度比 Big Head 浏览器发送请求的速度还快,它突然出现并向 Hooli 发送了包含 Big Head 授权码的请求。他们获得了访问令牌,Big Head 的请求失败了。

目前,任何拥有授权码的人都可以将其兑换为访问令牌。我们需要确保只有发起请求的人才能进行交换。

我们通过在客户端上安全地存储一个密钥来实现这一点。我们会对该密钥进行哈希处理,并将其与第一个请求一起发送给 Hooli。在使用授权码交换访问令牌时,我们会将原始密钥发送给 Hooli。然后,Hooli 会比较哈希值,以证明该请求来自同一个客户端。

该过程称为代码交换密钥证明(PKCE)。

流动

  • code_verifier=random_string_456:Pied Piper 发送给 Hooli 的原始随机字符串。
  • code_challenge=hashed_string_123:散列代码验证器。

这安全吗?不太安全……

攻击#7:对受信任的URI进行重定向URI操作

假设 Pied Piper 有两个注册的重定向 URI:

  • https://piedpiper.com/callback
  • https://piedpiper.com/share-files-callback

第一个端点授权并压缩 Hooli 驱动器文件,第二个端点与其他用户公开共享文件。(这也可能是一个端点,但查询参数不同。)

尽管我们防止 Endframe 用完全恶意的内容(例如https://endframe.com)替换重定向 URI,但如果 Endframe 可以以某种方式拦截请求,他们仍然可以修改 URI 以指向另一个端点。

解决方案?让客户端在使用授权码交换访问令牌时再次发送当前 URI。这样,Hooli 就可以验证重定向 URI 是否与原始请求中的 URI 匹配。

流动

最后说明

本文只是对 OAuth 流程的非正式解释,实际规范要长得多。例如,客户端密钥、刷新令牌、客户端凭证流程、令牌授予等内容均未涵盖。

此外,安全实施需要很多细节。您最好不要自行实现 OAuth 客户端。

如果您想深入了解或想要在您的应用程序中实现 OAuth,这里有一些很好的资源。

最后,如果您不想自己实现 OAuth,这正是我们构建Stack Auth的原因,您只需切换一个开关即可获得 Google/Github/Microsoft/和更多 OAuth 登录!

文章来源:https://dev.to/fomalhautb/i-finally-understand-oauth-2ldf
PREV
使用 Micronaut、Kafka 和 Debezium 构建事件驱动的应用程序
NEXT
使用 AWS Amplify 的无服务器联系表单