單點登錄三種方式,夠狠!夠勁爆!
本文轉(zhuǎn)載自微信公眾號「小明菜市場」,作者小明菜市場 。轉(zhuǎn)載本文請聯(lián)系小明菜市場公眾號。
前言
單點登錄分為三種機制,這三種機制分別為http無狀態(tài)協(xié)議,會話機制,登錄機制等這三種機制。
http無狀態(tài)協(xié)議
web應(yīng)用采用browser/server架構(gòu),http作為通信協(xié)議,http是無狀態(tài)協(xié)議,瀏覽器的每一次請求,服務(wù)器都會獨立處理,用如下圖進行說明,三次請求和響應(yīng)之間沒有任何關(guān)系。即,根據(jù)上圖可以看到,http的請求是無狀態(tài)的協(xié)議。
會話機制
瀏覽器第一次訪問請求服務(wù)器的時候,服務(wù)器再次創(chuàng)建一個會話,并把數(shù)據(jù)發(fā)送給瀏覽器,瀏覽器保存會話id,服務(wù)器從請求中通過id判斷是不是一個用戶。
服務(wù)器在內(nèi)存中保存會話對象,瀏覽器怎么保存會話id呢?你可能會想到兩種方式
- 請求參數(shù)
- cookie 將會話id作為每一個請求的參數(shù),服務(wù)器接收請求自然能解析參數(shù)獲得會話id,并借此判斷是否來自同一會話,很明顯,這種方式不靠譜。那就瀏覽器自己來維護這個會話id吧,每次發(fā)送http請求時瀏覽器自動發(fā)送會話id,cookie機制正好用來做這件事。cookie是瀏覽器用來存儲少量數(shù)據(jù)的一種機制,數(shù)據(jù)以”key/value“形式存儲,瀏覽器發(fā)送http請求時自動附帶cookie信息
tomcat會話機制當(dāng)然也實現(xiàn)了cookie,訪問tomcat服務(wù)器時,瀏覽器中可以看到一個名為“JSESSIONID”的cookie,這就是tomcat會話機制維護的會話id,使用了cookie的請求響應(yīng)過程如下圖
登錄狀態(tài)
有了會話機制,登錄狀態(tài)就是假設(shè)瀏覽器第一次請求服務(wù)器需要輸入用戶名和密碼驗證身份,服務(wù)器拿到持有這個會話的用戶就是合法用戶,應(yīng)該吧這個會話標記為已授權(quán)或者已登錄。標識如下所示;
- HttpSession session = request.getSession();
- session.setAttribute("isLogin", true);
用戶再次訪問,會查看到如下的登錄狀態(tài)
- HttpSession session = request.getSession();
- session.getAttribute("isLogin");
實現(xiàn)的模型如下所示:
每次請求受保護資源時都會檢查會話對象中的登錄狀態(tài),只有 isLogin=true 的會話才能訪問,登錄機制因此而實現(xiàn)。
下面開始闡述實現(xiàn)方式
實現(xiàn)方式之一:父域Cookie
Cookie 的作用域由 domain 屬性和 path 屬性共同決定。domain 屬性的有效值為當(dāng)前域或其父域的域名/IP地址,在 Tomcat 中,domain 屬性默認為當(dāng)前域的域名/IP地址。path 屬性的有效值是以“/”開頭的路徑,在 Tomcat 中,path 屬性默認為當(dāng)前 Web 應(yīng)用的上下文路徑。
如果將 Cookie 的 domain 屬性設(shè)置為當(dāng)前域的父域,那么就認為它是父域 Cookie。Cookie 有一個特點,即父域中的 Cookie 被子域所共享,換言之,子域會自動繼承父域中的Cookie。
利用 Cookie 的這個特點,不難想到,將 Session ID(或 Token)保存到父域中不就行了。沒錯,我們只需要將 Cookie 的 domain 屬性設(shè)置為父域的域名(主域名),同時將 Cookie 的 path 屬性設(shè)置為根路徑,這樣所有的子域應(yīng)用就都可以訪問到這個 Cookie 了。不過這要求應(yīng)用系統(tǒng)的域名需建立在一個共同的主域名之下,如 tieba.baidu.com 和 map.baidu.com,它們都建立在 baidu.com 這個主域名之下,那么它們就可以通過這種方式來實現(xiàn)單點登錄。
實現(xiàn)方式之二:認證中心
我們可以部署一個認證中心,專門處理這些問題 用戶統(tǒng)一在認證中心進行登錄,登錄成功后,認證中心記錄用戶的登錄狀態(tài),并將 Token 寫入 Cookie。應(yīng)用系統(tǒng)檢查當(dāng)前請求有沒有 Token,如果沒有,說明用戶在當(dāng)前系統(tǒng)中尚未登錄,那么就將頁面跳轉(zhuǎn)至認證中心。由于這個操作會將認證中心的 Cookie 自動帶過去,因此,認證中心能夠根據(jù) Cookie 知道用戶是否已經(jīng)登錄過了。如果認證中心發(fā)現(xiàn)用戶尚未登錄,則返回登錄頁面,等待用戶登錄,如果發(fā)現(xiàn)用戶已經(jīng)登錄過了,就不會讓用戶再次登錄了,而是會跳轉(zhuǎn)回目標 URL ,并在跳轉(zhuǎn)前生成一個 Token,拼接在目標 URL 的后面,回傳給目標應(yīng)用系統(tǒng)。
應(yīng)用系統(tǒng)拿到 Token 之后,還需要向認證中心確認下 Token 的合法性,防止用戶偽造。確認無誤后,應(yīng)用系統(tǒng)記錄用戶的登錄狀態(tài),并將 Token 寫入 Cookie,然后給本次訪問放行。(注意這個 Cookie 是當(dāng)前應(yīng)用系統(tǒng)的,其他應(yīng)用系統(tǒng)是訪問不到的。)當(dāng)用戶再次訪問當(dāng)前應(yīng)用系統(tǒng)時,就會自動帶上這個 Token,應(yīng)用系統(tǒng)驗證 Token 發(fā)現(xiàn)用戶已登錄,于是就不會有認證中心什么事了。著名的認證中心有ApereoCAS。XXL-SSO
LocalStorage跨域?qū)崿F(xiàn)
如何讓 Session ID(或 Token)在多個域中共享。父域 Cookie 確實是一種不錯的解決方案,但是不支持跨域。那么有沒有什么奇淫技巧能夠讓 Cookie 跨域傳遞呢?很遺憾,瀏覽器對 Cookie 的跨域限制越來越嚴格。Chrome 瀏覽器還給 Cookie 新增了一個 SameSite 屬性,此舉幾乎禁止了一切跨域請求的 Cookie 傳遞(超鏈接除外),并且只有當(dāng)使用 HTTPs 協(xié)議時,才有可能被允許在 AJAX 跨域請求中接受服務(wù)器傳來的 Cookie。
不過,在前后端分離的情況下,完全可以不使用 Cookie,我們可以選擇將 Session ID (或 Token )保存到瀏覽器的 LocalStorage 中,讓前端在每次向后端發(fā)送請求時,主動將 LocalStorage 的數(shù)據(jù)傳遞給服務(wù)端。這些都是由前端來控制的,后端需要做的僅僅是在用戶登錄成功后,將 Session ID (或 Token )放在響應(yīng)體中傳遞給前端。
在這樣的場景下,單點登錄完全可以在前端實現(xiàn)。前端拿到 Session ID (或 Token )后,除了將它寫入自己的 LocalStorage 中之外,還可以通過特殊手段將它寫入多個其他域下的 LocalStorage 中。其示例代碼如下所示:
- // 獲取 token
- var token = result.data.token;
- // 動態(tài)創(chuàng)建一個不可見的iframe,在iframe中加載一個跨域HTML
- var iframe = document.createElement("iframe");
- iframe.src = "http://app1.com/localstorage.html";
- document.body.append(iframe);
- // 使用postMessage()方法將token傳遞給iframe
- setTimeout(function () {
- iframe.contentWindow.postMessage(token, "http://app1.com");
- }, 4000);
- setTimeout(function () {
- iframe.remove();
- }, 6000);
- // 在這個iframe所加載的HTML中綁定一個事件監(jiān)聽器,當(dāng)事件被觸發(fā)時,把接收到的token數(shù)據(jù)寫入localStorage
- window.addEventListener('message', function (event) {
- localStorage.setItem('token', event.data)
- }, false);
前端通過 iframe+postMessage() 方式,將同一份 Token 寫入到了多個域下的 LocalStorage 中,前端每次在向后端發(fā)送請求之前,都會主動從 LocalStorage 中讀取 Token 并在請求中攜帶,這樣就實現(xiàn)了同一份 Token 被多個域所共享。