sessionStorage不能跨標簽頁解決方案
現有的瀏覽器存儲機制
localStorage :~5MB,數據永久保存直到用戶手動刪除
sessionStorage :~5MB,數據只在當前標簽頁有效
cookie :~4KB,可以設置成永久有效
session cookie :~4KB,當用戶關閉瀏覽器時刪除(並非總能立即刪除)
安全的認證token保存
一些重要的系統會要求當用戶關閉標簽頁時會話立刻到期。
為了達到這個目的,不僅絕對不應該使用cookies來保存任何敏感信息(例如認證token)。甚至session-cookies也無法滿足要求,它在標簽頁關閉(甚至瀏覽器完全關閉)后還會持續存活一定時間。
(任何時刻我們都不應該只使用cookies,它還有其他很多問題需要討論,例如CSRF)
這些問題就使得我們在保存認證token時應使用內存或sessionStorage。sessionStorage的好處是它允許跨多個頁面保存數據,並且也支持瀏覽器刷新操作。這樣用戶就可以在多個頁面之間跳轉或刷新頁面而保持登錄狀態。
Good。我們將token保存在sessionStorage,並在每次請求服務器時將token放在請求頭中來完成用戶的身份認證。當用戶關閉標簽頁,token會立即過期。
但多標簽頁怎么辦?
即便是在單頁面應用中也有一個很常見的情況,用戶經常希望打開多個標簽頁。而此場景下將token保存在sessionStorage中將會帶來很差的用戶體驗,每次開啟一個標簽頁都會要求用戶重新登錄。沒錯,sessionStorage不支持跨標簽頁共享數據。
利用localStorage事件來跨標簽頁共享sessionStorage
我利用localStorage事件提出了一種解決方案。
當用戶新開一個標簽頁時,我們先來詢問其它已經打開的標簽頁是不是有需要給我們共享的sessionStorage數據。如果有,現有的標簽頁會通過localStorage事件來傳遞數據到新打開的標簽頁中,我們只需要復制一份到本地sessionStorage即可。
傳遞過來的sessionStorage絕對不會保存在localStorage,從localStorage事件將數據中復制並保存到sessionStorage,這個流程是在同一個調用中完成,沒有中間狀態。而且數據是對應事件攜帶的,並不在localStorage中。(譯者注:作者意圖解釋這個方案的安全性)
在線例子
點擊“Set the sessionStorage”,然后打開多個標簽頁,你會發現sessionStorage共享了。
// 為了簡單明了刪除了對IE的支持
(function(){
if(!sessionStorage.length) {
// 這個調用能觸發目標事件,從而達到共享數據的目的
localStorage.setItem('getSessionStorage',Date.now());
};
// 該事件是核心
window.addEventListener('storage',function(event){
if(event.key =='getSessionStorage') {
// 已存在的標簽頁會收到這個事件
localStorage.setItem('sessionStorage',JSON.stringify(sessionStorage));
localStorage.removeItem('sessionStorage');
} elseif(event.key =='sessionStorage'&& !sessionStorage.length) {
// 新開啟的標簽頁會收到這個事件
vardata =JSON.parse(event.newValue),
value;
for(keyindata) {
sessionStorage.setItem(key, data[key]);
}
}
});
})();
(譯者注:上面的代碼是我從在線demo中截取的,原文中並無提到)
接近完美
我們現在擁有了一個幾乎非常安全的方案來保存會話token在瀏覽器里,並支持良好的多標簽頁用戶體驗。現在當用戶關閉標簽頁后能確保會話立即過期。難道不是么?
chrome和firefox都支持當用戶進行“重新打開關閉的標簽頁”或“撤銷關閉標簽頁”時恢復sessionStorage。F**k!(譯者注:作者原文用的是“Damn it!”,注意到那個嘆號了嗎?)
safari在這個問題上處理是正確的,它並不會恢復sessionStorag(只測試了上述這三個瀏覽器)。
對用戶而言,能夠確定sessionStorag已經過期的方法是直接重新打開網站,而不是選擇“重新打開關閉的標簽頁”。
除非chrome和firefox能夠解決這個bug。(但我預感開發組會稱其為“特性”)
即便存在這樣的bug,使用sessionStorag依然要比session-cookies方案或其他方案要安全。如果我們希望得到一個更加完美的方案,我們就需要自己來實現一個內存的方案來代替sessionStorag。(onbeforeunload也能做到,但不是太可靠且每次刷新頁面也會被清空。window.name也不錯,但它太老了且也不支持跨域保護)
跨標簽頁共享memoryStorage
這應該是唯一一個真正安全的實現瀏覽器端保存認證token的方法了,並且要保證用戶打開多個標簽頁不需要重新登錄。
關閉標簽頁,會話立即過期–這次是真真兒的。
這個方案的缺點是, 當只有一個標簽頁時 ,瀏覽器刷新會導致用戶重新登錄。安全總是要付出點代價的,很明顯這個缺點可能是致命的。
在線例子
設置一個memoryStorage,然后打開多個標簽頁,你會發現數據共享了。關閉所有標簽頁token會立即永久過期(memoryStorage其實就是一個javascript對象而已)。
(function(){
window.memoryStorage = {};
functionisEmpty(o){
for(variino) {
returnfalse;
}
returntrue;
};
if(isEmpty(memoryStorage)) {
localStorage.setItem('getSessionStorage',Date.now());
};
window.addEventListener('storage',function(event){
if(event.key =='getSessionStorage') {
localStorage.setItem('sessionStorage',JSON.stringify(memoryStorage));
localStorage.removeItem('sessionStorage');
} elseif(event.key =='sessionStorage'&& isEmpty(memoryStorage)) {
vardata =JSON.parse(event.newValue),
value;
for(keyindata) {
memoryStorage[key] = data[key];
}
}
});
})();