以例項說明 OAuth2

zltl發表於2021-09-06

1 概述

OAuth2 是網際網路中廣泛使用的授權標準,常用於實現單點登入、第三方授權。雖然當前有 更完善的流程,但國內主要還是使用OAuth標準。國內一些服務商的OAuth是自己修改過的, 沒有依照標準文件實現,經常發生標準庫沒辦法完成授權的情況,不知意欲何為,使用時還 是需要依照服務商的開發文件。

OAuth2 解決的問題是第三方應用授權的問題,也可以用於一個龐大公司內多個系統使用統 一賬號的情況。它假設系統中授權伺服器是獨立的,對所有服務的訪問必須首先經過授權服 務器的授權。本文總是先提出一個系統結構,再考慮使用OAuth2標準解決其中授權的問題。 會囉嗦一些,網上有許多簡短精煉的介紹,或者直接看rfc6749,都可以滿足想要迅速瞭解 OAuth2標準的人。

Google 有個 OAuth 2.0 Playgroud 可以模擬他家各服務的 oauth2 授權過程。我也做了個 類似的,嘗試使用別家 OAuth2 API 時可以使用。

2 Authorization Code Grant

2.1 場景說明:第三方授權

這個場景是 OAuth2 最常出現的場景,以 Github 為例。假設現在有一個第三方服務,他的 功能是幫使用者定期檢查 Github 賬戶上的 Repo 是否上傳了敏感資訊,如金鑰,資料庫口令 等。

由於是第三方服務,且服務需要訪問使用者的 Repo 檔案,屬於個人資訊,Github 和第三方 服務都有義務徵求使用者同意。所以第三方服務需要獲得使用者授權,才能訪問使用者的 Repo, 而 Github 需要獲得使用者授權,才能讓第三方服務處理使用者的資源。

順序結構如下圖。三方服務向 Github 的授權伺服器請求授權,授權伺服器徵求使用者同意後, 允許三方應用訪問資源伺服器中使用者的 Repo。

oauth.1.png

2.2 第三方授權解決方案

在解釋如何使用 OAuth2 實現這個流程之前,先了解一些 OAuth2 中常見的幾個請求/返回引數。

access_token: OAuth2 流程最後產生的授權碼,擁有授權碼即可訪問資源,通常是 JWT 格式。
refresh_token: access_token 是有時效的,可以使用 refresh_token 請求新的 access_token 。 refresh_token 的生成和驗證都在授權伺服器上,所以一般不使用 JWT 格式,而是一個在授權伺服器中可以查詢到具體授權資訊和狀態的隨機字串。
code: OAuth2 的中間過程,表示使用者不久前確認了這個授權,可以透過 code 獲取 access_token 。
Github 的 OAuth2 管理三方登陸方案如下圖。

oauth.2.png

看起來複雜,其實 OAuth2 過程只有兩個步驟,這兩個步驟最後,三方應用伺服器將獲得 access_token 。

授權請求
一般情況下,使用者去往三方應用主頁,這裡假設主頁是 https://thirdapplication.com ,上面寫著說明應用功能如何強大有用的陳詞濫調,並且有 一個按鈕或連線,點開後跳轉到如下網址。


https://github.com/login/oauth/authorize? response_type=code&client_id=f41154b&state=xyz&redirect_uri=https://thirdapplication.com/examplecb

開啟之後一般會詢問是否允許三方應用訪問你的賬戶,有時會要求使用者登陸 Github 以確認 身份。使用者確認授權後,返回 302 令瀏覽器跳轉到如下網址, thirdapplication.com 是三方應用伺服器的地址。

https://thirdapplication.com/examplecb?code=WxSbIA&state=xyz

瀏覽器跳轉後,實際是跳轉到了三方伺服器,並且把 code 告訴了三方應用伺服器。

獲取 access_token
第一步結束時,三方應用伺服器獲得了 code ,使用 code 作為引數請求 https://github.com/login/oaut... 獲得 access_token , 這樣就可以 透過 access_token 訪問 Github 的 API 獲取使用者資訊,獲取 Repo 資訊了。

上述步驟涉及到幾個引數,這裡詳細說明。

client_id 和 client_secret
這兩個引數是 Github 提供的,去 建立一個應用 後即生成。授權請求步驟中,Github 需要 client_id 來標識現在請求的是哪個應用的訪問許可權。獲取 access_token 步驟中, Github 需要 client_secret 來驗證這個三方伺服器是不是偽造的。開發過程中千萬注 意保密 client_secret 。

redirect_uri
redirect_uri 出現在授權請求步驟和獲取 access_token 步驟中。表示授權成功後, 使用 302 跳轉將 code 發到哪個網址。標準要求驗證這個引數,所以這個引數在認證 伺服器裡是儲存的。 Github 上建立應用時也要求輸入一個 Authorization callback URL,也就是 redirect_uri。我覺得既然認證服務端儲存了這個地址, 請求引數中的 redirect_uri 是沒有必要的,但既然是標準就要遵守,大家都沒法連了。

state
狀態引數,302跳轉時原樣設定到引數中,可以把 session 寫在這裡。

3 Implicit Grant

3.1 應用場景:不需要經過三方應用服務端訪問資源

假設不需要透過三方伺服器,那麼三方伺服器也不必獲得 access_token , 只要三方應用 客戶端獲取 token 就可以了。

oauth.3.png

3.2 第三方授權解決方案

可以使用 OAuth2 的簡化模式(Implicit Grant)。微軟 提供類似於簡化模式的授權,但實 際他是 OpenID Connect。google 倒是有完整的簡化模式,文件說是為客戶端Web應用提供 的授權方式。跟我們的需求匹配。

oauth.4.png

簡化模式只有1個步驟就可以獲得 access_token,請求地址:

https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/drive.metadata.readonly&response_type=token&state=xyZk&redirect_uri=https://oauth2.example.com/code&client_id=client_id

然後返回跳轉 302 地址中包含 access_token:

https://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600

注意跳轉地址的引數是 # 與路徑分隔,而不是 ? 。這個 # 後面的引數是不會發往 伺服器的,所以只有客戶端獲得了 access_token 。

另外,這個 302 跳轉中的引數是沒有 refresh_token 的,access_token 超時時間是1天, 這麼說都夠了吧。如果時間還是不夠,可以嘗試在頁面上使用一個隱藏的 iframe 開啟第一 步的認證連結,由於 cookie 的存在,認證應該是直接透過並返回 access_token 的。但也 需要看認證伺服器是否這麼實現了。

4 Extension Grants

4.1 應用場景: 使用者自己開發的應用,不想登陸賬號授權,就想直接用

我自己也經常開發這類應用,比如說掃描思想健康的網站的磁力連線,下載後將較大的影片 檔案壓縮加密儲存到 google drive 裡。需要檢視時再用自己開發的客戶端下載解密播放。

oauth.5.png

4.2 授權解決方案

OAuth2 有直接拿使用者名稱和密碼當作引數去請求 access_token 的 Resource Owner Password Credentials Grant 模式。即使不提安全性,我覺得這個方案很不好用:我們公司之前使用一個公 司郵箱賬號傳送運維告警郵件,就是設定的 smtp 賬號和密碼,後來 ISO 27001 稽核組要 求三個月修改一次郵箱密碼,密碼改了之後沒改運維繫統,我們的告警郵件發不出去,掛了幾個 月都沒人發現。我讀書少,這個 Resource Owner Password Credentials Grant 模式還沒 見人用過。

OAuth2 也有 Client Credentials Grant 模式,只拿 client_id 和 client_secret 來驗 證。比賬號密碼強一點點,我也沒見人用過。國內常用的方式是直接提供token並要求三方 服務對請求作一系列密碼學操作,比如 阿里雲API 就提供AccessKeyID 和 AccessKeySecret,構造簽名後再直接請求業務伺服器。

Google 使用 OAuth2 的 Extension Grants 模式,他們稱作 Service Accounts, 使用他們提供的金鑰,構造JWT來請求 access_token ,再用 access_token 來訪問資源伺服器。例如:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=JWT-XXXXXXXXXX

返回

{
  "access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
  "scope": "https://www.googleapis.com/auth/prediction"
  "token_type": "Bearer",
  "expires_in": 3600
}

這樣就可以使用 access_token 訪問自己賬戶的資源了。

OAuth2 流程雖然標準並且不復雜,但具體實現還是挺繁瑣的。每個 token 都有時效, 需要在時效過期之前使用 refresh_token 重新整理 access_token ,給本來可以簡單 curl 或者 Postman 的流程增加了不少步驟,有條件的公司基本都會提供 SDK,在SDK中幫忙實現 了認證流程,不需要重複實現。沒有條件提供多種語言 SDK 的話,說服一些固執的合作伙伴按照 OAuth2 的文件實現授權再呼叫介面,並定期重新整理 token ,極難。最後經常把 資料結果儲存到獨立的資料庫例項中,再把資料庫賬號給合作伙伴,這是他們專家提供的 方案,大概也很靠譜吧。

相關文章