現代應用程式的組成複雜,驗證與授權的流程也隨之繁複,現代框架試圖將這一切隱藏起來,但也令開發者更難以區別驗證與授權的差異。實際上,這是兩件不同的任務,既可以在單一容器中實作,在涉及第三方時,也有OAuth2、OpenID Connect等協定規範。

區別驗證與授權

事情一開始很單純,「caterpillar才能看到這個訊息」既然如此,應用程式如何驗證你真的是caterpillar?

驗證(Authentication)是證明身分(identity)的機制,例如,authenticate(name, passwd)方法定義了如何使用name與passwd進行驗證。此外,驗證方式不僅是基於名稱及密碼,也有可能基於憑證(Certificate)之類的機制。

一旦caterpillar通過驗證,就可以看到訊息,也就是說,另外有個機制決定訊息資源可否授權觀看,就像授權(Authorization)定義了身分與資源之間的存取控制規則,例如,if(authorized()) { show("message"); }這個流程,定義了"message"是否可以顯示。

在最簡單的情境中,驗證與授權可能混雜在一起,例如,「caterpillar才能看到這個訊息」,只要if(authenticate(name, passwd)) { show("message"); }就可以實現,到了這時候,authenticate()與上述的authorized()的任務就重疊了;然而,當應用程式有了一定的複雜性之後,驗證與授權的概念就會被分離開來。

例如,在Web容器當中,如果使用者驗證通過之後(authenticate()的實作傳回true),常見在Session物件中放個Token,代表已驗證,後續資源頁面判斷Session中存在Token(authorized()()傳回true),就會顯示相關的訊息資源,此時,authenticate()與authorized(),就是被分離的驗證與授權機制。

在更複雜的情況下,就必須有更複雜的驗證、授權方式,像是Java EE容器安全或Spring Security之類的框架,就定義了使用者、角色、資源等名詞,在驗證成功後,會以某方式儲存Token,其中包含角色等資訊,而在存取資源時,憑藉的是角色與資源之間已定義好的對應關係,決定是否授予資源

OAuth2授權協定

當應用程式只運行在單一容器時,使用者與資源、驗證與授權,只要在單一容器上定義或實作,就可以了;然而,若應用程式的資源是分散在多臺機器(多個容器),而且,存取每個資源前,若都要驗證,就會麻煩了。相對地,若客戶端能夠在驗證一次之後,帶著授權資訊來請求多個資源伺服器,而資源伺服器不需進行驗證,只認授權資訊來提供資源就好了。

最簡單的方式之一,就是在驗證通過後,客戶端透過HTTP基本驗證的原理,也就是透過BASIC標頭來攜帶授權資訊,然而這只適用於簡單的情境,在更複雜的場景中,需要將授權流程獨立出來,在驗證無誤之後,授權伺服器發給客戶端Access Token,客戶端再拿著Access Token向多個資源擁有者提出請求,等到資源擁有者確認Access Token合法性之後,才授予受保護的資源。

為了讓這類被獨立出來的授權流程有一致性,就有了OAuth規範。在先前專欄〈從簡單到繁複的OAuth2〉就談過,依需求的不同,目前OAuth2就規範了四種授權類型流程;不過,OAuth2本身未規範Access Token應該是什麼樣子,因此,為了增加Access Token的安全(像是避免被竄改),以及增加Token本身攜帶資訊的能力,我們可以使用JSON Web Tokens(https://jwt.io),簡稱JWT,它對Token制定了規範,具有對Token進行簽署、資源伺服器可以直接確認Token等優點。

必須區別的是,雖然OAuth2在流程中會涉及授權伺服器,授權伺服器在一開始勢必要處理驗證的問題,然而怎麼處理驗證,並不在OAuth2的規範。因為,在OAuth2的官方網站一開始也寫了,它是個用於授權的協定(protocol for authorization)。

在OAuth2結合JWT的場景中,Token中可能會帶有使用者名稱、角色等資訊,有些開發者誤以為它們是用於驗證,實際上,這些資訊是用於授權,Token中的資訊是在驗證過後才能取得;另一方面,Token只提供授權資訊,至於資源提供者收到Token後,如何運用其中資訊來決定資源的提供方式,OAuth2也不規範這塊。

簡單來說,OAuth2只是個協定,規範了授權資訊如何請求、提供,然而,並未規範如何實作驗證、如何根據授權資訊提供資源等,因為,這些是Java EE或Spring Security等安全框架的事,OAuth2與這類安全框架,基本上不存在取代的關係。

驗證協定OpenID Connect

OAuth2是第三方授權的協定規範,那麼,是否有第三方驗證的規範呢?也就是將驗證相關資訊,註冊在可信任的身分提供者(Identity Provider),在依賴方(Relaying Party)需要驗證的場合時,由身分提供者來提供身分資訊,依賴方取得身分後進行驗證,以便進一步使用依賴方的功能。例如,有些社交網站常被用來作為身分提供者,一般人可直接使用社交網站上的帳號登入,因此,這類機制常被稱為社交登入(Social login)。

曾經的OpenID 1和2是獨立的協定,也被Google、Yahoo等業者支援,然而後來有些開發者,試著使用OAuth2結合JWT,在JWT中放入驗證資訊以實現驗證(而不是授權),於是,進一步地建立了OpenID Connect(OIDC)規範(https://bit.ly/2NJYWRI)。

由於OIDC是基於OAuth2,因此,我們如果是在認識OAuth2的情況下,會比較容易理解OpenID Connect。

同時,OIDC改變了OAuth2的部份語意以用於驗證,相對於OAuth2取得的Access Token是用於授權,OIDC中的ID Token是用來驗證使用者是否為其宣稱的身分,其中也包含其他使用者資訊,ID Token使用JWT來攜帶資訊,OIDC也規範了取得ID Token後對其進行驗證的方式。

簡單來說,在OAuth2中,授權伺服器在一開始須處理驗證的問題,然而該怎麼處理驗證,並不在OAuth2的規範之中,是由OIDC補足這塊,例如,授權伺服器也許一開始用Spring Security透過資料庫進行驗證,現在可以實作AuthenticationProvider,透過OIDC從第三方OpenID提供者取得ID Token,以便進行驗證,不過,要注意的是,在驗證通過之後,授權流程等就不關OIDC的事了。

試著從規範來理解

OAuth2或OpenID Connect規範的細節很多,想瞭解並不容易,現代程式庫或框架隱藏了許多細節,只留下必要的部份給開發者設定或實作,更多時候是第三方應用程式隱藏了更多流程,只留下自己的一套設定給開發者遵守,JWT其實只是個資訊載體,其中可能包含授權、驗證資訊,或兩者皆有,這一切混淆後,就常令開發者往往搞不清楚:現在是在做驗證還是授權?

若是如此,開發者應該試著從規範來理解整個流程,釐清哪些細節被程式庫、框架或應用程式隱藏,如此一來,才能認清何時該用Java EE容器安全或Spring Security,哪時該用OAuth2,哪個地方又該採用OpenID Connect。

作者簡介


Advertisement

更多 iThome相關內容