第 87 天:Python Web開發 OAuth2.0 簡介
- 2019 年 12 月 21 日
- 筆記

我們經常看到或者用到一些可以使用微博、微信、支付寶等帳號登錄的應用,還有一些微博助手、微信公眾號助手、客戶端之類的東西,這些是怎麼做的呢,背後的原理是什麼呢?為什麼公眾號配置起來那麼繁瑣呢?什麼是 access token、refresh token?等等,今天我們來了解下開放式授權模式 OAuth
隨著互聯網的發展,各種應用相互交叉,到處需要用戶登錄,資訊安全成為了不可迴避的問題,應用需要擴展,用戶需要更好的體驗,資訊需要更安全的保障,為了滿足這些需求,互聯網技術不斷推陳出新,從通訊安全,到各種協議框架,有無數的解決方案。
其中 OAuth 框架是很閃耀的一個,一經推出,就得到各大互聯網公司的積極響應,到2010年推出了 OAuth 2.0, 不但修補了 1.0 的安全漏洞,而且簡化了授權流程,得到更廣泛的應用,成為了主流。從 PC 到 Web,從移動端到物聯網,越來越多的應用構建在 OAuth 框架之上,那麼 OAuth 是什麼呢?
OAuth 是什麼
OAuth 框架提供了一種認證和授權機制,可以讓用戶將其受保護的資源授權給其他應用來訪問或者使用。
阮一峰老師對 授權有個形象比喻:授權機制相當於你給快遞員一個臨時密碼(授權),快遞員可以使用這個密碼打開小區門禁,將快遞送到你家門口,而後臨時密碼將失效(詳見參考鏈接)。
這個例子中,你就是 用戶
,小區是 受保護的資源
,快遞員是其他應用(第三方應用
)。
如果沒有這個機制:
- 要麼你就得去小區門口拿快遞,不方便,
- 要麼就得告訴所有可能給你送快遞的快遞員門禁密碼,不安全
有了 OAuth 框架(協議),既方便,又安全
OAuth2.0
這裡不打算介紹 OAuth 1.0,原因是:
- OAuth 1.0 存在安全漏洞
- OAuth 1 和 後面的 OAuth 1a 交流流程比較複雜
- OAuth 2.0 安全性好,應用更廣泛
如果想了解更多關於 OAuth 的知識,請訪問參考鏈接
角色
OAuth2.0 實際上就是讓第三方服務獲得用戶在資源伺服器上的授權的過程,會涉及到 4 種角色
- 資源擁有者(
Resource owner
),即用戶 - 認證伺服器(
Authorization server
),用來認證用戶憑證,頒布授權碼的伺服器 - 資源伺服器(
Resource Server
),存放用戶受保護的資源的伺服器 - 第三方應用(
Client
),也稱之為客戶端(後續皆稱 客戶端),需要得到用戶授權,以便訪問用戶受保護的資源的應用程式
不是任何客戶端都能得到授權的,在開通 OAuth 授權之前,需要先到認證伺服器或者資源伺服器上註冊,註冊成功會得到
appid
和app_secret
,用來向認證伺服器表明應用的身份
角色之間關聯
了解了 OAuth2.0 框架中的主要角色,有必要了解下角色之間關聯關係
- 認證伺服器 和 資源伺服器:通常來說,認證伺服器和資源伺服器同屬於一個服務商,它們就有天生的關聯,而且是內部的安全的,甚至它們可以部署在同一個伺服器(Web 伺服器)上
- 用戶 和 服務商:對於像 Github、微信這樣的知名應用,用戶會主動在這些應用或服務上註冊,填寫的資料資訊,設置的昵稱,產生的文章、上傳的照片,等等將成為用戶的資源,這些資源被存放在資源伺服器上
- 客戶端 和 服務商:客戶端,即第三方應用,要從服務商的資源伺服器中獲取數據,給用戶提供額外服務,必須在服務商處註冊,提供應用的基本資訊,認證資訊,服務域名,申請用戶授權的範圍、甚至企業資質(例如申請微信公眾號的企業服務)等等,申請通過後,服務商會返回
appid
和app_secret
,作為客戶端和服務商的交互憑證 - 用戶 和 客戶端:客戶端提供了特別的服務,可以吸引到用戶,為了讓用戶體驗更好,引導用戶通過授權的方式,從而獲取授權範圍內的用戶資訊,例如 Openid,作為用戶在客戶端上的唯一標識,從而建立起和用戶的關聯
至此,四個角色之間的關聯就建立好了,下面開始介紹具體的授權方式
基本授權流程
沒有比圖更能說明白流程的,借用 RFC6749 文檔中的圖:

授權流程圖
- A 客戶端,向用戶發出授權請求
- B 用戶同意或者拒絕客戶端的授權請求,假設是同意
- C 客戶端拿著用戶的授權請求認證伺服器做認證
- D 如果 C 通過認證,認證伺服器將返回
Access Token
,即可以訪問資源的令牌 - E 客戶端使用
Access Token
請求資源伺服器上的資源 - F 資源伺服器驗證了
Access Token
後,返回受保護的資源
流程中最核心的是讓客戶端獲得 Access Token
,之後在訪問受保護資源時,就不需要用戶反覆授權了
Access Token
顯然不是用戶在資源伺服器上的密碼,是有認證伺服器頒發的,那麼也可以被銷毀
Access Token
和之前課程中的 JWT 是類似的,實際上 JWT 是 OAuth 認證的一個特例
授權模式
根據授權流程,OAuth2.0 定義了 4 種針對不同應用場景的授權模式
- 授權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
授權碼模式
授權碼模式是最完整,安全性最高的授權模式,也是最常用的一種模式,其特點是通過客戶端的後台伺服器與認證伺服器交互,如圖:

授權碼模式流程圖
注意:上圖中的步驟 A, B, C 在通過用戶代理端( User-Agent 一般指瀏覽器)時,被拆分成了兩部分
- A 用戶訪問客戶端的客戶端,後者將前者導向認證伺服器
- B 用戶確定是否授權給客戶端
- C 假設用戶給予授權,認證伺服器將用戶導向客戶端事先指定的"重定向URI"(Redirection URI),同時附上一個授權碼
- D 客戶端收到授權碼,通過後台伺服器,附上早先的"重定向URI",向認證伺服器申請令牌。這一步對用戶不可見
- E 認證伺服器核對了授權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)
接下來說明一下過程中所包含一下參數
不同認證伺服器上的參數名稱有可能不同,但含義相同,例如
client_id
一般被appid
代替
步驟 A,客戶端申請認證的 URI,包含以下參數:
response_type
:表示授權類型,必選項,此處的值固定為code
,因為需要先獲取授權碼client_id
:表示客戶端的ID,必選項,是在認證伺服器分配給客戶端的id,即 appidredirect_uri
:表示重定向URI,可選項scope
:表示申請的許可權範圍,可選項state
:表示客戶端的當前狀態,可以指定任意值,認證伺服器會原封不動地返回這個值
步驟 C,認證伺服器回應的 URI,包含以下參數:
code
:表示授權碼,必選項。該碼的有效期應該很短,通常設為 10 分鐘,客戶端只能使用該碼一次,否則會被認證伺服器拒絕。該碼與客戶端 ID 和重定向 URI,是一一對應關係state
:如果客戶端的請求中包含這個參數,認證伺服器的回應也必須一模一樣包含這個參數
步驟 D,客戶端向認證伺服器申請令牌的 HTTP 請求,包含以下參數:
grant_type
:表示使用的授權模式,必選項,此處的值固定為authorization_code
code
:表示上一步獲得的授權碼,必選項redirect_uri
:表示重定向 URI,必選項,且必須與 A 步驟中的該參數值保持一致client_id
:表示客戶端 ID,必選項
步驟 E,認證伺服器發送的 HTTP 響應,包含以下參數:
access_token
:表示訪問令牌,必選項token_type
:表示令牌類型,該值大小寫不敏感,必選項,可以是bearer類型或mac類型expires_in
:表示過期時間,單位為秒。如果省略該參數,必須其他方式設置過期時間refresh_token
:表示更新令牌,用來獲取下一次的訪問令牌,可選項scope
:表示許可權範圍,如果與客戶端申請的範圍一致,此項可省略
認證伺服器會以 JSON 的形式返回
access_token
數據,且不允許做快取,以提高安全性
簡化模式
簡化模式不需要通過客戶端後台伺服器,直接在瀏覽器中向認證伺服器申請令牌,跳過了授權碼
這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證,如圖:

簡化模式流程圖
- A 客戶端將用戶導向認證伺服器
- B 用戶決定是否給予客戶端授權
- C 假設用戶給予授權,認證伺服器將用戶導向客戶端指定的
重定向URI
,並在URI的Hash部分包含了訪問令牌access_token
- D 瀏覽器向資源伺服器發出請求,其中不包括上一步收到的Hash值
- E 資源伺服器返回一個網頁,其中包含的程式碼可以獲取Hash值中的令牌
- F 瀏覽器執行上一步獲得的腳本,提取出令牌
- G 瀏覽器將令牌發給客戶端
接下來說明一下過程中所包含一下參數
步驟 A,客戶端發送 HTTP 請求,包含的參數:
response_type
:表示授權類型,此處的值固定為token
,必選項client_id
:表示客戶端的ID,必選項redirect_uri
:表示重定向的URI,可選項scope
:表示許可權範圍,可選項state
:表示客戶端的當前狀態,可以指定任意值,認證伺服器會原封不動地返回這個值
步驟 C,認證伺服器回應客戶端的 URI,包含以下參數:
access_token
:表示訪問令牌,必選項。token_type
:表示令牌類型,該值大小寫不敏感,必選項expires_in
:表示過期時間,單位為秒。如果省略該參數,必須其他方式設置過期時間scope
:表示許可權範圍,如果與客戶端申請的範圍一致,此項可省略state
:如果客戶端的請求中包含這個參數,認證伺服器的回應也必須一模一樣包含這個參數
有個需要注意的地方,步驟 C,返回的 access_token 放在重定向 URL 的 Fragment 中,即錨點中, # 後面,例如
http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600
因為錨點中的內容不會發送給後台,從而減少了一次數據傳輸,降低了一定風險
對於簡化模式獲得的 access_token 有效期很短,一般是會話級的,即當會話結束時就失效
密碼模式
密碼模式中,用戶向客戶端提供自己的用戶名和密碼,客戶端使用這些資訊,向"服務商提供商"索要授權。
在這種模式中,用戶必須把自己的密碼給客戶端,但是客戶端不得儲存密碼(既然用戶信任你,你就必須兌現這個承諾)。
密碼模式的特性決定,需要用在用戶對客戶端高度信任的情況下,比如客戶端是作業系統的一部分,或者由一個著名公司出品,而認證伺服器只有在其他授權模式無法執行的情況下,才能考慮使用這種模式
這裡只簡單介紹下,不做做詳細講解,如有興趣了解,可以查閱文末參考
客戶端模式
客戶端模式指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於 OAuth 框架所要解決的問題
在這種模式中,用戶直接向客戶端註冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題
客戶端模式,就像二道販子(只為借用比喻,並無貶義),將原始服務包裝後,再提供給最終用戶,常見於多租戶的 Saas 系統,例如統一提供支付通道、處理 GPS 資訊等
這裡也只簡單介紹下,如有興趣了解,可以查閱文末參考
刷新 access_token
在 授權碼模式中,授權伺服器可以會同時返回 refresh_token
,用來在 access_token
過期前,重新獲取新的access_token
,不需要用戶重新確認授權,有助於提高用戶體驗
在 access_token 過期前,客戶端可用 refresh_token 向授權伺服器發送請求,例如,假設 b.com 是授權伺服器地址,請求大體是:
https://b.com/oauth/token? grant_type=refresh_token& client_id=CLIENT_ID& client_secret=CLIENT_SECRET& refresh_token=REFRESH_TOKEN
grant_type
: 授權類型,值為 'refresh_token'client_id
: 客戶端 id,即第三方應用在授權伺服器上註冊被分配的 idclient_secret
: 客戶端和授權伺服器通行的密鑰,由授權伺服器頒發,在特殊需要確認的情況下需要作為驗證條件refresh_token
: 用戶獲取新的access_token
的refresh_token
安全的 OAuth2.0
上面較為詳細的講述了 OAuth2.0 框架,了解了在開放網路中如何安全的獲取用戶授權的技術細節,但再完善的交互方案、再複雜的嚴密的通訊過程,都避免不了中間人攻擊,當客戶端和認證伺服器之間通過 http 協議交互數據時,會被截取通訊內容,從而獲得用戶的授權,這是不能接受的,所以 OAuth2.0 需要建立在 https 協議上,將通訊內容加密,最大程度的防止資訊被竊取。
如果 OAuth2.0 框架用在敏感資訊交互上時,必須使用 https 協議確保安全,但並不是說只能支援 https,對於非敏感數據,或者不重要的授權,可以使用 http 協議作為通訊方式
總結
本節課程著重介紹了 OAuth2.0 授權框架,從它的作用,到具體的技術細節,做了較為詳細的講述,如果需要用安全的授權,需要將通訊建立在 https 協議之上。由於 OAuth 概念較多,流程複雜,這節沒有涉及到具體的編程實踐,下一節,我們以 Github 為例,使用之前介紹過的 Authlib 模組,用 Flash 實現一個第三方應用,作為實踐,敬請期待
參考
- RFC 6749: https://tools.ietf.org/html/rfc6749
- 比喻: http://www.ruanyifeng.com/blog/2019/04/oauth_design.html
- http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
- https://docs.authlib.org/en/stable/basic/oauth2.html