本文由 Haseeb Anwar 發(fā)表在 medium,經(jīng)原作者授權(quán)由 InfoQ 中文站翻譯并分享。
我們都在網(wǎng)站或者手機(jī)應(yīng)用中見(jiàn)過(guò)“谷歌登陸”和“綁定 Facebook“這樣的按鈕。如果你點(diǎn)擊這個(gè)按鈕,就會(huì)有一個(gè)窗口彈出并顯示“這個(gè)應(yīng)用想要訪問(wèn)你的公共個(gè)人主頁(yè)、通訊錄……“,同時(shí)它會(huì)詢問(wèn)你是否授權(quán)。概括而言,這就是 OAuth。對(duì)于每個(gè)軟件工程師、安全專家甚至是黑客,理解這些協(xié)議都是非常重要的。
前言
本文是一篇關(guān)于 OAuth 2.0 與 OpenID Connect 協(xié)議的完整指南,這兩個(gè)協(xié)議是用于授權(quán)和認(rèn)證的使用最廣泛的的協(xié)議。OAuth 2.0 用于授權(quán),OpenID Connect 用于認(rèn)證。有兩種 OAuth 2.0 授權(quán)流程最為常見(jiàn):服務(wù)端應(yīng)用程序的授權(quán)碼流程和基于瀏覽器的應(yīng)用程序的隱式流程。OpenID Connect 是 OAuth 2.0 協(xié)議之上的標(biāo)識(shí)層,以使 OAuth 適用于認(rèn)證的用例。
為什么需要 OAuth?
為了更好地理解 OAuth 誕生的理由,我們需要理解一個(gè)術(shù)語(yǔ):代理授權(quán)。
代理授權(quán)
代理授權(quán)是一種允許第三方應(yīng)用訪問(wèn)用戶數(shù)據(jù)的方法。
兩種代理授權(quán)的方式
有兩種代理授權(quán)的方式:一是你將賬號(hào)密碼提供給第三方應(yīng)用,以便它們可以代表你來(lái)登陸賬號(hào)并且訪問(wèn)數(shù)據(jù);二是你通過(guò) OAuth 授權(quán)第三方應(yīng)用訪問(wèn)你的數(shù)據(jù),而無(wú)需提供密碼。(我相信我們都不會(huì)選擇交出我們的密碼!)
現(xiàn)在,我們知道了 OAuth 的必要性和重要性,讓我們更深入地研究這個(gè)協(xié)議。
什么是 OAuth?
OAuth(Open Authorization,即開(kāi)放授權(quán))是一個(gè)用于代理授權(quán)的標(biāo)準(zhǔn)協(xié)議。它允許應(yīng)用程序在不提供用戶密碼的情況下訪問(wèn)該用戶的數(shù)據(jù)。
OAuth 2.0 術(shù)語(yǔ)表
為理解這個(gè)協(xié)議,我們需要理解以下術(shù)語(yǔ):
- 資源所有者(Resource Owner):擁有客戶端應(yīng)用程序想要訪問(wèn)的數(shù)據(jù)的用戶。
- 客戶端(Client):想要訪問(wèn)用戶數(shù)據(jù)的的應(yīng)用程序
- 授權(quán)服務(wù)端(Authorization Server):通過(guò)用戶許可,授權(quán)客戶端訪問(wèn)用戶數(shù)據(jù)的授權(quán)服務(wù)端。
- 資源服務(wù)端(Resource Server):存儲(chǔ)客戶端要訪問(wèn)的數(shù)據(jù)的系統(tǒng)。在某些情況下,資源服務(wù)端和授權(quán)服務(wù)端是同一個(gè)服務(wù)端。
- 訪問(wèn)令牌:訪問(wèn)令牌是客戶端可用于訪問(wèn)資源服務(wù)端上用戶授權(quán)的數(shù)據(jù)的唯一密鑰。
以下是 OAuth 2.0 抽象流程圖,讓我們一起看看上述術(shù)語(yǔ)在圖中的應(yīng)用

OAuth2.0 抽象流程圖
授權(quán)密鑰(Authorization Key)或者權(quán)限(Grant)可以是授權(quán)碼或者令牌的類型。下文我們將會(huì)提到不同的權(quán)限和授權(quán)密鑰。現(xiàn)在,讓我們先詳細(xì)解釋授權(quán)的流程。
- 用戶通過(guò)點(diǎn)擊按鈕啟動(dòng)整個(gè)授權(quán)流程。這個(gè)按鈕通常類似于“谷歌登陸“、”Facebook 登陸“或者通過(guò)其他的應(yīng)用登陸。
- 然后客戶端將用戶重定向到授權(quán)服務(wù)端。在重定向的過(guò)程中,客戶端將類似客戶 ID、重定向 URI 的信息發(fā)送給授權(quán)服務(wù)端。
- 授權(quán)服務(wù)端處理用戶認(rèn)證,并顯示授權(quán)許可窗口,然后從用戶方獲得授權(quán)許可。如果你通過(guò)谷歌登陸,你必須向谷歌,而不是客戶端,提供登陸證書(shū)——例如向 accounts.google.com 提供登陸證書(shū)。
- 如果用戶授權(quán)許可,則授權(quán)服務(wù)端將用戶重定向到客戶端,同時(shí)發(fā)送授權(quán)密鑰(授權(quán)碼或令牌)。
- 客戶端向資源服務(wù)端發(fā)送包含授權(quán)密鑰的請(qǐng)求,要求資源服務(wù)端返回用戶數(shù)據(jù)。
- 資源服務(wù)端驗(yàn)證授權(quán)密鑰,并向客戶端返回它所請(qǐng)求的數(shù)據(jù)。
這就是用戶在不提供密碼的情況下,允許第三方應(yīng)用訪問(wèn)用戶數(shù)據(jù)的過(guò)程。但與此同時(shí),有一些問(wèn)題出現(xiàn)了:
- 我們?nèi)绾蜗拗瓶蛻舳酥辉L問(wèn)資源服務(wù)端上的部分?jǐn)?shù)據(jù)?
- 如果我們只希望客戶端讀取數(shù)據(jù),而沒(méi)有權(quán)限寫(xiě)入數(shù)據(jù)呢?
這些問(wèn)題將我們引導(dǎo)至 OAuth 技術(shù)術(shù)語(yǔ)中另一部分很重要的概念:授權(quán)范圍(Scope)。
OAuth 中的授權(quán)范圍(Scope)
在 OAuth 2.0 中,授權(quán)范圍用于限制應(yīng)用程序訪問(wèn)某用戶的數(shù)據(jù)。這是通過(guò)發(fā)布僅限于用戶授權(quán)范圍的權(quán)限來(lái)實(shí)現(xiàn)的。
當(dāng)客戶端向授權(quán)服務(wù)端發(fā)起權(quán)限請(qǐng)求時(shí),它同時(shí)隨之發(fā)送一個(gè)授權(quán)范圍列表。授權(quán)客戶端根據(jù)這個(gè)列表生成一個(gè)授權(quán)許可窗口,并通過(guò)用戶授權(quán)許可。如果用戶同意了其授權(quán)告知,授權(quán)客戶端將發(fā)布一個(gè)令牌或者授權(quán)碼,該令牌或授權(quán)碼僅限于用戶授權(quán)的范圍。
舉個(gè)例子,如果我授權(quán)了某客戶端應(yīng)用訪問(wèn)我的谷歌通訊錄,則授權(quán)服務(wù)端向該客戶端發(fā)布的令牌不能用于刪除我的聯(lián)系人,或者查看我的谷歌日歷事件——因?yàn)樗鼉H限于讀取谷歌通訊錄的范圍。
OAuth 2.0 的設(shè)置
在討論 OAuth 流程之前,最好先了解一些 OAuth 的配置。當(dāng)發(fā)起授權(quán)權(quán)限的請(qǐng)求時(shí),客戶端將一些配置數(shù)據(jù)作為查詢參數(shù)發(fā)送給授權(quán)服務(wù)端。這些基本的查詢參數(shù)包括:
- 響應(yīng)類型(response_type):我們希望從授權(quán)服務(wù)端獲得的響應(yīng)類型
- 授權(quán)范圍(scope):客戶端希望訪問(wèn)的授權(quán)范圍列表。授權(quán)服務(wù)端將使用這個(gè)列表為用戶產(chǎn)生同意授權(quán)許可窗口。
- 用戶 ID(client_id):由授權(quán)服務(wù)在為 OAuth 設(shè)置客戶端時(shí)提供。此 ID 可幫助授權(quán)服務(wù)端確定正在發(fā)送 OAuth 流程的客戶端。
- 重定向通用資源標(biāo)識(shí)符(redirect_uri):用于告知授權(quán)服務(wù)器當(dāng) OAuth 流程完成后重定向的地址
- 客戶密碼(client_secret):由授權(quán)服務(wù)提供,根據(jù) OAuth 流程,這個(gè)參數(shù)可能需要也可能不需要。我們將在授權(quán)碼流程中會(huì)了解到它的重要性。
了解不同的 OAuth 流程
兩種最常用的 OAuth2.0 流程是:基于服務(wù)器的應(yīng)用程序所使用的授權(quán)碼流程,以及純 JAVAScript 單頁(yè)應(yīng)用所使用的隱式流程。
為了解釋 OAuth 的各類流程,接下來(lái)我將用谷歌作為 OAuth 服務(wù)提供者。
授權(quán)碼流程
授權(quán)碼流程,或者說(shuō)授權(quán)碼權(quán)限,是理想的 OAuth 流程。它被認(rèn)為是非常安全的,因?yàn)樗瑫r(shí)使用前端途徑(瀏覽器)和后端途徑(服務(wù)器)來(lái)實(shí)現(xiàn) OAuth2.0 機(jī)制。

OAuth2.0 授權(quán)碼流程
客戶端通過(guò)將用戶重定向到授權(quán)服務(wù)端來(lái)發(fā)起一個(gè)授權(quán)流程,其中,response_type需被設(shè)置成code。這告知了授權(quán)服務(wù)端用授權(quán)碼來(lái)響應(yīng)。該流程的 URI 如下所示:
復(fù)制代碼
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
在上述請(qǐng)求中,客戶端請(qǐng)求能夠訪問(wèn)該用戶公共主頁(yè)和聯(lián)系人的用戶許可,這是在scope請(qǐng)求參數(shù)中設(shè)置的。這個(gè)請(qǐng)求的結(jié)果是授權(quán)碼,客戶端可以使用該授權(quán)碼來(lái)交換訪問(wèn)令牌。一個(gè)授權(quán)碼如下所示:
復(fù)制代碼
4/W7q7P51a-iMsCeLvIaQc6bYrgtp9
為什么用授權(quán)碼來(lái)交換令牌?
訪問(wèn)令牌是唯一能用于訪問(wèn)資源服務(wù)端上的數(shù)據(jù)的東西,而不是授權(quán)碼。所以為什么在客戶端實(shí)際需要訪問(wèn)令牌的情況下,將response_type設(shè)置成授權(quán)碼呢?這是因?yàn)檫@樣做能使 OAuth 流程非常安全。

OAuth2.0 授權(quán)碼流程
問(wèn)題:訪問(wèn)令牌是我們不希望任何人能訪問(wèn)的秘密信息。如果客戶端直接請(qǐng)求訪問(wèn)令牌,并將其存儲(chǔ)在瀏覽器里,它可能會(huì)被盜,因?yàn)闉g覽器并不是完全安全的。任何人都能看見(jiàn)網(wǎng)頁(yè)的代碼,或者使用開(kāi)發(fā)工具來(lái)獲取訪問(wèn)令牌。
解決方案:未了避免將訪問(wèn)令牌暴露在瀏覽器中,客戶端的前端從授權(quán)服務(wù)端獲得授權(quán)碼,然后發(fā)送這個(gè)授權(quán)碼到客戶端的后端。現(xiàn)在,為了用授權(quán)碼交換訪問(wèn)令牌,我們需要一個(gè)叫做客戶密碼(client_secret)的東西。這個(gè)客戶密碼只有客戶端的后端知道,然后后端向授權(quán)服務(wù)端發(fā)送一個(gè) POST 請(qǐng)求,其中包含了授權(quán)碼和客戶密碼。這個(gè)請(qǐng)求可能如下所示:
復(fù)制代碼
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
授權(quán)服務(wù)端會(huì)驗(yàn)證客戶密碼和授權(quán)碼,然后返回一個(gè)訪問(wèn)令牌。后端程序存儲(chǔ)了這個(gè)訪問(wèn)令牌并且可能使用此令牌來(lái)訪問(wèn)資源服務(wù)端。這樣一來(lái),瀏覽器就無(wú)法讀取訪問(wèn)令牌了。
隱式流程
當(dāng)你沒(méi)有后端程序,并且你的網(wǎng)站是一個(gè)僅使用瀏覽器的靜態(tài)網(wǎng)站時(shí),應(yīng)該使用 OAuth2.0 隱式流程。在這種情況下,當(dāng)你用授權(quán)碼交換訪問(wèn)令牌時(shí),你跳過(guò)發(fā)生在后端程序的最后一步。在隱式流程中,授權(quán)服務(wù)端直接返回訪問(wèn)令牌。

OAuth2.0 授權(quán)碼流程
客戶端將瀏覽器重定向到授權(quán)服務(wù)端 URI,并將response_type設(shè)置成token,以啟動(dòng)授權(quán)流程。授權(quán)服務(wù)端處理用戶的登錄和授權(quán)許可。請(qǐng)求的返回結(jié)果是訪問(wèn)令牌,客戶端可以通過(guò)這個(gè)令牌訪問(wèn)資源服務(wù)端。
隱式流程被認(rèn)為不那么安全,因?yàn)闉g覽器負(fù)責(zé)管理訪問(wèn)令牌,因此令牌有可能被盜。盡管如此,它仍然被單頁(yè)應(yīng)用廣泛使用。
認(rèn)證與授權(quán)
正如我們所知,OAuth 解決了代理授權(quán)的問(wèn)題,但是它沒(méi)有提供一個(gè)認(rèn)證用戶身份的標(biāo)準(zhǔn)方法。你可以這樣認(rèn)為:
- OAuth2.0 用于授權(quán)
- OpenID Connect 用于認(rèn)證
如果你無(wú)法區(qū)分這些術(shù)語(yǔ),則以下是它們之間的區(qū)別:
- 認(rèn)證(Authentication)是確保通信實(shí)體是其所聲稱的實(shí)體。
- 授權(quán)(Authorization)是驗(yàn)證通信實(shí)體是否有權(quán)訪問(wèn)資源的過(guò)程。
換言之,認(rèn)證關(guān)注的是你是誰(shuí),授權(quán)關(guān)注的是你有什么權(quán)限。
OpenID Connect
OpenID Connect 是在 OAuth2.0 協(xié)議之上的標(biāo)識(shí)層。它拓展了 OAuth2.0,使得認(rèn)證方式標(biāo)準(zhǔn)化。

OAuth 不會(huì)立即提供用戶身份,而是會(huì)提供用于授權(quán)的訪問(wèn)令牌。 OpenID Connect 使客戶端能夠通過(guò)認(rèn)證來(lái)識(shí)別用戶,其中,認(rèn)證在授權(quán)服務(wù)端執(zhí)行。它是這樣實(shí)現(xiàn)的:在向授權(quán)服務(wù)端發(fā)起用戶登錄和授權(quán)告知的請(qǐng)求時(shí),定義一個(gè)名叫openid的授權(quán)范圍。在告知授權(quán)服務(wù)器需要使用 OpenID Connect 時(shí),openid是必須存在的范圍。
客戶端發(fā)起的用于 OpenID Connect 認(rèn)證請(qǐng)求 URI 會(huì)是如下的形式:
復(fù)制代碼
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
該請(qǐng)求的返回結(jié)果是客戶端可以用來(lái)交換訪問(wèn)令牌和 ID 令牌的授權(quán)碼。如果 OAuth 流程是隱式的,那么授權(quán)服務(wù)端將直接返回訪問(wèn)令牌和 ID 令牌。ID 令牌是 JWT,或者又稱 JSON Web Token。JWT 是一個(gè)編碼令牌,它由三部分組成:頭部,有效負(fù)載和簽名。在獲得了 ID 令牌后,客戶端可以將其解碼,并且得到被編碼在有效負(fù)載中的用戶信息,如以下例子所示:
復(fù)制代碼
{ "iss": "https://accounts.google.com", "sub": "10965150351106250715113082368", "email": "[email protected]", "iat": 1516239022, "exp": 1516242922}
聲明(Claim)
ID 令牌的有效負(fù)載包括了一些被稱作聲明的域。基本的聲明有:
- iss:令牌發(fā)布者
- sub:用戶的唯一標(biāo)識(shí)符
- email:用戶的郵箱
- iat:用 Unix 時(shí)間表示的令牌發(fā)布時(shí)間
- exp:Unix 時(shí)間表示的令牌到期時(shí)間
然而,聲明不僅限于上述這些域。由授權(quán)服務(wù)器對(duì)聲明進(jìn)行編碼。客戶端可以用這些信息來(lái)認(rèn)證用戶。
如果客戶端需要更多的用戶信息,客戶端可以指定標(biāo)準(zhǔn)的 OpenID Connect 范圍,來(lái)告知授權(quán)服務(wù)端將所需信息包括在 ID 令牌的有效負(fù)載中。這些范圍包括個(gè)人主頁(yè)(profile)、郵箱(email)、地址(address)和電話(phone)。
結(jié)語(yǔ)
練習(xí)你所學(xué)習(xí)的內(nèi)容總是好的。你可以訪問(wèn) Google OAuth 2.0 Playground 來(lái)使用 OAuth2.0 的授權(quán)范圍、授權(quán)碼和令牌。