如何在head与body脚本间安全通信?防止第三方脚本窃取数据
客户端安全通信方案(绕过第三方脚本拦截)
针对你这种head脚本与body应用脚本被第三方脚本隔开的场景,以下是几种无需服务器端支持、且能有效防止第三方脚本窃取数据的方案:
方案1:预共享密钥加密传输(最安全)
利用Web Crypto API的对称加密算法,将敏感数据加密后存在全局,body脚本用预共享的密钥解密。第三方即使拿到加密数据,没有密钥也无法破解。
实现步骤:
- 预共享密钥:在head脚本和body应用脚本中硬编码同一个AES密钥(建议用Base64编码混淆,避免明文暴露)。
- head脚本加密数据:
// head 脚本 (async function() { // 预共享的AES密钥(Base64编码,需自行生成) const keyStr = 'YOUR_BASE64_ENCODED_AES_KEY'; const sharedKey = await crypto.subtle.importKey( 'raw', Uint8Array.from(atob(keyStr), c => c.charCodeAt(0)), { name: 'AES-GCM' }, false, ['encrypt'] ); // 你的敏感数据 const secretData = { userId: 123, token: 'xxx' }; const encodedData = new TextEncoder().encode(JSON.stringify(secretData)); const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM要求12字节IV const encryptedData = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, sharedKey, encodedData ); // 将加密数据和IV存为不可枚举的全局属性(避免被常规遍历发现) const storageKey = Math.random().toString(36).slice(2); Object.defineProperty(window, storageKey, { value: { encrypted: encryptedData, iv }, enumerable: false, configurable: true }); // 把存储密钥加密后存在固定全局变量(供body脚本获取) const encodedStorageKey = new TextEncoder().encode(storageKey); const keyIv = crypto.getRandomValues(new Uint8Array(12)); const encryptedStorageKey = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: keyIv }, sharedKey, encodedStorageKey ); window.__encryptedStorageKey = { data: encryptedStorageKey, iv: keyIv }; })(); - body应用脚本解密数据:
// body 应用脚本 (async function() { const keyStr = 'YOUR_BASE64_ENCODED_AES_KEY'; // 和head脚本一致的密钥 const sharedKey = await crypto.subtle.importKey( 'raw', Uint8Array.from(atob(keyStr), c => c.charCodeAt(0)), { name: 'AES-GCM' }, false, ['decrypt'] ); // 解密存储密钥 const { data: encryptedStorageKey, iv: keyIv } = window.__encryptedStorageKey; const decodedStorageKey = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: keyIv }, sharedKey, encryptedStorageKey ); const storageKey = new TextDecoder().decode(decodedStorageKey); // 获取加密数据并解密 const { encrypted, iv } = window[storageKey]; const decodedData = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, sharedKey, encrypted ); const secretData = JSON.parse(new TextDecoder().decode(decodedData)); // 清理全局残留数据 delete window[storageKey]; delete window.__encryptedStorageKey; // 处理你的数据 console.log(secretData); })();
方案2:一次性限时访问函数(轻量)
如果不需要强加密,可通过限时、一次性的全局函数传递数据,降低第三方脚本窃取的概率。
实现步骤:
- head脚本定义访问函数:
// head 脚本 const secretData = { userId: 123, token: 'xxx' }; const expireTime = Date.now() + 5000; // 5秒后过期 let isCalled = false; window.__fetchSecretData = function() { if (!isCalled && Date.now() < expireTime) { isCalled = true; delete window.__fetchSecretData; // 调用后立即删除函数 return secretData; } return null; }; - body应用脚本调用函数:
// body 应用脚本 const secretData = window.__fetchSecretData(); if (secretData) { // 处理数据 console.log(secretData); }
注意:此方案依赖body脚本快速加载执行,若第三方脚本提前调用函数会导致数据泄露,适合对安全性要求不极高的场景。
方案3:Closed Shadow DOM存储(隔离性强)
利用Closed模式的Shadow DOM存储数据,外部脚本无法直接访问Shadow Root,仅head脚本可提供访问入口。
实现步骤:
- head脚本创建Shadow DOM存储数据:
// head 脚本 const secretData = { userId: 123, token: 'xxx' }; const shadowHost = document.createElement('div'); shadowHost.style.display = 'none'; document.head.appendChild(shadowHost); // Closed模式:外部脚本无法获取shadowRoot const shadowRoot = shadowHost.attachShadow({ mode: 'closed' }); // 将数据存在Shadow DOM内的元素上 const dataEl = document.createElement('span'); dataEl.dataset.secret = JSON.stringify(secretData); shadowRoot.appendChild(dataEl); // 定义一次性访问函数 let isCalled = false; window.__getSecret = function() { if (!isCalled) { isCalled = true; delete window.__getSecret; return JSON.parse(dataEl.dataset.secret); } return null; }; - body应用脚本调用函数:
// body 应用脚本 const secretData = window.__getSecret(); if (secretData) { // 处理数据 console.log(secretData); }
优势:Shadow DOM的Closed模式天然隔离外部脚本,即使第三方脚本遍历DOM也无法访问内部数据,仅能通过你提供的函数获取。
内容的提问来源于stack exchange,提问作者Yossi Rubin
相关产品推荐
相关产品推荐

