
OAuth 于 2007 年首次推出。它最初由 Twitter 创建,因为 Twitter 希望能够允许第三方应用代表用户发布推文。想象一下,如果今天设计类似的应用,你会怎么做?一种方法是直接要求用户输入用户名和密码。因此,你创建一个非官方的 Twitter 客户端,并向用户显示一个登录页,上面写着"使用 Twitter 登录"。用户照做了,但他们实际上并没有登录 Twitter,而是将数据发送给了你------这个替他们登录 Twitter 的第三方服务。

这种做法有很多弊端。即使你信任第三方应用,但如果它们没有正确存储你的密码,导致有人窃取了你的密码,该怎么办?你绝对不应该把密码透露给这样的第三方网站。
你可能会想,使用API 密钥怎么样?因为你要访问 Twitter 的 API 来为用户发布数据,而对于 API ,你需要使用 API 密钥 。但 API 密钥是 通用的 。你需要的是 特定于用户的 API 密钥。

为了解决这些问题,OAuth 应运而生。你会看到它是如何解决所有这些问题的,但 OAuth 的核心是访问令牌access token ,它有点像特定用户的 API 密钥。应用获取访问令牌后,就可以使用它代表用户执行操作,或访问用户的数据。
OAuth 的工作原理

OAuth 的使用方式多种多样,这也是它如此难以理解的原因之一。在本文中,我们将探讨一个典型的 OAuth 流程。
我将使用 YNAB 来举例。如果你还没用过,YNAB 就像是 Mint 的付费版。你把它连接到一个银行账户,它会从该账户中提取你所有的交易流水,并用非常漂亮的图表展示给你。你可以对支出进行分类,它会告诉你,比如,你在食品杂货上的支出太多了。它可以帮助你管理财务。所以,我想使用 YNAB,并把它连接到大通银行,但我不想输入我的大通银行密码。所以我打算使用 OAuth。
我们先来看一下流程,然后再了解具体是怎么回事。实际上,我们会把流程看两遍 , 因为我认为你至少需要看两遍 OAuth 流程才能理解具体是怎么回事。
OAuth 流程1
首先,我在 YNAB,我想连接 Chase 作为数据源。OAuth 流程如下:
-
YNAB 将我重定向到 Chase。
-
在 Chase,我使用我的用户名和密码登录。
-
Chase 向我展示了一个屏幕,上面写着"YNAB 想要连接到 Chase。请选择您想要授予 YNAB 访问权限的账户"。它会显示我所有账户的列表。假设我只选择我的支票账户,授予 YNAB 对该账户的读取权限,然后点击"确定"。
4.我从 Chase 重定向回 YNAB,现在,神奇的是,YNAB 已连接到 Chase。

这是从用户角度的体验。但究竟发生了什么?背后究竟发生了什么魔法,让 YNAB 能够访问我在 Chase 的数据?
最终目标是获得访问令牌
记住, OAuth 的最终目标是让 YNAB 获得一个访问令牌access token ,这样它就可以访问我在 Chase 的数据了。不知怎么的,在我执行这个流程的过程中,YNAB 最终获得了一个访问令牌。为了避免意外,我会先告诉你它是如何获得访问令牌的,然后再更详细地讲解整个过程。
关于安全的简短说明
Chase 是如何向 YNAB 提供访问令牌的?当您从 Chase 重定向回 YNAB 时,Chase 可能只是在 URL 中添加了访问令牌。它可能会将您重定向回如下 URL:
ini
https://www.ynab.com/redirect?access_token=123
然后 YNAB 就能获取访问令牌。
这样做不太好!!
访问令牌应该是秘密的,但 URL 最终可能会出现在浏览器的历史记录或某些服务器日志中,在这种情况下,任何人都可以轻松看到您的访问令牌。
因此,Chase 可以从技术上 将您重定向回 YNAB,并使用 URL 中的访问令牌 ,然后 YNAB 就会获得访问令牌。OAuth 流程结束。但我们不会这样做,因为在 URL 中发送访问令牌并不安全。
当您从 Chase 重定向回 YNAB 时,Chase 会在 URL 中向 YNAB 发送授权码 authorization code 。
授权码(authorization code)不是访问令牌(access token)!Chase 向 YNAB 发送授权码,YNAB 再用授权码交换访问令牌 。它(YNAB )在 后端 向 Chase 发出 请求(基于 HTTPS 的后端 POST 请求)来实现这一点,这意味着没有人能够看到访问令牌。

然后 YNAB 就获得了访问令牌。OAuth 流程结束。OAuth 成功。
** OAuth 的两个部分**

让我们来谈谈刚才看到的内容。概括地说,OAuth 流程包含两个部分。第一个是用户同意流程 ,即您(用户)登录并选择授予访问权限。这是 OAuth 的关键部分,因为在 OAuth 中,我们始终希望用户能够亲自参与并拥有控制权。
另一部分是授权码流程 。这是 YNAB 实际获取访问令牌的流程 。
让我们更详细地讨论一下它的工作原理。此外,我们还要讨论一些术语,因为 OAuth 有非常具体的术语。
-
用户 是 资源所有者(resource owner)
-
第三方App 我们称之为 **OAuth 客户端(OAuth client)**或 OAuth 应用程序(OAuth app)
-
你登录的服务器称为授权服务器(resource server) 。你获取用户数据的服务器称为资源服务器(resource server) (这可能与授权服务器相同)。
-
在授权服务器((resource server))上,当用户选择允许的内容时,这些内容被称为范围(scopes) 。

我会尝试使用该术语,因为以后如果您需要阅读其他 OAuth 文档,则需要熟悉这些术语。
因此,让我们使用新术语再次分析一下。
OAuth 流程2
您有 OAuth 客户端(OAuth clients)。OAuth 客户端(OAuth clients)想要访问资源服务器(resource server)上的数据,而该数据属于资源所有者(resource owner)。

为此,OAuth 客户端(OAuth client )会重定向到授权服务器(authorization server)。用户登录,同意范围(scopes) (即此令牌允许访问的内容),然后用户会重定向回 OAuth 客户端(OAuth client),URL 中会包含授权码(authorization code)。

在(OAuth App)后端,OAuth 客户端将授权码(authorization code)和客户端密钥(client secret)(稍后会讨论客户端密钥)发送到授权服务器(authorization server),授权服务器使用访问令牌(access token)进行响应。

这是完全相同的流程,但使用了刚刚讨论过的新术语。现在来谈谈具体细节。已经从用户的角度了解了这个流程,现在从开发者的角度看看它是什么样子。
注册新应用

要使用 OAuth,首先需要注册一个新应用。例如,GitHub 提供 OAuth。如果您想为 GitHub 创建一个新应用,则需要先注册它。不同的服务在应用注册时需要不同类型的数据,但每个服务至少需要
-
应用程序名称,因为当用户访问 GitHub 时,GitHub 需要能够说"Amazon Web Services 正在请求对您的 repos 和 gists 的读取权限"
-
重定向 URI。我们稍后会讨论它是什么。
GitHub 将会提供:
-
客户端 ID。这是一个公共 ID,您将使用它来发出请求
-
客户端密钥。您将使用它来验证您的请求。
ok,您已经注册了 OAuth 应用。假设您的应用是 YNAB,并且您的一位用户想要连接到 Chase。因此,您启动了一个新的 OAuth 流程......这是您的第一个流程!
第一步:您将他们重定向到 Chase 授权服务器的 OAuth 端点,并在 URL 中传递以下参数:

-
客户端 ID,我们刚才讲过了。
-
重定向 URI。用户在 Chase 上完成操作后,Chase 会将其重定向回此处。由于您是 YNAB 应用,因此这将是一个 YNAB 网址。
-
响应类型(Response type),通常是"代码(code)",因为我们通常希望获取授权码(authorization code),而不是不太安全的访问令牌(access token)。
-
范围(Scopes)。那么我们请求的范围(Scopes)是什么?也就是说,我们想要访问哪些用户数据?
这些信息足以让授权服务器验证请求并向用户显示一条消息,例如"YNAB 正在请求读取您的帐户的权限"。
授权服务器如何验证请求?如果客户端 ID 无效,请求立即失效。如果客户端 ID 有效,授权服务器需要检查重定向 URI。由于客户端 ID 是公开的,任何人都可以获取 YNAB 客户端 ID,并创建自己的 OAuth 流程,该流程会访问 Chase,但随后会将用户返回到例如 evildude.com 的网站。因此,在注册应用时,您必须告知 Chase 有效的重定向 URI 是什么样的。此时,您需要告知 Chase 只有 YNAB.com 的 URI 才有效,从而避免出现 evildude.com 的情况。
如果一切有效,授权服务器可以使用客户端 ID 获取应用程序名称(可能是应用程序图标),然后显示用户同意屏幕。
用户将单击他们想要授予 YNAB 访问权限的帐户,然后点击"确定"。
Chase 会将他们重定向回您提供的重定向 URI,比如说 ynab.com/oauth-callback?authorization_code=xyz
。
注:你可能想知道,URI 和 URL 有什么区别?因为我差不多两者都用。URL 指的是我们熟知和喜爱的任何网站 URL。URI 则更为通用。URL 是 URI 的一种,但 URI 还有许多其他类型。

我之所以说是重定向 URI 而不是重定向 URL,是因为移动应用没有 URL。它们只有 URI,也就是它们自己定义的协议,可能类似于
myapp://foobar
。所以,如果你只做 Web 工作,那么每次读取 URI 时,你都可以把它理解成 URL。如果你做移动工作,读取 URI 时,你就知道你的用例也受支持。
这样,用户就会被重定向回 ynab.com/oauth-callback?authorization_code=xyz
,现在您的应用就拥有了授权码。您将该授权码连同客户端密钥一起发送到 Chase 授权服务器。为什么要包含客户端密钥?因为授权码就在 URL 中。任何人都可以看到它,并且任何人都可以尝试用它来交换访问令牌。这就是为什么我们需要发送客户端密钥,这样 Chase 的服务器就可以说:"哦,是的,我记得我为这个客户端 ID 生成过这个授权码,并且客户端密钥匹配。这是一个有效的请求。"
然后它返回访问令牌(access token)。请注意,在 OAuth 流程的每一步,他们都仔细考虑了攻击者可能利用该流程的方式,并添加了安全措施*。这正是它如此复杂的一个重要原因。
一位从事安全行业的朋友可靠地告诉我,OAuth 设计人员从惨痛的经历中吸取了很多教训,这也是它如此复杂的另一个原因:因为它必须反复修补。
另一个重要原因是我们希望用户能参与其中。这让事情变得复杂,因为所有用户内容都必须放在前端,这很不安全,因为任何人都可以看到。而且所有安全内容都必须放在后端。
我一直说前端和后端,但在 OAuth 文档中,他们说的是前通道(front-channel)和后通道(back-channel )。我们来聊聊原因。

前通道(front-channel)和后通道(back-channel )
因此,OAuth 不使用"前端"和"后端"这两个术语,而是使用"前通道"和"后通道"。前通道指的是 GET 请求,任何人都可以看到 URL 中的参数;后通道指的是 POST 请求,其中的数据会被加密(作为 POST 正文的一部分)。OAuth 不使用"前端"或"后端"的原因是,您可以使用 JavaScript 发出 POST 请求!因此,理论上,您可以在 JavaScript 中,通过发出 POST 获取请求,直接在前端用授权码交换访问令牌。
现在,这种方法有一个很大的问题,那就是您还需要客户端密钥才能发出该请求。当然,一旦密钥在前端并可通过 JavaScript 访问,它就不再是密钥了。任何人都可以访问它。因此,除了使用客户端密钥之外,还有一种称为 PKCE 的另一种方法 ,拼写为 PKCE,发音为"pixie"(认真的)。它不如在后端使用客户端密钥执行操作那么安全,但如果后端不适合您,您可以使用 PKCE 来完成。所以要知道,如果您的应用程序没有后端,您仍然可以进行 OAuth。
我可能会在以后的文章中介绍 PKCE,因为它现在也被推荐用于标准流程,因为它有助于防止授权码拦截。
移动应用也存在同样的问题。除非你的移动应用有后端组件,比如某个后端服务器,否则如果你把客户端密钥放在移动应用中,任何人都可以获取,因为现在有大量工具可以从移动应用中提取字符串。所以,与其在应用中包含客户端密钥,不如再次使用 PKCE 来获取访问令牌。
还有两个值得了解的术语: 前通道(front-channel)和后通道(back-channel) 。

此时,您已经从用户的角度和开发人员的角度了解了 OAuth 流程的样子,并且看到了使其安全的组件。
最后一些想法
最后我想提一下,OAuth 有很多不同的实现方式。我上面介绍了主要的推荐 OAuth 流程,但有些人可能会通过在重定向中传回访问令牌(access token)而不是授权令牌(authorization token)来实现 OAuth(这种做法被称为"隐式流程")。有些人可能会使用 PKCE 来实现。甚至还有一种无需用户同意即可实现 OAuth 的方法,但这种做法并不推荐。
我们没有涉及的 OAuth 的另一部分是令牌会过期,您需要刷新它们。这通过刷新流程实现。此外,OAuth 的核心在于授权,但某些工作流使用 OAuth 进行登录,例如当您使用"使用 Google 登录"功能时。这使用 OpenID Connect(简称 OIDC),它是 OAuth 之上的一层,它不仅返回访问令牌,还返回用户数据。我在这里提到这一点是因为当您在网络上搜索 OAuth 时,会看到很多不同的流程,您可能会对它们为何各不相同感到困惑。原因是,OAuth 不像 HTTP 那样简单直接,OAuth 可以有很多不同的表现形式。