
Web安全:完整防护指南
XSS防护
// React - 默认已进行HTML转义
// 危险:dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // 切勿对不可信输入使用
// 安全:使用前先清理
import DOMPurify from 'dompurify'
function SafeHTML({ content }) {
const clean = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
ALLOWED_ATTR: [],
})
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
// 服务端:转义所有用户内容
import escapeHtml from 'escape-html'
app.get('/profile', (req, res) => {
const username = escapeHtml(req.query.username)
res.send(`<h1>Welcome, ${username}!</h1>`)
})
// CSP头阻止内联脚本
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'nonce-{RANDOM}'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"connect-src 'self' https://api.example.com"
)
next()
})

CSRF防护
import csrf from 'csurf'
import cookieParser from 'cookie-parser'
app.use(cookieParser())
app.use(csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }))
// 在表单中包含CSRF令牌
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() })
})
// 前端:在AJAX请求中包含令牌
const token = document.querySelector('meta[name="csrf-token"]').content
fetch('/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token,
},
body: JSON.stringify({ data: 'value' }),
})
// 单页应用的双重提交Cookie模式
function getCSRFToken() {
return document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]
}
// SameSite cookie提供额外保护
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict', // 阻止跨站请求
maxAge: 3600000,
})

SQL注入防护
// 绝不:字符串拼接
const query = `SELECT * FROM users WHERE id = ${userId}` // 易受攻击
// 始终:参数化查询
import { Pool } from 'pg'
const pool = new Pool()
async function getUser(userId) {
const result = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[userId] // 参数化 - 安全
)
return result.rows[0]
}
// 使用Prisma ORM - 默认安全
const user = await prisma.user.findUnique({ where: { id: userId } })
// 使用Zod进行输入验证
import { z } from 'zod'
const UserIdSchema = z.string().uuid()
const SearchSchema = z.object({
query: z.string().max(100).regex(/^[a-zA-Z0-9 ]+$/),
page: z.number().int().positive().max(1000),
})
app.get('/search', async (req, res) => {
const { query, page } = SearchSchema.parse(req.query)
const results = await searchProducts(query, page)
res.json(results)
})

使用Helmet.js配置安全头
import helmet from 'helmet'
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-abc123'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
upgradeInsecureRequests: [],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: 'deny' },
noSniff: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}))
// 速率限制
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { error: '请求过多,请稍后再试。' },
})
app.use('/api/', limiter)
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1小时
max: 5,
skipSuccessfulRequests: true,
})
app.use('/api/auth/login', loginLimiter)
输入验证与清理
import { z } from 'zod'
import validator from 'validator'
const RegisterSchema = z.object({
email: z.string().email().toLowerCase().trim(),
password: z.string()
.min(12, '密码至少12个字符')
.regex(/[A-Z]/, '必须包含大写字母')
.regex(/[0-9]/, '必须包含数字')
.regex(/[^A-Za-z0-9]/, '必须包含特殊字符'),
username: z.string()
.min(3).max(30)
.regex(/^[a-zA-Z0-9_-]+$/, '仅允许字母、数字、连字符、下划线'),
})
// 文件上传验证
const FileSchema = z.object({
mimetype: z.enum(['image/jpeg', 'image/png', 'image/webp']),
size: z.number().max(5 * 1024 * 1024), // 最大5MB
})
function validateFile(file) {
// 检查实际内容,而非仅MIME类型
const magic = file.buffer.slice(0, 4)
const isPNG = magic.equals(Buffer.from([0x89, 0x50, 0x4e, 0x47]))
const isJPEG = magic[0] === 0xff && magic[1] === 0xd8
return isPNG || isJPEG
}
安全检查清单
| 类别 |
控制措施 |
| XSS |
CSP、输出编码、DOMPurify |
| CSRF |
SameSite cookie、CSRF令牌 |
| 注入 |
参数化查询、输入验证 |
| 头 |
Helmet.js、HSTS、X-Frame-Options |
| 认证 |
速率限制、bcrypt、MFA |