OAuth(是 Open Authorization 开放授权的缩写),在全世界得到广泛应用,目前的版本是2.0版。
本文会对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749。
OAuth 2.0 是一个开放标准,用于授权用户访问另一个应用程序的资源,而无需将用户的凭据(比如用户名和密码)直接提供给第三方应用。它被广泛应用于各种网络服务,如社交媒体、云存储和在线支付等领域。
1. 不使用 OAuth 有什么问题?
举个简单的例子,就比如我们的应用程序想要获取 GitHub 用户的仓库信息,但是 GitHub 不允许外部网站直接读取用户的仓库信息,换言之就是需要用户授权我们的应用程序才能读取到用户的仓库信息。
那么我们的应用程序怎么获取到用户的授权呢?
传统的做法是让用户提供 GitHub 账号和密码给我们的应用程序,这样我们就可以读取仓库信息了。但是这样有几个缺陷:
(1)我们的应用程序为了不定期的获取仓库信息,会保存用户的密码,这样很不安全。
(2)GitHub 不得不部署密码登录,但是单纯的密码登录并不安全。
(3)我们的应用程序相当于拥有了用户的账号,用户没法限制应用程序的授权范围和有效期。
(4)用户只有修改密码才能收回应用程序的权力,但是修改密码会使得其他的第三方应用程序失效。
(5)只要有一个应用程序被破解,用户的密码就会被泄漏,所有受密码保护的数据也就会跟着泄漏。
OAuth 就是为了解决上面这些问题而诞生的。
2. 有关的名词、术语
在详细讲解 OAuth 之前,需要了解几个名词。这对我们后续的理解至关重要!
名词、术语 | 解释 |
---|---|
Authorization server | 授权服务器,验证资源所有者并获得授权。验证成功后向客户端颁发访问令牌的服务器。 |
Resource server | 资源服务器,托管受保护资源的服务器,接收并响应携带访问令牌的资源请求。 |
Resource Owner | 资源所有者,一种能够授予对受保护资源的访问权限的实体。 |
Client | 代表发出受保护资源请求的应用程序,比如我们开发的网站就是这里说的应用程序。 |
授权服务器可以是与资源服务器相同的服务器,也可以是单独的实体。单个授权服务器发布的访问令牌能被多个资源服务器所接受。
3. OAuth 协议流程
上图展示了这四个角色之间是如何交互的。相关步骤详细解释如下:
(A):客户端向资源所有者请求授权()。授权请求可以直接向资源所有者发起,或者通过作为中介的授权服务器间接发起。比如我们点击 GitHub 登录时,会跳往一个页面让我们进行授权。
(B):客户端接收授权授予(Authorization Grant),该授权授予是表示资源所有者的授权的凭证。比如我们请求 GitHub 授权页面后,点击授权按钮之后,会返回一个 code
(授权码),这就是授权的凭证。
(C):客户端请求授权服务器,并出示授权授予,如果通过身份验证授权服务器会返回访问令牌(Access Token)。如果在我们的应用程序中的话,这里是后端服务器向授权服务器发送请求,并携带 client_id
和 client_srcret
两个字段。
(D):授权服务器对客户端进行身份验证并验证授权授予,如果授权授予有效,则颁发访问令牌。
(E):客户端向资源服务器请求受保护的资源,并通过提供访问令牌进行身份验证。
(F):资源服务器验证访问令牌,如果访问令牌有效,则为客户端提供服务。
4. 授权授予(Authorization Grant)
授权授予是表示资源所有者的授权(访问其受保护的资源)的凭证,客户端使用该授权来获得访问令牌。该规范定义了四种授予类型——授权码、隐式授权、资源所有者密码凭据和客户端凭据——以及用于定义其他类型的可扩展性机制。
4.1. 授权码(Authorization Code)
相比其他的授权方式,这种方法是最安全的,也是使用最多的授权方式。这也是我们主要介绍的第三方登录授权方式。
授权码是通过使用授权服务器作为客户端和资源所有者之间的中介来获得的。客户端不是直接向资源所有者请求授权,而是将资源所有者引导到授权服务器(通过其在 [RFC2616] 中定义的用户代理),授权服务器又将资源所有者用授权码引导回客户端。
在使用授权码将资源所有者引导回客户端之前,授权服务器对资源所有者进行身份验证并获得授权。因为资源所有者只通过授权服务器进行身份验证,所以资源所有者的凭据永远不会与客户端共享。
授权码提供了一些重要的安全优势,例如对客户端进行身份验证的能力,以及将访问令牌直接传输到客户端,而无需通过资源所有者的用户代理将其传递给其他人,包括资源所有者。
4.2. 隐式授权(Implicit)
隐式授权是针对使用诸如 JavaScript 之类的脚本语言在浏览器中实现的客户端而优化的简化授权代码流。在隐式流中,不是向客户端颁发授权码,而是直接向客户端颁发访问令牌
作为资源所有者授权的结果。授予类型是隐式的,因为没有颁发中间凭证(如授权码)。
在隐式授予流期间颁发访问令牌时,授权服务器不会对客户端进行身份验证。在某些情况下,可以通过用于向客户端传递访问令牌的重定向URI来验证客户端身份。访问令牌可以被暴露给资源所有者或具有对资源所有者的用户代理的访问权的其他应用。
隐式授权提高了一些客户端(例如作为浏览器内应用程序实现的客户端)的响应能力和效率,因为它减少了获得访问令牌所需的往返次数。然而,这种便利性应与使用隐式授权的安全影响进行权衡,尤其是当授权码授权类型可用时。
4.3. 资源所有者密码凭据(Resource Owner Password Credentials)
资源所有者密码凭据(即用户名和密码)可以直接用作获得访问令牌的授权授予。只有当资源所有者和客户端之间存在高度信任时(例如,客户端是设备操作系统或高特权应用程序的一部分),以及当其他授权授予类型不可用时(例如授权码),才可以使用资源所有者密码凭据。
即使此授予类型要求客户端直接访问资源所有者凭据,资源所有者凭据被用于单个请求,并交换为访问令牌。这种授权类型可以通过与长期访问令牌或刷新令牌交换凭据,消除客户端存储资源所有者凭据以供将来使用的需要。
4.4. 客户端凭据(Client Credentials)
当授权范围仅限于客户端控制下的受保护资源,或仅限于先前与授权服务器安排的受保护资源时,客户端凭据(或其他形式的客户端身份验证)可用作授权授予。通常,当客户端代表自己行事(客户端也是资源所有者)或根据先前与授权服务器安排的授权请求访问受保护资源时,客户端凭据可用作授权授予。
5. 访问令牌(Access Token)
访问令牌是用于访问受保护资源的凭据。访问令牌是表示颁发给客户端的授权的字符串。访问令牌通常对客户端是不透明的。令牌表示特定的访问范围和持续时间,由资源所有者授予,并由资源服务器和授权服务器强制执行。
令牌可以表示用于检索授权信息的标识符,或者能以可验证的方式包含授权信息(即由一些数据和签名组成的令牌)。
访问令牌提供了一个抽象层,用资源服务器可以理解的单个令牌替换不同的授权结构(例如,用户名和密码)。这种抽象使得发布访问令牌比用于获取它们的授权授予更具限制性,并消除了资源服务器理解广泛身份验证方法的需要。
基于资源服务器安全要求,访问令牌可以具有不同的格式、结构和使用方法(例如,加密属性)。
6. 刷新令牌(Refresh Token)
刷新令牌是用于获取访问令牌的凭据。刷新令牌由授权服务器颁发给客户端,用于在当前访问令牌无效或过期时获得新的访问令牌,或者用于获得具有相同或更窄范围的附加访问令牌(访问令牌的生存期可能比资源所有者授权的生存期更短,权限更少)。根据授权服务器的判断,发布刷新令牌是可选的。如果授权服务器发布刷新令牌,则在发布访问令牌时会包含刷新令牌。
刷新令牌是一个字符串,表示资源所有者授予客户端的授权。刷新令牌通常对客户端是不透明的。令牌表示用于检索授权信息的标识符。与访问令牌不同,刷新令牌只会发送给授权服务器,不会发送给资源服务器。
上图是获取刷新令牌,以及通过刷新令牌来获取访问令牌的流程,详细的解释如下:
(A):客户端通过向授权服务器进行身份验证并提供授权授权来请求访问令牌。在这步之前还有用户授权的流程,该流程在这里被省略。
(B):授权服务器对客户端进行身份验证并验证授权授予,如果授权授予有效,则颁发访问令牌和刷新令牌。
(C):客户端通过提供访问令牌向资源服务器获取受保护的资源。
(D):资源服务器验证访问令牌,如果有效,则为请求提供响应数据。
(E):重复步骤(C)和(D),直到访问令牌到期。如果访问令牌过期,则跳到步骤(G);否则,它会发出另一个获取受保护的资源的请求。
(F):如果访问令牌无效(可能是访问令牌到期),资源服务器返回一个访问令牌错误的信息。
(G):客户端通过向授权服务器进行身份验证并呈现刷新令牌来请求新的访问令牌。客户端身份验证要求基于客户端类型和授权服务器的策略。
(H):授权服务器对客户端进行身份验证并验证刷新令牌,如果有效,则发布新的访问令牌(以及可选的新的刷新令牌)。
7. 结语
该文章主要介绍了一些 OAuth 2.0 的名词和术语。如果大家对 OAuth 感兴趣的话,后面我们还会讲解 OAuth 的使用。我们会以 GitHub 为例来实现第三方登录。
具体的代码我已经写好了,项目地址:实现 GitHub 第三方登录