基於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
- 使用者首次訪問web App1,App1發現使用者未登入,攜帶目前訪問地址302到CAS Server登入頁。
- CAS Server登入頁檢查登入Session不存在,返回一個登入頁面。
- 填寫賬號,點選登入。
- CAS Server驗證賬號資訊成功,建立一個Ticket Granting Ticket(TGT),這個TGT就是當前登入使用者的session key。同時,建立一個service ticket並攜帶service ticket key,st key 作為引數跳轉回App1。
- App1用get傳送st key 去CAS Server驗證,驗證通過後返回登入使用者資訊。
- App1使用返回的登入使用者資訊構建當前系統的登入狀態,並用一個JSESSIONID標記(JSESSIONID是Apache的預設名),並攜帶這個JSESSIONID重新訪問App1。
- App1驗證JSESSIONID,登入成功,展示登入成功頁面。
- 第二次訪問,驗證JSESSIONID,直接訪問。
站點App2
- 使用者首次訪問web App2,App2發現使用者未登入,攜帶目前訪問地址302到CAS Server登入頁。
- CAS Server登入頁攜帶有App1生成的TGT,那麼直接做TCT的驗證,驗證成功不需要登入,建立一個App2的st key,302回App2。
- 後續和以上的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 //認證成功後的回撥地址,就是我們配置裡面的
授權端點有很多功能,這裡主要做了兩件事:
- 先判斷待過來的引數是否合法,比如clientid是不是配置裡面的,引數有沒有按要求、規範傳過來,引數是否被篡改,未驗證通過會報錯。
- 授權與否的校驗,根據攜帶的引數,判斷如果登入,就直接回調 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