1. 程式人生 > >基於OIDC(OpenID Connect)的SSO

基於OIDC(OpenID Connect)的SSO

在[認證授權]系列部落格中,分別對OAuth2和OIDC在理論概念方面進行了解釋說明,其間雖然我有寫過一個完整的示例(https://github.com/linianhui/oidc.example),但是卻沒有在實踐方面做出過解釋。在這裡新開一個系列部落格,來解釋其各種不同的應用場景。因為OIDC是在OAuth2之上的協議,所以這其中也會包含OAuth2的一些內容。

OIDC協議本身有很多的開源實現,這裡選取的是基於.Net的開源實現基於IdentityServer4。本系列的原始碼位於https://github.com/linianhui/oidc.example。clone下來後用管理員身份執行build.ps1來部署整個系統,其中可能會彈出UAC警告(指令碼會修改host檔案,記得允許管理員讀寫這個檔案先)。部署完後的樣子如下:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

640?wx_fmt=png&wxfrom=5&wx_lazy=1

本文中主要是關注一下SSO這部分的內容,主要是跨一級域單點登入統一登出功能。其中涉及到的站點有一下4個:

  1. oidc-server.dev:利用oidc實現的統一認證和授權中心,SSO站點。

  2. oidc-client-hybrid.dev:oidc的一個客戶端,採用hybrid模式。

  3. oidc-client-implicit.dev:odic的另一個客戶端,採用implicit模式。

  4. oidc-client-js.dev:oidc的又一個客戶端,採用implicit模式,純靜態網站,只有js和html,無服務端程式碼。

單點登入

通常來講,SSO包括統一的登入統一的登出

這兩部分。基於OIDC實現的SSO主要是利用OIDC服務作為使用者認證中心作為統一入口,使得所有的需要登入的地方都交給OIDC服務來做。更直白點說就是把需要進行使用者認證的客戶端中的使用者認證這部分都剝離出來交給OIDC認證中心來做。具體的互動流程如下:

0?wx_fmt=png

其中這三個客戶端是完全獨立的位於不同的域名之下,且沒有任何依賴關係,三者均依賴oidc-server.dev這個站點進行認證和授權,通訊協議為HTTP,那麼下面則通過它們之間的HTTP訊息來解釋其具體的流程。這個過程中使用fiddler來進行監視其所有的請求。

第1步:OIDC-Client- 觸發認證請求

在瀏覽器開啟oidc-client-implicit.dev

這個站點,開啟後如下(QQ這個先不管它,後面單獨介紹)。

0?wx_fmt=png

點選Oidc Login後,會觸發一個302的重定向操作。具體的HTTP請求和響應資訊如下:

0?wx_fmt=png

Request:Get後面的URL是我們點選Oidc Login的Url,這個URL包含一個引數,代表登入成功後所要回到的頁面是哪裡。

Response:伺服器返回了一個302重定向。

  1. Location的Url指向了oidc-server.dev這個站點,其中還攜帶了一大堆引數(引數後面一小節介紹);

  2. Set-Cookie設定了一個nonce的cookie,主要目的用於安全方面。

第2步:OIDC-Client - 認證請求

緊接上一步,瀏覽器在接收到第1步的302響應後,會對Location所指定的URL發起一個Get請求。這個請求攜帶的引數如下:

0?wx_fmt=png

其中引數的含義在OIDC的認證請求有詳細的解釋(注:其中採用的認證型別不管是authorization code,或者implict,還是hybrid都無關緊要,它們的區別只是其適用場景的差異,並不影響整個流程)。

  1. client_id=implicit-client:發起認證請求的客戶端的唯一標識,這個客戶端事先已經在oidc-server.dev這個站點註冊過了。

  2. reponse_mode=form_post:指示oidc伺服器應該使用form表單的形式返回資料給客戶端。

  3. response_type=id_token:區別於oauth2授權請求的一點,必須包含有id_token這一項。

  4. scope=openid profile:區別於oauth2授權請求的一點,必須包含有openid這一項。

  5. state:oauth2定義的一個狀態字串,這裡的實現是加密儲存了一些客戶端的狀態資訊(用於記錄客戶端的一些狀態,在登入成功後會有用處),oidc會在認證完成後原樣返回這個引數。

  6. nonce:上一步中寫入cookie的值,這字串將來會包含在idtoken中原樣返回給客戶端。

  7. redirect_uri:認證成功後的回撥地址,oidc-server.dev會把認證的資訊傳送給這個地址。

第3步:OIDC-Server - 驗證請求資訊

oidc-server.dev站點會驗證第2步中傳遞過來的資訊,比如client_id是否有效,redircet_uri是否合法,其他的引數是否合法之類的驗證。如果驗證通過,則會進行下一步操作。

第4步:OIDC-Server - 開啟登入頁面

在oidc-server.dev站點驗證完成後,如果沒有從來沒有客戶端通過oidc-server.dev登陸過,那麼第2步的請求會返回一個302重定向重定向到登入頁面如果是已經登入,則會直接返回第5步中生產重定向地址

0?wx_fmt=png

瀏覽器會開啟響應訊息中Location指定的地址(登入頁面)。如下:

0?wx_fmt=png

第5步:OIDC-Server - 完成使用者登入,同時記錄登入狀態

在第四步輸入賬戶密碼點選提交後,會POST如下資訊到伺服器端。

0?wx_fmt=png

伺服器驗證使用者的賬號密碼,通過後會使用Set-cookie維持自身的登入狀態。然後使用302重定向到下一個頁面。

第6步:瀏覽器 - 開啟上一步重定向的地址,同時自動發起一個post請求

0?wx_fmt=png

form的地址是在第2步中設定的回撥地址,form表單中包含(根據具體的認證方式authorization code,implict或者hybrid,其包含的資訊會有一些差異,這個例子中是採用的implicit方式)如下資訊:

  1. id_token:id_token即為認證的資訊,OIDC的核心部分,採用JWT格式包裝的一個字串。

  2. scope:使用者允許訪問的scope資訊。

  3. state:第1步中傳送的state,原樣返回。

  4. session_state:會話狀態。

id_token包含的具體的資訊如下:

0?wx_fmt=png

其中包含認證的伺服器資訊iss,客戶端的資訊aud,時效資訊nbf和exp,使用者資訊sub和nickname,會話資訊sid,以及第1步中設定的nonce。還有其簽名的資訊alg=RS256,表示idtoken最後的一段資訊(上圖中淺藍色的部分)是oidc-server.dev使用RSA256對id_token的header和payload部分所生產的數字簽名。客戶端需要使用oidc-server.dev提供的公鑰來驗證這個數字簽名

第7步:OIDC-Client - 接收第6步POST過來的引數,構建自身的登入狀態

0?wx_fmt=png

客戶端驗證id_token的有效性,其中驗證所需的公鑰來自OIDC的發現服務中的jwk_uri,這個驗證是必須的,目的時為了保證客戶端得到的id_token是oidc-sercer.dev頒發的,並且沒有被篡改過,以及id_token的有效時間驗證。數字簽名的JWT可以保證id_token的不可否認性,認證和完整性,但是並不能保證其機密性,所以id_token中千萬不要包含有機密性要求的敏感的資料。如果確實需要包含,則需要對其進行加密處理(比如JWE規範)。其中驗證也包含對nonce(包含在id_token中)的驗證(第1步設定的名為nonce的cookie)。

在驗證完成後,客戶端就可以取出來其中包含的使用者資訊來構建自身的登入狀態,比如上如中Set-Cookie=lnh.oidc這個cookie。然後清除第1步中設定的名為nonce的cookie。

最後跳轉到客戶端指定的地址(這個地址資訊被儲存在第1步中傳遞給oidc-server.dev的state引數中,被oidc-server.dev原樣返回了這個資訊)。然後讀取使用者資訊如下(這裡讀取的是id_token中的完整資訊):

0?wx_fmt=png

其他的客戶端登入

登入流程是和上面的步驟是一樣的,一樣會發起認證請求,區別在於已經登入的時候會在第4步直接返回post資訊給客戶端的地址,而不是開啟一個登入頁面,這裡就不再詳細介紹了。大家可以在本地執行一下,通過fiddler觀察一下它們的請求流程。貼一下oidc-client-hybrid.dev這個客戶端登入後的頁面吧:

0?wx_fmt=png

統一退出

退出的流程相比登入簡單一些。如下圖:

0?wx_fmt=png

其中核心部分在於利用瀏覽器作為中間的媒介,來逐一的通知已經登入的客戶端退出登入。

第1步:OIDC-Client - 觸發登出請求

點選Logout連結。

0?wx_fmt=png

點選退出後會觸發一個GET請求,如下:

0?wx_fmt=png

上圖這個請求會返回一個302的響應,Location的地址指向oidc-server.dev的一個endsession的介面。同時會通過Set-Cookie來清除自身的cookie。

第2步:OIDC-Client - 登出請求

瀏覽器通過GET訪問上一步中指定的Location地址。

0?wx_fmt=png

介面地址定義在OIDC的發現服務中的end_session_endpoint欄位中,引數資訊定義在OIDC的Front-Channel-Logout規範中。

第3步:OIDC-Server - 驗證登出請求

驗證上圖中傳遞的資訊,如果資訊無誤則再一次重定向到一個地址(這裡是IdentityServer4的實現機制,其實可以無需這個再次重定向的)。

第4步:OIDC-Server - 登出自身,返回包含IFrame的HTML

瀏覽器開啟第3步中重定向的地址:

0?wx_fmt=png

響應中會通過Set-Cookie(idsrv和idsrv.session)清除oidc-server.dev自身的登入狀態。然後包含一個HTML表單頁面,上圖中iframe指向的地址是IdentityServer4內部維持的一個地址。訪問這個地址後的資訊如下:

1 <!DOCTYPE html>2 <html>3     <style>iframe{display:none;width:0;height:0;}</style>4     <body>5         <iframe src='http://oidc-client-implicit.dev/oidc/front-channel-logout-callback?sid=b51ea235574807beb0deff7c6db6a381&iss=http%3A%2F%2Foidc-server.dev'></iframe>6         <iframe src='http://oidc-client-hybrid.dev/oidc/front-channel-logout-callback?sid=b51ea235574807beb0deff7c6db6a381&iss=http%3A%2F%2Foidc-server.dev'></iframe>7     </body>8 </html>

上面程式碼中的iframe是真正的呼叫已經登入的客戶端進行登出的地址(IdentityServer4會記錄下來已經登入的客戶端,沒有登陸過的和沒有配置啟用Front-Channel-Logout的則不會出現在這裡)。其中iframe指向的地址是OIDC客戶端在oidc-server.dev中註冊的時候配置的地址。引數則是動態附加上去的引數。

最後頁面中包含一個js指令碼檔案,在頁面load完成後,跳轉到第2步中指定的post_logout_redirect_uri指向的回撥頁面。

第5步:OIDC-Client - 處理登出回撥通知

在瀏覽器訪問上面程式碼中iframe指向的地址的時候,被動登出的OIDC客戶端會接收到登出通知。

0?wx_fmt=png

響應中通過Set-Cookie(lnh.oidc)清除了需要被動登出的客戶端的Cookie。至此,統一的登出完成。

總結

本文介紹了基於OIDC實現的SSO的工作原理和流程,但並未涉及到OIDC的具體實現IdentityServer4的是如何使用的(這部分通過讀我提供的原始碼應該是很容易理解的),旨在解釋一下如何用OIDC實現SSO,而非如何使用OIDC的某一個實現框架。OIDC是一個協議族,這些具體每一步怎麼做都是有標準的規範的,所以側重在了用HTTP來描述這個過程,這樣這個流程也就可以用在java,php,nodejs等等開發平臺上。

參考

本文原始碼:https://github.com/linianhui/oidc.example

認證授權:http://www.cnblogs.com/linianhui/category/929878.html

Id Token:http://www.cnblogs.com/linianhui/p/openid-connect-core.html#auto_id_5

JWT:http://www.cnblogs.com/linianhui/p/oauth2-extensions-protocol-and-json-web-token.html#auto_id_5

數字簽名:http://www.cnblogs.com/linianhui/p/security-based-toolbox.html#auto_id_16

OIDC:http://openid.net/connect/

IdentityServer4:https://github.com/IdentityServer/IdentityServer4

原文:http://www.cnblogs.com/linianhui/p/oidc-in-action-sso.html

.NET社群新聞,深度好文,歡迎訪問公眾號文章彙總 http://www.csharpkit.com

640?wx_fmt=jpeg