65.9K
CodeProject 正在变化。 阅读更多。
Home

OAuth2 授权流程及其示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (11投票s)

2017年2月22日

CPOL

16分钟阅读

viewsIcon

71613

OAuth2 授权流程的入门介绍,通过真实世界示例进行解释。

引言

我决定写这篇文章,是因为在我开始学习 OAuth2 时,找不到任何能够帮助我理解全局的资料,特别是那些包含真实世界示例的资料。
OAuth 授权框架的唯一真正信息来源是(并且仍然是)原始的 RFC6749,但它对于想了解 OAuth2 概述而不想深入技术细节的人来说,内容太多了。

本文的范围

好的,那么本文的范围是什么呢?本文的范围是提供关于 OAuth2 认证协议的介绍和概述。它主要面向那些对 OAuth2 有“一些了解”,想更深入地理解各种授权流程,但又不想深入了解每个 HTTP 请求需要哪些字段的人。因此,在这篇文章中,我故意不使用任何技术术语,如“access_token”、“clientId”、“ClientSecret”等等。对于如何实现这些认证流程的所有技术细节,RFC6749 提供了完整的参考,如果您是正在寻找任何请求中要使用的字段的详细描述的软件开发人员,那么这篇文章不适合您。

为什么我们需要开放认证框架?

是的,这是我开始研究它时提出的第一个问题。
我有什么问题可以用 OAuth2 解决?

如果我们查看 RFC6749,第一句话是这样说的:

The OAuth 2.0 authorization framework enables a third-party
   application to obtain limited access to an HTTP service, either on
   behalf of a resource owner by orchestrating an approval interaction
   between the resource owner and the HTTP service, or by allowing the
   third-party application to obtain access on its own behalf.

这句话有点含糊,充满了抽象的词汇……第一次读的时候我并没有真正理解。让我们试着摆脱学术性的正式陈述,让它更简单(但又可能稍微不那么精确)。

所以,在这个定义中,如果您正在开发一个应用程序,您的应用程序就是第三方应用程序。而所有那些令人垂涎的社交网络(但不仅仅是这些),如 Facebook、LinkedIn、Twitter,就是您希望代表您的应用程序的用户访问的 HTTP 服务,在这些抽象定义中,用户被称为资源所有者

现在,终于,多亏了 OAuth2,您的应用程序/网站可以采用一种优雅、安全的方式来访问通常由最终用户拥有的/产生的数据(再次强调,如 Facebook 好友、LinkedIn 帖子、Twitter),而无需知道他们的密码。您甚至可以使用 Facebook 或 Google 为您提供合适的身份验证管理,从而节省大量开发工作,并避免一遍又一遍地编写相同的身份验证代码!

OAuth2 授权流程

OAuth2 框架提供了四种不同类型的授权流程。根据您要创建的产品(网站、移动应用、独立软件)以及您想覆盖的场景类型,您需要选择一种工作流程而不是另一种。因此,您需要了解各种流程之间的区别。我的意图是直接通过一些示例来向您解释这些区别。让我们看看我是否能成功……

授权码

我们从 RFC6749 中的授权码定义开始。如果您对正式定义感到好奇,它看起来是这样的:

引用

1.3.1. 授权码



   授权码是通过使用授权服务器作为客户端和资源所有者之间的中介来获得的。客户端不直接向资源所有者请求授权,而是通过其用户代理(根据 [RFC2616] 定义)将资源所有者重定向到授权服务器,而授权服务器反过来将资源所有者带回客户端,并附带授权码。
   在将授权码连同资源所有者一起返回给客户端之前,授权服务器会验证资源所有者并获得授权。由于资源所有者只与授权服务器进行身份验证,因此资源所有者的凭据永远不会与客户端共享。
   授权码提供了一些重要的安全优势,例如能够验证客户端,以及将访问令牌直接传输给客户端,而无需通过资源所有者的用户代理,并可能暴露给他人,包括资源所有者。
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)

   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)

   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)
   (此处原文为4个段落,合并为1个,以适应上下文)

现在,这些话是什么意思呢?让我们尝试举一些例子……

您有一个网站,并想为您的注册用户提供一项功能:例如,他们可以直接在您的网站上发布推文,例如发布他们的状态或使用您的应用程序实现的某些目标。
为了实现这一点,任何用户都应该向您提供他的 Twitter 用户名和密码,如果他希望您使用他的账户进行推文。但是用户不想与他可能不信任的网站分享他的个人 Twitter 凭据。更不用说,如果他更改了他的 Twitter 密码,该功能将停止工作……

这是一个可以使用授权码授权的典型场景。通过授权码授权,最终用户可以通过您的网站访问 Twitter,并授予您的网站在某些限制下代表他操作 Twitter 的永久“授权”。

真实生活示例

这是一个取自 LinkedIn 网页应用的示例。您是一名 LinkedIn 用户,并希望连接您的 Twitter 账户,以便您可以直接从 LinkedIn 在 Twitter 上发布一些更新。这是 LinkedIn 开发人员可以实现的功能,使用的是授权码授权流程。因此,在 LinkedIn 的“设置”部分,您可以点击 Twitter 设置区域中的“更改”按钮。

点击“更改”后,您将进入一个特定区域,您可以在其中“连接”您的 Twitter 账户。

在这里,授权码流程现在会将您转移到 Twitter 网站,在那里会要求您输入用户名和密码。您无需与 LinkedIn 分享您的 Twitter 用户名和密码。您只是授权 LinkedIn 为您做一些事情。Twitter 的弹出窗口甚至会提前告诉您 LinkedIn 将能够使用这些授权做什么(这个权限列表就是 SCOPE)。

在您向 Twitter 提供用户名和密码后,浏览器会将您重定向回 LinkedIn,此时,在不知道任何 Twitter 凭据的情况下,LinkedIn 可以代表您在 Twitter 上执行某些操作。您只是委托了 LinkedIn 在 Twitter 上进行操作。

更多细节

只要您在网站上将用户配置文件连接到一个或多个账户,并且例如授权网站访问 LinkedIn、Facebook、Twitter 等(甚至可以同时访问所有这些)拥有的信息,就可以使用授权码授权。

这些工作流程假设网站的开发人员将在服务器的某个地方存储在网站(以上示例中的 LinkedIn)和您要连接的 API(以上示例中的 Twitter)之间进行交换的授权码。

这是因为网站必须“记住”已提供给它的某些身份验证信息,并在将来再次使用它们来操作(例如)Twitter,以代表用户发布推文。

隐式授权

同样,我们从 RFC6749 中的正式定义开始。它说明如下:

引用

1.3.2. 隐式

   隐式授权是一种简化的授权码流程,针对使用脚本语言(如 JavaScript)在浏览器中实现的客户端进行了优化。在隐式流程中,客户端不会获得授权码,而是直接获得访问令牌(作为资源所有者授权的结果)。授权类型是隐式的,因为没有颁发中间凭据(如授权码)(然后用于获取访问令牌)。
   在隐式流程中颁发访问令牌时,授权服务器不会对客户端进行身份验证。在某些情况下,可以通过用于将访问令牌交付给客户端的重定向 URI 来验证客户端身份。访问令牌可能会暴露给资源所有者或具有访问资源所有者用户代理的其他应用程序。
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)

   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   隐式授权提高了某些客户端(例如在浏览器中实现的客户端应用程序)的响应性和效率,因为它减少了获取访问令牌所需的往返次数。然而,这种便利性应与使用隐式授权的安全影响(如第 10.3 节和第 10.16 节所述)进行权衡,尤其是当授权码授权类型可用时。

   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)

 

现在,再次,让我们将这些翻译成一些您可以真正“看到”和“触摸”的示例。

您正在开发一个 Web 应用或一些 JavaScript/客户端代码,它允许用户访问一些第三方资源(再次强调,如 Facebook、Twitter、Google 等),但您没有(或者不想有)任何服务器端代码来存储与用户相关的身份验证/委托信息。

这是一个可以使用隐式授权的典型场景。它与授权码授权的主要区别在于它不涉及任何服务器端代码或活动。它仅在客户端和用户希望访问的第三方 API 之间进行。

带有网页的真实生活示例

这是一个取自 Google 开发文档的示例。您可以在此处找到原文。Google 的人创建了一个包含一些 JavaScript 代码的网页。他们想用这段代码,从网页上访问某个最终用户的 Google Drive 中的文件列表。不涉及服务器交互,这是隐式授权流程的关键部分。

因此,他们在网页上提供了一个按钮,要求“登录”或“授权”页面请求文件列表。最终用户应该点击这个按钮。

一旦最终用户(直到此时还是匿名的)点击了按钮,Web 客户端会打开另一个窗口,并将他重定向到 Google 页面,在那里会要求用户输入他的 Google 密码。同样,就像在上一个工作流程中一样,用户只将他的凭据提供给 Google,而不是其他人。

用户输入密码后,登录窗口关闭,隐式授权流程将他重定向回之前的网页,在那里会传递关于最终用户已授权此应用的信息。最后,网页获得了授权,可以代表用户进行操作,并可能显示他存储在 Google Drive 中的文件列表。

演示页面不幸没有走到这一步,您只能从屏幕截图中推断出该页面现在已获得授权。在这三个步骤中,一切都在 Web 客户端和第三方服务(Google Drive)之间发生。没有服务器代码在此存储任何形式的持久化授权。

带有移动应用的真实生活示例

我将使用我最喜欢的移动应用之一 Duolingo 来展示一个例子,说明在登录过程中可以实现隐式授权流程。

当您第一次在手机上安装 Duolingo 并启动它时,您会看到一个这样的屏幕:

如果您点击“我已经有一个账户”,您会看到三个很棒的选项。

顶部是 Duolingo 的用户名和密码输入框。我们稍后会看到这个选项。
屏幕底部,用红色框起来的,是隐式授权流程的入口点。在这个应用中,用户可以使用 Google 或 Facebook 授权应用使用他的 Facebook/Google 身份。然后我们点击“Facebook”按钮,会看到以下屏幕:

重要的是要理解,在这一步,我们不再在应用内部操作。应用打开了一个单独的窗口,现在用户即将登录 Facebook,并再次授予 Duolingo 应用某种授权。最后一个屏幕,在点击登录后,会提供一个关于用户将要做什么的摘要,执行登录并重定向回应用。

更多细节

隐式授权与授权码授权非常相似,但由于一些技术原因,它不如前者安全。运行在同一台机器/设备上的其他应用程序可能会窃取客户端和第三方库之间交换的授权信息并加以利用。更多关于这方面以及其他优缺点的详细信息超出了本介绍的范围。我建议的最佳参考资料是这篇文章

资源所有者密码凭据授权

同样,这里是 RFC6749 中关于此流程的定义:

引用

1.3.3. 资源所有者密码凭据

   资源所有者密码凭据(即用户名和密码)可直接用作授权授权以获取访问令牌。当资源所有者和客户端之间存在高度信任时(例如,客户端是设备操作系统的一部分或是一个特权很高的应用程序),并且当其他授权授权类型不可用时(例如授权码),则应使用凭据。
   即使此授权类型需要客户端直接访问资源所有者凭据,资源所有者凭据也仅用于单个请求,并与访问令牌交换。此授权类型可以通过用长期有效的访问令牌或刷新令牌替换凭据来消除客户端存储资源所有者凭据供将来使用的需求。
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)

   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)

这意味着,如果您有一个独立的软件(如 Windows 应用)或一个移动应用,并且用户可以在应用中直接输入用户名/密码来验证身份并访问幕后需要身份验证的 Web 服务。

此授权流程可以在我们拥有一个特定公司创建的专有应用程序来访问该公司自身提供的服务,而无需使用任何第三方登录的情况下实现。

我将尝试为您举几个好的例子……

好的例子可能是:

  • Netflix 移动应用要求输入 Netflix 用户名/密码才能访问 Netflix 世界。您当然会在 Netflix 应用中输入您的 Netflix 凭据,凭据就是为此目的而生的,对吧?
  • Facebook 应用要求输入 Facebook 凭据才能访问 Facebook。

糟糕的例子,意味着使用资源所有者密码凭据授权会是错误选择的例子是:

  • 一个“财务管理应用”要求您输入银行账户凭据才能连接到该银行账户。这应该使用隐式授权来完成!
  • 一个“游戏应用”要求您直接在应用中输入 Google 凭据来执行某些操作。该应用可能会窃取这些凭据,您不应该信任任何要求您这样做的应用!

带有移动应用的真实生活示例

基本上,任何要求输入在同一软件/网站注册后获得的用户名和密码的应用都有可能实现资源所有者密码凭据授权

为了再次使用 Duolingo,我们可以将此屏幕作为参考,即初始登录屏幕:

在屏幕顶部,您可以输入您在网站注册时获得的 Duolingo 用户名/密码。这可以使用资源所有者密码凭据授权流程来实现:Duolingo 应用要求输入密码来访问……Duolingo!这很合理……

另一个简单的例子?LinkedIn 及其应用。

再次,您将在 LinkedIn 应用中输入 LinkedIn 凭据。这里没有信任问题,因为您将提供给该应用的凭据只能用于 LinkedIn。

客户端凭据授权

最后但同样重要,这里是 OAuth2 中正式定义的客户端凭据授权:

引用

1.3.4. 客户端凭据

   客户端凭据(或其他形式的客户端身份验证)可用作授权授权,当授权范围仅限于客户端控制下的受保护资源,或与授权服务器预先安排的受保护资源时。客户端凭据通常用作授权授权,当客户端代表自己行事(客户端也是资源所有者)或基于与授权服务器预先安排的授权来请求访问受保护资源时。
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)
   (此处原文为3个段落,合并为1个,以适应上下文)

这次“展示”一个真实世界的例子有点难,因为这个流程是基于服务器的,一些代码会更合适。尽管如此,我还是会尝试通过一些示例来让您了解一些网站可能使用客户端凭据授权来解决某个问题。

示例 1:您有一个网站,并且在服务器端代码中,您需要访问后端云存储来保存/检索数据。

示例 2:您有一个网站,并且在服务器端,您想访问第三方 API 来检索最新的股票交易值。第三方 API 提供给您(网站所有者)并且只有您才能访问此 API 的可能性,使用某种形式的身份验证(这就是 Client Secret 参数变得有用的情况之一)。

网站中的真实世界示例

这里有一个房地产搜索引擎。这是一个经典的聚合门户:当该门户的匿名用户搜索房产时,门户在后台会查询许多其他房地产门户,例如红色框内的那些。

现在,这个特定的网站可能只是从其他门户抓取内容,我知道。
但我们也可以说,这些对其他门户的查询可能是使用客户端凭据授权流程完成的。

客户端凭据授权流程不涉及任何最终用户。该门户的服务器代码(而不是网页)会使用其他门户(如这个AcladoAnibis等)提供的 Web API 进行请求,并向它们提供一些凭据,简而言之,就是用于访问 WebAPI 的“门户”的用户名和密码。

更多细节

客户端凭据授权是一个不涉及任何最终用户的流程。它应该仅用于机器到机器的身份验证,以允许某些服务器端代码访问受保护的资源,如第三方 Web API。
当然,这个服务器代码的开发者,也就是我们例子中的门户,必须在第三方 API 的提供者那里注册他的门户,并获得某种客户端识别方式,以便他能够执行查询。
也许这个网站每月支付一定的费用才能查询其他门户。无论他们如何实现,很明显这里没有进行任何最终用户身份验证。最终用户不扮演任何角色。

为什么写这篇文章?

每个作者都有自己解释主题的方式。每个读者都有自己学习和理解主题的方式。在我学习 OAuth2 的过程中,我发现大多数文章都是从 A 到 Z,介绍了框架的每一个可能的细节(定义、角色、流程等),然后试图将这些碎片组合在一起。
我的方法想有所不同,为您提供真实世界的示例并尝试解释它们。希望这种方法有效,我期待着根据您的反馈和评论来改进这篇文章。

历史

2017-02-19:初稿。

2017-02-23:为每个授权类型添加了取自 RFC6749 OAuth2 规范的正式定义。

2017-02-23:修订了标题,以更好地阐明文章的内容和范围。

© . All rights reserved.