OAuth 2.0
简介
OAuth 2.0 是一个业界标准的开放授权协议(authorization protocol),它允许开发者应用在不获取用户名与密码的前提下,访问用户授权的资源。
OAuth 2.0 协议定义了四种授权模式:授权码模式(Authorization Code)、客户端模式(Client Credentials)、授权码扩展模式(PKCE)、设备码模式(Device Code)。
授权模式 | 描述 | 应用场景 |
---|---|---|
授权码模式(Authorization Code) | 用户授权,生成授权码 Code,开发者应用通过 Code 获取 Tokens | App 有服务器,可存储应用密钥,可以与服务提供商进行秘钥交换 |
客户端模式(Client Credentials) | 无需用户授权,开发者应用生成一个鉴权令牌,调用服务提供商的公开应用级接口。 | 调用应用级接口。 |
授权码扩展模式(PKCE) | 用户授权,生成授权码 Code,开发者应用通过 Code 获取Tokens | App 无服务器,无法存储应用密钥,通过随机字符串与服务提供商进行交互。 |
设备码模式(Device Code) | 弱输入设备,用户通过手机扫码授权,开发者应用获取Tokens | 不支持浏览器或输入受限的设备(如穿戴设备),在手机等设备上扫描二维码并确认授权,与服务提供商交互。 |
注意,不管哪一种授权方式,在第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。以防止令牌被滥用,没有备案过的第三方应用,是拿不到令牌的。
应用角色
在OAuth 2.0的协议交互中,有四个角色的定义:
-
资源所有者(Resource Owner):能够授予对受保护资源的访问权限的实体。当资源所有者是个人时,它被称为最终用户。
-
资源服务器(Resource Server):托管受保护资源的服务器,能够接受并使用访问令牌响应受保护的资源请求。
-
客户端应用(Client Application):会请求访问存放在资源服务器上的资源,这些资源是属于资源所有者的。
-
授权服务器(Authorization Server):对客户端应用进行授权,授权通过后客户端应用才可以访问资源服务器上的资源。认证服务器和资源服务器可以是同一个应用,也可以分开独立部署。
认证授权
资源所有者会给客户端应用认证授权(Authorization Grant),认证授权时,需要认证服务器和资源服务器进行配合。
授权码模式
授权码模式(authorization code),指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
授权码是通过使用授权服务器作为客户端和资源所有者之间的中介来获取的。客户端不是直接向资源所有者请求授权,而是将资源所有者引导至授权服务器,授权服务器又将资源所有者引导回带有授权代码的客户端。
流程
授权码模式的流程:
-
用户进入开发者的 App 客户端,并选择应用服务提供商的登录方式。
如,用户选择微信、微博等应用服务商提供的授权方式。
-
开发者的 App 客户端发起授权码 Code 请求。
-
App 客户端将认证服务器的授权页面呈现给用户,用户确认授权。
-
服务提供商的认证服务器通过开发者应用配置的回调地址,返回授权码 Code。
-
开发者应用发起 Code 换 Token 请求。
-
服务提供商的认证服务器返回令牌(Access Token)、用户信息(ID Token)。
操作步骤
授权码 Code 请求
【HTTP 请求】
示例:
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
参数说明:
参数 | Required | 说明 |
---|---|---|
response_type | Y | 授权类型,此处的值固定为"code" |
client_id | Y | 客户端ID |
redirect_uri | N | 重定向URI |
scope | N | 申请的权限范围 |
state | N | 客户端的当前状态,可以是任意值,认证服务器会原封不动地返回这个值。 |
用户登录及授权
向认证服务器发送了请求授权码C ode 的请求后,一般情况下,客户端 APP 会拉起一个登录授权的页面,并引导用户进行授权,以及该应用所申请授权的 Scope 权限列表,输入华为帐号及密码完成登录授权。
例如,以华为帐号为例:
认证服务器返回授权码 Code
用户确认登录并授权后,认证服务器会返回授权码 Code、回调跳转的 redirect_uri,并通过HTTP状态码 302 响应,二次跳转至前面的重定向 地址 redirect_uri,这样就将 Code 返回给客户端 APP了。
示例:
-
如果用户同意授权,则回调请求中带有授权码 Code
HTTP/1.1 302 Found Location: https://client.athena.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
-
如果用户不同意授权,则回调请求中包含错误消息。
HTTP/1.1 400 Bad Request https://client.athena.com/cb?error=1107&error_description=access+denied
参数说明:
参数 | Required | 说明 |
---|---|---|
code | Y | 认证服务器返回的授权码 |
state | Y | 如果客户端应用的请求中有这个参数,即这个参数的值 |
注意,HTTP 状态码 302,表示这是一个重定向响应。它会重定向到 header 中指定的 location 地址。通常浏览器会自动的再去请求这个 location,重新获取资源。也就是说,这个会使得浏览器发起两次请求。
这里会调用回调的URL,并将授权码 code 返回给应用程序。
授权码 Code 换取鉴权令牌
应用程序获取到授权码 code 后,再次请求认证服务器,即可换取 Access Token、Refresh Token 等。
【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%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&client_secret=bGFycnkxMDI0
参数说明:
参数 | Required | 说明 |
---|---|---|
grant_type | Y | 授权类型,此处的值固定为"authorization_code" |
code | Y | 授权码 |
redirect_uri | Y | 重定向URI,必须与前面的参数值保持一致 |
client_id | Y | 客户端ID |
client_secret | N | 客户端凭据 |
注意,通常在生产环境中,Code 换 AT 时,通常还会携带客户端凭据(client_secret),用于服务器区分不同的客户端。
【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":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
参数说明:
参数 | Required | 说明 |
---|---|---|
access_token | Y | 访问令牌 |
token_type | Y | 令牌类型,可以是 bearer 类型或 mac 类型 |
expires_in | Y | 过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 |
refresh_token | N | 更新令牌,用来获取下一次的访问令牌 |
scope | N | 权限范围 |
客户端模式
客户端模式用于访问跟用户无关的资源,因此不需要用户授权。开发者应用可通过客户端模式生成一个鉴权令牌,访问服务提供商的公开应用级接口 API。
通常情况下,应用开发者需要在服务提供商,创建一个客户端 ID(Client ID),通过该客户端 ID,就能获取一个鉴权令牌 Access Token,使用该 AT 就能访问服务提供商公开的应用级 API 接口,访问用户授权的资源。
流程
它的步骤如下:
-
客户端 APP 向认证服务器进行身份认证,并请求访问令牌(access token)。
-
认证服务器确认无误后,向客户端 APP 提供访问令牌。
操作步骤
开发者应用发起客户端模式请求
示例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
参数说明:
参数 | Required | 说明 |
---|---|---|
grant_type | Y | 授权类型,此处的值固定为"client_credentials" |
scope | N | 权限范围 |
client_id | N | 客户端ID |
client_secret | N | 客户端凭据 |
注意,通常在生产环境中,该请求还会携带,
client_id
、client_secret
等信息,用于认证服务器必须验证客户端身份。
认证服务器颁发令牌
认证服务器向客户端发送访问令牌
示例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"Bearer",
"expires_in":3600,
"example_parameter":"example_value"
}
接着客户端APP就可以凭借此鉴权令牌访问服务提供商公开的应用级接口 API。
授权码扩展模式(PKCE)
此模式为授权码的扩展模式,若开发者应用无服务端,无法存储用户密钥,则推荐此模式完成鉴权,通过应用客户端接入获取鉴权令牌,无需密钥交付,有效提升开发者应用的安全防护。
流程
具体流程步骤如下:
-
用户选择某服务提供商的帐号登录,开发者的客户端准备并存储随机字符串与其编码值,发起包含编码值的授权码 Code 请求。
-
认证服务器呈现登录授权页面给用户,用户确认授权。
-
认证服务器通过开发者应用配置的回调地址,返回授权码 Code。
-
开发者的客户端发起 Code 换 Token 请求,包含应用存储的随机字符串。
-
认证服务器返回令牌(Access Token)、用户信息等。
操作步骤
授权码 Code 请求
开发者的 APP 需提前准备一个随机字符串(code_verifier),并选择编码方法(code_challenge_method),对该随机字符串进行编码得到编码值(code_challenge),将 code_verifier 本地存储,拼接一个包含 code_challenge_method 和 code_challenge 的授权码 Code 请求,请求访问认证服务器。
示例:
GET /authorize?response_type=code&state=state_parameter_passthrough_value&nonce=nonce_parameter_passthrough_value&code_challenge=ovoy4lehgHbv8uNmif_hak3bH2_Ylk6_fWP0UL232QQ&code_challenge_method=S256&
client_id=XXXXXX&redirect_uri=XXXXX&scope=XXXX&display=touch
参数说明:
参数 | Required | 说明 |
---|---|---|
grant_type | Y | 授权类型,此处的值固定为"code" |
client_id | Y | 客户端ID |
redirect_uri | N | 重定向URI |
scope | N | 申请的权限范围 |
state | N | 客户端的当前状态,可以是任意值,认证服务器会原封不动地返回这个值。 |
code_challenge_method | Y | 对code_verifier进行编码的方法。 |
code_challenge | Y | 对code_verifier进行编码的方法。 |
授设备码模式(Device Code)
对于弱输入设备(不支持浏览器或输入受限的设备,如穿戴设备),可以通过服务提供商的扫码功能,获取到设备码,用户授权后,凭借此设备码换取鉴权令牌,访问用户授权的数据资源。
流程
具体流程步骤如下:
-
开发者 APP 请求设备码、用户码。
-
认证服务器返回用户码 Code、设备码 Code。
-
开发者 APP 生成二维码并呈现给用户。
-
用户通过手机等终端扫描二维码,确认登录授权。
-
开发者服务器周期性地用设备码 Code 请求换取 Token。
-
认证服务器在用户确认授权后,返回令牌(Access Token)、用户信息(ID Token)。
操作步骤
获取设备码 Code 的请求
示例:
POST /device/code HTTP/1.1
Host: server.example.com
Cache-Control: no-cache
client_id=1406020730>&
scope=xxxxx
获取设备码 Code 的响应
示例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"create_time": 1569813512,
"device_code": "bGFycnkxMDI0",
"expire_in": 1800,
"interval": 3,
"scan_expire_in": 120,
"user_code": "aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vbGFycnkxMDI0",
"verification_url": "https://server.example.com/device"
}
扫码用户登录及授权
开发者 APP 在获取到授权码 Code 请求的响应后,根据响应体中的校验URL(verification_url)与用户码 (user_code),生成一个二维码。并在弱输入设备屏幕上显示二维码。
示例:
GET https://server.example.com/device?
user_code=aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vbGFycnkxMDI0&
lang=zh-cn
用户通过手机扫码(如服务提供商的帐号扫码功能),对弱输入设备呈现的二维码进行扫码后,弹出一个授权页面,显示应用的名称和图标,以及该应用所申请授权的scope权限列表。
以华为帐号为例,展示如下内容:
待用户扫码确认授权后,返回授权成功页面。
获取令牌的请求
弱输入设备中的开发者应用,在获得前面响应的授权码 Code 后,根据响应体中的 interval 为时间间隔,轮询调用获取令牌的接口请求令牌,直到获取令牌 Access Token,或轮询周期为止。
示例:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730
参数说明:
参数 | Required | 说明 |
---|---|---|
grant_type | Y | 授权类型,此处的值固定为 "urn:ietf:params:oauth:grant-type:device_code" |
device_code | Y | 设备认证码 |
client_id | Y | 客户端ID |
client_secret | Y | 客户端凭据 |
获取令牌的响应
示例:
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"
}
参数说明:
参数 | Required | 说明 |
---|---|---|
access_token | Y | 授权令牌 |
token_type | Y | 令牌类型 |
expires_in | N | 令牌过期时间 |
refresh_token | N | 刷新令牌 |
scope | N | 权限范围 |
Legacy:简化模式
简化模式(implicit)是一种简化的授权代码流,针对使用 JavaScript 等脚本语言在浏览器中实现的客户端。
在隐式流程中,不向客户端颁发授权代码,而是直接向客户端颁发访问令牌(作为资源所有者授权的结果)。授予类型是隐式的,因为不会颁发任何中间凭证,例如:授权代码(用于稍后获取访问令牌)。
在隐式授权流程期间颁发访问令牌时,授权服务器不会对客户端进行身份验证。在某些情况下,可以通过用于将访问令牌传递给客户端的重定向 URI 来验证客户端身份。访问令牌可以暴露给资源所有者或有权访问资源所有者的用户代理的其他应用程序。
隐式授予提高了某些客户端(如作为浏览器内应用程序实现的客户端)的响应能力和效率,因为它减少了获取访问令牌所需的往返次数。然而,应该权衡这种便利性与使用隐式授权的安全影响,特别是当授权代码授权类型可用时。
流程
简化模式的授权流程:
Figure 4: Implicit Grant Flow
+----------+
| 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.
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤。
其授权流程,如下图所示:
-
step 1. 客户端将用户导向认证服务器。
-
step 2. 客户端应用让用户通过认证服务器进行登录,将用户重定向到认证服务器的登录页面。
-
step 3. 用户登录认证服务器,进行授权认证。
-
step 4. 用户登录成功后,
-
提示用户是否对客户端应用进行授权(用户 -> 授权服务器);
-
用户选择同意后,会被重定向回客户端应用,同时直接返回访问令牌(access token)。
-
-
step 5. 浏览器重定向到客户端应用,客户端应用获取到访问令牌(access token),用户授权完成。
当用户成功登录之后,重定向到客户端应用时,access token 会直接返回给客户端应用。这意味着 access token 在客户端应用中是可见的。而 Authorization Code(授权码模式),access token 是在web服务器中的,对客户端来说不可见。
并且,客户端应用只发送 client id 到认证服务器。如果连同 client password 一起发送的话,client password 需要存储在客户端应用中,这会是一个安全隐患,很容易通过破解手段拿到存放在客户端应用程序中的 client password。
请求和响应
- 请求
参数说明:
参数 | Required | 说明 |
---|---|---|
response_type | Y | 授权类型,此处的值固定为"token" |
client_id | Y | 客户端ID |
redirect_uri | N | 重定向URI |
scope | N | 申请的权限范围 |
state | N | 客户端的当前状态,可以是任意值,认证服务器会原封不动地返回这个值。 |
【示例】
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
- 响应
参数说明:
参数 | Required | 说明 |
---|---|---|
access_token | Y | 访问令牌 |
token_type | Y | 令牌类型 |
expires_in | Y | 过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 |
scope | N | 权限范围 |
state | N | 如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。 |
【示例】
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
认证服务器用 HTTP Header的 Location 字段,指定浏览器重定向的网址。注意,在这个网址的 Hash 部分包含了令牌。
根据上面的D步骤,下一步浏览器会访问 Location 指定的网址,但是 Hash 部分不会发送。
接下来的 E 步骤,服务提供商的资源服务器发送过来的代码,会提取出 Hash 中的令牌。
Legacy:用户密码模式
资源所有者的密码凭证(resource Owner Password Credentials),即用户名和密码,可以直接用作获得访问令牌的授权授予。
只有当资源所有者和客户端之间存在高度信任时,以及当其他授权授予类型不可用时(例如授权代码),才应该使用凭据。例如,客户端是设备操作系统的一部分或具有高度特权的应用程序。
尽管这种授权类型要求客户端直接访问资源所有者凭据,但资源所有者凭据用于单个请求,并被交换为访问令牌。此授权类型可以通过与长期访问令牌或刷新令牌交换凭据,从而消除客户端存储资源所有者凭据以供将来使用的需要。
流程
用户密码模式的流程如下:
Figure 5: Resource Owner Password Credentials Flow
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
-
用户向客户端提供用户名和密码。
-
客户端将用户名和密码发给认证服务器,向后者请求令牌。
-
认证服务器确认无误后,向客户端提供访问令牌。
请求和响应
请求
参数说明:
参数 | Required | 说明 |
---|---|---|
response_type | Y | 授权类型,此处的值固定为"password" |
username | Y | 用户名 |
password | Y | 用户的密码 |
scope | N | 权限范围 |
【示例】
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
响应
【示例】
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"
}
参考: