2

引言

关于应用系统用户身份管理需求,包括身份认证、权限授权、单点登录、联合身份认证等业务场景,业界有一堆的标准和规范,比如单点登录的CAS、Kerberos,第三方身份认证OpenID,第三方用户授权OAuth,联合身份认证和授权数据标准SAML等。每种技术有各自的应用场景,也存在交叉场景,想要把他们搞清楚,需要了解各种技术的工作原理和应用场景。今天就从其中一个技术开始,对OAuth2.0用户授权框架做一个简单介绍,想对框架全面了解的可以参考框架的标准 RFC6749

598px-Oauth_logo.svg.png

OAuth概述

引用维基百科介绍,OAuth主要的应用场景为:

开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

举一个通俗的例子,用户把照片、视频、联系人数据存储在内容托管云服务R(Resource)中的Picture、Video、Contact三个模块中;用户使用在线照片打印服务P(Printer),用户需要让P服务读取R服务中的照片进行打印,但不想让P服务读取R服务中的其他数据。

传统方式下,用户只能向P服务提供R服务的用户名密码,P服务通过用户名密码登录R服务,读取照片,并且不能限制P服务读取的范围。

使用OAuth框架,通过以下授权流程,在不暴露用户密码的情况下,向P服务授予有限的操作S服务的权限,整体流程如下:

  1. 用户登录P服务,点击获取R服务权限的链接。
  2. 浏览器跳转到R服务,用户登录R服务后,跳出向P服务授予权限的界面。
  3. 用户选择授予Picture模块只读有效期1小时三个权限的授权选项,并提交。
  4. 页面跳转回P服务,并携带R服务生产的授权码(Picture模块只读权限)。
  5. P服务获得授权码,通过授权码(附加clint_id和client_secret)向R服务发起读取Picture模块请求。
  6. R服务验证用户信息和授权码后,向P服务提供Picture的读取权限。

OAuth发展至今,共有三个版本,分别为:初始化版本OAuth1.0;漏洞修复版本OAuth 1.0a;不向后兼容的OAuth2.0版本。

2.0版本主要是修复了前面版本的安全漏洞,对授权的流程进行了优化,提供了更丰富的使用场景,由于优化精简了授权的步骤,所以不能向后兼容。本文重点介绍OAuth2.0的业务流程,为便于描述,下文如果没有特殊说明OAuth指的就是OAuth2.0。

服务角色

根据RFC描述,定义了4中服务角色,分别描述如下:

  1. 资源所有者 Resource Owner
能够授予对受保护资源的访问权限的实体,当资源所有者是人员时,资源所有者就是最终用户。
  1. 资源服务器 Resource Server
托管受保护资源的服务器,能够使用访问令牌(Access Token)接受和响应受保护的资源请求。
  1. 客户端 Client
代表资源所有者,经其授权后向受保护资源发起请求的应用程序。
  1. 授权服务器 Authorization Server
授权服务器对资源所有者进行认证并获取授权后,向客户端颁发访问令牌(Access Token)

协议运行流程

     +--------+                               +---------------+
     |        |--(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) 客户端向资源所有者申请授予资源访问权限。如示例中的P服务授予授权界面。
B) 资源所有者向客户端授予资源访问授权。如示例中的用户授予予Picture模块只读有效期1小时三个权限的授权选项,并把授予的权限凭证返回给客户端。根据使用场景,Oauth规范定义了四中权限授予类型,具体在下文描述。
C) 客户端向授权服务器提交授权凭证,申请获取授权令牌。
D) 授权服务器校验凭证,并向客户端返回授权令牌。
E) 客户端向资源服务器提交授权令牌,申请访问资源。
F) 资源服务器校验令牌合法性,并向客户端提供资源服务。

客户端注册

客户端在申请授权服务前,需要先到认证服务器上注册。以GitHub作为授权服务器为例,用户先到Register a new OAuth application注册一个第三方应用。注册后,系统为应用生成Client ID和Client Secret两个参数,用户后续的授权。

注册页面
image.png

注册结果
image.png

权限授予类型

授权授予是表示资源所有者授权客户端的权限凭据,客户端使用此凭证获取访问令牌。规范定义了四种授权类型:授权代码模式(Authorization code)、隐含模式(Implicit)、资源所有者密码凭据模式(Resource Owner Password)和客户端凭据模式(Client Credentials),概述如下。

授权代码模式(Authorization code)

授权代码模式是功能最完善,流程最严密,安全性最好的授权模式。这种模式和其他模式的最大区别,是授权过程中,由客户端的后台服务器端参与,即要求客户端是有后台服务器端处理能力的服务,如Web服务。

     +----------+
     | 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)

需要特别说明下,Client以Web服务为例,这里的Client是指Client的后台服务器端,User-Agent是指浏览器,授权的流程如下。
A) 用户打开浏览器,访问Client的Web页面,点击获取授权链接;浏览器把用户重定向到授权服务器。
B) 用户通过认证凭证(用户名密码)登录授权服务器。
C) 用户在授权服务器授权页面选择要授权给Client访问的权限并提交;提交后,授权服务器生成授权代码,把浏览器重定向到附带授权代码的Client指定的页面;Client页面将授权代码提交到自己的后台服务器端。
D) Client后台服务把授权代码+client_id+client_secret(后两者在客户端向授权服务器注册时生成)提交给授权服务器进行验证。
E) 授权服务器验证通过后,向Client返回Access Token和Refresh Token(可选)。
之后,Client就可以拿个Access Token,向资源服务器请求获取资源的服务。

下面在介绍下各步骤的请求和返回内容。
获取授权码请求
请求示例:

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
    &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

参数说明:

  1. response_type,必填。固定为code
  2. client_id,必填。Client向授权服务器注册时生成的ID。
  3. redirect_uri,可选。授权成功后返回授权代码的重定向地址,此地址也可以在授权服务器上注册。
  4. scope,可选。指定访问请求的范围,含义由授权服务器设计。
  5. state,推荐。客户端用来在请求和回调之间保持状态的不透明值。授权服务器在将user-agent重定向回客户端时包含此值。参数用于防止跨站请求伪造。

获取授权代码响应
如果授权成功,则授权服务器通过URL重定向向Client返回授权代码。
响应示例:

HTTP/1.1 302 Found
     Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
               &state=xyz

参数说明:

  1. code,必填。授权成功后授权服务器生成的授权代码,基于安全性考虑,授权代码由如下要求

    • 授权代码需要设置较短的过期时间,以减轻授权代码泄露的风险,一般推荐10分钟。
    • 授权代码只能使用一次,如果多次使用,则此授权代码相关的所有Token都会被撤销。
  2. state。和Request中的参数对应。

获取Access Token请求
请求示例:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

参数说明:

  1. grant_type,必填。固定为authorization_code
  2. code,必填。授权代码。
  3. redirect_uri。和获取授权码请求地址一致。
  4. client_id,必填。客户端的client_id。
关于client_secret
根据网上其他资料描述,此请求request-body中还需要携带client_secret参数,以保证授权的安全性。但是根据RFC规范的描述,并不推荐使用此方式,而是推荐使用浏览器的HTTP Basic authentication认证方案,如上例所示:把认证信息(client_id:client_secret)Base64编码后放在HTTP头的Authorization字段中。

获取Access Token响应
响应示例:

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

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

参数说明:

  1. access_token,必填。访问令牌。
  2. token_type,必填。令牌类型,可以是bearer类型或mac类型。
  3. expires_in。过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  4. refresh_token,可选。更新令牌,用与获取下一次的访问令牌。
  5. scope,可选。权限范围,客户端申请填写的一致。

隐含模式(Implicit)

隐含模式(implicit grant type)授权过程不需要Client后台服务器端参与,直接在浏览器中向授权服务器申请令牌,跳过了"授权码"这个步骤。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

     +----------+
     | 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 |
     |         |
     +---------+

资源所有者密码凭据模式(Resource Owner 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)   |               |
     +---------+                                  +---------------+

客户端凭据模式(Client Credentials)

当客户端请求访问其控制下的受保护资源时,客户端只能使用其客户端凭据(或其他支持的身份验证方法)来请求访问令牌。或者其他资源拥有者之前与授权服务器安排好的资源拥有者(其方法不在本文的范围之内)。客户端凭据授予类型必须仅供机密客户端使用。

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

刷新令牌Refresh Token

在授权代码模式和资源所有者密码凭据模式中,获取Access Token的过程中会返回可选的Refresh Token,Refresh Token用于获取Access Token。由于Access Token需包含在每个和资源服务器的请求中,使用频率高,所以应该设置较短的有效期,以减少泄露带来的风险,当Access Token过期是,可以通过Refresh Token中授权服务器中换取新的Access Token。
刷新过期的Access Token的流程如下。

  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+

乘着风
107 声望12 粉丝

五岁时,妈妈告诉我,人生的关键在于快乐。上学后,人们问我长大了要做什么,我写下“快乐”。他们告诉我,我理解错了题目,我告诉他们,他们理解错了人生。——约翰·列侬