理解 OAuth2 协议

文章首发于:github.com/USTB-musion…html

写在前面

昨天在组内分享了第三方登陆与单点登陆,其中着重分享了第三方登陆当中的oAuth2协议,在这里记录整理一下。oAuth协议是一个受权的开放网络标准,主要是用来解决第三方登陆的,即所谓第三方登陆,实际上就是oAuth的受权。前端

什么是第三方登陆

不少网站登陆时,容许使用第三方网站的身份来进行登陆,这称为“第三方登陆”。好比知乎和慕课网等,可使用微信,QQ,或微博来进行登陆。一个网站想接入第三方登陆,须要用到oAuth这个协议。git

什么是oAuth

oAuth是一个关于受权的开放网络标准,用来受权第三方应用,获取用户的数据。其最终的目的是为了给第三方应用颁发一个有时效性的令牌access_token,第三方应用根据这个access_token就能够去获取用户的相关资源,如头像,昵称,email这些信息。如今你们用的基本是2.0的版本。github

oAuth2.0的这个协议是从RFC 6749这篇文章当中提出来的,若是想了解更全面的信息,能够点击👆这篇文章进行了解。下面来介绍一下oAuth2的角色和流程。web

协议流程

在详细介绍oAuth2协议流程以前,先来简单了解几个角色,方便后续的理解。docker

  • Resource Owner,资源全部者,由于是请求用户的头像和昵称的一些信息,因此资源的全部者通常指用户本身。
  • Client,客户端,如web网站,app等
  • Resource Server,资源服务器,托管受保护资源的服务器
  • Authorization Server,受权服务器,通常和资源服务器是同一家公司的应用,主要是用来处理受权,给客户端颁发令牌
  • User-agent,用户代理,通常为web浏览器,在手机上就是app

了解了上面这些角色以后,来看下oAuth2.0的运行流程是怎么样的。json

+--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+
复制代码

(A). 用户打开客户端(Client),客户端向受权服务器(Resource Owner)发送一个受权请求后端

(B). 用户赞成给客户端(Client)受权浏览器

(C). 客户端使用刚才的受权去向认证服务器(Authorization Server)认证bash

(D). 认证服务器认证经过后,会给客户端发放令牌(Access Token)

(E). 客户端拿着令牌(Access Token),去向资源服务器(Resource Server)申请获取资源

(F). 资源服务器确认令牌以后,给客户端返回受保护的资源(Protected Resource)

受权方式

在oAuth2当中,定义了四种受权方式,针对不一样的业务场景:

  • 受权码模式(authorization code): 流程最完整和严密的一种受权方式,服务器和客户端配合使用,主要是针对web服务器的状况采用
  • 简化模式(implicit):主要用于移动应用程序或纯前端的web应用程序,主要是针对没有web服务器的状况采用
  • 密码模式(resource owner password credentials):不推荐,用户须要向客户端提供本身的帐号和密码,若是客户端是自家应用的话,也是能够的
  • 客户端模式(client credentials):客户端以本身的名义,而不是用户的名义,向“服务提供商”进行认证,如微信公众号以此access_token来拉取全部已关注用户的信息,docker到dockerhub拉取镜像等

受权码模式

+----------+
 | Resource |
 |   Owner  |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier      +---------------+
 |         -+----(A)-- & Redirection URI ---->|               |
 |  User-   |                                 | Authorization |
 |  Agent  -+----(B)-- User authenticates --->|     Server    |
 |          |                                 |               |
 |         -+----(C)-- Authorization Code ---<|               |
 +-|----|---+                                 +---------------+
   |    |                                         ^      v
  (A)  (C)                                        |      |
   |    |                                         |      |
   ^    v                                         |      |
 +---------+                                      |      |
 |         |>---(D)-- Authorization Code ---------'      |
 |  Client |          & Redirection URI                  |
 |         |                                             |
 |         |<---(E)----- Access Token -------------------'
 +---------+       (w/ Optional Refresh Token)
复制代码

Note: The lines illustrating steps (A), (B), and (C) are broken into two parts as they pass through the user-agent.

受权码模式如上图所示,这种流程是功能最完整,流程也是最严密的受权方式,适用于那些有后端的web应用。它的特色是经过客户端的后台服务器和服务商的认证服务器进行通信。它的流程以下,若是我想使用github来接入第三方登陆:

(A). 用户(Resource Owner)在用户代理(User-Agent,如web浏览器,app)上选择了第三方应用(如github)来进行登陆,会重定向到github的受权端点:

https://github.com/login/oauth/authorize?
   response_type=code&
   client_id=your_code&
   redirect_uri=重定向的url&
   scope=read&
   state=uuid
复制代码
字段 描述
response_type 必须,在受权码模式中固定为code
client_id 必须,惟一标识了客户端,在github注册时得到的客户端ID
redirect_url 客户端在github注册的重定向url,用户赞成或拒绝的时候都会跳转到这个重定向url
scope 可选,请求资源范围,若有多项,使用多个空格隔开
state 推荐,客户端生成的随机数,资源服务器会原样返回,防止CSRF的攻击

(B). 页面跳转后,github会要求用户登陆,而后询问是否给予客户端受权,用户点击赞成。

(C). 而后github就会将受权码(Authorization Code)返回给redirect_uri(重定向uri)。

redirect_uri?code=xxxxxxx
复制代码
字段 描述
code 必须,受权码
state 防止CSRF攻击的参数

(D). 客户端(Client)在经过在URL中取出受权码以后,就能够在后端向github请求令牌

https://github.com/login/oauth/access_token?
  client_id=your_code&
  client_secret=your_secret&
  grant_type=authorization_code&
  code=取出的code&
  redirect_uri=重定向的url
复制代码
字段 描述
client_id 必须,客户端在github注册的惟一标识
client_secret 必须,客户端在github注册时返回的密钥
grant_type 必须,authorization_code/refresh_code
code 必须,上一步中取出的受权码
redirect_uri 必须,完成受权以后的回调地址,与在github注册时的一致

(E). github给redirect_uri指定的地址返回AccessToken,经过JSON格式返回

{
  "access_token":"xxxxxxx",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"xxxxxxx"
}
复制代码

客户端就能够在后端取到access_token,在这段json中,还返回了一个refresh_token,这个refresh_token表示用于访问下一次的更新令牌,refresh_token的时效性比access_token长,当access_token过时时,可使用refresh_token换取新的access_token。

简化模式

简化模式主要针对没有后端的纯前端应用,在这种状况下,由于没有后端,因此就不能采用受权码模式的这种流程了,必需要把access_token存在前端。

+----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier     +---------------+
 |         -+----(A)-- & Redirection URI --->|               |
 |  User-   |                                | Authorization |
 |  Agent  -|----(B)-- User authenticates -->|     Server    |
 |          |                                |               |
 |          |<---(C)--- Redirection URI ----<|               |
 |          |          with Access Token     +---------------+
 |          |            in Fragment
 |          |                                +---------------+
 |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
 |          |          without Fragment      |     Client    |
 |          |                                |    Resource   |
 |     (F)  |<---(E)------- Script ---------<|               |
 |          |                                +---------------+
 +-|--------+
   |    |
  (A)  (G) Access Token
   |    |
   ^    v
 +---------+
 |         |
 |  Client |
 |         |
 +---------+
复制代码

Note: The lines illustrating steps (A) and (B) are broken into two parts as they pass through the user-agent.

主要是B这个步骤,页面跳转到github网站,用户赞成给予客户端受权。github就会把令牌做为URL参数,跳转回到redirect_uri的这个回调地址。

回调地址#token=xxxxxx
复制代码

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

密码模式

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

+----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      v
      |    Resource Owner
     (A) Password Credentials
      |
      v
 +---------+                                  +---------------+
 |         |>--(B)---- Resource Owner ------->|               |
 |         |         Password Credentials     | Authorization |
 | Client  |                                  |     Server    |
 |         |<--(C)---- Access Token ---------<|               |
 |         |    (w/ Optional Refresh Token)   |               |
 +---------+                                  +---------------+

        Figure 5: Resource Owner Password Credentials Flow
复制代码

密码模式就是用户向客户端提供本身的帐号和密码,客户端使用这些信息去向咱们的服务提供商去索要一个受权。

客户端模式

客户端以本身的名义,而不是用户的名义,向“服务提供商”进行认证,如微信公众号以此access_token来拉取全部已关注用户的信息,docker到dockerhub拉取镜像等。

+---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

                 Figure 6: Client Credentials Flow
复制代码

客户端模式,顾名思义就是指客户端以本身的名义而不是用户的名义去向服务的提供商去作一个认证,严格来讲,这种模式并非 oAuth 框架要解决的问题,在这种客户端模式下呢,它是直接经过客户端的密钥和id去获取一个access_token的,不须要用户去参与。

单点登陆

那关于 oAuth2 的理解呢,大概介绍这么多的内容。oAuth 协议主要是用来解决第三方登陆的,但聊到不一样场景下的登陆方案时,除了第三方登陆以外,还有一个概念,就是单点登陆。

单点登陆就是在多个系统中,用户只需登陆一次,各个系统就能够感知该用户已经登陆。好比说你登陆了天猫,淘宝也会自动登陆。简单地理解,单点登陆就是这样,它经过将两个或多个产品中的用户登陆逻辑抽离出来,经过只输入一次用户名和密码,就能够达到同时登陆多个产品的效果。

单点登陆实现方案

第一种是同一父域下的单点登陆,好比说hr.oa.com,km.oa.com,fuli.oa.com,那这种状况就能够经过将domain属性设置为二级域名oa.com来共享cookie,而后服务端经过共享session就能够实现单点登陆。除了共享session以外固然也能够用JWT这种方式进行实现。

那第二种就是针对不一样域下的单点登陆,好比说淘宝和天猫,它的二级域名是不相同的。这种状况,就要解决cookie不共享的问题。如今主流的方案就是使用cas来实现。

总结

来简单总结一下,针对不一样业务场景下登陆的主流解决方案,第一种是针对同一公司,同一父域下的单点登陆解决方案,这种状况由于cookie是同父域下的,设置cookie的domain属性能够实现cookie共享。而后服务端session共享就能够实现单点登陆。但还有一种这种方式解决方案是JWT。JWT就是json web token。实际上就是一个字符串,由头部,载荷和签名三部分组成。

第二种是针对同一公司,可是不一样域下的单点登陆解决方案,好比说淘宝和天猫的单点登陆,那这种方式的主流解决方案是CAS。

那第三种就是不一样公司,不一样域下的就使用第三方登陆功能实现。如第三方网站须要接入微信登陆,QQ登陆,微博登陆等,那第三方登陆功能的实现呢,就用到刚才介绍的 oAuth2.0 的协议。

参考文章

OAuth 2.0 的四种方式

相关文章
相关标签/搜索