“敏感数据应该只在后端处理,前端只是展示。”——这句话听起来很安全,但在现代富客户端应用(如金融、医疗、企业 SaaS)中,前端不可避免地会接触令牌、用户隐私字段、加密密钥甚至临时凭证。
一旦处理不当,轻则信息泄露,重则导致账户接管(Account Takeover)。本文将揭示前端安全的常见误区,并提供可落地的防御策略。
[h1]误区 1:把 JWT 存在 localStorage 就“够安全”[/h1]
// 千万别这样做!
localStorage.setItem('token', jwt);
问题:localStorage 对 XSS(跨站脚本攻击)完全不设防。只要页面存在一个未过滤的 innerHTML = userInput,攻击者就能窃取令牌。
正确做法:
- 使用 HttpOnly + Secure Cookie 存储身份令牌;
- 若必须用前端存储(如 SPA 需要读取 token payload),则:
- 仅存
access_token的非敏感部分(如sub,exp); - 敏感操作(如修改密码)强制走后端验证 session。
- 仅存
[h1]误区 2:认为“HTTPS 就能防所有中间人攻击”[/h1]
HTTPS 确实加密传输,但无法阻止恶意扩展、调试器或 DevTools 窃取内存中的数据。
例如:
const secretKey = await crypto.subtle.importKey(...); // Web Crypto API 密钥
// 此时,任何能执行 JS 的上下文(包括恶意插件)都可能通过原型污染或代理拦截访问它
防御策略:
- 最小化敏感数据在内存中的驻留时间:用完立即置空;
- 使用 Web Workers 隔离敏感计算(主 JS 线程无法直接访问 Worker 内存);
- 对极度敏感操作(如生物识别认证),引导用户到专用安全上下文页面(无第三方脚本)。
[h1]误区 3:用前端加密“保护”用户数据[/h1]
常见场景:用户输入银行卡号,前端用 AES 加密后再传给后端。
风险:
- 加密密钥通常硬编码在 JS 中,等于“把保险柜钥匙贴在柜门上”;
- 攻击者可直接调用你的加密函数,绕过 UI 限制。
正确思路:
- 前端加密不能替代后端安全;
- 如果必须前端加密(如端到端加密聊天),应使用 用户口令派生密钥(PBKDF2/Argon2),且密钥绝不经服务器;
- 使用 Web Crypto API 而非第三方库(避免实现漏洞)。
[h1]误区 4:忽略 CSP(内容安全策略)[/h1]
即使代码无 XSS 漏洞,第三方 CDN 被劫持、NPM 供应链攻击(如 event-stream 事件)也可能注入恶意脚本。
必须部署 CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com 'unsafe-inline'? NO!;
object-src 'none';
base-uri 'self';
[h1]误区 5:在 URL 或 console.log 中泄露敏感信息[/h1]
// 危险!token 可能出现在浏览器历史、Referer 头、日志系统
window.location.href = `/profile?token=${token}`;
// 开发时打印,上线忘了删
console.log('User SSN:', user.ssn);
实践建议:
- 敏感参数永远不要出现在 URL,改用 POST body 或安全 Cookie;
- 使用 ESLint 插件(如
eslint-plugin-security)禁止console.log提交到主干; - 在生产构建中自动剥离
console.*(Vite/Rollup/Webpack 均支持)。
[h1]误区 6:信任来自 postMessage 的消息[/h1]
微前端、嵌入式 Widget 场景下,window.postMessage 是常见通信方式,但:
window.addEventListener('message', (e) => {
const data = e.data; // ❌ 未验证 origin!
eval(data.code); // 更糟!
});
安全接收消息:
window.addEventListener('message', (e) => {
// 1. 严格校验来源
if (e.origin !== 'https://your-trusted-partner.com') return;
// 2. 验证数据结构(用 Zod/io-ts)
if (!isValidMessage(e.data)) return;
// 3. 绝不执行动态代码
});
[h1]误区 7:忽视“退出登录”的彻底性[/h1]
用户点击“退出”,你只清了 localStorage?
隐患:
- HttpOnly Cookie 仍在,会话未真正终止;
- Service Worker 缓存可能保留敏感响应;
- 内存中的变量未清理(可通过 DevTools 查看)。
完整登出流程:
- 调用
/api/logout使服务端 session 失效; - 清除所有本地存储(
localStorage,sessionStorage, IndexedDB); - 调用
navigator.serviceWorker.getRegistrations().then(...)注销 SW; - 重定向到无状态的登出页(避免返回按钮回退到已登录页面)。
[h1]总结:前端安全思维框架[/h1]
| 原则 | 实践 |
| 最小暴露 | 敏感数据不在前端出现,除非绝对必要 |
| 纵深防御 | HTTPS + CSP + HttpOnly + 输入过滤 多层防护 |
| 假设被攻破 | 即使 XSS 发生,也要限制攻击者能做什么(如令牌短期有效、关键操作需二次验证) |
| 自动化检测 | 用 Lighthouse、OWASP ZAP、ESLint-security 扫描漏洞 |
[alert title="🔐 记住:"]前端是攻击面,不是信任边界。真正的安全,始于承认“我的代码可能运行在敌方环境中”。[/alert]