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

原文: [認證受權] 3.基於OAuth2的認證(譯)

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

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

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

本文旨在幫助潛在的身份提供者如何基於OAuth2構建用戶身份認證。實際上,若是你說「我有OAuth2,而且我須要身份認證」,那麼請繼續閱讀。github

什麼是認證(Authentication)?

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

然而,OAuth沒有告訴應用程序上述任何信息。OAuth對用戶沒有任何說明,也沒有說明如何證實他們的存在,即便他們就在那裏。對於OAuth的Client而言,它請求一個token,獲得一個token,並用這個token訪問一些API。但它不知道是誰受權的應用程序,以及甚至還有一個用戶在那裏。實際上,OAuth的大部分問題在於Client和被訪問的資源之間的鏈接上在用戶不存在的狀況下使用這種委託訪問。這對於Client受權來講是好的,可是對於用戶身份認證來講卻很是糟糕,由於認證須要肯定用戶是否存在(以及他們是誰)。json

另一個的混淆的因素,一個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的身份認證,以及可能出現什麼問題,以及如何確保安全和美味。ide

使用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),不知道會不會被接受。

相關文章
相關標籤/搜索