什么是SSO?
如果你點(diǎn)開這里,那么我默認(rèn)你已經(jīng)知道什么是SSO單點(diǎn)登錄。如果你還不知道那么可能本文對(duì)你也許沒多大幫助。
前言
為什么要寫前言?其實(shí)我很懶我不想寫文章、前言只是發(fā)個(gè)牢騷,很多文章都有寫到SSO單點(diǎn)登錄的實(shí)現(xiàn)說明,但大多都是介紹什么是session、什么是cookie之類的,千篇一律。文章很長(zhǎng)、但都略過了怎么在一個(gè)瀏覽器中不同域名下共享登錄狀態(tài)的核心操作,這很氣人!為了節(jié)省時(shí)間本篇僅講解如何共享的問題。
正文
一、假設(shè)我們有網(wǎng)站 A(www.a.com)和網(wǎng)站B(www.b.com)。在某一時(shí)刻用戶登錄了網(wǎng)站A,那么怎么通知用戶本地的網(wǎng)站B標(biāo)記用戶已經(jīng)登錄呢?也許有點(diǎn)繞,看代碼。我們首先需要在網(wǎng)站A的登錄成功頁(yè)面嵌入如下HTML代碼(這樣在我們登錄A成功的同時(shí)嵌入B):
<iframe src="http://www.b.com" style="height: 0px;width: 0px;display: none;"></iframe>
二、當(dāng)然這還沒有完成,嵌入了B的目的就是要網(wǎng)站B在該瀏覽器標(biāo)記用戶已經(jīng)登錄那么標(biāo)記誰登錄了?這個(gè)誰就需要我們從A的登錄結(jié)果中傳遞過去。在網(wǎng)站A的登錄成功頁(yè)面繼續(xù)嵌入如下javascript代碼(實(shí)現(xiàn)數(shù)據(jù)從A到iframe中B的傳遞)postMessage實(shí)例參考:
<script>
var data=JSON.stringify({
msg:'建議以字符串形式傳輸字符串',
token:'這是從后臺(tái)而來的token'
})
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(data,'*');
</script>
三、此時(shí)我們已經(jīng)通過postMessage方法傳輸數(shù)據(jù)到B,那么下一步我們就必須在B中接收該數(shù)據(jù)并存貯就好了(我們第一步中iframe中的src不一定非要指向B的首頁(yè),可以是B的某個(gè)頁(yè)面即可,以下所指的B頁(yè)面均指該src指向的頁(yè)面)。那么我們?cè)贐頁(yè)面下嵌入如下javascript代碼接收A的數(shù)據(jù)并存貯:
<script>
// 有消息從父級(jí)傳來時(shí) 存貯 tokenData
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;
//e.data即是我們從A傳來的數(shù)據(jù) 這里使用localStorage進(jìn)行存貯(請(qǐng)勿使用sessionStorage)
localStorage.setItem("tokenData",e.data);
},false);
</script>
四、其實(shí)到上面就完成了共享的過程,第三步就相當(dāng)于在網(wǎng)站B在用戶瀏覽器中存了一些數(shù)據(jù),當(dāng)用戶打開網(wǎng)站B時(shí)通過localStorage.getItem("tokenData")即可判斷用戶的是否登錄,當(dāng)然這個(gè)數(shù)據(jù)一般僅存貯用戶的一個(gè)標(biāo)識(shí)和過期時(shí)間,是否過期則交由服務(wù)器仔細(xì)判斷。
實(shí)例
以下提供完整的實(shí)例代碼進(jìn)行測(cè)試
- a.html
<title>A首頁(yè)</title>
<meta charset="utf-8">
<p>
<button onclick="login();" id="login">同步登錄</button>
<p id="msg" style="display: none;">該用戶已經(jīng)登錄
<button onclick="localStorage.clear();" id="login">注銷</button>
</p>
</p>
<iframe src="http://www.b.com" style="height: 0px;width: 0px;display: none;"></iframe>
<script>
// 是否顯示“同步登錄”按鈕
var tokenData=localStorage.getItem('tokenData')
if(tokenData){
document.getElementById('login').style.display='none';
document.getElementById('msg').style.display='block';
}
// 同步登錄
function login(){
let data=JSON.stringify({
msg:'建議以字符串形式傳輸字符串',
token:'這是從后臺(tái)而來的token'
})
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(data,'*');
}
// 有消息從父級(jí)傳來時(shí) 存貯 tokenData
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;
localStorage.setItem("tokenData",e.data);
},false);
</script>
- b.html
<title>B首頁(yè)</title>
<meta charset="utf-8">
<p>
<button onclick="login();" id="login">同步登錄</button>
<p id="msg" style="display: none;">該用戶已經(jīng)登錄
<button onclick="localStorage.clear();" id="login">注銷</button>
</p>
</p>
<iframe src="http://www.a.com" style="height: 0px;width: 0px;display: none;"></iframe>
<script>
// 是否顯示“同步登錄”按鈕
var tokenData=localStorage.getItem('tokenData')
if(tokenData){
document.getElementById('login').style.display='none';
document.getElementById('msg').style.display='block';
}
// 同步登錄
function login(){
let data=JSON.stringify({
msg:'建議以字符串形式傳輸字符串',
token:'這是從后臺(tái)而來的token'
})
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(data,'*');
}
// 有消息從父級(jí)傳來時(shí) 存貯 tokenData
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;
localStorage.setItem("tokenData",e.data);
},false);
</script>
效果(注:A、B域名均在本地模擬)
- 未登錄之前A、B的效果均如下圖:

- 點(diǎn)擊A “同步登錄”按鈕 然后打開B
www.b.com如果你已經(jīng)打開,那么請(qǐng)刷新B即可看到如下效果:

- 例子很簡(jiǎn)潔由于一些細(xì)節(jié)并未過于深究,比如點(diǎn)擊A的“同步登錄”按鈕,A的狀態(tài)并未改變等,感興趣可以自行完善。
結(jié)語
也許你可能會(huì)覺得文章與標(biāo)題不符,因?yàn)檫@里并未講到后端服務(wù)器也未曾進(jìn)行登錄,僅僅實(shí)現(xiàn)的跨域同步信息。那么我就再啰嗦一下,跨域同步信息就是SSO實(shí)現(xiàn)的核心,無論是否前后端分離,你最終都會(huì)獲取到一個(gè)token或者sessionid之類的,共享這個(gè)數(shù)據(jù)就能實(shí)現(xiàn)效果。這里的A頁(yè)面和B頁(yè)面不僅僅指A、B的首頁(yè)而是指某一個(gè)可直接訪問的頁(yè)面即可。關(guān)于安全的問題,我們說了token類似:{id:1,time:1538884342},SSO服務(wù)器需要驗(yàn)證該token是否未被篡改、是否過期,id 為1的用戶是否登錄。略!