Node.js 常用安全模块大全汇总

导言

我们的目标是建坚不可摧的Node.js应用

一、Node.js常见安全隐患

Node.js因其高性能和易用性而广受欢迎,但默认配置下存在一些安全隐患:

  1. HTTP头部信息暴露 :可能泄露服务器技术栈
    • 底裤没了
  2. 缺乏请求限制:易受DDoS攻击
  3. 输入验证不足:XSS和SQL注入风险
  4. 认证机制薄弱:会话劫持和CSRF攻击

二、常用三大安全中间件

1. Helmet - 您的HTTP头部卫士

Helmet通过设置 HTTP 头部增强安全性

ini 复制代码
const helmet = require('helmet');
app.use(helmet());

核心防护功能​:

  • Content-Security-Policy: 设置内容安全策略,防止 XSS 攻击。
  • Cross-Origin-Opener-Policy: 帮助隔离页面进程。
  • Cross-Origin-Resource-Policy: 阻止跨域加载资源。
  • Origin-Agent-Cluster: 更改进程隔离方式,使其基于源。
  • Referrer-Policy: 控制 Referer 头。
  • Strict-Transport-Security: 告诉浏览器优先使用 HTTPS。
  • X-Content-Type-Options: 避免 MIME 嗅探。
  • X-DNS-Prefetch-Control: 控制 DNS 预取。
  • X-Download-Options: 强制下载保存(仅适用于 Internet Explorer)。
  • X-Frame-Options: 防止点击劫持攻击。
  • X-Permitted-Cross-Domain-Policies: 控制 Adobe 产品的跨域行为。
  • X-Powered-By: 移除服务器信息头,防止简单攻击。
  • X-XSS-Protection: 禁用浏览器的 XSS 过滤器。

生产环境建议​:

php 复制代码
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", "cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "images.example.com"]
    }
  },
  hsts: {
    maxAge: 63072000, // 2年
    includeSubDomains: true,
    preload: true
  }
}));

2. Express Rate Limit - 流量控制阀门

防止暴力破解和DDoS攻击的利器:

php 复制代码
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟窗口
  max: 100, // 每个IP最多100个请求
  message: '请求过于频繁,请15分钟后再试',
  standardHeaders: true, // 返回标准RateLimit头部
  legacyHeaders: false // 禁用非标准头部
});

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

多级限流策略​:

php 复制代码
// 登录接口更严格的限制
const authLimiter = rateLimit({
  windowMs: 5 * 60 * 1000, // 5分钟
  max: 5,
  message: '尝试登录次数过多,请5分钟后再试'
});

app.use('/api/auth', authLimiter);

3. CORS - 跨域资源共享管理

精细控制跨域访问:

javascript 复制代码
const cors = require('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('不允许的跨域请求'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // 预检请求缓存24小时
};

app.use(cors(corsOptions));

三、认证与授权

1. Passport.js - 认证中间件

支持500+种认证策略的模块化系统:

javascript 复制代码
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTStrategy = require('passport-jwt').Strategy;

// 本地策略(用户名密码)
passport.use(new LocalStrategy(
  { usernameField: 'email' },
  async (email, password, done) => {
    try {
      const user = await User.findOne({ email });
      if (!user) return done(null, false);
      
      const isValid = await bcrypt.compare(password, user.password);
      if (!isValid) return done(null, false);
      
      return done(null, user);
    } catch (err) {
      return done(err);
    }
  }
));

// JWT策略
passport.use(new JWTStrategy({
  jwtFromRequest: req => req.cookies.jwt,
  secretOrKey: process.env.JWT_SECRET
}, (payload, done) => {
  User.findById(payload.sub)
    .then(user => done(null, user || false))
    .catch(err => done(err));
}));

2. Bcrypt - 密码存储的最佳实践

永远不要明文存储密码(多加盐哈哈哈):

javascript 复制代码
const bcrypt = require('bcrypt');
const saltRounds = 12; // 成本系数

// 密码哈希
async function hashPassword(password) {
  return await bcrypt.hash(password, saltRounds);
}

// 密码验证
async function comparePassword(input, hash) {
  return await bcrypt.compare(input, hash);
}

安全要点​:

  • 盐值(salt)自动生成并包含在哈希中
  • 成本系数建议10-12(兼顾安全性和性能)
  • 使用异步版本避免阻塞事件循环

3. JWT - 无状态认证方案

php 复制代码
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');

// 生成令牌
function generateToken(user) {
  return jwt.sign({
    sub: user.id,
    jti: uuidv4(), // 唯一标识符
    iat: Math.floor(Date.now() / 1000), // 签发时间
    role: user.role
  }, process.env.JWT_SECRET, {
    expiresIn: '1h',
    algorithm: 'HS256'
  });
}

// 验证中间件
function authenticate(req, res, next) {
  const token = req.cookies.jwt;
  if (!token) return res.status(401).send('需要认证');
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).send('无效令牌');
  }
}

安全增强措施​:

  • 设置合理的过期时间
  • 使用HTTPS传输
  • 考虑实现令牌黑名单机制
  • 避免在令牌中存储敏感信息

四、输入处理

1. Express Validator - 请求验证

less 复制代码
const { body, validationResult } = require('express-validator');

app.post('/register', [
  // 验证规则
  body('email').isEmail().normalizeEmail(),
  body('password').isLength({ min: 8 }).withMessage('密码至少8个字符'),
  body('age').isInt({ min: 18 }).withMessage('必须年满18岁'),
  body('username').trim().escape() // 清理输入
], (req, res) => {
  // 检查验证结果
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  
  // 处理有效请求
});

常用验证方法​:

  • .isEmail() - 验证邮箱格式
  • .isLength() - 验证长度
  • .matches() - 正则匹配
  • .isURL() - 验证URL
  • .custom() - 自定义验证逻辑

2. XSS防护组合拳

javascript 复制代码
const xss = require('xss');
const sanitizeHtml = require('sanitize-html');

// 输入清理
function cleanInput(input) {
  // 先转义特殊字符
  let cleaned = xss(input);
  
  // 然后清理HTML标签(保留安全标签)
  cleaned = sanitizeHtml(cleaned, {
    allowedTags: ['b', 'i', 'em', 'strong', 'a'],
    allowedAttributes: {
      'a': ['href', 'title']
    },
    allowedSchemes: ['http', 'https']
  });
  
  return cleaned;
}

// 使用示例
app.post('/comment', (req, res) => {
  const cleanComment = cleanInput(req.body.comment);
  // 存储清理后的内容
});

五、依赖安全

1. npm audit - 内置漏洞检查

bash 复制代码
# 检查漏洞
npm audit

# 自动修复
npm audit fix

# 生产环境检查
npm audit --production

2. Snyk - 持续监控

bash 复制代码
# 安装CLI
npm install -g snyk

# 测试项目
snyk test

# 监控项目
snyk monitor

# 创建持续集成测试
snyk wizard

集成到CI/CD​:

yaml 复制代码
# GitHub Actions示例
name: Security Scan
on: [push, pull_request]
jobs:
  snyk:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

六、高级安全策略

1. CSRF(Cross-site request forgery,跨站请求伪造)防护

javascript 复制代码
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

// 先设置cookie解析
app.use(cookieParser());

// 然后启用CSRF保护
const csrfProtection = csrf({ cookie: true });

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

app.post('/process', csrfProtection, (req, res) => {
  // 处理受保护的表单
});

前端配合​:

xml 复制代码
<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="{{csrfToken}}">
  <!-- 其他表单字段 -->
</form>

2. 安全日志记录

php 复制代码
const winston = require('winston');
const { combine, timestamp, json } = winston.format;

// 创建安全日志记录器
const securityLogger = winston.createLogger({
  level: 'info',
  format: combine(
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    json()
  ),
  transports: [
    new winston.transports.File({ 
      filename: 'logs/security.log',
      level: 'warn'
    }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

// 记录安全事件
function logSecurityEvent(event) {
  securityLogger.warn({
    message: event.message,
    type: event.type,
    user: event.user,
    ip: event.ip
  });
}

七、实战,构建安全Express应用

php 复制代码
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { expressjwt: jwt } = require('express-jwt');
const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator');
const xss = require('xss');
const winston = require('winston');

// 初始化应用
const app = express();

// 1. 基础安全中间件
app.use(helmet());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));

// 2. 认证中间件
app.use(jwt({ 
  secret: process.env.JWT_SECRET, 
  algorithms: ['HS256'],
  getToken: req => req.cookies.jwt
}).unless({ 
  path: ['/login', '/register'] 
}));

// 3. 路由定义
app.post('/login', 
  [
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 })
  ],
  async (req, res) => {
    // 验证输入
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    try {
      // 清理输入
      const email = xss(req.body.email);
      const password = req.body.password; // 密码不需要XSS清理
      
      // 查找用户
      const user = await User.findOne({ email });
      if (!user) {
        logSecurityEvent({
          message: '登录尝试使用不存在的邮箱',
          type: 'AUTH_FAILURE',
          ip: req.ip
        });
        return res.status(401).json({ error: '认证失败' });
      }
      
      // 验证密码
      const isValid = await bcrypt.compare(password, user.password);
      if (!isValid) {
        logSecurityEvent({
          message: '登录尝试使用错误密码',
          type: 'AUTH_FAILURE',
          user: user.id,
          ip: req.ip
        });
        return res.status(401).json({ error: '认证失败' });
      }
      
      // 生成令牌
      const token = generateToken(user);
      
      // 设置安全cookie
      res.cookie('jwt', token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 3600000 // 1小时
      });
      
      res.json({ success: true });
      
    } catch (err) {
      logSecurityEvent({
        message: '登录处理错误',
        type: 'AUTH_ERROR',
        error: err.message,
        ip: req.ip
      });
      res.status(500).json({ error: '服务器错误' });
    }
  }
);

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`安全服务器已启动,监听端口 ${PORT}`);
});

八、持续安全实践

  1. 定期依赖更新​:

    sql 复制代码
    npm outdated
    npm update
  2. 安全头检查 ​:

    使用SecurityHeaders.com扫描您的网站

  3. 自动化扫描​:

    • OWASP ZAP
    • Nessus
    • Burp Suite
  4. 安全审计​:

    bash 复制代码
    npm audit
    snyk test
  5. 监控和响应​:

    • 设置日志告警
    • 建立安全事件响应流程
    • 定期进行安全培训

结语

在构建应用的时候回来看看,保护好自己的应用安全

相关推荐
拾光拾趣录3 分钟前
for..in 和 Object.keys 的区别:从“遍历对象属性的坑”说起
前端·javascript
OpenTiny社区14 分钟前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠43 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞1 小时前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构