OAuth
什麼是 OAuth?
高層次來說,OAuth 不是一個 API 或者服務:它是一個授權開放標準,並且任何人都可以實現它。
更明確來說,OAuth 是一個標準,應用程式可以用來提供客戶端程式“安全授權訪問”。OAuth 在 HTTPs 上工作並通過訪問令牌授權裝置、API、服務、和應用程式,而不是憑證。
有兩個版本的 OAuth:OAuth 1.0a 和 OAuth 2.0 。這些規範是完全不同的,並且不能一起使用,它們之間沒有向後相容。
為什麼是 OAuth?
OAuth 的建立,是對直接認證模式的回應。這個模式因 HTTP Basic 認證而聞名,提示使用者輸入使用者名稱和密碼。Basic 認證一直用於伺服器端應用程式 API 認證的原始表單:替代每次請求傳送使用者名稱和密碼,使用者傳送一個 API key ID 和金鑰。在 OAuth 之前,網站提示你鍵入使用者名稱和密碼到一個表單中,它們會以你的身份登入到你的資料中(例如:你的 Gmail 賬戶)。通常這叫做密碼反模式。
為了能建立一個更好的 Web 系統,聯合身份被建立用於單點登入(SSO)。在這種場景下,終端使用者和他們的身份提供商對話,並且身份提供商生成一個加密的簽名令交給應用程認證使用者。應用程式信任身份提供商。只要信任關係和簽名斷言工作,就沒問題。下圖顯示如何工作。
聯合身份因 SAML 2.0(一個釋出於 2005/03/15 的 OASIS 標準) 出名。它是一個巨大的規範,但是主要的兩個元件是它的認證請求協議(也叫做 Web SSO)和它打包身份特性並對其簽名的方式,被叫做 SAML 斷言。
SAML
SAML 在你的瀏覽器中是一個基礎的會話快取讓你訪問 Web 應用程式。它受限於裝置檔案型別和瀏覽器之外的場景。
OAuth 和 API
我們構建 API 的方式也發生了很大的變化。在 2015 年,大家投入 WS-* 構建 Web 服務。如今,大多數開發者已經遷移到 REST 和無狀態 API。REST 簡單的說是 HTTP 命令通過網路推送 JSON 包。
開發者構建大量的 API。如今 API 經濟可能是你在會議室中常聽見的詞彙。公司需要保護他們的 REST API,以一種允許很多裝置訪問它們的方式。在從前,你要輸入你的使用者名稱、密碼目錄,然後 app 以你的身份登入。這就產生了授權問題。
“我如何允許一個 app 訪問我的資料而不需要給它我的密碼?”
如果你見過下面的會話,那就是我們在談論的。一個應用程式請求它是否可以代表你訪問資料。
這就是 OAuth。
OAuth 是一個用於 REST/API 委託授權框架。它使應用程式能夠在不洩露使用者密碼的情況下獲得使用者資料的有限訪問(scopes)。它從授權中解綁認證並支援多使用者例項定位到不同裝置的能力。它支援服務到服務的應用程式、以瀏覽器為基礎的應用程式、移動端/原生 app、控制檯程式和電視應用程式。
你可以認為這就像旅館的房卡,但是用於應用程式。如果你有一個旅館的房卡,你可以進入你的房間。你如果獲取一個旅館的房卡?你必須在前臺做一個認證流程以得到房卡。認證後獲得房卡,你可以通過旅館訪問資源。
簡單的分解後, OAuth 是:
- App 從使用者請求授權
- 使用者授權 App 並且傳遞驗證
- App 提交授權驗證到伺服器獲取一個令牌
- 令牌被限制只能訪問使用者為特定應用授權的內容
OAuth 主要元件
OAuth 構建於下列主要元件之上:
- 作用域和准許(Scopes and Consent)
- 參與者(Actors)
- 客戶端(Clients)
- 令牌(Tokens)
- 授權服務(Authorization Server)
- 工作流(Flows)
OAuth 作用域
作用域是當應用程式請求許可時你在授權頁面上看到的內容。它們是客戶端在請求令牌時請求的許可權包(許可權包:一堆許可權)。它們是程式設計師在寫應用程式時寫的。
作用域從強制中解耦授權策略決策。這是 OAuth 的第一個關鍵點。許可權是最重要的。它們不隱藏在需要你逆向工程的應用程式層後面。它們通常在 API 文件中列出:這些作用域是這個應用程式需要的。
你需要獲得准許。這叫做第一次使用信任。它在 Web 上是十分重要的使用者體驗變更。大多數人在 OAuth 之前僅使用使用者名稱和密碼會話框。現在你有提出新的頁面,你必須培訓使用者使用它。重新培訓網民是困難的。有各種各樣的使用者,從精通科技的年輕人到不熟悉這一流程的祖父母。它在 Web 上是一個新的概念,如今很重要。現在你需要授權和准許。
准許在應用程式的基礎上是可以變化的。它可以是時間敏感的時間範圍(天,周,月),但並不是所有平臺都允許你選擇持續時間。需要注意的是,當你准許時,應用程式可以代表你做事情,例如:LinkedIn 向你的社交網路中的每個人傳送垃圾郵件。
OAuth 是網際網路規模的解決方案,因為它針對每一個應用程式。通常你有能力登入到一個儀表盤以檢視你給什麼應用程式准許訪問以及取消准許。
OAuth 參與者
OAuth 工作流參與者如下:
- 資源擁有者:在資源伺服器上擁有資料。例如:我是我的 Facebook 個人文件的資源擁有者。
- 資源伺服器:應用程式要訪問的儲存資料的 API
- 客戶端:要訪問你資料的應用程式
- 授權伺服器:OAuth 引擎
資源擁有者是一個可以變更不同憑證的角色。它可以是一個終端使用者,也可以是一家公司。
客戶端可以是公開的和保密的。兩者間有一個明顯的區別在 OAuth 命名規則中。保密的客戶端可以通過儲存祕密以被信任。保密的客戶端不是在桌面執行或者通過應用商店分發。人們不能逆向工程它們以獲取金鑰。它們在一個受保護的區域(終端使用者不能訪問它們)執行。
公共客戶端是瀏覽器、手機 app、和物聯網裝置。
客戶端註冊也是 OAuth 的關鍵元件。它像 OAuth 的車管所。你需要為你的應用程式獲取車牌號。這就是你的應用程式如何在授權會話顯示 Logo。
OAuth 令牌
訪問令牌是客戶端用於訪問資源伺服器(API)的令牌。它們意味著是暫時的。用小時和分鐘來考慮它們,而不是天和月。你不需要一個機密的客戶端以獲取訪問令牌。你可以通過公共客戶端獲取訪問令牌。它們被設計為優化網際網路規模問題。因為這些令牌是短暫和可向外擴充套件的,它們不能被取消,你只需要等待它們過期。
另一個令牌是重新整理令牌。這有效期是比較長的(天、月、年)。它用於獲取新的令牌。要獲取重新整理令牌,應用程式通常需要具有認證的機密客戶端。
重新整理令牌可以被取消。當在儀表盤中取消一個應用程式的訪問時,你殺掉了它的重新整理令牌。這給了你強制客戶端輪換祕密的能力。你要做的是,使用你的重新整理令牌去獲取新的訪問令牌,訪問令牌通過網路命中訪問所有 API 資源。每一次你重新整理你的訪問令牌你將獲取一個新的加密簽名令牌。系統中內建了輪換按鈕。
OAuth 規範沒有定義令牌是什麼樣子。它可以是任何你想要的格式。但通常是這樣,你要這些令牌是一個 JWT(JSON Web Tokens)。簡而言之,JWT 是一個安全和值得信任的令牌驗證標準。JWT 允許你使用簽名對資訊(稱為宣告)進行數字簽名,並且可以在以後使用祕密簽名金鑰進行驗證。
令牌從授權伺服器上的端點上取回。兩個主要的端點是授權端點和令牌端點。它們針對不同的用例進行了分隔。授權端點是你要從使用者那獲得准許和授權的地方。返回一個使用者已經准許的授權許可。然後授權傳遞到令牌端點。令牌端點處理許可並且說“好極了,這是你的重新整理令牌和你的訪問令牌”。
你可以使用訪問令牌訪問 API。一旦令牌過期,你必須帶著重新整理令牌返回令牌端點以獲取新的訪問令牌。
缺點是這會引起很多開發者的摩擦。OAuth 最大的痛點是開發者必須管理重新整理令牌。你把狀態管理推到每個客戶端開發者的身上。你得到重新整理按鈕的好處,但是你製造了開發者的痛點。那就是為什麼開發者喜愛 API 金鑰的原因。他們只需要拷貝、貼上金鑰,把它們放在一個文字里,然後就完成了。API 金鑰對於開發者來說非常方便,但是對於安全性來說非常糟糕。
這裡有一個付費遊戲的問題。讓開發者做 OAuth 流提高安全性,但會產生更多的摩擦。對於工具箱和平臺來說是有機會簡化事情並幫助管理令牌。幸好,OAuth 現在已經相當成熟了,你最喜歡的語言或框架可能有工具可以簡化事情。
我們已經略微聊了關於客戶端型別、令牌型別、授權伺服器端點和我們如何把它們傳遞到資源伺服器。我提到兩個不同的流:獲取授權和獲取令牌。這些不需要在同一個渠道上產生。前端渠道通過瀏覽器。瀏覽器重定向使用者到授權伺服器,使用者給予准許。這在使用者的瀏覽器上產生。一旦使用者獲得授權許可並交給應用程式,客戶端應用程式就不再需要使用瀏覽器完成 OAuth 流獲取令牌了。
令牌將被客戶端應用程式使用,以便它能夠代表你訪問資源。我們叫做後端渠道。後端渠道是一個直接從客戶端應用程式發起,到資源伺服器的 HTTP 呼叫,以交換授權許可獲得令牌。這些渠道用於不同的流取決於你有什麼樣的裝置能力。
例如,一個前端渠道流通過使用者代理授權看起來是這樣的:
- 資源擁有人啟動流,委託訪問,保護資源
- 客戶端帶著需要的作用域通過瀏覽器傳送授權請求重定向到授權伺服器上的授權端點
- 授權伺服器返回一個准許會話,說“你是否允許這個應用程式訪問這些作用域?”,當然你需要認證應用程式,假如你沒有認證你的資源伺服器,它會要求你登入。如果你已經快取了會話快取,你只會看到準可會話框。檢視準可會話,然後同意。
- 授權許可通過瀏覽器重定向回傳到應用程式。這就是前端渠道發生的所有事情。
在這個流程中還有一個變數叫做隱式流程。我們馬上就會講到。
這是在網路中看到的。
Request
GET https://accounts.google.com/o/oauth2/auth?scope=gmail.insert gmail.send
&redirect_uri=https://app.example.com/oauth2/callback
&response_type=code&client_id=812741506391
&state=af0ifjsldkj
這是一個帶著一堆查詢引數的 GET 請求(不是以 URL 編碼為目的的示例)。作用域(Scopes)來自 Gmail 的 API。redirect_uri 是客戶端應用程式的 URL,授權許可應該返回到該 URL。這應該匹配來自客戶端註冊過程的值(在車管所)。你不希望授權退回到外部應用程式。響應型別改變 OAuth 流。客戶端 ID 也來自注冊流程。狀態是一個安全標記,類似 XRSF。
Response
HTTP/1.1 302 Found
Location: https://app.example.com/oauth2/callback?
code=MsCeLvIaQm6bTrgtp7&state=af0ifjsldkj
code
返回的是授權許可,state
確保非偽造並且來自同一個請求。
前端渠道完成後,一個後端渠道流產生了,交換授權碼得到訪問令牌。
客戶端應用程式傳送一個訪問令牌請求(帶著機密客戶端證照和客戶端 ID)到授權伺服器上的令牌端點。這個過程交換一個授權碼許可得到一個訪問令牌和(可選)一個重新整理令牌。客戶端通過訪問令牌訪問受保護的資源。
以下是原始 HTTP 看到的樣子。
Request
POST /oauth2/v3/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=MsCeLvIaQm6bTrgtp7&client_id=812741506391&client_secret={client_secret}&redirect_uri=https://app.example.com/oauth2/callback&grant_type=authorization_code
grant_type
是 OAuth 的擴充套件部分。從預先計算的角度來看,它是一個授權碼。它帶來了靈活性,可以用不同的方式描述這些授權。這在 OAuth 流中是最常見的型別。
Response
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA"
}
響應是 JSON。在使用令牌時,你可以時被動或主動的。主動是在你的客戶端中有一個定時器。被動是獲取一個錯誤然後嘗試重新獲取一個新的令牌。
一旦你有了訪問令牌,你可以在認證頭(使用 token_type
做為字首)中使用反問令牌發起對受保護資源的訪問。
curl -H "Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA" \
https://www.googleapis.com/gmail/v1/users/1444587525/messages
目前你有一個前端渠道,一個後端渠道、不同的端點和不同的客戶端。你必須為不同的用例混合和匹配這些。這提高了 OAuth 的複雜性,可能會讓人感到困惑。
OAuth 工作流
- 隱式流(Implicit Flow)
- 授權碼流(Authorization Code Flow)
- 客戶端憑證流(Client Credential Flow)
- 資源擁有者密碼流(Resource Owner Password Flow)
- 斷言流(Assertion Flow)
- 裝置流(Device Flow)
OAuth 不是一個認證協議
總結 OAuth 2.0 的一些錯誤觀念:它不向後相容 OAuth 1.0。它將所有通訊的簽名替換為 HTTPS。如今當人們談論 OAuth 時,他們談論的是 OAuth 2.0。
因為 OAuth 是一個授權框架不是一個協議,你可能有互操作性問題。在團隊實現 OAuth 時有很多變數,你可以需要自定義程式碼與供應商整合。
OAuth 2.0 不是一個認證框架。
我們一直在討論委託授權。而不是認證使用者,這是關鍵。OAuth 2.0 單獨來說絕對和使用者沒有一點關係。你只是通過令牌訪問資源。
最近幾年 OAuth 產生了有巨量的附屬。它們又在 OAuth 上新增了複雜性以完成各種企業場景。例如:JWT 可用做互操作令牌,可以對其進行簽名和加密。
通過 OAuth 2.0 偽認證
通過 OAuth 登入因 FaceBook Connect 和 Twitter 出名。在這個工作流下,客戶端通過訪問令牌訪問 /me
端點。它只是說客戶端可以通過令牌訪問資源。人們發明這個假的端點做為通過令牌獲取使用者資訊的一種方式。它不是獲取使用者資訊的標準方式。標準中沒有提及要實現這個端點。訪問令牌是不透明的。它們為 API 而設計的,而不是為包含使用者資訊而設計的。
通過身份認證,你要回答的是使用者是誰、使用者何時認證的和使用者如何認證的。通常可以使用 SAML 斷言回答這些問題,而不是訪問令牌和授權許可。這是為什麼我們把這個叫做偽認證的原因。
進入 OpenID Connect
為了解決偽認證問題,OAuth 2.0、Facebook Connect和 SAML 2.0 最好的部分聯合建立了 OpenID Connect。OpenID Connect(OIDC)擴充套件 OAuth 2.0,為客戶端提供一個新的簽名 id_token
和一個 UserInfo
端點以拉取使用者特性。不像 SAML,OIDC 提供一個作用域標準集和身份宣告。例如包含:profile
、email
、address
、phone
。
OIDC 是通過使事物完全動態來實現網際網路可擴充套件的。不再需要像 SAML 要求的那樣,下載後設資料和聯合。內建了註冊、發現、動態聯合的後設資料。你可以鍵入你的郵件地址,然後它動態的發現你的 OIDC 提供商,動態下載後設資料,動態知道它要使用什麼證照,並且允許 BYOI(Bring Your Own Identity,帶上你自己的身份)。它支援企業的高保證級別和關鍵 SAML 用例。
OIDC 因 Google 和微軟出名,兩者都是早期使用者。Okta 在 OIDC 中也做了巨大投入。
初始請求中所改變是,它包含標準作用域(例如:openid
和 email
):
Request
GET https://accounts.google.com/o/oauth2/auth?
scope=openid email&
redirect_uri=https://app.example.com/oauth2/callback&
response_type=code&
client_id=812741506391&
state=af0ifjsldkj
Response
HTTP/1.1 302 Found
Location: https://app.example.com/oauth2/callback?
code=MsCeLvIaQm6bTrgtp7&state=af0ifjsldkj
code
返回的是授權許可,state
確保不被偽造並且來自同一個請求。
並且授權許可響應令牌包含一個 ID Token。
Request
POST /oauth2/v3/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=MsCeLvIaQm6bTrgtp7&client_id=812741506391&
client_secret={client_secret}&
redirect_uri=https://app.example.com/oauth2/callback&
grant_type=authorization_code
Response
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ..."
}
看得出這是在 OAuth 上很好的分層,返回一個 ID Token 做為結構化令牌。ID Token 是一個 JWT。JWT 比一個巨大的以 XML 為基礎的 SAML 斷言要小的多,並且可以極具效率的在不同的裝置間傳遞。一個 JWT 包含三個部分:訊息頭、訊息體和簽名。訊息頭表明用什麼演算法對其進行簽名,Claims(宣告) 在訊息體中,簽名內容儲存在簽名中。
OIDC 工作流包含以下幾個步驟:
- 發現 OIDC 後設資料
- 執行 OAuth 工作流獲取 ID Token 和訪問令牌
- 獲取 JWT 金鑰然後(可選)動態註冊客戶端程式
- 本地驗證 JWT ID Token,以內建日期和簽名為基礎
- 根據需要通過訪問令牌獲取使用者其它特性
OAuth 2.0 總結
OAuth 2.0 是一個用於授權訪問 API 的授權框架。它包含資源擁有者授權或者給予准許客戶端請求作用域。授權許可用於交換訪問令牌和重新整理令牌(取決於工作流)。有多個工作流適用不同的客戶端和授權場景。 JWT 可以用於結構化令牌,在授權伺服器和資源伺服器之間。
OAuth 有一個很大的安全的表面。確保使用安全的工具箱驗證所有輸入。
OAuth 不是一個認證協議。OIDC 擴充套件 OAuth 2.0 以適應驗證場景。
OAuth 2.0
OAuth 2.0 是工業級標準授權協議。 OAuth 2.0 聚焦於客戶端開發者便利性,為網頁應用程式、桌面客戶端、手機、客廳裝置提供特定的授權流程。
RFC6749 OAuth 2.0 授權框架
RFC6750 OAuth 2.0 授權框架:使用 Bearer Token
OAutho 2.0 安全當前最佳實踐——注意,此文件僅是草稿,很有可能隨時會被替換或者被棄用。在這裡引用的目的是為了提升對 OAuth 2.0 的理解和當前應用場景下 OAuth 2.0 存在的一些問題。
PKCE
RFC7636:Proof Key for Code Exchange
PKCE 是授權碼流(Authorization Code Flow)的一個擴充套件,以阻止幾個攻擊並且能夠安全的在公共客戶端間執行 OAuth 交換。
起初它設計用於保護移動端 app,由於它能夠阻止授權碼注入,所以對於任何 OAuth 客戶端來說它都很有用,甚至使用客戶端金鑰的 Web 應用程式。
許可權型別
Authorization Code
授權碼許可權型別用於保密,並且公共客戶端通過交換授權碼以獲取訪問令牌。
使用者通過重定向 URL 返回客戶端後,應用程式將會從 URL 中獲取授權碼並用於獲取一個訪問令牌。
在這個流下,推薦所有客戶端都使用 PKCE 擴充套件以便提供更好的安全性。
OAuth 2.1
OAuth 2.1 是一項正在努力中的工作。以鞏固和簡化 OAuth 2.0 中最常用的功能。
自從 2012 年釋出初版 OAuth 2.0 (RFC6749)以來,幾個新的徵求意見稿(RFC)也釋出了,要從核心規則中新增或移除功能。包括雲原生 Apps OAuth 2.0(RFC8252)、PKCE、瀏覽器應用授權、OAutho 2.0 安全當前最佳實踐。
和 OAuth 2.0 主要的不同如下:
- 所有使用授權碼流程的客戶端都需要 PKCE(Proof Key for Code Exchange)
- 必須使用精確字串匹配來比較重定向 URI
- 從 OAuth 2.0 中刪除隱式授權,也叫簡單模式(response_type=token)
- 從 OAuth 2.0 中移除密碼模式
- Bearer 令牌使用,移除在 URI 中 query 字串中使用 Bearer 令牌
- 公共客戶端重新整理令牌必須受傳送端限制或是一次性的
基於 OAuth 2.0 構建的協議
OpenID Connect
什麼是 OpenID Connect?
OpenID Connect 1.0 是在 OAuth 2.0 協議上的身份層。它允許客戶端基於認證伺服器執行認證以驗證終端使用者的身份,同時以一個可互操作和類 REST 的方式獲取終端使用者的基礎資訊。
OpenID Connect 允許所有的客戶端型別(包含以 Web 為基礎的、移動端、JavaScript 客戶端)請求和接收關於認證會話和終端使用者的資訊。規範套件是可擴充套件的,允許參與者使用可選功能(比如:身份資訊加密、OpenID 提供者發現、會話管理)。當可選功能對參與者有意義時。
OpenID Connect 如何不同於 OpenID 2.0?
OpenID Connect 如同 OpenID 2.0 一樣執行很多相同的任務,但以 API 友好的方式,並且用於原生和移動端應用程式。OpenID Connect 定義可選機制以實現健壯的簽名和加密。鑑於整合 OAuth 1.0a 和 OpenID 2.0 需要擴充套件,在 OpenID Connect 中,協議自己整合了 OAuth 2.0 的能力。
規範組織
OpenID Connect 1.0 規範由以下文件組成:
- Core —— 定義 OpenID Connect 核心功能:在 OAuth 2.0 上構建認證並且使用 Claims 和終端使用者溝通訊息
- Discovery ——(可選)定義客戶端如何動態發現 OpenID 提供者資訊
- Dynamic Registration ——(可選)定義客戶端如何動態註冊 OpenID 提供者
- OAuth 2.0 Mutiple Response Types —— 定義幾個具體的 OAuth 2.0 返回型別
- OAuth 2.0 Form Post Response Mode —— (可選)定義如何返回 OAuth 2.0 授權響應引數(包含 OpenID Connect 認證響應引數)使用 HTML 表單值,通過使用者代理使用 HTTP POST 自動提交
- RP-Initiated Logout ——(可選)定義一個依賴方如何要求 OpenID 提供商從終端使用者退出登入
- Session Management ——(可選)定義如何管理 OpenID Connect 會話,包括以 postMessage 為基礎的退出登陸和 RP-initiated 退出登入功能
- Front-Channel Logout ——(可選)定義一個前端渠道退出登入機制(在 RP 頁面上不使用 OP iframe)
- Back-Channel Logout ——(可選)定義一個退出登入機制,直接使用 back-channel 在 OP 和 RP 溝通以退出登入
- OpenID Connect Federation ——(可選)定義 OP 和 RP 集合如何通過使用聯合操作建立信任
注: OP = OpenID Provider,OpenID 提供商。RP = Relying Party,依賴方。
兩個實現指南參考,適用於基於以 Web 為基礎的依賴方實現的獨立服務: