
OAuth2 對(duì)于我來(lái)說(shuō)是一個(gè)神秘的東西,我想初步的弄懂中間的整個(gè)流程,于是就去google搜索相關(guān)的文檔資料。
在瀏覽了參差不齊的各種文章后,簡(jiǎn)述 OAuth 2.0 的運(yùn)作流程 基本對(duì)于小白來(lái)說(shuō)是最淺顯明了的。
這篇文章以用戶使用 github 登錄網(wǎng)站留言為例,詳述 OAuth 2.0 的運(yùn)作流程。
整個(gè)OAuth2 的流程分為三個(gè)階段:
- 網(wǎng)站和 Github 之間的協(xié)商
- 用戶和 Github 之間的協(xié)商
- 網(wǎng)站和 Github 用戶數(shù)據(jù)之間的協(xié)商
由于這篇文章是簡(jiǎn)述,所以并不涉及代碼相關(guān)的東西,我在原來(lái)的文章基礎(chǔ)上添加了代碼相關(guān)的具體實(shí)現(xiàn)和一些關(guān)鍵網(wǎng)絡(luò)交互截圖說(shuō)明方便理解。對(duì)于一些文字,由于原文已經(jīng)寫(xiě)的很流暢嚴(yán)謹(jǐn),我直接就從原來(lái)的博文中復(fù)制過(guò)來(lái)了。
假如我有一個(gè)網(wǎng)站,你是我網(wǎng)站上的訪客,看了文章想留言表示「朕已閱」,留言時(shí)發(fā)現(xiàn)有這個(gè)網(wǎng)站的帳號(hào)才能夠留言,此時(shí)給了你兩個(gè)選擇:一個(gè)是在我的網(wǎng)站上注冊(cè)擁有一個(gè)新賬戶,然后用注冊(cè)的用戶名來(lái)留言;一個(gè)是使用 github 帳號(hào)登錄,使用你的 github 用戶名來(lái)留言。前者你覺(jué)得過(guò)于繁瑣,于是慣性地點(diǎn)擊了 github 登錄按鈕,此時(shí) OAuth 認(rèn)證流程就開(kāi)始了。
需要明確的是,即使用戶剛登錄過(guò) github,我的網(wǎng)站也不可能向 github 發(fā)一個(gè)什么請(qǐng)求便能夠拿到訪客信息,這顯然是不安全的。就算用戶允許你獲取他在 github 上的信息,github 為了保障用戶信息安全,也不會(huì)讓你隨意獲取。所以操作之前,我的網(wǎng)站與 github 之間需要要有一個(gè)協(xié)商。
1. 網(wǎng)站和 Github 之間的協(xié)商
Github 會(huì)對(duì)用戶的權(quán)限做分類,比如讀取倉(cāng)庫(kù)信息的權(quán)限、寫(xiě)入倉(cāng)庫(kù)的權(quán)限、讀取用戶信息的權(quán)限、修改用戶信息的權(quán)限等等。如果我想獲取用戶的信息,Github 會(huì)要求我,先在它的平臺(tái)上注冊(cè)一個(gè)應(yīng)用,在申請(qǐng)的時(shí)候標(biāo)明需要獲取用戶信息的哪些權(quán)限,用多少就申請(qǐng)多少,并且在申請(qǐng)的時(shí)候填寫(xiě)你的網(wǎng)站域名,Github 只允許在這個(gè)域名中獲取用戶信息。
此時(shí)我的網(wǎng)站已經(jīng)和 Github 之間達(dá)成了共識(shí),Github 也給我發(fā)了兩張門(mén)票,一張門(mén)票叫做 Client Id,另一張門(mén)票叫做 Client Secret。
我先去閱讀了一下github上相關(guān)OAuth2的資料,然后在這里注冊(cè)了一個(gè)應(yīng)用。


其中最后一個(gè)callback URL表示用戶授權(quán)之后github默認(rèn)要跳轉(zhuǎn)的url地址,在代碼中需要添加一個(gè)路由來(lái)處理針對(duì)這個(gè)地址的請(qǐng)求。
創(chuàng)建好之后就會(huì)顯示在OAuth Apps的列表中。

這一步非常簡(jiǎn)單,github生成了兩個(gè)鑰匙,Client ID和Client Secret。現(xiàn)在我的網(wǎng)站就可以使用合法的使用github提供的OAuth登陸機(jī)制了。
2. 用戶和 Github 之間的協(xié)商
用戶進(jìn)入我的網(wǎng)站,點(diǎn)擊 github 登錄按鈕的時(shí)候,我的網(wǎng)站會(huì)把上面拿到的 Client Id 交給用戶,讓他進(jìn)入到 Github 的授權(quán)頁(yè)面,Github 看到了用戶手中的門(mén)票,就知道這是我的網(wǎng)站讓他過(guò)來(lái)的,于是它就把我的網(wǎng)站想要獲取的權(quán)限擺出來(lái),并詢問(wèn)用戶是否允許我獲取這些權(quán)限。
如果用戶覺(jué)得我的網(wǎng)站要的權(quán)限太多,或者壓根就不想我知道他這些信息,選擇了拒絕的話,整個(gè) OAuth 2.0 的認(rèn)證就結(jié)束了,認(rèn)證也以失敗告終。如果用戶覺(jué)得 OK,在授權(quán)頁(yè)面點(diǎn)擊了確認(rèn)授權(quán)后,頁(yè)面會(huì)跳轉(zhuǎn)到我預(yù)先設(shè)定的 redirect_uri 并附帶一個(gè)蓋了章的門(mén)票 code。
這個(gè)時(shí)候,用戶和 Github 之間的協(xié)商就已經(jīng)完成,Github 也會(huì)在自己的系統(tǒng)中記錄這次協(xié)商,表示該用戶已經(jīng)允許在我的網(wǎng)站訪問(wèn)上直接操作和使用他的部分資源。
這個(gè)中間會(huì)涉及到非常多的流程,我選擇使用python基于flask來(lái)演示整個(gè)流程。
# github生成的兩把鑰匙
client_id = '1f93ab8ba338b032b8e7'
client_secret = 'f0cf5600d2749d1651f2d5f7225c81f562******'
@app.route('/', methods=['GET', 'POST'])
def index():
url = 'https://github.com/login/oauth/authorize'
params = {
'client_id': client_id,
# 如果不填寫(xiě)redirect_uri那么默認(rèn)跳轉(zhuǎn)到oauth中配置的callback url。
# 'redirect_uri': 'http://dig404.com/oauth2/github/callback',
'scope': 'read:user',
# 隨機(jī)字符串,防止csrf攻擊
'state': 'An unguessable random string.',
'allow_signup': 'true'
}
url = furl(url).set(params)
return redirect(url, 302)
當(dāng)用戶在瀏覽器中訪問(wèn)127.0.0.1:5000的時(shí)候,flask會(huì)將請(qǐng)求重定向到github的oauth服務(wù)頁(yè)面,重定向的url會(huì)攜帶上兩個(gè)主要的參數(shù),一個(gè)是client_id,一個(gè)是scope,這兩個(gè)參數(shù)可以讓github知道這個(gè)請(qǐng)求是從哪里過(guò)來(lái)的,并且想要獲取的權(quán)限。





其中最終重定向url中的code參數(shù)就是github分配的針對(duì)當(dāng)前登陸用戶的授權(quán)碼,也就是一張門(mén)票。在github的后臺(tái),這個(gè)code和client_id,user是對(duì)應(yīng)的。
到這里用戶和github之間的協(xié)商就完成了,剩下的事情就是網(wǎng)站和github之間的事情了。
3. 從 Github 獲取用戶的信息
第二步中,已經(jīng)拿到了蓋過(guò)章的門(mén)票 code,但這個(gè) code 只能表明,用戶允許我的網(wǎng)站從 github 上獲取該用戶的數(shù)據(jù),如果我直接拿這個(gè) code 去 github 訪問(wèn)數(shù)據(jù)一定會(huì)被拒絕,因?yàn)槿魏稳硕伎梢猿钟?code,github 并不知道 code 持有方就是我本人。
還記得之前申請(qǐng)應(yīng)用的時(shí)候 github 給我的兩張門(mén)票么,Client Id 在上一步中已經(jīng)用過(guò)了,接下來(lái)輪到另一張門(mén)票 Client Secret。
創(chuàng)建一個(gè)處理callback路由的處理函數(shù),首先是獲取github返回的code。
@app.route('/oauth2/<service>/callback')
def oauth2_callback(service):
print(service)
code = request.args.get('code')
# 根據(jù)返回的code獲取access token
access_token_url = 'https://github.com/login/oauth/access_token'
payload = {
'client_id': client_id,
'client_secret': client_secret,
'code': code,
# 'redirect_uri':
'state': 'An unguessable random string.'
}
r = requests.post(access_token_url, json=payload, headers={'Accept': 'application/json'})
access_token = json.loads(r.text).get('access_token')
# 拿到access token之后就可以去讀取用戶的信息了
access_user_url = 'https://api.github.com/user'
r = requests.get(access_user_url, headers={'Authorization': 'token ' + access_token})
return jsonify({
'status': 'success',
'data': json.loads(r.text)
})
拿著用戶蓋過(guò)章的 code 和能夠標(biāo)識(shí)個(gè)人身份的 client_id、client_secret 去拜訪 github,拿到最后的綠卡 access_token。
有了access_token之后就可以讀取用戶授權(quán)的信息了,最后為了演示我把讀取到的信息回顯到了網(wǎng)頁(yè)上。
這其中的過(guò)程對(duì)于用戶來(lái)說(shuō)不可見(jiàn)的,用戶最終在瀏覽器中的url還是第二步重定向的url。

拿到用戶信息后其實(shí)就相當(dāng)于用戶已經(jīng)登陸了,下一步就可以基于獲取到的用戶信息對(duì)用戶做一些業(yè)務(wù)相關(guān)的處理了。
整個(gè) OAuth2 流程在這里也基本完成了,文章中的表述很粗糙,比如 access_token 這個(gè)綠卡是有過(guò)期時(shí)間的,如果過(guò)期了需要使用 refresh_token 重新簽證。重點(diǎn)是讓讀者理解整個(gè)流程,細(xì)節(jié)部分可以閱讀 RFC6749 文檔。
希望對(duì)你理解 OAuth 2.0 有幫助。
我的博客即將搬運(yùn)同步至騰訊云+社區(qū),邀請(qǐng)大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=jgq8ithnd28h