Express.js 后端安全(使用 Nginx + VPS)

发布: (2026年1月12日 GMT+8 13:47)
9 分钟阅读
原文: Dev.to

I’m happy to translate the article for you, but I’ll need the full text of the post (the content you’d like translated). Could you please paste the article’s body here? Once I have the text, I’ll provide a Simplified‑Chinese translation while keeping the source line, formatting, markdown, and code blocks unchanged.

目录

  1. 威胁模型
  2. 为什么后端安全至关重要
  3. 安全基线
  4. 项目设置
  5. 安全响应头
  6. CORS 策略
  7. 身份验证与授权
  8. 输入验证与清理
  9. 限流
  10. 请求大小限制
  11. 日志与审计
  12. 密钥与环境安全
  13. TLS(HTTPS)与 Nginx 配置
  14. Nginx 限流
  15. 基础机器人/扫描器削减
  16. Nginx 推荐的安全响应头
  17. SSH 加固
  18. 防火墙(UFW)
  19. Fail2ban
  20. 自动安全更新
  21. 部署检查清单

威胁模型

公共后端的典型攻击向量

  • 暴力破解登录尝试
  • API 滥用 / 抓取
  • 凭证填充
  • 注入攻击(SQL/NoSQL)
  • CORS 配置错误
  • 令牌窃取(JWT、会话 Cookie)
  • 漏洞扫描器
  • 反向代理绕过(直接端口访问)
  • 通过弱 SSH 导致服务器被攻破

影响矩阵

威胁影响
NoSQL Injection数据库泄露
Credential Stuffing账户被接管
JWT Theft完全用户冒充
CSRF未授权操作
XSS令牌和会话泄露
API Abuse服务器及支付系统被利用

目标是通过使用多层独立防护来降低风险。

安全基线

控制项描述
强制使用 HTTPS所有流量均已加密
强大的身份验证策略基于 JWT 或会话
严格的 CORS白名单来源
服务器端验证永不信任客户端数据
限流阻止暴力破解和滥用
集中式日志记录审计日志
Nginx 反向代理防护隐藏内部端口
防火墙 + SSH 加固限制攻击面
Fail2ban + 自动安全补丁被动与主动防御

项目设置

# Core dependencies
npm i express helmet cors express-rate-limit cookie-parser compression

# Validation, logging, env handling
npm i zod pino pino-http dotenv

安全标头

Helmet 应用了一套合理的 HTTP 安全标头。

import helmet from "helmet";

app.use(helmet());

注意: 对于纯 API,除非您也提供 HTML 页面,否则可以跳过严格的 CSP。

CORS 策略

在生产环境中绝不要使用 *,尤其是涉及 cookie 或凭证时。

import cors from "cors";

const allowedOrigins = [
  "https://your-frontend.com",
  "https://www.your-frontend.com",
];

app.use(
  cors({
    origin: (origin, cb) => {
      // Allow non‑browser requests (e.g., Postman) or whitelisted origins
      if (!origin) return cb(null, true);
      if (allowedOrigins.includes(origin)) return cb(null, true);
      return cb(new Error("CORS blocked"), false);
    },
    credentials: true,
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
  })
);

身份验证与授权

概念描述
Authentication证明客户端的身份(例如,登录,JWT 发放)。
Authorization确定已认证身份可以执行的操作(基于角色的检查)。

最佳实践

  • 短期访问令牌,轮换刷新令牌。
  • 对特权路由进行基于角色的检查。
// Example role guard
export function requireRole(...roles) {
  return (req, res, next) => {
    if (!req.user || !roles.includes(req.user.role)) {
      return res.status(403).json({ status: false, message: "Forbidden" });
    }
    next();
  };
}

输入验证与清理

优先使用 模式验证(例如 Zod)而不是临时检查。

import { z } from "zod";

const createUserSchema = z.object({
  name: z.string().min(2).max(80),
  email: z.string().email(),
  password: z.string().min(8).max(72),
});

export function validate(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({
        status: false,
        message: "Validation failed",
        errors: result.error.issues,
      });
    }
    req.body = result.data; // use sanitized data downstream
    next();
  };
}

用法

app.post("/api/users", validate(createUserSchema), (req, res) => {
  // req.body is guaranteed to match the schema
});

限流

对每个端点类别应用不同的限制

import rateLimit from "express-rate-limit";

export const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 分钟
  max: 600,                // 每个窗口每个 IP 600 次请求
  standardHeaders: true,
  legacyHeaders: false,
});

export const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 20, // 对登录/注册更严格
  message: { message: "Too many login attempts. Try again later." },
});

app.use(globalLimiter);               // 应用于所有路由
app.use("/api/auth", authLimiter);    // 仅限认证路由

请求大小限制

防止负载滥用:

app.use(express.json({ limit: "200kb" }));
app.use(express.urlencoded({ extended: true, limit: "200kb" }));

日志记录与审计

结构化日志使分析更容易。

import pino from "pino";
import pinoHttp from "pino-http";

const logger = pino({ level: process.env.LOG_LEVEL || "info" });
app.use(pinoHttp({ logger }));

记录(但 绝不要 记录密码、令牌或原始密钥):

  • 认证失败
  • 可疑的流量激增
  • 限流阻止

密钥与环境安全

  • 在开发时将密钥存储在 .env 中,并且在生产环境中将其存储在服务器的环境变量中。
  • 绝不要.env 提交到版本控制。
  • 立即轮换已泄露的密钥。
# .env (example)
NODE_ENV=production
PORT=3000
JWT_SECRET=YOUR-JWT-SECRET-KEY

TLS(HTTPS) & Nginx 反向代理

架构

Internet → Nginx (443) → Express (127.0.0.1:8000)

安装 Certbot 并获取证书

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx -y

# Issue a certificate for your API sub‑domain
sudo certbot --nginx -d api.yourdomain.com

# Verify auto‑renewal timer
sudo systemctl status certbot.timer

Nginx 限流

将以下内容添加到 /etc/nginx/nginx.conf(位于 http {} 块内部):

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

示例 Server 块

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;

    # TLS (handled by Certbot)
    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;

    # Rate limiting
    limit_req zone=api_limit burst=20 nodelay;
    limit_conn conn_limit 20;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeouts
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

基本的机器人/扫描器减速

在 Nginx 级别阻止常见的垃圾请求。

# Block WordPress xmlrpc pingbacks (if you don't run WP)
location = /xmlrpc {
    deny all;
}

根据需要添加更多模式(例如 /.env/admin、已知的扫描器 User‑Agent)。

推荐的 Nginx 安全响应头

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;
# (CSP can be added if you serve HTML)

SSH 加固

# Disable root login
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

# Use key‑based auth only
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

# Change default port (optional)
# sudo sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config

sudo systemctl restart sshd
  • 保持私钥安全。
  • 使用密码短语并配合 SSH 代理。

防火墙 (UFW)

sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (adjust port if you changed it)
sudo ufw allow 22/tcp   # or 2222/tcp if you changed the port

# Allow HTTP/HTTPS (handled by Nginx)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

sudo ufw enable
sudo ufw status verbose

Fail2ban

sudo apt install fail2ban -y

# Basic jail for SSH
cat <<EOF > /etc/fail2ban/jail.local
[sshd]
enabled = true
port    = ssh
logpath = %(sshd_log)s
maxretry = 5
EOF

永远不要信任前端。 只信任后端并验证所有内容。

自动安全更新

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

Deployment Checklist

  • 通过 Nginx + Certbot 强制使用 HTTPS
  • 已应用 Helmet 安全头
  • 严格的 CORS 白名单
  • 使用 Zod(或类似库)进行输入验证
  • 限流(Express 与 Nginx)
  • 已配置请求大小限制
  • 结构化日志(pino)
  • 将机密安全存储,且不放入版本控制系统
  • 仅使用 SSH 密钥认证,禁用 root 登录
  • 已应用 UFW 防火墙规则
  • 为 SSH(以及可选的 Nginx)启用 Fail2ban
  • 已启用自动安全更新

最终思考

Express.js 后端安全是一门学科,而不是单一的配置或模块。强大的应用层限制、加固的反向代理以及适当锁定的服务器环境是实际安全的组成部分。

通过在 Express、Nginx 和 VPS 级别的加固中实现分层防御,您可以大幅降低 API 的攻击面,保护用户、数据和业务逻辑免受真实世界的威胁。

对于长期的生产使用,安全的后端不仅更安全,而且更可靠、可扩展且值得信赖。

Back to Blog

相关文章

阅读更多 »

你好,我是新人。

嗨!我又回到 STEM 的领域了。我也喜欢学习能源系统、科学、技术、工程和数学。其中一个项目是…