[認證 & 授權] 3. 基於OAuth2的認證(譯)


OAuth 2.0 規范定義了一個授權(delegation協議,對於使用Web的應用程序和API在網絡上傳遞授權決策非常有用。OAuth被用在各鍾各樣的應用程序中,包括提供用戶認證的機制。這導致許多的開發者和API提供者得出一個OAuth本身是一個認證協議的錯誤結論,並將其錯誤的使用於此。讓我們再次明確的指出:

OAuth2.0 不是認證協議。
OAuth2.0 不是認證協議。
OAuth2.0 不是認證協議。

混亂的根源來自於在認證協議的內部實際上使用了OAuth,開發人員看到OAuth組件並與OAuth流程進行交互,並假設通過簡單地使用OAuth,他們就可以完成用戶認證。這不僅不是事情的真相,而且對服務提供商,開發人員以及最終用戶而言都是危險的事情。

本文旨在幫助潛在的身份提供者如何基於OAuth2構建用戶身份認證。實際上,如果你說“我有OAuth2,並且我需要身份認證”,那么請繼續閱讀。

什么是認證(Authentication)?

在用戶訪問一個應用程序的上下文環境中認證會告訴應用程序當前用戶是誰以及其是否存在。一個完整的認證協議可能還會告訴你一些關於此用戶的相關屬性,比如唯一標識符、電子郵件地址以及應用程序說“早安”時所需要的內容。認證是關於應用程序中存在的用戶,而互聯網規模的認證協議需要能夠跨網絡和安全邊界來執行此操作。

然而,OAuth沒有告訴應用程序上述任何信息。OAuth對用戶沒有任何說明,也沒有說明如何證明他們的存在,即使他們就在那里。對於OAuth的Client而言,它請求一個token,得到一個token,並用這個token訪問一些API。但它不知道是誰授權的應用程序,以及甚至還有一個用戶在那里。實際上,OAuth的大部分問題在於Client和被訪問的資源之間的連接上在用戶不存在的情況下使用這種委托訪問。這對於Client授權來說是好的,但是對於用戶身份認證來說卻非常糟糕,因為認證需要確定用戶是否存在(以及他們是誰)。

另外一個的混淆的因素,一個OAuth的過程通常包含在一些認證的過程中:資源所有者在授權步驟中向授權服務器進行身份驗證,客戶端向令牌端點中的授權服務器進行身份驗證,可能還有其他的。OAuth協議中的這些認證事件的存在不能夠說明OAuth協議本身能夠可靠地傳送認證。(譯注:我覺得可能作者想表達的是雖然OAuth是這些認證事件的消費者,但卻不是生產者,所以不能因為使用了認證,就等同於OAuth可以直接提供認證。)

事實證明盡管如此,還有一些事情可以和OAuth一起使用,以便在授權和授權協議之上創建身份認證協議。幾乎在所有的這些情況下,OAuth的核心功能都將保持不變,而發生的事件是用戶將他們的身份委派給他們正在嘗試登錄的應用程序。然后,客戶端應用程序成為身份API的消費者,從而找出先前授權給客戶端的用戶。以這種方式建立身份驗證的一個主要好處是允許管理最終用戶的同意,這在互聯網規模的跨域身份聯合中是非常重要的。另一個重要的好處是,用戶可以同時將訪問其他受保護的API委托給他們的身份,使應用程序開發人員和最終用戶管理更簡單。通過一個調用,應用程序可以找出用戶是否登錄,應該調用什么用戶,下載照片進行打印,並將更新發布到其消息流。這種簡單性是非常有吸引力的,但當這兩件事情同時進行時,許多開發人員將這兩個功能混為一談。

認證(Authentication) VS 授權(Authorization) : 一個比喻

為了幫助弄清楚這件事情,可以通過一個比喻來思考這個問題:巧克力 VS 軟糖。在一開始,這兩件事情的本質是截然不同的:巧克力是一種原料,軟糖就是糖果。巧克力可以用來做許多不同的事情,甚至可以自己使用。軟糖可以由許多不同的東西制成,其中一種可能是巧克力,但是需要多種成分來制造軟糖,甚至不會用到巧克力。因此,巧克力等於軟糖是錯誤的,而巧克力等於巧克力軟糖肯定是誇大其詞的。

在這個比喻中,OAuth是巧克力。這是一個多功能的原料,對許多不同的東西是至關重要的,甚至可以自己使用。認證更像是軟糖,至少有一些成分必須以正確的方式匯集在一起​​,使其成為可能,OAuth也許是這些成分之一(可能是主要原料),但可能也根本不需要參與其中。你需要一個配方來說明說明如何組合它們。

事實上,有一些眾所周知的配方可以與特定的供應商進行合作,比如Facebook Connect、使用Twitter登錄以及OpenID Connect(為Google的登錄系統提供了支持)。這些配方每個都添加了一些項目到OAuth中以創建身份認證協議,比如通用的profile API。可以在沒有OAuth的情況下構建身份驗證協議嗎?當然可以,就像有很多種非巧克力軟糖一樣。但是我們今天在這里談論的是專門針對基於OAuth2的身份認證,以及可能出現什么問題,以及如何確保安全和美味。

使用OAuth進行認證的常見誤區

即使使用OAuth來構建身份驗證協議是非常有可能的,但是在身份提供者或者身份消費者方面,有許多事情可能會讓這些人脫節。本文中描述的做法旨在通知身份提供商的潛在的常見風險,並向消費者通報在使用基於OAuth的身份認證系統時可避免的常見錯誤。

Access Token作為身份認證的證明

由於身份認證通常發生在頒發access token的之前, 因此使用access token作為身份認證的證明是非常誘人的。然而, 僅僅擁有一個access token並沒有告訴Client任何東西。在OAuth 中, token被設計為對Client不透明(譯注:上一篇[認證授權] 2.OAuth2授權(續) & JSON Web Token中有介紹), 但在用戶身份認證的上下文環境中, Client需要能夠從token中派生一些信息。

此問題的根源在於Client不是OAuth access token的預期受眾。相反, 它是該token的授權提出者, 而受眾實際上是受保護的資源。受保護的資源通常不能夠僅通過token的單獨存在來判斷用戶是否存在, 因為 oauth 協議的性質和設計, 在客戶端和受保護資源之間的連接上用戶是不可用的。為了應對這一點, 需要有一個針對客戶本身的假象,這可以通過定義一個雙重目的(dual-purposing)的Client可以解析和理解的access token來完成。但是由於一般的OAuth沒有為access token本身定義特定的格式貨結構,因此諸如OpenId Connect的ID Token和Facebook Connect的Signed在響應中提供一個次要的標記,它將和access token一起發送給Client中。這可以使得Client對主要的access token保持不透明,就像常規的OAuth中的那樣。

訪問受保護的API作為身份認證的證明

由於access token可以用於獲取一組用戶屬性,因此擁有一個有效的access token作為身份認證的證明也是很誘人的。在一些情況下,這種假設是成立的,因為在授權服務器商經過身份認證的用戶上下文中,token是剛剛被創建的。但是在OAuth中,這並不是獲取access token的唯一方法,Refresh Token和assertions(Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants:https://tools.ietf.org/html/rfc7521)可以在用戶不存在的情況下獲取access token。而在某些情況下,用戶無需身份驗證即可獲得access token(譯注:比如[認證授權] 1.OAuth2授權 - 5.4 Client Credentials Grant)。

此外,在用戶不存在后,access token通常還會存在很長時間。記住,OAuth是一個授權協議(delegation protocol),這對它的設計至關重要。這意味着,如果一個Client想要確保身份認證是有效的,那么簡單的使用token獲取用戶屬性是不夠的,因為OAuth保護的是資源,獲取用戶屬性的API(identity API)通常沒有辦法告訴你用戶是否存在。

注入Access Token

另外一個額外的威脅(非常危險)是當Client接受來自token endpoint的token時。這可能會發生在使用implicit流程(這個流程中直接把acces token作為url的hash參數(譯注:[認證授權] 1.OAuth2 授權 - 5.2.2 Access Token Response))中,並且Client不正確的使用state參數的時候。如果應用程序在不同的組件中傳遞 access token以“共享”訪問權限的時候,也會發生此問題。這里的問題在於它開辟了一個注入access token到應用程序外部(並可能在應用程序外部泄露)的地方。如果Client不通過某種機制驗證access token,則它無法區分access token是有效的令牌還是攻擊的令牌。

可以通過使用Authorization code來緩解這一點,並且只能通過授權服務器的token API(token endpoint)並使用一個state的值來避免被攻擊者猜中。

缺乏受眾限制

另外一個問題是,通過access token獲取一組用戶屬性的OAuth API通常沒有為返回的信息的受眾做任何限制。換句話話說,很可能有一個幼稚的(naive)Client,從其他的Client拿到一個有效的token來作為自己的登錄事件。畢竟令牌是有效的,對API的訪問也會返回有效的用戶信息。問題在於沒有用戶做任何事情來證明用戶存在,在這種情況下,用戶甚至都沒有授權給幼稚的(naive)Client。

通過將Client的認證信息與Client可以識別和驗證的標識符一起傳遞給Client,可以緩解此問題,從而允許客戶端區分自身的身份認證與另一應用程序的身份認證。通過在OAuth的過程中直接向Client傳遞一組身份認證信息,而不是通過受OAuth保護的API這樣的輔助機制來緩解它,從而防止Client在稍后的過程中注入未知來源的不可信的信息。

注入無效的用戶信息

如果攻擊者能夠攔截或者替換來自Client的一個調用,它可能會改變返回的用戶信息,而客戶端卻無法感知這一情況。這將允許攻擊者通過簡單地在正確的調用序列中交換用戶標識符來模擬一個幼稚的(naive)Client上的用戶。通過在身份認證協議過程中(比如跟隨OAuth的Token的頒發過程)直接從身份提供程序中獲取身份認證信息,並通過可校驗的簽名保護身份認證信息,可以緩解這一點問題。

每個潛在的身份提供商的不同協議

基於OAuth 身份(identity)API的最大問題在於,即使使用完全符合OAuth的機制,不同的提供程序不可避免的會使用不同的方式實現身份(identity)API。比如,在一個提供程序中,用戶標識符可能是用user_id字段來表示的,但在另外的提供程序中則是用subject字段來表示的。即使這些語義是等效的,也需要兩份代碼來處理。換句話說,雖然發生在每個提供程序中的授權是相同的,但是身份認證信息的傳輸可能是不同的。此問題可以在OAuth之上構建標准的身份認證協議來緩解,這樣無論身份認證信息來自何處,都可以用通用的方式傳輸。

這個問題之所以出現,是因為此處討論的身份認證的機制被明確的排除在OAuth的范圍之內。OAuth定義了一個沒有特定格式的token(no specific token format),定義了一個沒有通用的范圍(no common set of scopes)的access token,並且沒有解決受保護資源如何驗證access token

基於OAuth的用戶認證的標准:OpenId Connect

OpenID Connect是2014年初發布的開放標准,定義了一種基於OAuth2的可互操作的方式來來提供用戶身份認證。實際上,它是眾所周知的巧克力軟糖的配方,已經被多數的專家們嘗試和測試了。應用程序不必為每個潛在的身份提供程序構建不同的協議,而是可以將一個協議提供給多個提供程序。由於OpenId Connect是一個開放標准,所以可以自由的沒有任何限制的和知識產權問題的來實現。

OpenId Connect是直接建立在OAuth2之上的,在大多數情況下,部署在一個基於OAuth的基礎設施之上。它還使用JOSN簽名和加密規范,用來在傳遞攜帶簽名和加密的信息。OpenId Connect避免了上面討論的很多誤區。

ID Tokens

OpenID Connect Id Token是一個簽名的JSON Web Token(JWT:RFC7519),它和OAuth access token一起提供給Client應用程序。Id Token包含一組關於身份認證會話的聲明(claim),包括用戶的標識(sub)、頒發令牌的提供程序的標識符(iss)、以及創建此標識的Client的標識符(aud)。此外,Id Token還包含token的有效生存期(通常非常短)以及其他相關的上下文信息。由於Client知道Id Token的格式,因此它能直接分析出token的內容而無需依賴外部服務。此外,OpenId Connect還頒發access token給Client,允許Client保持對token的不透明,因為這是屬於OAuth規范的一部分。最后,token本身是由提供程序的私鑰進行簽名的,除了在獲取token中受TLS的保護之外,還添加了一個額外的保護層,以防止類似的模擬攻擊。通過對此token的一些校驗檢查,Client可以保護自己免受大量常見的攻擊。

由於Id token是授權服務器簽名的,它還提供了在authorization code(c_hash)和access token(at_hash)上添加分離簽名的位置,這些hash可以由Client來驗證,同時仍保留authorization code和access token對Client不透明的語義,從而防止這一類的注入攻擊。

應該指出的是,Client不再需要使用access token,因為Id token已經包含了處理身份認證所需的所有信息。然而,為了保持和OAuth的兼容性,OpenId Connect會同時提供Id token和acces token。

UserInfo Endpoint

除了Id token包含的信息之外,還定義了一個包含當前用戶信息的標准的受保護的資源。如上所述,這些信息不是身份認證的一部分,而是提供附加的標識信息。比如說應用程序提示說“早上好:Jane Doe”,總比說“早上好:9XE3-JI34-00132A”要友好的多。它提供了一組標准化的屬性:比如profile、email、phone和address。OpenId Connect定義了一個特殊的openid scope,可以通過access token來開啟Id token的頒發以及對UserInfo Endpoint的訪問。它可以和其他scope一起使用而不發生沖突。這允許OpenId Connect和OAuth平滑的共存。

動態服務發現以及客戶端注冊

OAuth2為了允許各種不同的部署而編寫,但是這樣的設計並沒有指定這些部署如何設置以及組件之間如何互相了解,在OAuth自己的世界中這是沒問題的。在使用OpenId Connect時,一個通用的受保護的API部署在各種各樣的Client和提供者中,所有這些都需要彼此互相了解才能運行。對於每個Client來說,不可能事先了解有關每個提供程序,並且要求每個提供者了解每個潛在的Client,這將大大削弱擴展性。

為了抵消這種情況,OpenId Connect定義了一個發現協議,它允許Client輕松的獲取有關如何和特定的身份認證提供者進行交互的信息。在另一方面,還定義了一個Client注冊協議,允許Client引入新的身份提供程序(identity providers)。通過這兩種機制和一個通用的身份API,OpenId Connect可以運行在互聯網規模上運行良好,在那里沒有任何一方事先知道對方的存在。

兼容OAuth2

即使擁有這些強大的身份認證功能,OpenId Connect(通過設計)仍然與純粹的OAuth2兼容,使其可以在開發人員花費最小代價的情況下部署在在OAuth系統之上。實際上,如果服務已經使用了OAuth和JOSE規范(以及JWT),該服務以及可以很好的支持OpenId Connect了。

譯注 & 原文

原文成文應該時比較早,一些信息已經過時了,我做了部分的刪減,現在OpenId Connect已經成為了一個非常龐大的協議族了,有很多相關的輔助協議來完善認證授權的相關需求。OpenId Connect具體的信息參見這里:http://openid.net/connect/。本人翻譯水平一般,如有錯誤之處,歡迎指正!

原作者:Justin Richer 。文章地址: https://oauth.net/articles/authentication/。

備注:原文標題是“User Authentication with OAuth 2.0”,覺得有點不妥,本來很多人對於AuthenticationAuthorization的認知就有一些混淆,而OAuth2是一個Authorization協議,而不是Authentication的協議,故而在翻譯的時候調整了原文的名稱。同時提了一個Pull Request(https://github.com/aaronpk/oauth.net/pull/154),不知道會不會被接受。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM