一、概述
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 授權。
// view/index.pug
// html 設定授權連結,并拼接 client_id
doctype html
html
head
body
a(href='https://github.com/login/oauth/authorize?client_id=' + clientId) 擷取授權
授權回調處理
服務端定義 GitHub 授權回調路由,并使用回調參數交換通路令牌,再使用通路令牌擷取使用者資訊。
// 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
覺得寫得不錯可以關注下微信公衆号「松鼠專欄」
