本文由 Haseeb Anwar 發表在 medium,經原作者授權由 InfoQ 中文站翻譯并分享。
我們都在網站或者手機應用中見過“谷歌登陸”和“綁定 Facebook“這樣的按鈕。如果你點擊這個按鈕,就會有一個窗口彈出并顯示“這個應用想要訪問你的公共個人主頁、通訊錄……“,同時它會詢問你是否授權。概括而言,這就是 OAuth。對于每個軟件工程師、安全專家甚至是黑客,理解這些協議都是非常重要的。
前言
本文是一篇關于 OAuth 2.0 與 OpenID Connect 協議的完整指南,這兩個協議是用于授權和認證的使用最廣泛的的協議。OAuth 2.0 用于授權,OpenID Connect 用于認證。有兩種 OAuth 2.0 授權流程最為常見:服務端應用程序的授權碼流程和基于瀏覽器的應用程序的隱式流程。OpenID Connect 是 OAuth 2.0 協議之上的標識層,以使 OAuth 適用于認證的用例。
為什么需要 OAuth?
為了更好地理解 OAuth 誕生的理由,我們需要理解一個術語:代理授權。
代理授權
代理授權是一種允許第三方應用訪問用戶數據的方法。
兩種代理授權的方式
有兩種代理授權的方式:一是你將賬號密碼提供給第三方應用,以便它們可以代表你來登陸賬號并且訪問數據;二是你通過 OAuth 授權第三方應用訪問你的數據,而無需提供密碼。(我相信我們都不會選擇交出我們的密碼!)
現在,我們知道了 OAuth 的必要性和重要性,讓我們更深入地研究這個協議。
什么是 OAuth?
OAuth(Open Authorization,即開放授權)是一個用于代理授權的標準協議。它允許應用程序在不提供用戶密碼的情況下訪問該用戶的數據。
OAuth 2.0 術語表
為理解這個協議,我們需要理解以下術語:
- 資源所有者(Resource Owner):擁有客戶端應用程序想要訪問的數據的用戶。
- 客戶端(Client):想要訪問用戶數據的的應用程序
- 授權服務端(Authorization Server):通過用戶許可,授權客戶端訪問用戶數據的授權服務端。
- 資源服務端(Resource Server):存儲客戶端要訪問的數據的系統。在某些情況下,資源服務端和授權服務端是同一個服務端。
- 訪問令牌:訪問令牌是客戶端可用于訪問資源服務端上用戶授權的數據的唯一密鑰。
以下是 OAuth 2.0 抽象流程圖,讓我們一起看看上述術語在圖中的應用
OAuth2.0 抽象流程圖
授權密鑰(Authorization Key)或者權限(Grant)可以是授權碼或者令牌的類型。下文我們將會提到不同的權限和授權密鑰。現在,讓我們先詳細解釋授權的流程。
- 用戶通過點擊按鈕啟動整個授權流程。這個按鈕通常類似于“谷歌登陸“、”Facebook 登陸“或者通過其他的應用登陸。
- 然后客戶端將用戶重定向到授權服務端。在重定向的過程中,客戶端將類似客戶 ID、重定向 URI 的信息發送給授權服務端。
- 授權服務端處理用戶認證,并顯示授權許可窗口,然后從用戶方獲得授權許可。如果你通過谷歌登陸,你必須向谷歌,而不是客戶端,提供登陸證書——例如向 accounts.google.com 提供登陸證書。
- 如果用戶授權許可,則授權服務端將用戶重定向到客戶端,同時發送授權密鑰(授權碼或令牌)。
- 客戶端向資源服務端發送包含授權密鑰的請求,要求資源服務端返回用戶數據。
- 資源服務端驗證授權密鑰,并向客戶端返回它所請求的數據。
這就是用戶在不提供密碼的情況下,允許第三方應用訪問用戶數據的過程。但與此同時,有一些問題出現了:
- 我們如何限制客戶端只訪問資源服務端上的部分數據?
- 如果我們只希望客戶端讀取數據,而沒有權限寫入數據呢?
這些問題將我們引導至 OAuth 技術術語中另一部分很重要的概念:授權范圍(Scope)。
OAuth 中的授權范圍(Scope)
在 OAuth 2.0 中,授權范圍用于限制應用程序訪問某用戶的數據。這是通過發布僅限于用戶授權范圍的權限來實現的。
當客戶端向授權服務端發起權限請求時,它同時隨之發送一個授權范圍列表。授權客戶端根據這個列表生成一個授權許可窗口,并通過用戶授權許可。如果用戶同意了其授權告知,授權客戶端將發布一個令牌或者授權碼,該令牌或授權碼僅限于用戶授權的范圍。
舉個例子,如果我授權了某客戶端應用訪問我的谷歌通訊錄,則授權服務端向該客戶端發布的令牌不能用于刪除我的聯系人,或者查看我的谷歌日歷事件——因為它僅限于讀取谷歌通訊錄的范圍。
OAuth 2.0 的設置
在討論 OAuth 流程之前,最好先了解一些 OAuth 的配置。當發起授權權限的請求時,客戶端將一些配置數據作為查詢參數發送給授權服務端。這些基本的查詢參數包括:
- 響應類型(response_type):我們希望從授權服務端獲得的響應類型
- 授權范圍(scope):客戶端希望訪問的授權范圍列表。授權服務端將使用這個列表為用戶產生同意授權許可窗口。
- 用戶 ID(client_id):由授權服務在為 OAuth 設置客戶端時提供。此 ID 可幫助授權服務端確定正在發送 OAuth 流程的客戶端。
- 重定向通用資源標識符(redirect_uri):用于告知授權服務器當 OAuth 流程完成后重定向的地址
- 客戶密碼(client_secret):由授權服務提供,根據 OAuth 流程,這個參數可能需要也可能不需要。我們將在授權碼流程中會了解到它的重要性。
了解不同的 OAuth 流程
兩種最常用的 OAuth2.0 流程是:基于服務器的應用程序所使用的授權碼流程,以及純 JAVAScript 單頁應用所使用的隱式流程。
為了解釋 OAuth 的各類流程,接下來我將用谷歌作為 OAuth 服務提供者。
授權碼流程
授權碼流程,或者說授權碼權限,是理想的 OAuth 流程。它被認為是非常安全的,因為它同時使用前端途徑(瀏覽器)和后端途徑(服務器)來實現 OAuth2.0 機制。
OAuth2.0 授權碼流程
客戶端通過將用戶重定向到授權服務端來發起一個授權流程,其中,response_type需被設置成code。這告知了授權服務端用授權碼來響應。該流程的 URI 如下所示:
復制代碼
https://accounts.google.com/o/oauth2/v2/auth? response_type=code& client_id=your_client_id& scope=profile%20contacts& redirect_uri=https%3A//oauth2.example.com/code
在上述請求中,客戶端請求能夠訪問該用戶公共主頁和聯系人的用戶許可,這是在scope請求參數中設置的。這個請求的結果是授權碼,客戶端可以使用該授權碼來交換訪問令牌。一個授權碼如下所示:
復制代碼
4/W7q7P51a-iMsCeLvIaQc6bYrgtp9
為什么用授權碼來交換令牌?
訪問令牌是唯一能用于訪問資源服務端上的數據的東西,而不是授權碼。所以為什么在客戶端實際需要訪問令牌的情況下,將response_type設置成授權碼呢?這是因為這樣做能使 OAuth 流程非常安全。
OAuth2.0 授權碼流程
問題:訪問令牌是我們不希望任何人能訪問的秘密信息。如果客戶端直接請求訪問令牌,并將其存儲在瀏覽器里,它可能會被盜,因為瀏覽器并不是完全安全的。任何人都能看見網頁的代碼,或者使用開發工具來獲取訪問令牌。
解決方案:未了避免將訪問令牌暴露在瀏覽器中,客戶端的前端從授權服務端獲得授權碼,然后發送這個授權碼到客戶端的后端。現在,為了用授權碼交換訪問令牌,我們需要一個叫做客戶密碼(client_secret)的東西。這個客戶密碼只有客戶端的后端知道,然后后端向授權服務端發送一個 POST 請求,其中包含了授權碼和客戶密碼。這個請求可能如下所示:
復制代碼
POST /token HTTP/1.1Host: oauth2.googleapis.comContent-Type: Application/x-www-form-urlencodedcode=4/W7q7P51a-iMsCeLvIaQc6bYrgtp9&client_id=your_client_id&client_secret=your_client_secret_only_known_by_server&redirect_uri=https%3A//oauth2.example.com/code
授權服務端會驗證客戶密碼和授權碼,然后返回一個訪問令牌。后端程序存儲了這個訪問令牌并且可能使用此令牌來訪問資源服務端。這樣一來,瀏覽器就無法讀取訪問令牌了。
隱式流程
當你沒有后端程序,并且你的網站是一個僅使用瀏覽器的靜態網站時,應該使用 OAuth2.0 隱式流程。在這種情況下,當你用授權碼交換訪問令牌時,你跳過發生在后端程序的最后一步。在隱式流程中,授權服務端直接返回訪問令牌。
OAuth2.0 授權碼流程
客戶端將瀏覽器重定向到授權服務端 URI,并將response_type設置成token,以啟動授權流程。授權服務端處理用戶的登錄和授權許可。請求的返回結果是訪問令牌,客戶端可以通過這個令牌訪問資源服務端。
隱式流程被認為不那么安全,因為瀏覽器負責管理訪問令牌,因此令牌有可能被盜。盡管如此,它仍然被單頁應用廣泛使用。
認證與授權
正如我們所知,OAuth 解決了代理授權的問題,但是它沒有提供一個認證用戶身份的標準方法。你可以這樣認為:
- OAuth2.0 用于授權
- OpenID Connect 用于認證
如果你無法區分這些術語,則以下是它們之間的區別:
- 認證(Authentication)是確保通信實體是其所聲稱的實體。
- 授權(Authorization)是驗證通信實體是否有權訪問資源的過程。
換言之,認證關注的是你是誰,授權關注的是你有什么權限。
OpenID Connect
OpenID Connect 是在 OAuth2.0 協議之上的標識層。它拓展了 OAuth2.0,使得認證方式標準化。
OAuth 不會立即提供用戶身份,而是會提供用于授權的訪問令牌。 OpenID Connect 使客戶端能夠通過認證來識別用戶,其中,認證在授權服務端執行。它是這樣實現的:在向授權服務端發起用戶登錄和授權告知的請求時,定義一個名叫openid的授權范圍。在告知授權服務器需要使用 OpenID Connect 時,openid是必須存在的范圍。
客戶端發起的用于 OpenID Connect 認證請求 URI 會是如下的形式:
復制代碼
https://accounts.google.com/o/oauth2/v2/auth? response_type=code& client_id=your_client_id& scope=openid%20contacts& redirect_uri=https%3A//oauth2.example.com/code
該請求的返回結果是客戶端可以用來交換訪問令牌和 ID 令牌的授權碼。如果 OAuth 流程是隱式的,那么授權服務端將直接返回訪問令牌和 ID 令牌。ID 令牌是 JWT,或者又稱 JSON Web Token。JWT 是一個編碼令牌,它由三部分組成:頭部,有效負載和簽名。在獲得了 ID 令牌后,客戶端可以將其解碼,并且得到被編碼在有效負載中的用戶信息,如以下例子所示:
復制代碼
{ "iss": "https://accounts.google.com", "sub": "10965150351106250715113082368", "email": "johndoe@example.com", "iat": 1516239022, "exp": 1516242922}
聲明(Claim)
ID 令牌的有效負載包括了一些被稱作聲明的域。基本的聲明有:
- iss:令牌發布者
- sub:用戶的唯一標識符
- email:用戶的郵箱
- iat:用 Unix 時間表示的令牌發布時間
- exp:Unix 時間表示的令牌到期時間
然而,聲明不僅限于上述這些域。由授權服務器對聲明進行編碼。客戶端可以用這些信息來認證用戶。
如果客戶端需要更多的用戶信息,客戶端可以指定標準的 OpenID Connect 范圍,來告知授權服務端將所需信息包括在 ID 令牌的有效負載中。這些范圍包括個人主頁(profile)、郵箱(email)、地址(address)和電話(phone)。
結語
練習你所學習的內容總是好的。你可以訪問 Google OAuth 2.0 Playground 來使用 OAuth2.0 的授權范圍、授權碼和令牌。