Express 安全指南
Express.js 应用安全加固要点:HTTP 头、限流、输入验证、CSRF 和注入防御。
1. Helmet — 安全 HTTP 头
const helmet = require('helmet');
app.use(helmet());
// 自定义 CSP
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'cdn.jsdelivr.net'],
imgSrc: ["'self'", 'data:', 'https:'],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
},
}));
app.use(helmet.hsts({ maxAge: 63072000, includeSubDomains: true }));
2. 限流
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
message: { error: '请求过多,请稍后重试。' },
});
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 10,
skipSuccessfulRequests: true,
});
app.use('/api/', apiLimiter);
app.post('/auth/login', authLimiter, loginHandler);
3. 输入验证 (Zod)
const { z } = require('zod');
const createUserSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(128),
name: z.string().min(1).max(100).trim(),
});
function validate(schema) {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: '验证失败',
fields: result.error.flatten().fieldErrors,
});
}
req.body = result.data;
next();
};
}
app.post('/users', validate(createUserSchema), createUser);
4. CSRF 防护
const crypto = require('crypto');
function csrfTokenMiddleware(req, res, next) {
if (!req.cookies['csrf-token']) {
const token = crypto.randomBytes(32).toString('hex');
res.cookie('csrf-token', token, {
httpOnly: false,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
});
}
next();
}
function csrfVerify(req, res, next) {
const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
if (safeMethods.includes(req.method)) return next();
const cookieToken = req.cookies['csrf-token'];
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF token 无效' });
}
next();
}
5. SQL 注入防御
// 禁止:字符串拼接
// 错误: db.query(`SELECT * FROM users WHERE id = ${req.params.id}`)
// 正确:参数化查询
const { Pool } = require('pg');
const pool = new Pool();
app.get('/users/:id', async (req, res) => {
const { rows } = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[req.params.id]
);
res.json(rows[0] ?? null);
});
6. 安全检查清单
| 方面 | 措施 | 工具包 |
|---|---|---|
| HTTP 头 | 设置安全头 | helmet |
| 限流 | 节流请求 | express-rate-limit |
| 输入 | 验证与清洗 | zod / joi |
| 密码 | bcrypt 哈希 | bcryptjs |
| CORS | 白名单来源 | cors |
| CSRF | 双提交 Cookie | 自定义 |
| SQL | 参数化查询 | ORM / pg |
| 密钥 | 使用环境变量 | dotenv |