關於項目
記錄最近做的一個 demo,前端使用 React
,用 React Router
實現前端路由,Koa 2
搭建 API Server, 最后通過 Nginx
做請求轉發。
文章列表
第一篇:React + Node 單頁應用「一」前端搭建
React + Node 單頁應用「二」OAuth 2.0 授權認證 & GitHub 授權實踐
這是第二篇,介紹下 OAuth 2.0
授權機制,以及 Github App 授權過程,通過獲取授權使用 Github API。
OAuth 2.0
背景
傳統的 CS(Client-Server) 授權模式下,請求訪問受保護資源(用戶信息)時,客戶端需要向服務器提供資源所屬用戶的證書,為了讓第三方拿到資源,用戶就得將證書共享給第三方應用。這種方式會導致以下幾個問題:
- 第三方應用為了長久使用,需要存儲用戶憑證,特別是明文密碼。
- 服務器需要支持密碼驗證,雖然密碼存在固有的安全缺陷。
- 第三方應用獲得了資源的過度訪問權限,導致用戶在授權期間無法限制第三方應用的訪問權限。
- 用戶不能單獨撤銷某一個第三方應用的訪問權限,必須修改密碼來撤銷所有授權應用的權限。
- 任何第三方應用的漏洞都可能會威脅到用戶的密碼,以及對應密碼權限下的用戶數據。
為了解決這個問題,OAuth 協議引入了授權層,並將客戶端與服務端的角色區分開,在 OAuth 協議中,客戶端請求的 Access_token
,被托管在資源服務器,但受用戶控制,並且與用戶憑證完全不同。
舉個例子,現在有一家檔案館,檔案館中有很多資料,這些資料有些涉及到機密,有些只是常規資料,
研究員小李需要進入檔案館查找常規資料,於是就跑去找羅館長批條子
“館長館長,我要看一些常規資料,請給我批個條子吧”
羅館長了解了小李要看的是常規資料,很爽快地批了
“沒問題,給,這是同意的條子”
於是小李拿到條子后,徑直去找了檔案館門衛大壯
“大壯,你看這是館長給我批的查看常規資料的條子”,
大壯確認沒問題后
“嗯,既然館長同意了,來,這是常規文件的鑰匙”,
於是小李就拿着鑰匙進入館內開始找資料,館內每道房門都有一把鎖,如果房間里存放的是常規資料,小李只需要出示鑰匙就可以進入,但存放涉密資料的房間,小李的鑰匙打不開,必須找館長批一份查看涉密資料的條子,再拿着條子去大壯那兒換一把新的鑰匙。
角色
-
資源所有者
可以授權獲取受保護資源的實體,如果這個所有者是人,即常規意義的用戶,即檔案館的館長。 -
資源服務器
資源服務器用於存儲資源,接收請求,並將資源返回給攜帶合法Access_token
的請求,在我們的例子中,檔案館就充當着資源服務器的角色。 -
客戶端
獲取授權后,代表用戶請求資源的第三方應用,也就是故事中的研究員小李。 -
授權服務器
授權服務器在成功認證資源擁有者並獲取到授權后,發放Access_token
給客戶端,故事中大壯的主要工作內容就是發放訪問鑰匙。
授權流程
/**
* 協議流程
* 引自 RFC6749
*/
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
上面這張圖是引自 RFC6749 的 OAuth 2.0 授權流程,概括如下
- 第三方應用向用戶請求授權,用戶同意,第三方拿到授權許可
- 第三方拿着用戶的授權許可,請求授權服務器要訪問數據的
Access_token
- 帶着
Access_token
,第三方應用就可以訪問資源服務器中的資源了
Github APP 授權
Github 有一套非常完備的 API,在沒有獲得授權時,僅有部分功能可用,例如獲取用戶信息、倉庫信息等,並且調用次數被限制在了一小時僅允許60次
授權后,每小時調用次數放開到每小時 5000 次,並且申請授權時,可以選擇申請的權限范圍,例如申請 star 項目、follow 用戶的權限(我們這次用到的)等等,我們通過申請完整的權限,甚至可以寫一個 Github 第三方應用。
注冊並配置 APP
要獲取授權,首先需要在 Github 注冊一個應用,注冊這個應用之前,需要准備好兩個東西
- 用作應用主頁訪問的 URL,例如
github.lijundong.com
(如果是 IP 記得加協議頭) - 用作接受授權回調的的 URL,例如
github.lijundong.com/github/getauth
可依照以下路徑創建一個授權 APP,
Github > setting > Developer settings > OAuth Apps > New OAuth App
進入注冊界面,需要填寫應用名、應用主頁地址、應用簡介、以及回調 URL,創建完成會跳轉到 APP 管理頁面,在管理頁面可以更新應用信息以及上傳應用 Logo,並且你將看到你的應用的 Client ID
、Client Secret
,接下來獲取權限需要用到這兩個東西。
授權流程
GitHub 官方的授權流程:
第一步:頁面跳轉到 GitHub 的授權頁
在項目中,我選擇了用 <a>
標簽鏈接的方式跳轉。
GET https://github.com/login/oauth/authorize?client_id=xxx&scope=xxx
/**
* client_id:注冊應用的 client_id 必填
* scope:申請的權限范圍 選填,默認用戶權限為空
*/
第二步:回調接口收到 GitHub 的回調請求,獲得 code
我們在注冊應用時,設置了授權回調 URL,上一步中,Github 授權頁成功獲得用戶授權后,會帶上 code 請求我們設置的回調 URL,在這一步中,我們的 Server 就拿到了用戶的授權 code。
第三步:通過 code 獲取 Access_token
最后一步通過已有 code,加上應用的 client_id
和 client_secret
,我們向 Github 申請 Access_token
。
/**
* code:第二步獲取到的 code 必填
* client_secret:注冊應用 client_secret 必填
* client_id:注冊應用的 client_id 必填
*/
const rp = require('request-promise')
let option = {
uri: 'https://github.com/login/oauth/access_token?client_id=' + clientId + '&client_secret=' + clientSecret + '&code=' + code,
json: true
}
let tokenResp = await rp(option);
第四步:將獲得的 Access_token
寫入頁面的 cookie 中。
ctx.cookies.set('access_token', tokenResp.access_token, {
'httpOnly': false
})
獲取用戶數據
因為第三方應用請求 GitHub API 涉及到跨域,第一篇文章已經提到了Github API 只支持 XHR 跨域請求,一些同學如果用 Fetch 跨域請求 API 會導致請求 request type
變成 option
,所以項目中改用 axios
進行網絡請求。
這里以獲取登陸用戶基本信息為例,
import Axios from 'axios'
import Cookie from 'js-cookie'
const access_token = Cookie.get('access_token');
getLoginInfo() {
let that = this;
let url = API.GITHUB.GET_LOGIN_INFO;
Axios.get(url, {
params: {
access_token: access_token
}
}).then(function(res) {
that.setState({
loginInfo: res.data,
});
}).catch(function (error) {
console.error(error);
})
}
參考: