OAuth 2.0 的四种方式 - 阮一峰的网络日志 (ruanyifeng.com)

10 分钟理解什么是 OAuth 2.0 协议 | Deepzz’s Blog

概述

OAuth 2.0 的标准是 RFC 6749 文件。该文件先解释了 OAuth 是什么。

OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。……资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。

这段话的意思就是,OAuth 的核心就是向第三方应用颁发令牌。然后,RFC 6749 接着写道:

(由于互联网有多种场景,)本标准定义了获得令牌的四种授权方式(authorization grant )。

OAuth 2.0 列举了四种授权类型,分别用于不同的场景:

  • Authorization Code(授权码 code):服务器与客户端配合使用。
  • Implicit(隐式 token):用于移动应用程序或 Web 应用程序(在用户设备上运行的应用程序)。
  • Resource Owner Password Credentials(资源所有者密码凭证 password):资源所有者和客户端之间具有高度信任时(例如,客户端是设备的操作系统的一部分,或者是一个高度特权应用程序),以及当其他授权许可类型(例如授权码)不可用时被使用。
  • Client Credentials(客户端证书 client_credentials):当客户端代表自己表演(客户端也是资源所有者)或者基于与授权服务器事先商定的授权请求对受保护资源的访问权限时,客户端凭据被用作为授权许可。

注意,不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的

授权码

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

img

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

该方式需要资源服务器的参与,应用场景大概是:

  1. 资源拥有者(用户)需要登录客户端(APP),他选择了第三方登录。
  2. 客户端(APP)重定向到第三方授权服务器。此时客户端携带了客户端标识(client_id),那么第三方就知道这是哪个客户端,资源拥有者确定(拒绝)授权后需要重定向到哪里。
  3. 用户确认授权,客户端(APP)被重定向到注册时给定的 URI,并携带了第三方给定的 code。
  4. 在重定向的过程中,客户端拿到 code 与 client_idclient_secret 去授权服务器请求令牌,如果成功,直接请求资源服务器获取资源,整个过程,用户代理是不会拿到令牌 token 的。
  5. 客户端(APP)拿到令牌 token 后就可以向第三方的资源服务器请求资源了。

具体说明,这里以 coding 和 github 为例。当我想在 coding 上通过 github 账号登录时:

1、GET 请求 点击登录,重定向到 github 的授权端点:

1
2
3
4
5
https://github.com/login/oauth/authorize?
response_type=code&
client_id=a5ce5a6c7e8c39567ca0&
redirect_uri=https://coding.net/api/oauth/github/callback&
scope=user:email
字段 描述
response_type 必须,固定为 code,表示这是一个授权码请求。
client_id 必须,在 github 注册获得的客户端 ID。
redirect_uri 可选,通过客户端注册的重定向 URI(一般要求且与注册时一致)。
scope 可选,请求资源范围,多个空格隔开。
state 可选(推荐),如果存在,原样返回给客户端。

返回值:

https://coding.net/api/oauth/github/callback?code=fb6a88dc09e843b33f

字段 描述
code 必须。授权码
state 如果出现在请求中,必须包含。

授权错误

第一种,客户端没有被识别或错误的重定向 URI,授权服务器没有必要重定向资源拥有者到重定向URI,而是通知资源拥有者发生了错误。

第二种,客户端被正确地授权了,但是其他某些事情失败了。这种情况下下面地错误响应会被发送到客户端,包括在重定向 URI 中。

1
2
3
4
https://coding.net/api/oauth/github/callback?
error=redirect_uri_mismatch&
error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&
error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch
字段 描述
error 必须,必须是预先定义的错误码: 错误码
error_description 可选,错误描述
error_uri 可选,指向可解读错误的 URI
state 必须,如果出现在授权请求中

2、POST 请求 获取令牌 token,当获取到授权码 code 后,客户端需要用它获取访问令牌:

1
2
3
4
5
6
https://github.com/login/oauth/access_token?
client_id=a5ce5a6c7e8c39567ca0&
client_secret=xxxx&
grant_type=authorization_code&
code=fb6a88dc09e843b33f&
redirect_uri=https://coding.net/api/oauth/github/callback
字段 描述
grant_type 必须,固定为 authorization_code/refresh_token。
code 必须,上一步获取到的授权码。
redirect_uri 必须(如果请求/authorize接口有),完成授权后的回调地址,与注册时一致。
client_id 必须,客户端标识。
client_secret 必须,客户端密钥。

返回值:

1
2
3
4
5
6
{
"access_token":"a14afef0f66fcffce3e0fcd2e34f6ff4",
"token_type":"bearer",
"expires_in":3920,
"refresh_token":"5d633d136b6d56a41829b73a424803ec"
}
字段 描述
access_token 这个就是最终获取到的令牌。
token_type 令牌类型,常见有 bearer/mac/token(可自定义)。
expires_in 失效时间。
refresh_token 刷新令牌,用来刷新 access_token。

3、获取资源服务器资源,拿着 access_token 就可以获取账号的相关信息了:

1
curl -H "Authorization: token a14afef0f66fcffce3e0fcd2e34f6ff4" https://api.github.com/user

4、POST 请求 刷新令牌

我们的 access_token 是有时效性的,当在获取 github 用户信息时,如果返回 token 过期:(grant_type就为refresh_token)

1
2
3
4
5
6
https://github.com/login/oauth/access_token?
client_id=a5ce5a6c7e8c39567ca0&
client_secret=xxxx&
redirect_uri=https://coding.net/api/oauth/github/callback&
grant_type=refresh_token&
refresh_token=5d633d136b6d56a41829b73a424803ec
字段 描述
redirect_uri 必须
grant_type 必须,固定为 refresh_token
refresh_token 必须,上面获取到的 refresh_token

返回值:

1
2
3
4
5
6
{
"access_token":"a14afef0f66fcffce3e0fcd2e34f6ee4",
"token_type":"bearer",
"expires_in":3920,
"refresh_token":"4a633d136b6d56a41829b73a424803vd"
}

refresh_token 只有在 access_token 过期时才能使用,并且只能使用一次。当换取到的 access_token 再次过期时,使用新的 refresh_token 来换取 access_token

隐藏式

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

该方式一般用于移动客户端或网页客户端。隐式授权类似于授权码授权,但 token 被返回给用户代理再转发到客户端(APP),因此它可能会暴露给用户和用户设备上的其它客户端(APP)。此外,此流程不会对客户端(APP)的身份进行身份验证,并且依赖重定向 URI(已在服务商中注册)来实现此目的。

img

基本原理:要求用户授权应用程序,然后授权服务器将访问令牌传回给用户代理,用户代理将其传递给客户端。

1、同样以 coding 和 github 为例(response_type必须为token):

1
2
3
4
5
https://github.com/login/oauth/authorize?
response_type=token&
client_id=a5ce5a6c7e8c39567ca0&
redirect_uri=https://coding.net/api/oauth/github/callback&
scope=user:email
字段 描述
response_type 必须,固定为 token。
client_id 必须。当客户端被注册时,有授权服务器分配的客户端标识。
redirect_uri 可选。由客户端注册的重定向URI。
scope 可选。请求可能的作用域。
state 可选(推荐)。任何需要被传递到客户端请求的URI客户端的状态。

返回值:

1
2
3
4
https://coding.net/api/oauth/github/callback#
access_token=a14afef0f66fcffce3e0fcd2e34f6ff4&
token_type=token&
expires_in=3600
字段 描述
access_token 必须。授权服务器分配的访问令牌。
token_type 必须。令牌类型。
expires_in 推荐,访问令牌过期的秒数。
scope 可选,访问令牌的作用域。
state 必须,如果出现在授权请求期间,和请求中的 state 参数一样。

授权错误和上面一样

2、用户代理提取令牌 token 并提交给 coding

3、coding 拿到 token 就可以获取用户信息了

1
curl -H "Authorization: token a14afef0f66fcffce3e0fcd2e34f6ff4" https://api.github.com/user

这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

资源所有者密码模式

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

POST 请求 密码凭证流程

1
https://oauth.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
字段 描述
grant_type 必须,固定为 password。
username 必须,UTF-8 编码的资源拥有者用户名。
password 必须,UTF-8 编码的资源拥有者密码。
scope 可选,授权范围。

返回值:

1
2
3
4
5
6
{ 
"access_token" : "...",
"token_type" : "...",
"expires_in" : "...",
"refresh_token" : "...",
}

如果授权服务器验证成功,那么将直接返回令牌 token,改客户端已被授权。

这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。

客户端证书

这种模式只需要提供 client_idclient_secret 即可获取授权。一般用于后端 API 的相关操作。

POST 请求 客户端凭证流程:

https://oauth.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

字段 描述
grant_type 必须。必须设置到客户端证书中。
scope 可选。授权的作用域。

返回值

1
2
3
4
5
{ 
"access_token" : "...",
"token_type" : "...",
"expires_in" : "...",
}

如果授权服务器验证成功,那么将直接返回令牌 token,该客户端已被授权。