OAuth 2.0 综述
阅读原文时间:2021年05月02日阅读:1

OAuth 2.0 rfc6749 规范

OAuth 2.0 rfc6749 规范-带目录,阅读 RFC 文档的 工具

OAuth 官网

OAuth 是授权(authorization)框架,描述了系统中不同角色(服务提供商、用户、第三方应用)之间怎么实现交互。目前广泛使用的是 2.0 版本。

常见的应用场景(估计所有人都见过):授权登录注册。比如微信有很多用户,且微信提供了 OAuth2 接口。在我的网站上,为了简化用户注册流程,对接了微信提供的这个接口,并且在注册页面提供一个“通过微信登录”的链接或二维码,用户点击后会跳转到微信的授权登录页面(或者微信扫码进入授权页面),微信在用户授权后会将这个用户的基本信息发到我的网站,完成注册。注册后,每次都可以通过微信授权登录了。

如果需要做微信这样的平台型应用,为很多合作伙伴提供授权认证服务,可以考虑搭建自己的 OAuth 2.0 系统。但是要明白,这是挺复杂的一个系统。对于大部分应用开发人员,需要掌握的是如何调用其他平台的接口。

OAuth2 核心

OAuth2 提供了 4 种角色:

  • Resource Owner:资源拥有者,例如一个微信用户
  • Resource Server:资源服务器,例如微信用户的基本信息所在的服务器,可以跟授权服务器在同一台服务器上
  • Client:客户端(第三方应用,即资源使用者,也叫 Third-party application),例如我的网站需要使用微信用户的账号登录,我就是资源使用者
  • Authorization Server:授权服务器,管理 Resource Owner,Client 和 Resource Server 三者的关系

另外的常用名词有:

  • HTTP service:HTTP 服务的提供商,例如微信、GitHub。
  • User-Agent:用户代理,一般指浏览器。

token 都是字符串,授权服务器需要保存所有的 token 及对应的授权信息,以便校验请求。

access token 访问令牌

用于访问受保护资源的凭证。这个凭证表示特定访问范围和可用时间,由资源所有者授权,并由资源服务器和授权服务器执行,并发给客户端。

对资源的访问,需要 access token。

refresh token 刷新令牌

用于再次获取 access token 的凭证。这是可选项,可以自行决定是否让授权服务器发布 refresh token。如果发布 refresh token,会跟 access token 一同发出。refresh token 可以在 access token 失效后获取新的 access token,也可以获取更多一个 access token。

refresh token 只会跟授权服务器交互,不会发送到资源服务器。

Client 有两种类型:

  • 公开的:密码会发给 Client,所以 Client 可能会泄露密码。这个过程中密码传输到终端设备,例如浏览器,APP。

  • 私有的:密码不会发给 Client,所以 Client 不会泄露密码。

  • Web 应用:各个网站的微信授权登录就是典型的 Web 应用

  • User Agent(用户代理)应用:对于浏览器上运行的 JavaScript 应用,浏览器就是用户代理。用户代理应用可以保存在 web 服务器上,但应用程序只运行一次下载的用户代理。

  • 原生应用:无法保证 secret 的安全。

详细差异可以参考 这里

bearer 类型

参考 RFC6750

通过在请求中简单地包含访问令牌字符串来使用:

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

mac 类型

参考 OAuth-HTTP-MAC

通过发出消息认证代码(MAC)密钥以及用于签署 HTTP 请求的某些组件的访问令牌来使用:

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",
                   nonce="274312:dj83hs9s",
                   mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

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

                     Figure 1: 抽象的协议流程

详细步骤如下:

  1. (A) 客户端向资源拥有者请求授权。授权请求可以直接发送到资源所有者(如上图所示),或最好间接经由授权服务器作为中介。
  2. (B) 用户向客户端授权。这是代表了资源所有者授权的一个凭证。权限授予类型取决于客户端请求时授权使用的方法和授权服务器支持的类型。
  3. (C) 客户端通过向授权服务器进行认证并提交上一步获得的授权来请求令牌 access token。
  4. (D) 授权服务器验证客户端,并验证客户端提交的授权,如果有效,则发出令牌 access token。
  5. (E) 客户端向资源服务器请求受限资源,并通过提交 access token 来验证权限。
  6. (F) 资源服务器验证 access token,如果有效,则提供资源。

在步骤 B 中,客户端可以获得用户的授权。有四种授权模式。

第三方应用必须得到用户授权(authorization grant)后才能获得访问令牌(access token)。OAuth2 支持四种授权模式:

  • Authorization Code:授权码模式,私有和公共的 Client 都可以通过 authorization code 来获取 access token。用户通过重定向 URL 返回到 Client 后,Client 的应用程序将从 URL 获取授权码并使用它来请求 access token。
  • Implicit:隐式授权模式(简化模式),是供公开的 Client 使用的简化流程,其中 access token 在没有授权码交换的情况下立即返回。通常不推荐使用隐式流(并且一些服务器完全禁止该流)。 建议公开的 Client 使用授权代码流而不使用客户端密钥。
  • Password:密码模式,通过用户凭证(密码)来获取 access token。此时 Client 需要用户输入密码,所以不应该被第三方客户使用。 在此流程中,用户的用户名和密码直接交换为 access token。
  • Client Credentials:客户端模式,由 Client 用来在用户上下文之外获取 access token。通常被客户用来访问他们自己的资源,而不是用户的资源。Client 也是资源所有者,可以访问自己的资源。

另外,对于已经授权的 Client,还有两种方式可以再次获取 access token:

  • Device Code:设备代码授权模式,用于无浏览器或不方便输入的设备。通过之前获得的 device code 来获取 access token。
  • Refresh Token:在 Client 端的 access token 过期时,通过 refresh token 获取 access token。Client 可以通过这种方式保证 access token 的有效性(access token 的有效期通常很短),而无需与用户进一步交互。

授权码模式

最常用,微信、GitHub 等的授权登录注册就是这种方式。使用这种模式时,授权服务器会直接将授权发送到客户端的后台服务器,不需要经过用户代理,安全可靠。

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

   注意:说明步骤(A),(B)和(C)的行在通过用户代理时分为两部分。

                     Figure 3: 授权码模式的流程

授权码模式用于获取访问令牌和刷新令牌,并针对机密客户端进行了优化。授权码模式基于重定向,授权开始时从客户端跳转到授权服务器的页面,授权完成后授权服务器后台通知客户端,同时跳转回客户端的页面。客户端必须能够与资源所有者的用户代理(通常是 Web 浏览器)进行交互,并且能够从授权服务器接收传入请求。

详细步骤

  1. (A)资源所有者访问客户端,客户端将其导向授权服务器。
  2. (B)授权服务器验证资源所有者(通过用户代理)并确定资源所有者是否授予或拒绝客户端的访问请求。
  3. (C)假设资源所有者授予访问权限,授权服务器使用先前提供的重定向 URI 将用户代理重定向回客户端。重定向 URI 包括授权码和客户端先前提供的任何本地状态。
  4. (D)客户端通过授权码向授权服务器请求令牌。这一步对用户无感知。
  5. (E)授权服务器验证客户端和授权码,并确保收到的重定向 URI 与步骤 C 中用于重定向客户端的 URI 匹配。如果有效,授权服务器将返回访问令牌和可选的刷新令牌。

授权请求(对应步骤 A)

客户端通过 GET 方式使用以下参数来构造请求 URI:

  • response_type:必选。授权类型。只能是“code”。
  • client_id:必选。客户端的 ID。
  • redirect_uri:可选。重定向 URI。
  • scope:可选。申请权限的范围。
  • state:可选。客户端用于维护请求和回调之间状态的不透明值。授权服务器会原封不动的返回。该参数应该用于防止跨站点请求伪造。

授权请求示例:

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

授权响应(对应步骤 C)

授权服务器的响应也是“application/x-www-form-urlencoded”格式,包含以下参数:

  • code:必选。授权码。有效时间很短,建议设为10分钟(微信的是 2 小时)。一次有效,再次使用时会被授权服务器拒绝。授权码跟客户端 ID 和重定向 URI 绑定。
  • state:如果授权请求中包含这个参数,则一定会返回。

授权响应示例:

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

令牌申请请求(对应步骤 D)

客户端通过 POST 方式使用“application/x-www-form-urlencoded”格式将以下参数来构造请求 URI:

  • grant_type:必选,授权模式,只能是“authorization_code”。
  • code:必选,步骤 C 中获取的授权码。
  • redirect_uri:必选,重定向 URI,必须与步骤 A 中的保持一致。
  • client_id:必选,客户端 ID。

示例:

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

令牌申请响应(对应步骤 E)

授权成功后,响应包含以下参数:

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

示例:

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

隐式授权模式(简化模式)

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

   注意:说明步骤(A)和(B)的行在通过用户代理时分为两部分。

                       Figure 4: 隐式授权模式的流程

隐式授权特点

  • 直接在浏览器中向认证服务器申请令牌,所有步骤在浏览器中完成,不需要跟客户端服务器交互,跳过了”授权码”这个步骤。
  • 只支持获取访问令牌,不支持刷新令牌,并且针对操作特定重定向 URI 的公共客户端进行了优化。
  • 令牌对访问者可见,且不需要客户端认证。
  • 与授权码模式(客户端对授权和获取访问令牌发出两个独立请求)不同,客户端会在授权请求的响应中接收访问令牌(同一个请求中)。
  • 隐式授权不验证客户端身份,并且依赖于资源所有者的存在和重定向 URI 的注册。由于访问令牌被编码到重定向 URI 中,因此可能会暴露给资源所有者和同一设备上的其他应用程序。

详细步骤

  1. (A) 客户端将用户导向授权服务器,需要包含以下参数:客户端 ID、scope、state、重定向 URI。
  2. (B) 授权服务器认证资源所有者(通过用户代理),并判断是否向客户端授权。
  3. (C) 假设授权成功,授权服务器会使用上面提供的重定向 URI 跳转回客户端,并在 URI 的片段中附加 access token。
  4. (D) 用户代理(一般是浏览器)向资源服务器发请求,不包含上面附加 access token 的 URI 片段。
  5. (E) 资源服务器返回网页(通常是内嵌了脚本的 HTML 文档),用于获取完整的包含 access token 的重定向 URI 的。
  6. (F) 用户代理执行上一步获取的脚本,提取 access token。
  7. (G) 用户代理将 access token 发送到客户端。

授权请求

客户端通过添加下面的参数来构造请求 URI

  • response_type:必选。授权类型。只能是“token”。
  • client_id:必选。客户端的 ID。
  • redirect_uri:可选。重定向 URI。
  • scope:可选。申请权限的范围。
  • state:可选。客户端用于维护请求和回调之间状态的不透明值。授权服务器会原封不动的返回。该参数应该用于防止跨站点请求伪造。

客户端使用 HTTP 重定向响应或通过用户代理可用的其他方式将资源所有者定向到构造的 URI。示例:

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

授权服务器必须验证 redirect_uri 参数是否和客户端预留的参数一致。

Access Token 响应

用户授权后,授权服务器会颁发一个 access token 并发送到客户端,可用参数如下:

  • access_token:必选,访问令牌。
  • token_type:必选,令牌类型,该值大小写不敏感,可以是 bearer 类型或 mac 类型。
  • expires_in:过期时间,单位秒。如果用其他方式设置了过期时间,则可以省略该参数。
  • scope:可选,权限范围,默认如果与客户端申请的范围一致。
  • state:如果授权请求中包含这个参数,则一定会返回。

注意:这里没有 refresh token。

示例:

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

密码模式

     +----------+
     | 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: 密码模式流程

资源所有者将用户名和密码发送给客户端,然后客户端用这些凭证获取授权。

详细步骤

  1. (A)资源所有者向客户端提供用户名和密码。
  2. (B)客户端将用户名和密码发给认证服务器,请求访问令牌。
  3. (C)认证服务器向客户端提供访问令牌。

令牌申请请求(对应步骤 B)

客户端通过 GET 方式使用以下参数来构造请求 URI:

  • grant_type:必选。授权类型。只能是“password”。
  • username:必选。资源所有者的用户名。
  • password:必选。资源所有者的密码。
  • scope:可选。申请权限的范围。
  • state:可选。客户端用于维护请求和回调之间状态的不透明值。授权服务器会原封不动的返回。该参数应该用于防止跨站点请求伪造。

授权请求示例:

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)

授权成功后,响应包含以下参数:

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

示例:

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

客户端模式

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

                     Figure 6: Client Credentials Flow

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

详细步骤

  1. (A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
  2. (B)认证服务器确认无误后,向客户端提供访问令牌。

令牌申请请求(对应步骤 A)

客户端通过 GET 方式使用以下参数来构造请求 URI:

  • grant_type:必选。授权类型。只能是“client_credentials”。
  • scope:可选。申请权限的范围。

授权请求示例:

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

grant_type=client_credentials

令牌申请响应(对应步骤 B)

授权成功后,响应包含以下参数:

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

示例:

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

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

               Figure 2: 刷新过期的 Access Token

详细步骤

  1. (A) 客户端通过向授权服务器进行身份验证并提交授权许可来请求 access token。
  2. (B) 授权服务器对客户端进行身份验证并验证授权许可,如果有效,则发出 access token 和 refresh token。
  3. (C) 客户端向资源服务器递交 access token,以访问受保护的资源。
  4. (D) 资源服务器验证 access token,如果有效,则提供资源。
  5. (E) 重复进行上面两个步骤,直到 access token 过期。如果客户端知道 access token 过期,跳到第 7 步,否则继续发出访问资源的请求。
  6. (F) 因为 access token 已经失效了,资源服务器报错“invalid token error”。
  7. (G) 客户端通过向授权服务器进行身份验证并提交 refresh token 来请求 access token。客户端的授权请求基于客户端类型和授权服务器的策略。
  8. (H) 授权服务器验证客户端并验证 refresh token,如果有效则发出新的 access token(也可以发出一个新的 refresh token)。

令牌申请请求(对应步骤 G)

客户端通过 GET 方式使用以下参数来构造请求 URI:

  • grant_type:必选。授权类型。只能是“refresh_token”。
  • refresh_token:必选项。之前收到的更新令牌。
  • scope:可选。申请权限的范围。

授权请求示例:

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