OAuth 2.0 受权认证详解

1、认识 OAuth 2.0

1.1 OAuth 2.0 应用场景

OAuth 2.0 标准目前被普遍应用在第三方登陆场景中,如下是虚拟出来的角色,阐述 OAuth2 能帮咱们干什么,引用阮一峰这篇理解OAuth 2.0中的例子:php

有一个"云冲印"的网站,能够将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取本身储存在Google上的照片。html

问题是只有获得用户的受权,Google才会赞成"云冲印"读取这些照片。那么,"云冲印"怎样得到用户的受权呢?前端

传统方法是,用户将本身的Google用户名和密码,告诉"云冲印",后者就能够读取用户的照片了。这样的作法有如下几个严重的缺点。git

(1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。
(2)Google不得不部署密码登陆,而咱们知道,单纯的密码登陆并不安全。
(3)"云冲印"拥有了获取用户储存在Google全部资料的权力,用户无法限制"云冲印"得到受权的范围和有效期。
(4)用户只有修改密码,才能收回赋予"云冲印"的权力。可是这样作,会使得其余全部得到用户受权的第三方应用程序所有失效。
(5)只要有一个第三方应用程序被破解,就会致使用户密码泄漏,以及全部被密码保护的数据泄漏。github

1.2 名词概念

OAuth 就是为了解决上面这些问题而诞生的。在详解 OAuth 以前,须要明确一些基本的概念,从上面场景中抽象出如下概念。json

第三方应用程序segmentfault

Third-party application:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。后端

HTTP服务提供商浏览器

HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的Google。缓存

资源全部者

Resource Owner:资源全部者,本文中又称"用户"(user)。

用户代理

User Agent:用户代理,本文中就是指浏览器。

认证服务器

Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

资源服务器

Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,能够是同一台服务器,也能够是不一样的服务器。

知道了上面这些名词,就不难理解,OAuth的做用就是让"客户端"安全可控地获取"用户"的受权,从而能够和"服务商提供商"进行互动。

2、OAuth 的受权认证流程

2.1 认证思路

OAuth 在"客户端"与"服务提供商"之间,设置了一个 受权层(authorization layer)。"客户端"不能直接登陆"服务提供商",只能登陆受权层,以此将用户与客户端区分开来。"客户端"登陆受权层所用的令牌(token),与用户的密码不一样。用户能够在登陆的时候,指定受权层令牌的权限范围和有效期。

"客户端"登陆受权层之后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。

2.2 认证流程

官方 RFC 6749 文件中的 OAuth 2.0 流程图有点晦涩,优化了 一下:

  1. 用户访问第三方应用程序(简称:客户端)之后,客户端要求用户给予受权。
  2. 用户赞成给予客户端受权。
  3. 客户端使用第 2 步得到的受权,向认证服务器申请令牌。
  4. 认证服务器对客户端进行认证之后,确认无误,赞成发放令牌。
  5. 客户端使用令牌,向资源服务器申请获取资源。
  6. 资源服务器确认令牌无误,赞成向客户端开放资源。

上述中的第 2 步 是关键,即用户怎样才能给于客户端受权。有了这个受权之后,客户端就能够获取令牌,进而凭令牌获取资源。

3、四种受权模式

上一小节能够得出用户对客户端的受权动做是核心,客户端必须获得用户的受权(authorization grant),才能得到令牌(access token)。OAuth 2.0定义了四种受权方式:

3.1 受权码模式(authorization code)

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

3.2 简化模式(implicit)

有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,容许直接向前端颁发令牌。这种方式没有受权码这个中间步骤,因此称为(受权码)"隐藏式"(implicit)。

3.3 密码模式(resource owner password credentials)

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

3.4 客户端模式(client credentials)

最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。

4、受权码模式详解

4.1 受权码模式流程

受权码模式(authorization code)是功能最完整、流程最严密安全的受权模式。它的特色就是经过客户端的 后台服务器,与"服务提供商"的认证服务器进行互动。

注意这种方式适用于那些有后端的 Web 应用。受权码经过前端传送,令牌则是储存在后端,并且全部与资源服务器的通讯都在后端完成。这样的先后端分离,能够避免令牌泄漏。

受权码模式流程以下:

  1. 用户访问客户端,客户端将用户导向认证服务器。
  2. 用户选择是否给予客户端受权。
  3. 假设用户给予受权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个受权码。
  4. 客户端收到受权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的 后台服务器 上完成的,对用户不可见。
  5. 认证服务器核对了受权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

从上述的流程描述可知,只有第 2 步须要用户进行受权操做,以后的流程都是在客户端的后台和认证服务器后台以前进行"静默"操做,对于用户来讲是无感知的。

下面是上面这些步骤所须要的参数。

4.2 受权码模式流程的五个步骤

第 1 步骤

参数说明

第 1 步骤中,客户端申请认证的URI,包含如下参数:

  • response_type:表示受权类型,必选项,此处的值固定为"code"
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,能够指定任意值,认证服务器会原封不动地返回这个值。
示例

A 网站提供一个连接,用户点击后就会跳转到 B 网站,受权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意连接:

https://b.com/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

上面 URL 中:

response_type参数表示要求返回受权码(code);

client_id参数让 B 网站知道是谁在请求;

redirect_uri参数是 B 网站接受或拒绝请求后的跳转网址;

scope参数表示要求的受权范围(这里是只读)。

第 2 步骤

第 2 步骤中,用户跳转后,B 网站会要求用户登陆,而后询问是否赞成给予 A 网站受权。

第 3 步骤

参数说明

第 3 步骤中,服务器回应客户端的URI,包含如下参数:

  • code:表示受权码,必选项。该码的有效期应该很短,一般设为10分钟,客户端只能使用该码一次,不然会被受权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:若是客户端的请求中包含这个参数,认证服务器的回应也必须如出一辙包含这个参数。
示例

在第 2 步骤用户表示赞成以后,这时 B 网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个受权码,就像下面这样。

https://a.com/callback?code=AUTHORIZATION_CODE

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

第 4 步骤

参数说明

第 4 步骤中,客户端向认证服务器申请令牌的HTTP请求,包含如下参数:

  • grant_type:表示使用的受权模式,必选项,此处的值固定为"authorization_code"。
  • code:表示上一步得到的受权码,必选项
  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
  • client_id:表示客户端ID,必选项
示例

在第 3 步骤中,A 网站拿到受权码之后,就能够在后端,向 B 网站请求令牌。

https://b.com/oauth/token?
 client_id=CLIENT_ID&
 client_secret=CLIENT_SECRET&
 grant_type=authorization_code&
 code=AUTHORIZATION_CODE&
 redirect_uri=CALLBACK_URL

上面 URL 中:

client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,所以只能在后端发请求);

grant_type参数的值是AUTHORIZATION_CODE,表示采用的受权方式是受权码;

code参数是上一步拿到的受权码;

redirect_uri参数是令牌颁发后的回调网址。

第 5 步骤

参数说明

第 5 步骤中,认证服务器发送的HTTP回复,包含如下参数:

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

第 4 步骤中,B 网站收到请求之后,就会颁发令牌。具体作法是向redirect_uri指定的网址,发送一段 JSON 数据:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

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

上面 JSON 数据中,access_token字段就是令牌,A 网站在后端拿到了。注意:HTTP头信息中明确指定不得缓存。

5、令牌(Token)传递方式

当客户端(第三方应用程序)拿到访问资源服务器的令牌时,即可以使用这个令牌进行资源访问了。

在第三方应用程序拿到access_token后,如何发送给资源服务器这个问题并无在 RFC6729 文件中定义,而是做为一个单独的 RFC6750 文件中独立定义了。这里作如下简单的介绍,主要有三种方式以下:

  1. URI Query Parameter
  2. Authorization Request Header Field
  3. Form-Encoded Body Parameter

5.1 请求头参数传递

Authorization Request Header Field,由于在HTTP应用层协议中,专门有定义一个受权使用的Request Header,因此也可使用这种方式:

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

其中"Bearer "是固定的在access_token前面的头部信息。

5.2 表单编码传递

使用 Request Body 这种方式,有一个额外的要求,就是 Request Header 的Content-Type必须是固定的application/x-www-form-urlencoded,此外还有一个限制就是 不可使用 GET 访问,这个好理解,毕竟 GET 请求是不能携带 Request Body 的。

POST /resource HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

access_token=mF_9.B5f-4.1JqM

5.3 URI 请求参数传递

URI Query Parameter,这种使用途径应该是最多见的一种方式,很是简单,好比:

GET /resource?access_token=mF_9.B5f-4.1JqM HTTP/1.1
Host: server.example.com

在咱们请求受保护的资源的 Url 后面追加一个 access_token 的参数便可。另外还有一点要求,就是 Client 须要设置如下 Request Header 的 Cache-Control:no-store,用来阻止 access_token 不会被 Web 中间件给 log 下来,属于安全防御方面的一个考虑。

5.4 令牌的刷新

为了防止客户端使用一个令牌无限次数使用,令牌通常会有过时时间限制,当快要到期时,须要从新获取令牌,若是再从新走受权码的受权流程,对用户体验很是很差,因而 OAuth 2.0 容许用户自动更新令牌。

具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另外一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。

https://b.com/oauth/token?
  grant_type=refresh_token&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET&
  refresh_token=REFRESH_TOKEN

上面 URL 中:

grant_type参数为refresh_token表示要求更新令牌,此处的值固定为refresh_token,必选项;

client_id参数和client_secret参数用于确认身份;

refresh_token参数就是用于更新令牌的令牌。

B 网站验证经过之后,就会颁发新的令牌。

注意: 第三方应用服务器拿到刷新令牌必须存于服务器,经过后台进行从新获取新的令牌,以保障刷新令牌的保密性。

6、OAuth2的安全问题

6.1 CSRF攻击

应用程序在早期使用 OAuth2 的时候爆发过很多相关的安全方面的漏洞,其实仔细分析后会发现大都都是没有严格遵循 OAuth2 的安全相关的指导形成的,相关的漏洞事件自行搜索。

其实 OAuth2 在设计之初是已经作了不少安全方面的考虑,而且在 RFC6749 中加入了一些安全方面的规范指导。好比:

  1. 要求 Authorization server 进行有效的客户端验证;

  2. client_serect, access_token, refresh_token, code等敏感信息的安全存储(不得泄露给第三方)、传输通道的安全性(TSL的要求);

  3. 维持 refresh_token 和第三方应用的绑定,刷新失效机制;

  4. 维持 Authorization Code 和第三方应用的绑定,这也是state参数为何是推荐的一点,以防止CSRF攻击;

  5. 保证上述各类令牌信息的不可猜想行,以防止被猜想获得;

安全无小事,这方面是要靠各方面(开放平台,第三方开发者)共同防范的。

6.2 攻击流程

假设有用户张三,攻击者李四,第三方"云冲印"应用(它集成了第三方社交帐号登陆,而且容许用户将社交帐号和"云冲印"中的帐号进行绑定),以及 OAuth2 服务提供者 Google。

步骤1

攻击者李四登陆"云冲印"网站,而且选择绑定本身的 Google 帐号

步骤2

"云冲印"网站将李四重定向到 Google,因为他以前已经登陆过 Google,因此 Google 直接向他显示是否受权"云冲印"访问的页面。

步骤3

李四在点击"赞成受权"以后,截获 Google 服务器返回的含有Authorization code参数的HTTP响应。

步骤4

李四精心构造一个 Web 页面,它会触发"云冲印"网站向 Google 发起令牌申请的请求,而这个请求中的Authorization Code参数正是上一步截获到的 code。

步骤5

李四将这个 Web 页面放到互联网上,等待或者诱骗受害者张三来访问。

步骤6

张三以前登陆了"云冲印"网站,只是没有把本身的帐号和其余社交帐号绑定起来。在张三访问了李四准备的这个 Web 页面,令牌申请流程在张三的浏览器里被顺利触发,"云冲印"网站从 Google 那里获取到access_token,可是这个 token 以及经过它进一步获取到的用户信息却都是攻击者李四的。

步骤7

"云冲印"网站将李四的 Google 帐号同张三的"云冲印"帐号关联绑定起来,今后之后,李四就能够用本身的 Google 帐号经过 OAuth 登陆到张三在 "云冲印" 网站中的帐号,冠冕堂皇的冒充张三的身份执行各类操做。

从总体上来看,本次 CSRF 攻击的时序图应该是下面这个样子的:

从上图中能够看出,形成 CSRF 攻击漏洞问题的关键点在于,OAuth2 的认证流程是分为好几步来完成的,在上一章节受权码模式流程中的流程图中的第 4步骤中,第三方应用在收到一个 GET 请求时,除了能知道当前用户的 cookie,以及 URL 中的Authorization Code以外,难以分辨出这个请求究竟是用户本人的意愿,仍是攻击者利用用户的身份伪造出来的请求。

因而,攻击者就能使用移花接木的手段,提早准备一个含有本身的Authorization Code的请求,并让受害者的浏览器来接着完成后续的令牌申请流程。

6.3 解决方案

要防止这样的攻击其实很容易,做为第三方应用的开发者,只需在 OAuth 认证过程当中加入state参数,并验证它的参数值便可。具体细节以下:

  • 在将用户重定向到资源认证服务器受权界面的时候,为当前用户生成一个随机的字符串,并做为state参数加入到URL中,同时存储一份到 session 中。
  • 当第三方应用收到资源服务提供者返回的Authorization Code请求的时候,验证接收到的state参数值。若是是正确合法的请求,那么此时接收到的参数值应该和上一步提到的为该用户生成的state参数值(存于当前用户的 session 中)彻底一致,不然就是异常请求。
  • state参数值须要具有下面几个特性:
    • 不可预测性:足够的随机,使得攻击者难以猜到正确的参数值
    • 关联性:state参数值和当前用户会话(user session)是相互关联的
    • 惟一性:每一个用户,甚至每次请求生成的state参数值都是惟一的
    • 时效性:state参数一旦被使用则当即失效

state参数在 OAuth2 认证过程当中不是必选参数,所以在早期第三方应用开发者在集成 OAuth2 认证的时候很容易会忽略它的存在,致使应用易受 CSRF 攻击。因此必须对这个安全问题重视起来。

安全是双方的,须要第三方应用和资源服务提供商均要严格遵照安全规范。如 QQ 互联的 OAuth2 API 中,state 参数是强制必选的参数,受权接口是基于 HTTPS 的加密通道等;做为第三方开发者在使用消费这些服务的时候也应该重视注意安全中存在的漏洞。

7、OAuth2 参考资料及案例

7.1 参考资料

https://oauth.net/2/

Oauth Status Pages

RFC6749 : The OAuth 2.0 Authorization Framework

RFC6749中文版

7.2 案例

豆瓣OAuth2 API(Authorization Code)

QQ OAuth2 API(Authorization Code)

豆瓣OAuth2 API(Implicit )

QQ OAuth2 API(Implicit)

微信公众号获取access_token(Client Credentials Grant)

微博开发文档:受权机制

参考博文:

OAuth 2.0 的四种方式

理解OAuth 2.0

OAuth 2.0 的一个简单解释

OAuth2认证受权

理解OAuth2.0认证与客户端受权码模式详解

移花接木:针对OAuth2的攻击

OAuth受权与CSRF攻击

OAuth研究&学习笔记

相关文章
相关标签/搜索