OAuth图解指南
https://www.ducktyped.org/p/an-illustrated-guide-to-oauth
OAuth 于 2007 年首次推出。它是 Twitter 创建的,因为 Twitter 希望有一种方法允许第三方应用程序代表用户发布推文。花点时间想象一下今天设计类似的东西。你会怎么做呢?一种方法是向用户索要用户名和密码。于是,你创建了一个非官方的 Twitter 客户端,并向用户展示了一个登录界面,上面写着 “用 Twitter 登录”。用户照做了,但实际上他们并没有登录 Twitter,而是把他们的数据发送给了你,这个为他们登录 Twitter 的第三方服务。
这样做有很多不好的地方。即使你信任第三方应用程序,但如果他们没有正确存储你的密码,被人盗取了怎么办?千万不要把密码交给这样的第三方网站。
你可能在想的另一个问题是,API 密钥怎么办?因为你要通过 Twitter 的 API 来发布用户数据,而对于API,你需要使用API 密钥。但 API 密钥是通用的。你需要的是特定于某个用户的 API 密钥。
为了解决这些问题,OAuth 应运而生。你会看到它是如何解决所有这些问题的,但 OAuth 的核心是访问令牌,它有点像特定用户的 API 密钥。应用程序获得访问令牌后,就可以使用访问令牌代表用户采取行动,或访问用户的数据。
# OAuth 如何工作
OAuth 的使用方式多种多样,这也是它难以理解的原因之一。在本篇文章中,我们将介绍一个典型的 OAuth 流程。
我要使用的示例是 YNAB。如果你还没用过,YNAB 就像是付费版的 Mint。你将它连接到一个银行账户,然后它就会从该账户中提取你的所有交易,并以非常漂亮的图表向你展示。你可以对支出进行分类,然后它会告诉你,比如,你在日用品上花了太多钱。它能帮助你管理财务。所以,我想使用 YNAB,我想把它连接到大通银行,但我不想给它我的大通银行密码。因此,我要使用 OAuth。
让我们先看看流程,然后再了解发生了什么。实际上,我们要看两遍流程,因为我认为你至少需要看两遍 OAuth 流程,才能明白发生了什么。
首先,我在 YNAB,我想把大通银行作为一个来源连接起来。OAuth 流程是这样的
- YNAB 将我重定向到大通。
- 在大通,我使用用户名和密码登录。
- 大通显示一个屏幕,上面写着 “YNAB 想要连接到大通。请选择您希望YNAB访问的账户”。它会显示我所有账户的列表。比方说,我只选择我的支票账户,让YNAB读取该账户,然后点击 “确定”。
- 从大通银行,我被重新定向到YNAB,现在,YNAB神奇地与大通银行建立了连接。
这就是用户的体验。但这里发生了什么呢?后台发生了什么神奇的事情,让YNAB以某种方式访问了我在大通银行的数据?
# 最终目标是获得访问令牌
请记住,OAuth的最终目标是让YNAB最终获得一个访问令牌,这样它就可以访问我在大通银行的数据了。不知怎的,在我完成这个流程后,YNAB最终获得了一个访问令牌。我先告诉你YNAB是如何获得访问令牌的,然后再告诉你更多细节。
大通银行是如何给YNAB访问令牌的?当您从大通银行被重定向到YNAB时,大通银行可能只是在URL中添加了访问令牌。它可以将您重定向到这样的URL:
https://www.ynab.com/redirect?access_token=123
然后YNAB就能获得访问令牌了。
这是个坏主意
访问令牌应该是保密的,但URL可能会出现在您的浏览器历史记录或某些服务器日志中,在这种情况下,任何人都很容易看到您的访问令牌。
因此,从技术上讲,大通可以通过URL中的访问令牌将您重定向回YNAB,然后YNAB就会拥有访问令牌。OAuth流程结束。但我们不会这样做,因为在URL中发送访问令牌并不安全。
当您从大通银行被重定向到YNAB时,大通银行在URL中向YNAB发送了一个授权码。
授权码不是存取令牌!大通向YNAB发送授权码,YNAB将授权码交换为访问令牌。YNAB通过HTTPS向大通提出后台请求,即后台POST请求,这意味着没有人能看到访问令牌。
然后YNAB就拥有了访问令牌。OAuth流程结束。OAuth成功。
# OAuth的两个部分
让我们来谈谈刚才看到的内容。从高层次来看,OAuth流程有两个部分。第一部分是用户同意流,也就是用户登录并选择访问权限的地方。这是 OAuth 的关键部分,因为在 OAuth 中,我们总是希望用户积极参与和控制。
另一部分是授权代码流。这是YNAB实际获取访问令牌的流程。
让我们来详细了解一下具体是如何工作的。我们还要讨论一些术语,因为OAuth有非常特殊的术语。
- 我们说的是资源所有者,而不是用户。
- 我们说OAuth 客户端或OAuth 应用程序,而不是应用程序。
- 登录的服务器称为授权服务器。获取用户数据的服务器称为资源服务器(可以与授权服务器相同)。
- 在授权服务器上,当用户选择允许使用的内容时,这些内容被称为作用域。
我会尽量使用这个术语,因为如果你要阅读更多的 OAuth 文档,就需要熟悉这个术语。
所以,让我们用新术语再看一遍这个高层次的内容。
# OAuth 流程 2
你有 OAuth 客户端。OAuth 客户端希望访问资源服务器上的数据,而这些数据属于资源所有者。
为此,OAuth 客户端会重定向到授权服务器。用户登录,用户同意范围(此令牌允许访问的内容),然后用户被重定向回 OAuth 客户端,URL 中包含一个授权代码。
在后端,OAuth 客户端向授权服务器发送授权代码和客户秘密(我们稍后会讨论客户秘密),授权服务器则回复访问令牌。
这是完全相同的流程,只是使用了我们刚刚讨论过的新术语。现在我们来谈谈具体细节。我们已经从用户的角度了解了这一流程,下面让我们从开发者的角度了解一下它。
# 注册新应用程序
要使用 OAuth,首先需要注册一个新的应用程序。例如,GitHub 就提供了 OAuth。如果你想为 GitHub 创建一个新的应用程序,首先要注册它。不同的服务要求在应用程序注册时提供不同类型的数据,但每个服务都至少需要
- 应用程序名称,因为例如当用户访问 GitHub 时,GitHub 需要能够显示 “亚马逊网络服务请求对您的仓库和 gists 进行读取访问”。
- 重定向 URI。我们很快就会讲到重定向 URI 是什么。
GitHub 会回应
- 一个客户端 ID。这是一个公共 ID,你将用它来发出请求
- 一个客户秘密。您将用它来验证您的请求。
太棒了,你已经注册了 OAuth 应用程序。比方说,你的应用程序是 YNAB,你的一个用户想要连接到大通银行。因此,您将启动一个新的 OAuth 流程…这是您的第一个流程!
第一步:您将把他们重定向到大通授权服务器的 OAuth 端点,并在 URL 中传递这些参数:
- 客户 ID,也就是我们刚才提到的。
- 重定向 URI。一旦用户在大通完成操作,大通就会将其重定向到这里。这将是一个 YNAB URL,因为您就是 YNAB 应用程序。
- 响应类型,通常是 “代码”,因为我们通常希望获得授权代码,而不是安全性较低的访问令牌。
- 范围。也就是说,我们想要访问哪些用户数据?
这些信息足以让授权服务器验证请求,并向用户显示类似 "YNAB请求对您的账户进行读取访问 "的信息。
授权服务器如何验证请求?如果客户ID无效,请求立即无效。如果客户 ID 有效,授权服务器就需要检查重定向 URI。基本上,由于客户端ID是公开的,任何人都可以获取YNAB的客户端ID,然后创建自己的OAuth流程,点击大通,然后将用户返回,比方说,evildude.com。但这就是为什么当你注册应用时,你必须告诉大通一个有效的重定向 URI 是什么样的。这时,你会告诉大通只有 YNAB.com URI 才是有效的,从而避免出现 evildude.com 的情况。
如果一切都是有效的,授权服务器就可以使用客户端 ID 获取应用程序名称,或许还有应用程序图标,然后显示用户同意屏幕。
用户点击他们想让 YNAB 访问哪些账户,然后点击 “确定”。
大通会将他们重定向到您提供的重定向 URI,例如 ynab.com/oauth-callback?authorization_code=xyz。
题外话:你可能想知道 URI 和 URL 有什么区别?因为我两者都在用。URL 是我们熟悉和喜爱的任何网站的 URL。URI 更笼统。URL 是 URI 的一种,但还有许多其他类型的 URI。
我之所以说重定向 URI 而不是重定向 URL,是因为移动应用程序没有 URL。它们只有一个 URI,也就是它们编造的协议,看起来可能像 myapp://foobar。因此,如果你只做网络工作,无论何时读取 URI,你都可以将其读作 URL。如果你做的是移动工作,你可以读取 URI,并知道是的,你的用例也是受支持的。
因此,用户会被重定向到 ynab.com/oauth-callback?authorization_code=xyz,现在您的应用程序有了一个授权码。您可以将该授权码和您的客户端秘密一起发送到大通授权服务器。为什么要包含客户秘密?因为授权码也在 URL 中。因此,任何人都可以看到它,任何人都可以试图用它来交换访问令牌。这就是为什么我们需要发送客户秘密,这样大通的服务器就可以说:“哦,是的,我记得我为这个客户 ID 生成了这个代码,而且客户秘密也符合。这是一个有效请求”。
然后它就会返回访问令牌。请注意,在 OAuth 流程的每一个步骤中,他们都考虑到了如何利用该流程,并添加了防护措施*。这也是它如此复杂的一个重要原因。
- 一位从事安全工作的朋友可靠地告诉我,OAuth 的设计者们吸取了很多惨痛的教训,这也是它如此复杂的另一个原因:因为它不得不反复打补丁。
另一个重要原因是我们希望用户参与进来。这使它变得复杂,因为所有用户的东西都必须是前端的,这是不安全的,因为任何人都可以看到。而所有安全的东西都必须放在后端。
我一直在说前端和后端,但在 OAuth 文档中,他们说的是前端通道和后端通道。让我们来谈谈原因。
# 前端通道和后端通道
因此,OAuth 使用的不是前端和后端,而是前通道和后通道。前端通道指的是 GET 请求,任何人都能看到 URL 中的参数;后端通道指的是 POST 请求,数据已加密(作为 POST 主体的一部分)。OAuth 不使用前台或后台的原因是,你可以使用 JavaScript 发出 POST 请求!因此,从理论上讲,你可以在前端用 JavaScript 发送 POST 获取请求,将授权代码换成访问令牌。
现在,这样做有一个大问题,那就是你还需要客户端秘密才能发出请求。当然,一旦秘密出现在前端,并且可以用 JavaScript 访问,它就不再是秘密了。任何人都可以访问它。因此,有一种不同的方法来代替使用客户端秘密,这种方法叫做PKCE,拼写为 P-K-C-E,发音为 “pixie”(说真的)。这种方法不如在后台使用客户端秘密来得安全,但如果后台不是你的选择,你可以使用 PKCE 来实现。所以,如果你有一个没有后端的应用程序,你仍然可以使用 OAuth。
我可能会在以后的文章中介绍 PKCE,因为它现在也被推荐用于标准流程,因为它有助于防止验证代码被拦截。
移动应用程序也有同样的问题。除非你的移动应用程序有一个后端组件,比如某个后端服务器,否则如果你把你的客户秘密放在移动应用程序中,那么任何人都可以获取,因为有大量的工具可以从移动应用程序中提取字符串。因此,与其在应用程序中加入客户秘密,还不如使用 PKCE 来获取访问令牌。
以上就是需要了解的另外两个术语:前端渠道和后 端 渠道。
至此,你已经从用户和开发者的角度了解了 OAuth 流程,并看到了使其安全的组件。
最后我想说的是,OAuth 可以有很多不同的方式。我在上文介绍了推荐的主要 OAuth 流程,但有些人可能通过在重定向中传回访问令牌而不是授权令牌来实现 OAuth(这种做法被称为 “隐式流程”)。有些人可能会使用 PKCE。甚至还有一种方法可以在没有用户同意的情况下使用 OAuth,但我们并不推荐这样做。
OAuth 还有一部分我们没讲到,那就是令牌会过期,你需要刷新它们。这需要通过刷新流程来实现。此外,OAuth 只涉及授权,但有些工作流使用 OAuth 进行登录,例如当你使用 "用 Google 登录 "功能时。这使用的是 OpenID Connect 或 OIDC,它是 OAuth 的顶层,也会返回用户数据,而不仅仅是访问令牌。我之所以在这里提到这一点,是因为当你在网上查找 OAuth 时,会看到很多不同的流程,你可能会困惑为什么它们都不一样。原因在于,OAuth 并不像 HTTP 那样简单明了,OAuth 可以有很多不同的外观。
现在,你可以出去做自己的 OAuthing 了。祝你好运