理解 OAuth 2.0 协议

老牛浏览 339评论 0发表于

OAuth 是一个关于授权(authorization)的开发网络标准,在全世界得到广泛的应用,目前最新版本是 2.0 版。

本文对 OAuth 2.0 的设计思路和运行流程做一个简明通俗的解释,主要参考材料为 RFC 6749

1. 应用场景

举例说明来理解 OAuth 的适用场景。

有一个「云冲印」的网站,可以将用户存储在 Google 的照片,冲印出来。用户为了使用该服务,必须让「云冲印」读取自己存储在 Google 上的照片。

问题是只有得到用户的授权,Google 才会同意「云冲印」读取这些照片。那么,「云冲印」如何获取用户授权呢?

传统方法是,用户将自己的 Google 用户名和密码,告诉「云冲印」,后者就可以读取用户的照片了。

但是,这样的做法有以下几个严重缺点:

  • 「云冲印」为了后续的服务,会保存用户的密码,这样很不安全;

  • Google 不得不部署密码登录,而我们知道,单纯的密码登录并不安全;

  • 「云冲印」拥有了获取用户存储在 Google 所有资料的权利,用户没法限制「云冲印」获得授权的范围和有效期;

  • 用户只有修改密码,才能收回赋予「云冲印」的权利。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效;

  • 只要有一个第三方应用程序被破解,就会导致用户密码泄露,以及所有被密码保护的数据泄露。

OAuth 就是为了解决上面这些问题而诞生的。

2. 名词定义

在详细讲解 OAuth 2.0 之前,需要了解几个专用名词。它们对于读懂后面的讲解,尤其是流程图,至关重要。

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

  • HTTP service:HTTP 服务提供商,本文中简称「服务提供商」,即上一节例子中的 Google。

  • Resource Owner:资源所有者,本文中又称「用户」(user)。

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

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

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

知道了上面的这些名词,就不难理解,OAuth 的作用就是让「客户端」安全可控的获取「用户」的授权,与「服务提供商」进行互动。

3. 实现思路

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

「客户端」登录授权层以后,「服务提供商」根据令牌的权限范围和有效期,向「客户端」开放用户存储的资料。

4. 运行流程

OAuth 2.0 的运行流程如下图:

ff21a27b-b9d6-48d1-811b-9df046f69388
  • (A)用户打开客户端后,客户端要求用户给予授权;

  • (B)用户同意给予客户端授权;

  • (C)客户端使用上一步获得的授权,向认证服务器申请令牌;

  • (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌;

  • (E)客户端使用令牌,向资源服务器申请获取资源;

  • (F)资源服务器确认令牌无误,同意向客户端开放资源。

不难看出来,上面六个步骤之中,B 是关键,即用户怎样才能给予客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。

下面一一讲解客户端获取授权的四种方式。

5. 授权方式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0 定义了四种授权方式。

  1. 授权码授权(authorization code)

  2. 隐式授权(implicit)

  3. 密码授权(resource owner password credentials)

  4. 客户端证书授权(client credentials)

5.1 授权码授权

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

适用范围

  • 需要得到长期授权;

  • OAuth 客户端是 Web 应用服务器;

  • OAuth 访问令牌不宜泄露给用户的环境。

f3ed564a-43bf-4b3c-b510-91b429533fce

步骤如下:

  • (A)用户访问客户端,后者将前者导向认证服务器;

  • (B)用户选择是否给予客户端授权;

  • (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的「重定向 URI」(redirection URI),同时附上一个授权码;

  • (D)客户端收到授权码,附上早先的「重定向 URI」,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见;

  • (E)认证服务器核对了授权码和重定向 URI,确认无误之后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

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

A 步骤中,客户端申请认证的 URI,包含以下参数:

  • response_type:表示授权类型,必选,此处的值固定为 code

  • client_id:表示客户端的 ID,必选。

  • redirect_uri:表示重定向 URI,可选。

  • scope:表示申请的权限范围,可选。

  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

示例:

http
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https://client.example.com/callback HTTP/1.1
Host: server.example.com

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

  • code:表示授权码,必选。该码的有效期应该很短,通常设置为 10 分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系。

  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样的包含这个参数,可用于防范 XSRF 攻击。

示例:

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

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

  • grant_type:表示使用的授权模式,必选,此处的值固定为 authorization_code

  • code:表示上一步获得的授权码,必选。

  • redirect_uri:表示重定向 URI,必选,且必须与 A 步骤中的该参数值保持一致。

  • client_id:表示客户端 ID,必选。

示例:

http
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://client.example.com/callback

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

  • access_token:表示访问令牌,必选。

  • token_type:表示令牌类型,该值大小写不敏感,必选,可以是 bearer 或者 mac 类型。

  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须设置其他方式的过期时间。

  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选。

  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

示例:

http
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"
     }

从上面的代码可以看到,相关参数使用 JSON 格式发送。此外,HTTP 头中明确指定不得缓存。

5.2 隐式授权

隐式授权(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了「授权码」这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

适用范围

  • 仅需临时访问的场景;

  • 用户会定期在 API 提供者那里进行登录;

  • OAuth 客户端运行在浏览器中(Javascript、Flash 等);

  • 浏览器绝对可信,因为该授权类型可能会将访问令牌泄露给恶意用户或应用程序;

  • 一般在移动端授权时使用。

b460e750-5c29-450a-a073-4b2019b6dc0e

步骤如下:

  • (A)客户端将用户导向认证服务器;

  • (B)用户决定是否给予授权;

  • (C)假设用户给予授权,认证服务器将用户导向客户端指定的「重定向 URI」,并在 URI 的 Hash 部分包含了该令牌;

  • (D)浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值;

  • (E)资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌;

  • (F)浏览器执行上一步获得的脚本,提取出令牌;

  • (G)浏览器将令牌发给客户端。

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

A 步骤中,客户端发出的 HTTP 请求,包含以下参数:

  • response_type:表示授权类型,此处的值固定为 token,必选项;

  • client_id:表示客户端的 ID,必选项;

  • redirect_uri:表示重定向的 URI,可选项;

  • scope:表示权限范围,可选项;

  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

示例:

http
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https://client.example.com/callback HTTP/1.1
    Host: server.example.com

C 步骤中,认证服务器回应客户端的 URI,包含以下参数:

  • access_token:表示访问令牌,必选项;

  • token_type:表示令牌类型,该值大小写不敏感,必选项;

  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间;

  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略;

  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

示例:

http
HTTP/1.1 302 Found
     Location: http://example.com/callback#access_token=2YotnFZFEjr1zCsicMWpAA
               &state=xyz&token_type=example&expires_in=3600

认证服务器用 HTTP 头信息的 Location 栏,指定浏览器重定向的网址。注意,在这个网址的 Hash(#)部分包含了令牌。

根据上面的 D 步骤,下一步浏览器会访问 Location 指定的网址,但是 Hash 部分是不能发送的。接下来的 E 步骤,服务提供商的资源服务器发送过来的代码,会提取出 Hash 中的令牌,比如通过 JS 手动解析 Hash 部分,然后拿到 token。

从整个流程可以看出,相比授权码授权,隐式授权少了第一步获取 Authorization Code 的过程,因此变得更为简单。但正因为如此也降低了安全性。

5.3 密码授权

密码授权(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向「服务商提供商」索要授权。

适用范围

这种授权方式会直接将用户密码暴露给应用程序,因此应谨慎适用。一般来说,只有在信任度极高的客户授权才适用,比如官方移动应用、操作系统或高权限程序。

92367c2b-3a90-4869-bf6b-ef17b9fcd009

步骤如下:

  • (A)用户向客户端提供用户名和密码;

  • (B)客户端将用户名和密码发给认证服务器,向后者请求令牌;

  • (C)认证服务器确认无误后,向客户端提供访问令牌。

B 步骤中,客户端发出的 HTTP 请求,包含以下参数:

  • grant_type:表示授权类型,此处的值固定为 password,必选。

  • username:表示用户名,必选。

  • password:表示用户的密码,必选。

  • scope:表示权限范围,可选。

示例:

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

     grant_type=password&username=johndoe&password=A3ddj3w

C 步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。

http
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"
     }

上面代码中,各个参数的含义参见《授权码授权》一节。

整个过程中,客户端不得保存用户的密码。

5.4 客户端证书授权

客户端证书授权(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向「服务提供商」进行认证。严格地说,这正方式并不属于 OAuth 框架所要解决的问题。

适用范围

认证服务器不提供像用户数据这样的重要资源,仅仅是有限的只读资源或者一些开放的 API。例如适用了第三方的静态文件服务,如 Google Storage 或 Amazon S3。这样,你的应用需要通过外部 API 调用并以应用本身而不是单个用户的身份来读取或修改这些资源。此类场景就很适合用客户端证书授权的方式。

a992b31f-7ec8-4cbe-a462-f9ba474db2c6

步骤如下:

  • (A)客户端向认证服务器进行身份认证,并要求一个访问令牌;

  • (B)认证服务器确认无误后,向客户端提供访问令牌。

A 步骤中,客户端发出的 HTTP 请求,包含以下参数:

  • grant_type:表示授权类型,此处的值固定为 client_credentials,必选项。

  • scope:表示权限范围,可选项。

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

     grant_type=client_credentials

认证服务器必须以某种方式,验证客户端身份,比如 client_id 和 client_secret。

B 步骤中,认证服务器向客户端返回访问令牌。

示例:

http
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,
       "example_parameter":"example_value"
     }

上面代码中,各个参数的含义参见《授权码授权》一节。

6. 更新令牌

如果用户访问的时候,客户端的「访问令牌」已经过期,则需要使用「更新令牌」申请一个新的访问令牌。

客户端发出更新令牌的HTTP请求,包含以下参数:

  • granttype:表示使用的授权模式,此处的值固定为 refresh_token,必选。

  • refresh_token:表示早前收到的更新令牌,必选。

  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。

示例:

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

     grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

7. 实例

以新浪微博来说,在网站中用 OAuth 2.0 方式接入微博登录,具体流程如下:

  1. 在微博开放平台申请接入,得到 App KeyApp Secret,将登录组件部署到站点中。

  2. 点击微博登录按钮,会跳转到微博认证服务器,请求用户授权,请求 URI 如下:

    http
    https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://bbs.ilaoniu.cn/weibo/callback.php
  3. 用户输入微博账号密码登录,确认授权,微博认证服务器跳转到上一步的 redirect_uri 地址,并将获取到的授权码作为参数传过去:

    http
    http://bbs.ilaoniu.cn/weibo/callback.php?code=xxxxxx
  4. 网站服务器拿到授权码 code,去微博换取 access token

    http
    https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
  5. access token 就可以去服务器读取/修改用户信息了。当然,一些高级权限的用户接口调用,需要申请通过后才行。

接入其它使用 OAuth 2.0 进行授权的系统,比如 QQ 登录,和微博几乎一样,其实使用起来还是很简单的。

点赞
收藏
暂无评论,快来发表评论吧~
私信
老牛@ilaoniu
老牛,俗称哞哞。单纯的九零后理工小青年。喜欢折腾,爱玩,爱音乐,爱游戏,爱电影,爱旅游...
最后活跃于