一、概述
OAuth 是一份关于允许用户授权第三方应用访问其存储在其他网站上资源,而无需将用户名密码提供给第三方网站的开放标准。OAuth2 是 OAuth 的最新版本,同时也是被广泛应用的一个版本。
OAuth2 标准定义了一个 “用户授权 -> 数据获取” 的流程,理解了这个流程,也就理解了 OAuth2 的整体思路。
二、角色
流程即不同角色之间的交互,在进入具体的流程描述之前,我们需要了解流程中涉及的角色有哪些。OAuth2 中涉及的角色包括:
资源所有者(Resource Owner)
资源所有者就是用户,为了便于理解,以下简称为用户。
资源服务器(Resource Server)
资源服务器就是存储用户资源的服务器。
客户端(Client)
客户端也被称为第三方应用,即需要得到用户授权,让它可以访问用户资源的应用。在 Web 环境中,客户端由 “服务器” 和 “运行于浏览器中的网页” 组成,而在手机环境中,客户端由 “服务器” 和 “App” 组成。
授权服务器(Authorization Server)
授权服务器即为客户端颁发访问令牌(Access Token)的服务器。访问令牌是客户端访问资源服务器中存放的用户资源所需要出示的凭据,访问令牌一般会有资源访问权限(如,读、写、读写)、访问范围(如,所有数据、部分数据)、访问时间(如,一天、一小时)的限制。只有得到用户的授权,授权服务器才会为客户端颁发访问令牌。
三、整体流程
基于 OAuth2 的数据获取流程如上图所示,整个流程可以归纳为以下三个部分:
- 获取授权凭据。客户端向用户发起授权申请,用户自行决定是否允许客户端访问自己的资源,若用户允许客户端访问,则客户端会获得一个授权凭据。授权凭据是一个代表用户授权访问其资源的证明,在 OAuth 流程中,授权凭据主要用来交换访问令牌。
- 获取访问令牌。客户端携带上一步获取到的授权凭据向授权服务器发起请求,授权服务器验证客户端的身份和授权凭据后,向客户端颁发访问令牌。通常情况下,访问令牌的过期时间比较短,为了避免频繁的向用户申请授权,授权服务器在下发访问令牌的同时,还会下发一个“更新令牌”,更新令牌是用来给客户端刷新访问令牌用的。
- 获取用户资源。客户端携带访问令牌请求资源服务器,获取特定用户的资源,进行其他的业务操作。四、不同类型的授权凭据在 OAuth2 中,授权凭据存在 4 种不同的类型,在整体流程的「获取授权凭据」部分,不同类型的授权凭据让流程中的角色产生不同的交互。
授权码
授权码顾名思义即用户授权的凭据是一个“授权码”。大部分基于 OAuth2 的用户数据获取流程都使用授权码形式的授权凭据。授权码类型的流程如下:
- 用户访问客户端后,客户端引导用户访问授权服务器,通常情况下,在用户触发某些操作后,客户端会跳转授权服务器,跳转链接一般会携带客户端重定向链接,便于授权服务器重定向回客户端。
- 授权服务器向用户申请授权。
- 用户允许授权后,授权服务器引导用户携带授权码跳转到客户端。一般情况下,授权服务器会使用重定向链接跳转回客户端。
- 客户端服务器若检测到重定向链接中拼接的授权码,则使用授权码向授权服务器发起请求获取访问令牌。
隐式授权
隐式授权即不产生授权码的授权码模式,在隐式模式中,整个流程不存在授权码,用户在授权服务器授权通过后,授权服务器会直接生成访问令牌继续执行后面的操作,隐式模式适用于存在 “运行于浏览器中的网页” 的客户端,它的流程如下:
- 用户访问客户端后,客户端引导用户跳转授权服务器,跳转链接包含重定向回客户端的链接。
- 授权服务器向用户申请授权。
- 用户允许授权后,授权服务器使用重定向链接跳转回客户端,并在重定向链接后以 hash 形式(类似于 #foo,浏览器中的网页链接的 hash 不会随请求发送给服务器)拼接访问令牌。
- 客户端服务器在重定向链接中返回获取保存在 hash 中访问令牌的脚本,浏览器执行脚本后即可获取访问令牌。
密码凭据
密码凭据即客户端主动向用户申请访问资源所需的账号密码,然后使用账号密码向授权服务器发起请求,获取访问令牌。密码凭据适用于用户高度相信客户端的情况。
客户端凭据
在客户端凭据类型下,客户端即用户。在这种类型下,客户端直接向授权服务器发起请求获取访问令牌,不需要其他额外的证明。
五、使用
以下使用 Node.js 演示授权码类型下获取 GitHub 的 OAuth2 授权,涉及的库包括:
- koa
- axios
- pug
注册 GitHub OAuth 应用
OAuth2 是一个获取用户存储在其他网站上数据的标准,我们想要获取用户数据,就得先到用户数据所在的网站进行注册应用,GitHub 的 OAuth2 应用注册地址是 https://github.com/settings/applications/new,具体界面如下图所示。
上图中,Homepage URL 指的是你应用的地址,由于我们是本地测试应用,所以填写测试地址即可。Authorization callback URL 一项中填写的是用户授权后,授权服务器的回调地址。
点击 Register application 注册成功后,GitHub 会生成客户端 ID(Client ID)和客户端密钥(Client Secret ),这两个数据在后续的请求需要用到,需要保存到服务器应用
用户点击链接跳转到 GitHub 进行授权
对于浏览器应用而言,只需要在浏览器界面展示一个跳转 GitHub 的授权链接,用户点击了即可去 GitHub 授权。
代码语言:txt复制// view/index.pug
// html 设置授权链接,并拼接 client_id
doctype html
html
head
body
a(href='https://github.com/login/oauth/authorize?client_id=' clientId) 获取授权
授权回调处理
服务端定义 GitHub 授权回调路由,并使用回调参数交换访问令牌,再使用访问令牌获取用户信息。
代码语言:txt复制// router.js
router.get('/redirect', async (ctx) => {
const code = ctx.query.code // Code 为 GitHub 拼接在重定向链接后的参数
if (!code) {
ctx.throw(400, '回调 URL 无 code 字段')
return
}
// GitHub OAuth 要求参数
const param = {
client_id: config.clientId,
client_secret: config.clientSecret,
code,
}
// GitHub 交换访问令牌默认返回字符串,通过设置 accept 来控制结果返回 json 字符串
const opt = {
headers: {
accept: 'application/json',
},
}
// 使用 code 交换访问令牌
const { data } = await axios.post('https://github.com/login/oauth/access_token', param, opt)
const accessToken = data.access_token
if (!accessToken) {
ctx.throw(500, '交换访问令牌失败')
return
}
const { data: user } = await axios.get('https://api.github.com/user', {
headers: {
Authorization: `token ${accessToken}`,
},
})
if (!user) {
ctx.throw(500, '用户数据获取失败')
return
}
ctx.body = user
})
查看完整代码请前往 GitHub 搜索用户 yo-squirrel
觉得写得不错可以关注下微信公众号「松鼠专栏」