面試官:來(lái)說(shuō)說(shuō)單點(diǎn)登錄的三種實(shí)現(xiàn)方式
來(lái)源:cnblogs.com/yonghengzh/p/13712729.html
在 B/S 系統(tǒng)中,登錄功能通常都是基于 Cookie 來(lái)實(shí)現(xiàn)的。當(dāng)用戶登錄成功后,一般會(huì)將登錄狀態(tài)記錄到 Session 中,或者是給用戶簽發(fā)一個(gè) Token,無(wú)論哪一種方式,都需要在客戶端保存一些信息(Session ID 或 Token ),并要求客戶端在之后的每次請(qǐng)求中攜帶它們。在這樣的場(chǎng)景下,使用 Cookie 無(wú)疑是最方便的,因此我們一般都會(huì)將 Session 的 ID 或 Token 保存到 Cookie 中,當(dāng)服務(wù)端收到請(qǐng)求后,通過(guò)驗(yàn)證 Cookie 中的信息來(lái)判斷用戶是否登錄 。
單點(diǎn)登錄(Single Sign On, SSO)是指在同一帳號(hào)平臺(tái)下的多個(gè)應(yīng)用系統(tǒng)中,用戶只需登錄一次,即可訪問(wèn)所有相互信任的應(yīng)用系統(tǒng)。舉例來(lái)說(shuō),百度貼吧和百度地圖是百度公司旗下的兩個(gè)不同的應(yīng)用系統(tǒng),如果用戶在百度貼吧登錄過(guò)之后,當(dāng)他訪問(wèn)百度地圖時(shí)無(wú)需再次登錄,那么就說(shuō)明百度貼吧和百度地圖之間實(shí)現(xiàn)了單點(diǎn)登錄。
單點(diǎn)登錄的本質(zhì)就是在多個(gè)應(yīng)用系統(tǒng)中共享登錄狀態(tài)。如果用戶的登錄狀態(tài)是記錄在 Session 中的,要實(shí)現(xiàn)共享登錄狀態(tài),就要先共享 Session,比如可以將 Session 序列化到 Redis 中,讓多個(gè)應(yīng)用系統(tǒng)共享同一個(gè) Redis,直接讀取 Redis 來(lái)獲取 Session。
當(dāng)然僅此是不夠的,因?yàn)椴煌膽?yīng)用系統(tǒng)有著不同的域名,盡管 Session 共享了,但是由于 Session ID 是往往保存在瀏覽器 Cookie 中的,因此存在作用域的限制,無(wú)法跨域名傳遞,也就是說(shuō)當(dāng)用戶在 app1.com 中登錄后,Session ID 僅在瀏覽器訪問(wèn) app1.com 時(shí)才會(huì)自動(dòng)在請(qǐng)求頭中攜帶,而當(dāng)瀏覽器訪問(wèn) app2.com 時(shí),Session ID 是不會(huì)被帶過(guò)去的。實(shí)現(xiàn)單點(diǎn)登錄的關(guān)鍵在于,如何讓 Session ID(或 Token)在多個(gè)域中共享。
實(shí)現(xiàn)方式一:父域 Cookie
在將具體實(shí)現(xiàn)之前,我們先來(lái)聊一聊 Cookie 的作用域。
Cookie 的作用域由 domain 屬性和 path 屬性共同決定。domain 屬性的有效值為當(dāng)前域或其父域的域名/IP地址,在 Tomcat 中,domain 屬性默認(rèn)為當(dāng)前域的域名/IP地址。path 屬性的有效值是以“/”開(kāi)頭的路徑,在 Tomcat 中,path 屬性默認(rèn)為當(dāng)前 Web 應(yīng)用的上下文路徑。
如果將 Cookie 的 domain 屬性設(shè)置為當(dāng)前域的父域,那么就認(rèn)為它是父域 Cookie。Cookie 有一個(gè)特點(diǎn),即父域中的 Cookie 被子域所共享,換言之,子域會(huì)自動(dòng)繼承父域中的Cookie。
利用 Cookie 的這個(gè)特點(diǎn),不難想到,將 Session ID(或 Token)保存到父域中不就行了。沒(méi)錯(cuò),我們只需要將 Cookie 的 domain 屬性設(shè)置為父域的域名(主域名),同時(shí)將 Cookie 的 path 屬性設(shè)置為根路徑,這樣所有的子域應(yīng)用就都可以訪問(wèn)到這個(gè) Cookie 了。不過(guò)這要求應(yīng)用系統(tǒng)的域名需建立在一個(gè)共同的主域名之下,如 tieba.baidu.com 和 map.baidu.com,它們都建立在 baidu.com 這個(gè)主域名之下,那么它們就可以通過(guò)這種方式來(lái)實(shí)現(xiàn)單點(diǎn)登錄。
總結(jié):此種實(shí)現(xiàn)方式比較簡(jiǎn)單,但不支持跨主域名。
實(shí)現(xiàn)方式二:認(rèn)證中心
我們可以部署一個(gè)認(rèn)證中心,認(rèn)證中心就是一個(gè)專門負(fù)責(zé)處理登錄請(qǐng)求的獨(dú)立的 Web 服務(wù)。
用戶統(tǒng)一在認(rèn)證中心進(jìn)行登錄,登錄成功后,認(rèn)證中心記錄用戶的登錄狀態(tài),并將 Token 寫入 Cookie。(注意這個(gè) Cookie 是認(rèn)證中心的,應(yīng)用系統(tǒng)是訪問(wèn)不到的。)
應(yīng)用系統(tǒng)檢查當(dāng)前請(qǐng)求有沒(méi)有 Token,如果沒(méi)有,說(shuō)明用戶在當(dāng)前系統(tǒng)中尚未登錄,那么就將頁(yè)面跳轉(zhuǎn)至認(rèn)證中心。由于這個(gè)操作會(huì)將認(rèn)證中心的 Cookie 自動(dòng)帶過(guò)去,因此,認(rèn)證中心能夠根據(jù) Cookie 知道用戶是否已經(jīng)登錄過(guò)了。如果認(rèn)證中心發(fā)現(xiàn)用戶尚未登錄,則返回登錄頁(yè)面,等待用戶登錄,如果發(fā)現(xiàn)用戶已經(jīng)登錄過(guò)了,就不會(huì)讓用戶再次登錄了,而是會(huì)跳轉(zhuǎn)回目標(biāo) URL ,并在跳轉(zhuǎn)前生成一個(gè) Token,拼接在目標(biāo) URL 的后面,回傳給目標(biāo)應(yīng)用系統(tǒng)。
應(yīng)用系統(tǒng)拿到 Token 之后,還需要向認(rèn)證中心確認(rèn)下 Token 的合法性,防止用戶偽造。確認(rèn)無(wú)誤后,應(yīng)用系統(tǒng)記錄用戶的登錄狀態(tài),并將 Token 寫入 Cookie,然后給本次訪問(wèn)放行。(注意這個(gè) Cookie 是當(dāng)前應(yīng)用系統(tǒng)的,其他應(yīng)用系統(tǒng)是訪問(wèn)不到的。)當(dāng)用戶再次訪問(wèn)當(dāng)前應(yīng)用系統(tǒng)時(shí),就會(huì)自動(dòng)帶上這個(gè) Token,應(yīng)用系統(tǒng)驗(yàn)證 Token 發(fā)現(xiàn)用戶已登錄,于是就不會(huì)有認(rèn)證中心什么事了。
這里順便介紹兩款認(rèn)證中心的開(kāi)源實(shí)現(xiàn):
Apereo CAS 是一個(gè)企業(yè)級(jí)單點(diǎn)登錄系統(tǒng),其中 CAS 的意思是”Central Authentication Service“。它最初是耶魯大學(xué)實(shí)驗(yàn)室的項(xiàng)目,后來(lái)轉(zhuǎn)讓給了 JASIG 組織,項(xiàng)目更名為 JASIG CAS,后來(lái)該組織并入了Apereo 基金會(huì),項(xiàng)目也隨之更名為 Apereo CAS。 XXL-SSO 是一個(gè)簡(jiǎn)易的單點(diǎn)登錄系統(tǒng),由大眾點(diǎn)評(píng)工程師許雪里個(gè)人開(kāi)發(fā),代碼比較簡(jiǎn)單,沒(méi)有做安全控制,因而不推薦直接應(yīng)用在項(xiàng)目中,這里列出來(lái)僅供參考。
實(shí)現(xiàn)方式三:LocalStorage 跨域
// 獲取 token
var?token = result.data.token;
// 動(dòng)態(tài)創(chuàng)建一個(gè)不可見(jiàn)的iframe,在iframe中加載一個(gè)跨域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);
// 在這個(gè)iframe所加載的HTML中綁定一個(gè)事件監(jiān)聽(tīng)器,當(dāng)事件被觸發(fā)時(shí),把接收到的token數(shù)據(jù)寫入localStorage
window.addEventListener('message', function?(event) {
????localStorage.setItem('token', event.data)
}, false);補(bǔ)充:域名分級(jí)
后臺(tái)回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
