面試官:來說說單點登入的三種實現方式
前言
在 B/S 系統中,登入功能通常都是基於 Cookie 來實現的。當使用者登入成功後,一般會將登入狀態記錄到 Session 中,或者是給使用者簽發一個 Token,無論哪一種方式,都需要在客戶端儲存一些資訊(Session ID 或 Token ),並要求客戶端在之後的每次請求中攜帶它們。在這樣的場景下,使用 Cookie 無疑是最方便的,因此我們一般都會將 Session 的 ID 或 Token 儲存到 Cookie 中,當服務端收到請求後,通過驗證 Cookie 中的資訊來判斷使用者是否登入 。
單點登入(Single Sign On, SSO)是指在同一帳號平臺下的多個應用系統中,使用者只需登入一次,即可訪問所有相互信任的應用系統。舉例來說,百度貼吧和百度地圖是百度公司旗下的兩個不同的應用系統,如果使用者在百度貼吧登入過之後,當他訪問百度地圖時無需再次登入,那麼就說明百度貼吧和百度地圖之間實現了單點登入。
單點登入的本質就是在多個應用系統中共享登入狀態。如果使用者的登入狀態是記錄在 Session 中的,要實現共享登入狀態,就要先共享 Session,比如可以將 Session 序列化到 Redis 中,讓多個應用系統共享同一個 Redis,直接讀取 Redis 來獲取 Session。當然僅此是不夠的,因為不同的應用系統有著不同的域名,儘管 Session 共享了,但是由於 Session ID 是往往儲存在瀏覽器 Cookie 中的,因此存在作用域的限制,無法跨域名傳遞,也就是說當使用者在 app1.com 中登入後,Session ID 僅在瀏覽器訪問 app1.com 時才會自動在請求頭中攜帶,而當瀏覽器訪問 app2.com 時,Session ID 是不會被帶過去的。實現單點登入的關鍵在於,如何讓 Session ID(或 Token)在多個域中共享。
實現方式一:父域 Cookie
在將具體實現之前,我們先來聊一聊 Cookie 的作用域。
Cookie 的作用域由 domain 屬性和 path 屬性共同決定。domain 屬性的有效值為當前域或其父域的域名/IP地址,在 Tomcat 中,domain 屬性預設為當前域的域名/IP地址。path 屬性的有效值是以“/”開頭的路徑,在 Tomcat 中,path 屬性預設為當前 Web 應用的上下文路徑。
如果將 Cookie 的 domain 屬性設定為當前域的父域,那麼就認為它是父域 Cookie。Cookie 有一個特點,即父域中的 Cookie 被子域所共享,換言之,子域會自動繼承父域中的Cookie。
利用 Cookie 的這個特點,不難想到,將 Session ID(或 Token)儲存到父域中不就行了。沒錯,我們只需要將 Cookie 的 domain 屬性設定為父域的域名(主域名),同時將 Cookie 的 path 屬性設定為根路徑,這樣所有的子域應用就都可以訪問到這個 Cookie 了。不過這要求應用系統的域名需建立在一個共同的主域名之下,如 tieba.baidu.com 和 map.baidu.com,它們都建立在 baidu.com 這個主域名之下,那麼它們就可以通過這種方式來實現單點登入。
總結:此種實現方式比較簡單,但不支援跨主域名。
實現方式二:認證中心
我們可以部署一個認證中心,認證中心就是一個專門負責處理登入請求的獨立的 Web 服務。
使用者統一在認證中心進行登入,登入成功後,認證中心記錄使用者的登入狀態,並將 Token 寫入 Cookie。(注意這個 Cookie 是認證中心的,應用系統是訪問不到的。)
應用系統檢查當前請求有沒有 Token,如果沒有,說明使用者在當前系統中尚未登入,那麼就將頁面跳轉至認證中心。由於這個操作會將認證中心的 Cookie 自動帶過去,因此,認證中心能夠根據 Cookie 知道使用者是否已經登入過了。如果認證中心發現使用者尚未登入,則返回登入頁面,等待使用者登入,如果發現使用者已經登入過了,就不會讓使用者再次登入了,而是會跳轉回目標 URL ,並在跳轉前生成一個 Token,拼接在目標 URL 的後面,回傳給目標應用系統。
應用系統拿到 Token 之後,還需要向認證中心確認下 Token 的合法性,防止使用者偽造。確認無誤後,應用系統記錄使用者的登入狀態,並將 Token 寫入 Cookie,然後給本次訪問放行。(注意這個 Cookie 是當前應用系統的,其他應用系統是訪問不到的。)當使用者再次訪問當前應用系統時,就會自動帶上這個 Token,應用系統驗證 Token 發現使用者已登入,於是就不會有認證中心什麼事了。
這裡順便介紹兩款認證中心的開源實現:
- Apereo CAS 是一個企業級單點登入系統,其中 CAS 的意思是”Central Authentication Service“。它最初是耶魯大學實驗室的專案,後來轉讓給了 JASIG 組織,專案更名為 JASIG CAS,後來該組織併入了Apereo 基金會,專案也隨之更名為 Apereo CAS。
- XXL-SSO是一個簡易的單點登入系統,由大眾點評工程師許雪裡個人開發,程式碼比較簡單,沒有做安全控制,因而不推薦直接應用在專案中,這裡列出來僅供參考。
總結:此種實現方式相對複雜,支援跨域,擴充套件性好,是單點登入的標準做法。
實現方式三:LocalStorage 跨域
前面,我們說實現單點登入的關鍵在於,如何讓 Session ID(或 Token)在多個域中共享。
父域 Cookie 確實是一種不錯的解決方案,但是不支援跨域。那麼有沒有什麼奇淫技巧能夠讓 Cookie 跨域傳遞呢?
很遺憾,瀏覽器對 Cookie 的跨域限制越來越嚴格。Chrome 瀏覽器還給 Cookie 新增了一個 SameSite 屬性,此舉幾乎禁止了一切跨域請求的 Cookie 傳遞(超連結除外),並且只有當使用 HTTPs 協議時,才有可能被允許在 AJAX 跨域請求中接受伺服器傳來的 Cookie。
不過,在前後端分離的情況下,完全可以不使用 Cookie,我們可以選擇將 Session ID (或 Token )儲存到瀏覽器的 LocalStorage 中,讓前端在每次向後端傳送請求時,主動將 LocalStorage 的資料傳遞給服務端。這些都是由前端來控制的,後端需要做的僅僅是在使用者登入成功後,將 Session ID (或 Token )放在響應體中傳遞給前端。
在這樣的場景下,單點登入完全可以在前端實現。前端拿到 Session ID (或 Token )後,除了將它寫入自己的 LocalStorage 中之外,還可以通過特殊手段將它寫入多個其他域下的 LocalStorage 中。
關鍵程式碼如下:
// 獲取 token
var token = result.data.token;
// 動態建立一個不可見的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中繫結一個事件監聽器,當事件被觸發時,把接收到的token資料寫入localStorage
window.addEventListener('message', function (event) {
localStorage.setItem('token', event.data)
}, false);
前端通過 iframe+postMessage() 方式,將同一份 Token 寫入到了多個域下的 LocalStorage 中,前端每次在向後端傳送請求之前,都會主動從 LocalStorage 中讀取 Token 並在請求中攜帶,這樣就實現了同一份 Token 被多個域所共享。
總結:此種實現方式完全由前端控制,幾乎不需要後端參與,同樣支援跨域。
補充:域名分級
從專業的角度來說(根據《計算機網路》中的定義),.com、.cn 為一級域名(也稱頂級域名),.com.cn、baidu.com 為二級域名,sina.com.cn、tieba.baidu.com 為三級域名,以此類推,N 級域名就是 N-1 級域名的直接子域名。
從使用者的角度來說,一般把可支援獨立備案的主域名稱作一級域名,如 baidu.com、sina.com.cn 皆可稱作一級域名,在主域名下建立的直接子域名稱作二級域名,如 tieba.baidu.com 為二級域名。
為了避免歧義,本人將使用“主域名“替代”一級域名“的說法。
最後
感謝你看到這裡,我是程式設計師麥冬,一個java開發從業者,深耕行業六年了,每天都會分享java相關技術文章或行業資訊
歡迎大家關注和轉發文章,也歡迎大家關注我的公眾號:程式設計師麥冬,回覆“007”領取200頁的Java核心面試資料整理
相關文章
- 單點登入的三種實現方式
- 面試官:說說Java物件的四種引用方式面試Java物件
- 面試官:給我說一下你專案中的單點登入是如何實現的?面試
- 面試官:說說反射的底層實現原理?面試反射
- vue實現單點登入的N種方式Vue
- 面試題:給我說一下你專案中的單點登入是如何實現的?面試題
- 面試官: 有了解過ReentrantLock的底層實現嗎?說說看面試ReentrantLock
- 面試官:說說你知道的幾種負載均衡分類面試負載
- 面試官:說說Java 原子類面試Java
- 面試官問:你有多少種方式實現三欄佈局?面試
- 面試官:說說CountDownLatch,CyclicBarrier,Semaphore的原理?面試CountDownLatch
- 面試官:說說Java物件的組成面試Java物件
- 面試官讓說出8種建立執行緒的方式,我只說了4種,然後掛了。。。面試執行緒
- 面試官:你說你精通 Docker,那你來詳細說說 Dockerfile 吧面試Docker
- 面試官:說說雙親委派模型?面試模型
- 面試官:如何實現掃碼登入功能?面試
- 面試官:說說你對ThreadLocal的瞭解面試thread
- 面試官:來說一說Go語言的函式呼叫慣例面試Go函式
- 面試官:說說降級、熔斷、限流面試
- 面試官問:ZooKeeper 有幾種節點型別?別再說 4 種啦!面試型別
- 實現登入態的幾種方式
- 面試官:說說資料庫事務吧面試資料庫
- 阿里P7面試官:請你簡單說一下類載入機制的實現原理?阿里面試
- 面試官:同學,說說 Applink 的使用以及原理面試APP
- 面試官:說說你對react生命週期的理解面試React
- 面試官說:來談談限流-從概念到實現,一問你就懵逼了?面試
- 透過面試題來說說Promise面試題Promise
- 面試官 | 說說移動端專案適配面試
- 騰訊面試官:兄弟,你說你會Webpack,那說說他的原理?面試Web
- 面試官:說說你對網路請求加密的理解?面試加密
- 面試官:說說什麼是泛型的型別擦除?面試泛型型別
- 面試官:說一說如何優雅的關閉執行緒池,我:shutdownNow,面試官:粗魯!面試執行緒
- 一口氣說出 6種 延時佇列的實現方法,面試官也得服佇列面試
- 說說如何在登入頁實現生成驗證碼功能
- 答面試官問:如何防超賣,有幾種實現方式面試
- 面試官:說一說Zookeeper中Leader選舉機制面試
- 面試官:實戰中用過CountDownLatch嗎?詳細說一說,我:啊這面試CountDownLatch
- 面試官:說一說CyclicBarrier的妙用!我:這個沒用過面試