Mitchell Anicas 2021-07-29
OAuth 2 简介
引言
OAuth 2 是一种授权框架,允许应用程序(如 Facebook、GitHub 和 DigitalOcean)获得对用户在 HTTP 服务上账户的有限访问权限。它通过将用户身份验证委托给托管用户账户的服务,并授权第三方应用访问该账户来实现这一目标。OAuth 2 为 Web 应用、桌面应用以及移动设备提供了多种授权流程。
本指南面向应用程序开发者,概述了 OAuth 2 的角色、授权许可类型、使用场景和工作流程。
OAuth 角色
OAuth 定义了四种角色:
- 资源所有者(Resource Owner):即授权应用程序访问其账户的用户。应用程序对用户账户的访问权限受限于所授予的授权范围(例如只读或读写权限)。
- 客户端(Client):即希望访问用户账户的应用程序。在访问之前,必须获得用户的授权,并且该授权需经 API 验证。
- 资源服务器(Resource Server):托管受保护用户账户的服务器。
- 授权服务器(Authorization Server):验证用户身份后,向应用程序颁发访问令牌(access token)。
从应用开发者的角度来看,服务的 API 同时承担了资源服务器和授权服务器的角色。我们将这两个角色合称为服务或API。
抽象协议流程
了解了 OAuth 的角色之后,我们来看一下它们之间的一般交互流程:

抽象协议流程图
以下是图中各步骤的详细说明:
- 应用程序向用户请求授权,以访问服务中的资源。
- 如果用户授权了该请求,应用程序将收到一个授权许可(authorization grant)。
- 应用程序向授权服务器(API)请求访问令牌,同时提供自身的身份凭证和授权许可。
- 如果应用程序的身份已通过验证,且授权许可有效,授权服务器(API)将向应用程序颁发一个访问令牌。至此,授权完成。
- 应用程序向资源服务器(API)请求资源,并出示访问令牌进行身份验证。
- 如果访问令牌有效,资源服务器(API)将把资源返回给应用程序。
实际流程会因所使用的授权许可类型而有所不同,但这是总体思路。我们将在后续章节中探讨不同的授权许可类型。
应用注册
在将 OAuth 用于你的应用程序之前,你必须先在服务提供商处注册你的应用。这通常通过服务网站的开发者或 API 部分的注册表单完成,你需要提供以下信息(可能还包括应用的其他详情):
- 应用名称(Application Name)
- 应用网站(Application Website)
- 重定向 URI(Redirect URI)或回调 URL(Callback URL)
重定向 URI 是服务在用户授权(或拒绝)你的应用后将用户重定向到的位置,因此它必须是你应用中能处理授权码或访问令牌的部分。
客户端 ID 与客户端密钥
注册完成后,服务将为你颁发客户端凭证,包括客户端 ID(Client ID)和客户端密钥(Client Secret):
- 客户端 ID 是一个公开暴露的字符串,由服务 API 用来识别你的应用,也用于构建呈现给用户的授权 URL。
- 客户端密钥 用于在应用请求访问用户账户时,向服务 API 证明自身身份,必须严格保密,仅限应用与 API 之间共享。
授权许可(Authorization Grant)
在前述抽象协议流程中,前四个步骤涉及获取授权许可和访问令牌。授权许可的类型取决于应用请求授权的方式以及 API 所支持的许可类型。
OAuth 2 定义了三种主要的授权许可类型,各自适用于不同场景:
- 授权码(Authorization Code):用于服务器端应用
- 客户端凭证(Client Credentials):用于具有 API 访问权限的应用
- 设备码(Device Code):用于无浏览器或输入受限的设备
警告:OAuth 框架还规定了另外两种许可类型——隐式流程(Implicit Flow)和密码许可(Password Grant)。然而,这两种类型均被认为不安全,不再推荐使用。
接下来,我们将详细介绍各种授权许可类型的使用场景和流程。
授权许可类型:授权码(Authorization Code)
授权码许可是最常用的类型,因为它专为服务器端应用优化——这类应用的源代码不会公开暴露,能够安全地保管客户端密钥。这是一种基于重定向的流程,意味着应用必须能够与用户代理(即用户的 Web 浏览器)交互,并通过用户代理接收 API 授权码。
授权码流程

步骤 1 —— 构建授权码链接
首先,用户会看到一个类似如下的授权链接:
https://cloud.digitalocean.com/v1/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
各参数说明:
https://cloud.digitalocean.com/v1/oauth/authorize:API 的授权端点client_id=CLIENT_ID:应用的客户端 ID(API 用它识别应用)redirect_uri=CALLBACK_URL:授权后服务将用户代理重定向到的地址response_type=code:表明应用请求的是授权码许可scope=read:指定应用请求的访问权限级别
步骤 2 —— 用户授权应用
用户点击链接后,需先登录服务以验证身份(若尚未登录)。随后,服务会提示用户授权或拒绝该应用访问其账户。下图是 DigitalOcean 的授权界面示例:

步骤 3 —— 应用接收授权码
如果用户点击“授权应用”,服务会将用户代理重定向到注册时指定的回调 URL,并附带一个授权码。例如:
https://dropletbook.com/callback?code=AUTHORIZATION_CODE
步骤 4 —— 应用请求访问令牌
应用向 API 的令牌端点发送 POST 请求,提交授权码及自身凭证(包括客户端密钥),以换取访问令牌。例如向 DigitalOcean 发送的请求:
POST https://cloud.digitalocean.com/v1/oauth/token
参数:
client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=CALLBACK_URL
步骤 5 —— 应用接收访问令牌
如果授权有效,API 将返回包含访问令牌(以及可选的刷新令牌)的响应,例如:
{
"access_token": "ACCESS_TOKEN",
"token_type": "bearer",
"expires_in": 2592000,
"refresh_token": "REFRESH_TOKEN",
"scope": "read",
"uid": 100101,
"info": {
"name": "Mark E. Mark",
"email": "mark@thefunkybunch.com"
}
}
此时,应用已完成授权。它可在令牌有效期内(或未被撤销前),根据授权范围使用该令牌访问用户账户。如果颁发了刷新令牌,可在原令牌过期后用于获取新的访问令牌。
关于 PKCE(Proof Key for Code Exchange)
如果公共客户端(如移动端或单页应用)使用授权码流程,授权码可能被拦截。PKCE(发音为“pixie”)是授权码流程的一项扩展,可缓解此类攻击。
PKCE 的工作原理如下:
- 客户端在每次授权请求时生成一个**代码验证器(code verifier)**并保存。
- 客户端将该验证器通过特定算法转换为代码挑战(code challenge),并在授权请求中一并发送挑战值和转换方法。
- 授权服务器记录挑战值和方法,并正常返回授权码。
- 客户端在后续的令牌请求中附上原始的代码验证器。
- 授权服务器使用相同的算法将验证器转换为挑战值,并与之前记录的挑战值比对。若不匹配,则拒绝请求。
建议所有客户端都使用 PKCE 以提升安全性。
授权许可类型:客户端凭证(Client Credentials)
客户端凭证许可允许应用访问自身的服务账户。适用场景包括:应用需要更新其注册描述或重定向 URI,或通过 API 访问存储在其服务账户中的其他数据。
客户端凭证流程
应用通过向授权服务器发送其客户端 ID 和密钥来请求访问令牌。例如:
POST https://oauth.example.com/token
参数:
grant_type=client_credentials
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
如果凭证验证通过,授权服务器将返回访问令牌。此时,应用即可代表自身账户进行操作。
注意:DigitalOcean 目前不支持客户端凭证许可类型,因此上述链接指向的是虚构的授权服务器
oauth.example.com。
授权许可类型:设备码(Device Code)
设备码许可适用于无浏览器或输入受限的设备(如智能电视、游戏主机),使用户能更方便地授权这些设备访问其账户。
设备码流程
用户在设备(如电视)上启动应用。
应用向设备授权端点发送 POST 请求,例如:
POST https://oauth.example.com/device client_id=CLIENT_ID该端点不验证设备身份,而是返回以下信息:
device_code:用于标识设备的唯一代码user_code:用户需在另一台设备(如手机或电脑)上输入的验证码verification_uri:用户输入验证码的网址interval:建议的轮询间隔(秒)expires_in:设备码有效期(秒)
示例响应:
{ "device_code": "IO2RUI3SAH0IQuESHAEBAeYOO8UPAI", "user_code": "RSIK-KRAM", "verification_uri": "https://example.okta.com/device", "interval": 10, "expires_in": 1600 }注:设备码也可表示为二维码,供移动设备扫描。
用户在电脑或手机上访问
verification_uri,输入user_code并登录账户,随后在同意界面上授权该设备。与此同时,设备会定期轮询访问令牌端点,直到获得令牌或收到错误(如
authorization_pending、slow_down、access_denied或expired_token)。若用户授权成功,访问令牌端点将返回访问令牌。
注意:DigitalOcean 目前不支持设备码许可类型,示例中的链接为虚构地址。
访问令牌使用示例
获得访问令牌后,应用即可在令牌有效期内(或未被撤销前),根据授权范围通过 API 访问用户账户。
以下是一个使用 curl 的 API 请求示例,其中包含访问令牌:
curl -X POST \
-H "Authorization: Bearer ACCESS_TOKEN" \
"https://api.digitalocean.com/v2/$OBJECT"
只要访问令牌有效,API 就会按规范处理请求。若令牌过期或无效,API 将返回 invalid_request 错误。
刷新令牌流程(Refresh Token Flow)
当访问令牌过期后,继续使用它调用 API 会返回“无效令牌”错误。此时,如果当初颁发访问令牌时也提供了刷新令牌(refresh token),则可用它向授权服务器申请新的访问令牌。
示例请求:
POST https://cloud.digitalocean.com/v1/oauth/token
参数:
grant_type=refresh_token
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&refresh_token=REFRESH_TOKEN
结语
通过本指南,你应该已经理解了 OAuth 2 的基本工作原理,并掌握了在不同场景下应选择哪种授权流程。