引言
在 Web 开发中,安全是不可或缺的一环。前端作为用户与服务器交互的第一道防线,承担着重要的安全职责。本文将深入讲解三种常见的前端安全威胁及其防护方案:XSS(跨站脚本攻击)、CSRF(跨站请求伪造)和 CSP(内容安全策略)。
一、XSS(跨站脚本攻击)
什么是 XSS
XSS 攻击是指攻击者向网页中注入恶意脚本,当其他用户浏览该页面时,脚本会在其浏览器中执行,从而窃取用户信息、劫持会话等。
XSS 的三种类型
反射型 XSS:恶意脚本通过 URL 参数传递,立即反射回页面
存储型 XSS:恶意脚本存储在服务器上(如评论区),每次加载时都被执行
DOM 型 XSS:通过修改 DOM 树结构触发,不经过服务器
防护方案
1. 转义用户输入
javascript
// 安全的 HTML 转义函数
function escapeHtml(text) {
const htmlEntities = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, char => htmlEntities[char]);
}
// 使用示例
const userInput = '<script>alert("XSS")</script>';
const safeOutput = escapeHtml(userInput);
// 输出:<script>alert("XSS")</script>
2. 使用 Content Security Policy (CSP)
css
<!-- 在 HTML 头部添加 CSP 元标签 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
object-src 'none';
base-uri 'self';
form-action 'self';">
3. 避免危险的 DOM 操作
ini
// ❌ 危险做法
element.innerHTML = userInput;
// ✅ 安全做法
element.textContent = userInput;
// 或者使用 DOM API
const textNode = document.createTextNode(userInput);
element.appendChild(textNode);
4. 使用现代框架的内置防护
React、Vue 等现代框架默认会对插值表达式进行转义:
javascript
// React - 自动转义
function SafeComponent({ userInput }) {
return <div>{userInput}</div>; // 安全
}
// ❌ 需要小心使用 dangerouslySetInnerHTML
function UnsafeComponent({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
}
二、CSRF(跨站请求伪造)
什么是 CSRF
CSRF 攻击利用用户的登录状态,诱使用户在不知情的情况下执行非预期的操作,如转账、修改密码等。
攻击原理
- 用户登录网站 A,获得认证 Cookie
- 用户访问恶意网站 B
- 网站 B 包含指向网站 A 的请求(如
<img src="https://a.com/transfer?to=hacker&amount=1000">) - 浏览器自动携带网站 A 的 Cookie,请求被执行
防护方案
1. CSRF Token
javascript
// 后端生成 token
app.get('/form', (req, res) => {
const csrfToken = generateToken();
res.cookie('csrfToken', csrfToken, { httpOnly: true });
res.render('form', { csrfToken });
});
// 前端在请求中携带 token
fetch('/api/action', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
// 后端验证 token
app.post('/api/action', (req, res) => {
const token = req.headers['x-csrf-token'];
if (!validateToken(token, req.cookies.csrfToken)) {
return res.status(403).json({ error: 'CSRF token invalid' });
}
// 处理请求
});
2. SameSite Cookie 属性
php
// 设置 Cookie 的 SameSite 属性
res.cookie('sessionId', session_id, {
httpOnly: true,
secure: true,
sameSite: 'strict' // 或 'lax'
});
strict: 完全禁止跨站请求携带 Cookielax: 允许部分安全的跨站请求(如 GET 导航)none: 允许所有跨站请求(需要配合 secure)
3. 验证 Referer 头
javascript
// 中间件验证 Referer
function checkReferer(req, res, next) {
const referer = req.headers.referer;
const allowedDomains = ['https://yourdomain.com'];
if (!referer || !allowedDomains.some(domain => referer.startsWith(domain))) {
return res.status(403).json({ error: 'Invalid referer' });
}
next();
}
app.post('/api/action', checkReferer, (req, res) => {
// 处理请求
});
三、CSP(内容安全策略)
CSP 的作用
CSP 通过白名单机制,控制浏览器可以加载和执行哪些资源,是防御 XSS 的最后一道防线。
常用指令
| 指令 | 说明 | 示例 |
|---|---|---|
default-src |
默认策略 | default-src 'self' |
script-src |
脚本来源 | script-src 'self' https://cdn.example.com |
style-src |
样式来源 | style-src 'self' 'unsafe-inline' |
img-src |
图片来源 | img-src 'self' data: https: |
connect-src |
连接来源 | connect-src 'self' https://api.example.com |
font-src |
字体来源 | font-src 'self' https://fonts.googleapis.com |
object-src |
插件来源 | object-src 'none' |
base-uri |
base 标签目标 | base-uri 'self' |
form-action |
表单提交目标 | form-action 'self' |
完整示例
css
<!-- 通过 HTTP 响应头设置 -->
<!-- Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.example.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
-->
开发阶段:CSP 报告
xml
<!-- 启用 CSP 报告 -->
<meta http-equiv="Content-Security-Policy-Report-Only"
content="default-src 'self'; report-uri /csp-report">
<!-- 后端接收报告 -->
app.post('/csp-report', (req, res) => {
const report = req.body;
console.log('CSP Violation:', report);
// 记录到日志系统
res.status(204).send();
});
四、综合防护实践
1. 安全 Headers 配置(Express 示例)
php
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
xssFilter: true,
noSniff: true,
referrerPolicy: { policy: "strict-origin-when-cross-origin" }
}));
2. 输入验证与输出编码
javascript
// 输入验证
function validateInput(input, rules) {
for (const [field, rule] of Object.entries(rules)) {
if (!rule.test(input[field])) {
throw new Error(`Invalid ${field}`);
}
}
return input;
}
// 输出编码函数库
const encode = {
html: (str) => str.replace(/[&<>"']/g, c =>
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])
),
url: (str) => encodeURIComponent(str),
json: (str) => JSON.stringify(str)
};
总结
前端安全是一个系统工程,需要多层防护:
- XSS 防护:转义输入、使用 CSP、避免危险 DOM 操作
- CSRF 防护:使用 Token、设置 SameSite Cookie、验证 Referer
- CSP 策略:建立资源白名单,限制可执行内容
记住安全原则:
- 永远不要信任客户端输入
- 默认拒绝,显式允许
- 纵深防御,多层防护
通过合理配置和持续监控,可以大幅降低前端安全风险,保护用户数据和系统安全。