1. 程式人生 > >基於IdentityServer4的OIDC實現單點登入(SSO)原理簡析

基於IdentityServer4的OIDC實現單點登入(SSO)原理簡析

# 寫在前面

IdentityServer4的學習斷斷續續,兜兜轉轉,走了不少彎路,也花了不少時間。可能是因為沒有閱讀原始碼,也沒有特別系統的學習資料,相關文章很多園子裡的大佬都有涉及,有系列文章,比如:

曉晨大佬的:https://www.cnblogs.com/stulzq/p/8119928.html

蟋蟀 大佬的:
https://www.cnblogs.com/xishuai/tag/[34]%E5%B0%8F%E8%8F%9C%E5%AD%A6%E4%B9%A0%E7%BC%96%E7%A8%8B-IdentityServer4/

李念輝、楊旭大佬均有相關文章,翻翻便可找到,就不一一列舉了。

但是,不知大家有沒有體會,好像看了很多id4(IdentityServer4,下同)的文章,對:

  • oidc究竟是個什麼蛤蟆?
  • OAuth2.0和OpenId Connect究竟有啥區別?
  • id4切確是什麼東西?
  • id4能幹些啥?
  • id4為什麼這麼設計?
  • id4各授權流程的區別是啥?
  • id4的SSO是基於什麼原理?

我還是時不時:

老實說,這些問題我也一懂半懂,還有就是看多了、時間跨度大,有的以為懂了提筆又忘了。這時大佬肯定說:

”誰叫你不去看原始碼?“

我默默的留下了兩行老淚:馬上看,馬上看。

但是我覺得id4很多新手和我一樣都有這困擾,上手門檻確實有的高。誠然看原始碼是個不錯的學習方法,但上來就讓新人或使用者看原始碼,無疑很不利於推廣、直接勸退啊,畢竟大部分都是跟我一樣的菜逼(對不起拖後腿了)。

對應前面提到的一串問題,我不打算在本文一一解答,我今天著重想聊的是:

id4的SSO是基於什麼原理?

寫本文的初衷是看到李念輝大佬的https://www.cnblogs.com/linianhui/p/oidc-in-action-sso.html,所以本文算算是讀博筆記。

啥是SSO?

SSO,全稱Single sign-on :在多個應用系統中,只需要登入一次,就可以訪問其他相互信任的應用系統。

比如你登入京東後檢視我的訂單:https://order.jd.com 然後再去檢視購物車https://cart.jd.com/cart.action就不需要重新登入。雖然這裡頂級域名一致,但其實單點登入並沒有此要求。

單點登入,很容易望文生義,以為單點登入就是限制使用者只能在一處登入。

下面我們說說我們我們常用的SSO的常用的實現方式。

SSO--基於Cookie的實現簡析

這種方式比較簡單,使用也比較廣泛。

比如我有兩個系統:a.example.com 和 b.example.com,很簡單,只需要搞個 passport.example.com 登入成功後往:example.com 這個頂級域寫登入成功的cookie就行了。而後不管你是c.example.com還是d.example.com或是+∞.example.com都只需要驗證登入的cookie就行,簡單方便。

不過這種實現方式有個比較大的缺陷:

不能跨域,不能跨頂級的域。

我不能說我登入成功後往jd.com域名下寫cookie吧。還有就是每個業務域名都要做登入cookie的校驗邏輯 ,不過這算小問題。

既然存在問題,就解決問題吧(這實在沒辦法解決發現問題的人啊)

SSO--基於CAS流程實現簡析

CAS簡介

Central Authentication Service,簡稱:CAS, 是一個單點登入框架或者說解決方案,開始是由耶魯大學的一個組織開發,後來歸到apereo管理。 同時CAS也是開源的,遵循apache 2.0協議,目前程式碼放在github上:https://github.com/apereo/cas

開啟就驚呆了,看到吧,一堆開源專案在用,這logo閃瞎我的鈦合金狗眼了

我們看下github簡介:

”CAS是一個企業級的、與語言無光的Web SSO解決方案,同時也嘗試整合授權和鑑權的需求。“

既然它是一個解決方案,那我們看看它到底提出了啥。

CAS的方案泳道圖分析

CAS SSO標準流程,看圖說話吧。

右鍵可看查原圖

我們看看發生了啥:

站點App1

  1. 使用者首次訪問web App1,App1發現使用者未登入,攜帶目前訪問地址302到CAS Server登入頁。
  2. CAS Server登入頁檢查登入Session不存在,返回一個登入頁面。
  3. 填寫賬號,點選登入。
  4. CAS Server驗證賬號資訊成功,建立一個Ticket Granting Ticket(TGT),這個TGT就是當前登入使用者的session key。同時,建立一個service ticket並攜帶service ticket key,st key 作為引數跳轉回App1。
  5. App1用get傳送st key 去CAS Server驗證,驗證通過後返回登入使用者資訊。
  6. App1使用返回的登入使用者資訊構建當前系統的登入狀態,並用一個JSESSIONID標記(JSESSIONID是Apache的預設名),並攜帶這個JSESSIONID重新訪問App1。
  7. App1驗證JSESSIONID,登入成功,展示登入成功頁面。
  8. 第二次訪問,驗證JSESSIONID,直接訪問。

站點App2

  1. 使用者首次訪問web App2,App2發現使用者未登入,攜帶目前訪問地址302到CAS Server登入頁。
  2. CAS Server登入頁攜帶有App1生成的TGT,那麼直接做TCT的驗證,驗證成功不需要登入,建立一個App2的st key,302回App2。
  3. 後續和以上的5,6,7,8 補邏輯相同,不贅述。

CAS的流程大概於此,實際的實現可能會複雜一點,可能會遇到各式各樣的問題。但有理論支撐,總體實現起來還是簡單,可靠有保證的。

下面說說基於Id4的OIDC是怎麼做單點登入的。

SSO--基於Id4的OIDC實現簡析

先準備環境

把官方samples下下來:https://github.com/IdentityServer/IdentityServer4/tree/master/samples

我刪掉了其他專案,剩下這兩個,一目瞭然:

分別把這兩個站點部署為:

http://odic.server.net

http://sso.client.net

我們再看一眼Idoc服務端配置:

配置IdentityServer,Configs新增這麼一個客戶端:

// sso implicit client
new Client
{
    ClientId = "ssoimplicit",  //這個client id 跟 MfcImplicit 裡面的配置要一致
    ClientName = "sso implicit clinet",
    AllowedGrantTypes = GrantTypes.Implicit,                
    RedirectUris = { "http://sso.client.net/signin-oidc" },
    PostLogoutRedirectUris = { "http://sso.client.net/signout-callback-oidc" },
    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email
    }
}

ok,我們用下抓包工具觀察下登入流程。

1、受保護客戶端頁面的訪問

sso.client.net是客戶端,而登入頁在sso.client.net服務端

我們先開啟需要登入才可以訪問的:http://sso.client.net/Home/Secure

這裡302到了授權端點 http://odic.server.net/connect/authorize

2、授權端點對客戶端請求的驗證

這就是步驟1中, 302挑戰的授權端點攜帶的引數。

我們先看看這些引數

client_id:ssoimplicit //我們前面配置的clinenid=
reponse_mode:form_post //指示oidc伺服器返回資料的形式是form表單
response_type:id_token //區別於oauth2授權請求的一點,必須包含有id_token這一項。
scope:openid profile //區別於oauth2授權請求的一點,必須包含有openid這一項。
state:oauth2定義的一個狀態字串,這裡的實現是加密儲存了一些客戶端的資訊,讓你最後可以在登入成功後帶回到客戶端,這個引數聽重要的
nonce:上一步中寫入cookie的值,這字串將來會包含在idtoken中原樣返回給客戶端,做安全校驗用。
redirect_uri:http://sso.client.net/signin-oidc //認證成功後的回撥地址,就是我們配置裡面的

授權端點有很多功能,這裡主要做了兩件事:

  1. 先判斷待過來的引數是否合法,比如clientid是不是配置裡面的,引數有沒有按要求、規範傳過來,引數是否被篡改,未驗證通過會報錯。
  2. 授權與否的校驗,根據攜帶的引數,判斷如果登入,就直接回調 redirect_uri引數地址:http://sso.client.net/signin-oidc,否,302到登入頁,引導使用者登入。

3、登入

初次登入,步驟2中的授權端點判斷當前未登入,還是302,跳轉登入頁,引導使用者登入授權。

點選登入,跳轉到是否授權頁面,這個頁面不一定展示,可通過配置Client的RequireConsent=false,跳過這個頁面。

of course Yes!

4、登入成功,客戶端構建登入狀態

我們看看點Yes Alow 之後的請求。

可以清楚的看到去到了:

http://odic.server.net/connect/authorize/callback

callback,哦,這是一個登入回撥,它幹了啥呢,我們仔細看響應:

哦,它這裡響應回了一個頁面,這個頁面只有一個表單,當頁面一載入完成立刻post表單到:action='http://sso.client.net/signin-oidc' 這個地址。並且仔細看看錶單的引數,前面的那些scope、state啊這些引數全都帶了過來,有意思,沒錯,這精妙的設計也是規範之一。

這裡它為什麼不帶上clientid呢?哈哈,客戶端自己肯定知道自己的clientid的,另外,還有中間這一大串的id_token你忘了嗎,這裡面可是可以攜帶資訊的哦。

來,我們看看這個id_token究竟是何方妖孽。

我們看到id_token帶有登入使用者的資訊:

iss:token發放的伺服器地址

aud:clientid

sid:會話資訊

kid:當前token的識別符號

name:使用者名稱

此外還有比如id_token的發放時間,過期時間,nonce,使用者非機密資訊等等。還有藍色部分需要使用客戶端公鑰驗證的簽名等等。

這個時候客戶端已經拿到登入使用者的資訊了,這時客戶端直接使用登入使用者資訊,構建當前應用sso.client.net的登入狀態即可。

比如下面的這個 Set名為Implicit的Cookie:

這個Cookie是可以刪掉的,它本身只維持了在sso.client.net的登入狀態而已,如果你刪掉它,它就會重新跑到授權端點:http://odic.server.net/connect/authorize 去驗證一下,發現當前會話還是處於登入狀態的,然後又302到登入回撥地址http://sso.client.net/signin-oidc,然後/signin-oidc從state引數裡面取出redirect_url,302回到當前頁面。

最後我們來看一下登入成功的頁面

單點登出

單點登出我就不細說了,使用:

//指定登入方案的方式登出
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);

//或者直接
await HttpContext.SignOutAsync();

暴力點刪除cookie也可以,不過那隻能算是半退出狀態吧。

總結

通過對以上一個使用Id4構建的OIDC實現的登入流程來看,OIDC的SSO它完全無光域名的,id4登入成功後,客戶端通過使用id_token來構建自身的登入狀態,一個client如此,N個皆然。

大家好像感覺這個SSO的實現方式跟前面的CAS流程很像誒,我們再看一遍前面CAS的圖

好像是發現了啥不得了的東西。

沒錯:openid也是基於CAS流程的一個實現(我根據理解猜的 沒有證據)。

再多說兩句

id4確實是好東西,暫時用不上也要多瞭解、學習,最好寫個部落格做個筆記加深下理解。

在理解的基礎上不要去背各種Flow有啥區別什麼的,知道什麼場景下用那種流程就行,也沒幾個。

善用官方文件、Sample。

本文示例原始碼

https://github.com/gebiWangshushu/cnblogs-demos

參考

https://yq.aliyun.com/articles/636281

https://www.cnblogs.com/linianhui/p/oidc-in-action-sso.h