說說單點登陸的三種實現方式

前言

在 B/S 系統中,登陸功能一般都是基於 Cookie 來實現的。當用戶登陸成功後,通常會將登陸狀態記錄到 Session 中,或者是給用戶簽發一個 Token,不管哪種方式,都須要在客戶端保存一些信息(Session ID 或 Token ),並要求客戶端在以後的每次請求中攜帶它們。在這樣的場景下,使用 Cookie 無疑是最方便的,所以咱們通常都會將 Session 的 ID 或 Token 保存到 Cookie 中,當服務端收到請求後,經過驗證 Cookie 中的信息來判斷用戶是否登陸 。html

單點登陸(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 這個主域名之下,那麼它們就能夠經過這種方式來實現單點登陸。網絡

總結:此種實現方式比較簡單,但不支持跨主域名。app

實現方式二:認證中心

咱們能夠部署一個認證中心,認證中心就是一個專門負責處理登陸請求的獨立的 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 爲二級域名。

爲了不歧義,本人將使用「主域名「替代」一級域名「的說法。

相關文章
相關標籤/搜索