avatar

OAuth 2.0 学习

OAuth 2.0 介绍

OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息。
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0。

在传统的客户端-服务器身份验证模型中,客户端请求访问限制资源(受保护的资源)是通过使用资源所有者凭证向资源服务器进行身份验证。为了让第三方应用程序访问受限制的资源,资源所有者需要将自己的凭证提供给第三方。这会带来一些问题和局限性:

  • 需要第三方应用程序来存储资源所有者的凭证以供将来使用,通常是密码明文。
  • 服务器需要支持密码验证,尽管密码方式有安全性弱点。
  • 第三方应用程序获得了访问限制资源的权限并可以持续访问,而且也有访问限制资源子集的能力。
  • 资源所有者不能撤消对单个第三方的访问权限而不撤销所有第三方的访问权限,如果要撤销授权就只能修改密码。
  • 任何第三方应用程序被攻击都会导致用户密码以及其保护的资源损害。

OAuth通过引入授权层解决了这些问题,并将客户角色与资源角色分开。在OAuth中,客户端请求访问受控资源,由资源所有者控制并由资源服务器托管,并且发出的凭证与资源所有者的凭证不同。而不是直接使用资源所有者的凭证来访问受保护的资源。大致流程如下:

  1. 客户端申请访问令牌(包含访问具体范围,生存期和其他访问属性的字符串)。
  2. 访问令牌经资源所有者的批准由授权服务器向第三方客户端颁发。
  3. 客户端使用访问令牌访问资源服务器托管的受保护资源。

OAuth 2.0 中的角色

OAuth 2.0为用户和应用定义了如下四种角色:

  • 资源拥有者(resource owner)

    资源拥有者是指拥有共享数据的人或应用。比如Facebook或者Google的用户就是是资源拥有者,他们拥有的资源就是他们的数据。资源拥有者一般指人,这也是最常见的情况。但资源拥有者也可以是一个应用,OAuth 2.0规范中包含这两种可能性。

  • 资源服务器(resource server)

    资源服务器是指托管资源的服务器。比如,Facebook或Google就是资源服务器(或者有一个资源服务器)。

  • 客户端应用(client)

    客户端应用是指请求访问存储在资源服务器的资源的应用。资源被资源拥有者所拥有。客户端应用可以是一个请求访问用户Facebook账号的第三方游戏。

  • 授权服务器(authorization server)

    授权服务器是指授权客户端应用能够访问资源拥有者所拥有的资源。授权服务器和资源服务器可以是同一个服务器,但不是必须的。如果这两个服务器是分开的,OAuth 2.0没有讨论这个两个服务器应该如何通信。这是由资源服务器和授权服务器开发者自己设计决定的。

OAuth 2.0 授权方式

OAuth 2.0为我们提供了四种授权方式,如下:

  • 授权码(authorization-code)
  • 隐藏式(implicit)
  • 密码式(password):
  • 客户端凭证(client credentials)

注意:不管哪一种授权方式,第三方应用申请令牌之前,都必须先到要访问的系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。
下面我们就一一来了解一下。

授权码方式

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。如下图:

授权码方式

A:用户访问客户端,客户端将用户导向授权服务器,通过用户代理(User-Agent)发送包括它的客户端标识符、请求的范围、本地状态和一个重定向URI,授权服务器在授予(或拒绝)访问权后将其发送给用户代理。URI如下:

http://authorization-server/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

在URI中response_type参数表示要求返回授权码(code),client_id参数让授权服务器知道是谁在请求,redirect_uri参数是授权服务器接受或拒绝请求后的跳转网址(一般需要encode),scope参数表示要求的授权范围(这里是只读)

B:授权服务器对资源所有者进行身份验证(通过用户代理,让用户输入用户名和密码),并确定资源所有者是否授予或拒绝客户端的访问请求。

C:用户跳转后,假如资源所有者同意授权请求,那么授权服务器将会使用前面提供的或者事先指定的重定向URI(redirection URI),重定向到客户端,并附上一个授权码(code)和一个前面提供的本地状态(state)(如果有的话,则会原值返回)。

https://client/callback?code=AUTHORIZATION_CODE

上面 URL 中,code参数就是授权码。

D:客户端收到授权码,附上早先的重定向URI,向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。在发出请求时,授权服务器对客户端进行身份验证。请求参数包含授权代码、用于获得验证的授权代码的重定向URI、标识客户端身份的client id和client secret。

https://authorization-server/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRETgrant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL

上面 URL 中,client_id参数和client_secret参数用来让 authorization-server 确认 client 的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是令牌颁发后的回调网址。

E:授权服务器对客户端进行身份验证,验证授权代码,并确保所收到的重定向URI与用于在步骤C中对客户端重定向的URI相匹配,如果有效,授权服务器将发送访问令牌access token和刷新令牌refresh token(可选)。

然后授权服务器给我们返回授权码,如下:

{    
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read",
"other":"..."
}

这里的参数主要是:

  • access_token:表示访问令牌。必选项。
  • token_type:表示令牌类型。该值大小写不敏感,必选项,可以是bearer类型或mac类型。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • refresh_token:表示更新令牌。可选项,用来获取下一次的访问令牌。
  • scope:表示权限范围。可选项,如果与客户端申请的范围一致,此项可省略。

最后我们拿到access_token去访问受保护的资源即可,如下:

http://resource-server/api/userinfo?access_token=ACCESS_TOKEN

隐藏式

有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)。如下图:
隐藏式

A:Client网站提供一个链接,要求用户跳转到authorization-server,授权用户数据给 Client 网站使用。

https://authorization-server/oauth/authorize?response_type=token&client_id=CLIENT_IDredirect_uri=CALLBACK_URL&scope=read

上面 URL 中,response_type参数为token,表示要求直接返回令牌。

B:授权服务器对资源所有者进行身份验证(通过用户代理,让用户输入用户名和密码),并确定资源所有者是否授予或拒绝客户端的访问请求。

C:用户跳转到 authorization-server ,登录后同意给予 client 网站授权。这时,authorization-server就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为 URL 参数,传给 client。

https://client/callback#token=ACCESS_TOKEN

上面 URL 中,token参数就是令牌,client因此直接在前端拿到令牌。

注意:令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

密码式

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。如下图:
密码式

A:Client要求用户提供authorization-server的用户名和密码。

B:拿到用户名密码以后,Client 就直接向 authorization-server 请求令牌。

https://authorization-server/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID

上面 URL 中,grant_type参数是授权方式,这里的password表示"密码式",username和password是 authorization-server 的用户名和密码。

C:授权服务器认证用户名和密码信息正确后,然后返回客户端access_token等信息,这里是以JSON的方式返回,类似授权码方式时返回的JSON

这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。

客户端凭证

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。如下图:
客户端凭证

A:客户端直接向授权服务器发起认证请求,URI如下:

https://authorization-server/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

上面 URL 中,grant_type参数等于client_credentials表示采用凭证式,client_idclient_secret用来让 authorization-server 确认 client 的身份

B:authorization-server 网站验证通过以后,直接返回令牌。

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

好了,OAuth 2.0 到这里就差不多了,更多的内容请访问官网

参考资料

OAuth 2.0官网

文章作者: 毛毛是只猫
文章链接: http://lshaolin.github.io/posts/443da21b/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 毛毛是只猫