localStorage跨域存储,实战篇

您所在的位置:网站首页 跨域传递token localStorage跨域存储,实战篇

localStorage跨域存储,实战篇

2023-08-04 15:30| 来源: 网络整理| 查看: 265

先简单介绍两个概念:

(1)localStorage: 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;当页面被关闭时,存储在 sessionStorage 的数据会被清除。

(2)同源策略(same-origin policy):是浏览器执行的一种安全措施,目的是为了保证用户信息的安全,防止恶意的网站窃取数据。相关定义请参考developer.mozilla.org/zh-CN/docs/…

应用场景

一般用于业务域名众多,但是需要共享本地缓存数据。举个例子🌰,比如我用Chrome浏览天猫网站,在搜索栏搜索‘云安全’,过几天我用Chrome进了淘宝网,点击搜索栏想要获取到‘云安全’这个搜索记录,这个时候就需要用到localStroage的跨域存储(当然,也可以将搜索记录存储在服务端,这也是另一种解决办法)

实现原理 主要是通过postMessage来实现跨源通信 可以实现一个公共的iframe部署在某个域名中,作为共享域 将需要实现localStorage跨域通信的页面嵌入这个iframe 接入对应的SDK操作共享域,从而实现localStorage的跨域存储 具体实现

(注:以下代码均使用TypeScript编写)

1.共享域 CrossStorageHub

需要用到的数据结构

interface PermissionInfo { /** * 允许的源 * @example /\.example\.(?:com|net|cn)(?:\:\d+)?$/ */ origin: RegExp; /** * 允许的请求方法 */ allow: string[]; } 复制代码

PermissionInfo主要用来配置域名的相关权限

interface ParamsInfo { /** * 键 */ key: string; /** * set值 */ value: string; /** * 批量del */ keys: string[]; } interface RequestInfo { /** * 请求唯一标识 */ id: number; /** * 请求方法 */ method: 'get' | 'set' | 'del' | 'clear'; /** * 请求参数 */ params: ParamsInfo; } interface ResponseInfo { /** * 请求唯一标识 */ id: number; /** * 错误信息 */ error: string; /** * 请求结果 */ result: string; } 复制代码

初始化,检测localStorage是否可用

public static init(permissions: PermissionInfo[]): void { let available = true; // 判断localStorage是否可用 if (!window.localStorage) available = false; if (!available) { window.parent.postMessage('unavailable', '*'); return; } this._permissions = permissions || []; this._installListener(); window.parent.postMessage('ready', '*'); }; 复制代码

安装窗口消息事件所需的侦听器,兼容IE8及以上版本

private static _installListener(): void { const listener = this._listener; if (window.addEventListener) { window.addEventListener('message', listener, false); } else { window['attachEvent']('onmessage', listener); } }; 复制代码

核心函数,处理来自父窗口的消息,忽略任何来源与权限不匹配的消息。根据method调用对应的函数('get' | 'set' | 'del' | 'clear'),响应请求的唯一标识,错误信息,以及result。

private static _listener(message: MessageEvent): void { let origin: string, request: RequestInfo, method: string, error: string, result: string[] | string, response: string; origin = message.origin; // 检查message.data是否为有效json try { request = JSON.parse(message.data); } catch (err) { return; } // 校验requesthod数据类型 if (!request || typeof requesthod !== 'string') { return; } method = requesthod; if (!method) { return; } else if (!CrossStorageHub._permitted(origin, method)) { error = 'Invalid permissions for ' + method; } else { try { result = CrossStorageHub['_' + method](request.params); } catch (err) { error = err.message; } } response = JSON.stringify({ id: request.id, error: error, result: result }) window.parent.postMessage(response, origin); }; 复制代码

为了防止恶意网站的接入,所以进行权限检测,不满足条件的消息将被过滤。

private static _permitted(origin: string, method: string): boolean { let available: string[], match: boolean; available = ['get', 'set', 'del', 'clear']; if (!this._inArray(method, available)) { return false; } for (const entry of this._permissions) { if (!(entry.origin instanceof RegExp) || !(entry.allow instanceof Array)) { continue; } match = entry.origin.test(origin); if (match && this._inArray(method, entry.allow)) { return true; } } return false; }; 复制代码 /** * 取数据 * @param params */ private static _get(params: ParamsInfo): string { const { key } = params; return window.localStorage.getItem(key); } 复制代码

剩下的 set | del | clear 函数都是针对localStorage相关API的调用,我就不一一列举了

cross-storage-iframe.html crossStorage.init([ { origin: /\\*/, allow: ['get', 'set', 'del'] } ]); 复制代码

将代码编译成js文件进行引入。这里为了方便测试将origin配置为/\\*/,允许任何域名访问get,set,del方法。

2.父窗口CrossStorageSDK实现

主要是用来载入共享域并与之进行通信

sdk的初始化,加载cross-storage-iframe,以及监听message事件

class CrossStorageSDK { private static readonly _storageSrc: string = "https://www.example.com/cross-storage-iframe.html"; private static readonly _storageOrign: string = "https://www.example.com"; private static _ready: boolean = false; private static _callbacks: { [key: string]: (response?: ResponseInfo) => Promise | void } = {}; public static async init(): Promise { return new Promise((resolve, reject) => { this._installListener(); this._installCrossStorageIframe(); this._callbacks['ready'] = () => resolve(); this._callbacks['unavailable'] = () => reject(); }) } /** * 安装cross-storage-iframe */ private static _installCrossStorageIframe(): void { const iframe = document.createElement('iframe'); iframe.frameBorder = iframe.width = iframe.height = '0'; iframe.name = 'cross-storage'; iframe.src = this._storageSrc; document.body.appendChild(iframe); } /** * 安装窗口消息事件所需的侦听器 * 兼容IE8及以上版本 */ private static _installListener(): void { const listener = this._listener; if (window.addEventListener) { window.addEventListener('message', listener, false); } else { window['attachEvent']('onmessage', listener); } }; } 复制代码

处理来自cross-storage-iframe的事件消息,通过请求唯一标识进行回调。

private static _listener(message: MessageEvent): void { let response: ResponseInfo; // 忽略其他源发送的消息 if (message.origin !== CrossStorageSDK._storageOrign) return; if (message.data === 'unavailable') { CrossStorageSDK._callbacks['unavailable'](); return; } if (message.data === 'ready' && !CrossStorageSDK._ready) { CrossStorageSDK._ready = true; CrossStorageSDK._callbacks['ready'](); return; } // 检查message.data是否为有效json try { response = JSON.parse(message.data); } catch (err) { return; } CrossStorageSDK._callbacks[response.id] && CrossStorageSDK._callbacks[response.id](response) }; 复制代码

对cross-storage-iframe的localStorage进行get操作。

public static get(key: string): Promise { return new Promise((resolve, reject) => { const id = new Date().getTime(); const request: RequestInfo = { id: id, method: 'get', params: { key: key } } this._callbacks[id] = (response: ResponseInfo) => { response.error ? reject(response.error) : resolve(response.result); } this._sendRequest(request); }); } private static _sendRequest(request: RequestInfo): void { window.frames[0].postMessage(JSON.stringify(request), this._storageOrign); } 复制代码 如何测试

a.html和b.html分别接入CrossStorageSDK,调用sdk所暴露的相关方法,就能实现localStorage的跨域(源)通信。

最后

Safari浏览器由于隐私限制,默认是禁用localStorage跨域存储的,需要用户主动开启设置。

如有遗漏或哪里写的不对,欢迎指正。

完整代码地址:github.com/leaveLi/cro…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3