阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 中间件安全注意事项

中间件安全注意事项

作者:陈川 阅读数:55542人阅读 分类: Node.js

中间件安全的重要性

Koa2中间件作为请求处理的核心环节,安全漏洞可能导致数据泄露、服务瘫痪等严重后果。中间件执行顺序直接影响安全策略的有效性,错误配置可能绕过关键安全检查。

输入验证与过滤

所有进入中间件的参数必须进行严格验证。使用koa-parameterjoi进行结构化验证:

const Joi = require('joi');

app.use(async (ctx, next) => {
  const schema = Joi.object({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{8,30}$'))
  });
  
  const { error } = schema.validate(ctx.request.body);
  if (error) {
    ctx.throw(400, error.details[0].message);
  }
  await next();
});

特别注意:

  1. ctx.request.body进行深度过滤
  2. 处理文件上传时验证MIME类型
  3. 数字参数必须校验范围

身份认证中间件

JWT验证中间件需要完整实现以下安全措施:

const jwt = require('koa-jwt');
const { SECRET } = require('../config');

app.use(jwt({
  secret: SECRET,
  algorithms: ['HS256'],
  getToken: ctx => {
    if (ctx.header.authorization) {
      return ctx.header.authorization.split(' ')[1];
    }
    return null;
  }
}).unless({
  path: [/^\/public/, /^\/login/]
}));

关键点:

  • 必须指定算法白名单
  • token应从HttpOnly的Cookie中获取而非URL
  • 实现token自动刷新机制
  • 黑名单处理需使用Redis等持久化存储

防注入攻击

SQL注入和NoSQL注入需要不同防御策略:

// MongoDB防注入
app.use(async (ctx, next) => {
  if (ctx.query) {
    Object.keys(ctx.query).forEach(key => {
      if (typeof ctx.query[key] === 'string') {
        ctx.query[key] = ctx.query[key].replace(/\$/g, '');
      }
    });
  }
  await next();
});

// SQL参数化查询中间件
app.use(async (ctx, next) => {
  ctx.dbQuery = (sql, params) => {
    return pool.execute(sql, params);
  };
  await next();
});

请求频率限制

分布式环境下需使用Redis实现限流:

const ratelimit = require('koa-ratelimit');
const Redis = require('ioredis');

app.use(ratelimit({
  db: new Redis(),
  duration: 60000,
  max: 100,
  id: ctx => ctx.ip,
  disableHeader: false,
  whitelist: ['127.0.0.1']
}));

应针对不同路由设置不同阈值:

  • 登录接口:5次/分钟
  • API端点:100次/分钟
  • 文件上传:10次/小时

响应头安全设置

安全头中间件应包含以下最低配置:

app.use(async (ctx, next) => {
  ctx.set({
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'X-XSS-Protection': '1; mode=block',
    'Content-Security-Policy': "default-src 'self'",
    'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
  });
  await next();
});

动态CSP策略示例:

const csp = require('koa-csp');

app.use(csp({
  directives: {
    scriptSrc: [
      "'self'",
      ctx => `'nonce-${ctx.state.nonce}'`
    ]
  }
}));

错误处理安全

生产环境错误处理应避免泄露堆栈信息:

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = process.env.NODE_ENV === 'development' 
      ? { error: err.message, stack: err.stack }
      : { error: 'Internal Server Error' };
    
    // 关键错误触发警报
    if (err.isCritical) {
      alertSystem.notify(err);
    }
  }
});

依赖组件安全

定期检查依赖漏洞:

npm audit --production

使用npm shrinkwrap锁定依赖版本,中间件应验证第三方包签名:

const { verify } = require('tweetnacl');
const packageSig = require('package-signature');

app.use(async (ctx, next) => {
  if (!verify(packageSig, publicKey)) {
    ctx.throw(403, 'Invalid package signature');
  }
  await next();
});

文件上传防护

文件上传中间件必须包含:

const upload = require('koa-multer');
const { extname } = require('path');

const storage = upload.diskStorage({
  destination: 'uploads/',
  filename: (ctx, file, cb) => {
    const ext = extname(file.originalname);
    if (!['.jpg', '.png'].includes(ext)) {
      return cb(new Error('Invalid file type'));
    }
    cb(null, `${Date.now()}${ext}`);
  }
});

app.use(upload({
  storage,
  limits: {
    fileSize: 1024 * 1024 * 5,
    files: 1
  }
}));

额外措施:

  • 使用clamav扫描上传文件
  • 存储在非web根目录
  • 强制重命名文件

会话管理

Redis会话中间件安全配置:

const session = require('koa-session');
const RedisStore = require('koa-redis');

app.keys = ['complex_key_here'];

app.use(session({
  store: new RedisStore({
    host: '127.0.0.1',
    port: 6379,
    password: 'redis_password'
  }),
  key: 'secure.sess',
  maxAge: 86400000,
  renew: true,
  secure: true,
  httpOnly: true,
  sameSite: 'strict'
}, app));

会话固定防护:

app.use(async (ctx, next) => {
  if (ctx.session.isNew) {
    ctx.session.regenerate();
  }
  await next();
});

日志与审计

安全审计日志应包含:

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const latency = Date.now() - start;

  auditLogger.log({
    timestamp: new Date(),
    method: ctx.method,
    path: ctx.path,
    ip: ctx.ip,
    userAgent: ctx.headers['user-agent'],
    status: ctx.status,
    latency,
    userId: ctx.state.user?.id
  });
});

敏感操作需单独记录:

app.use(async (ctx, next) => {
  if (['POST', 'PUT', 'DELETE'].includes(ctx.method)) {
    securityLogger.log({
      action: ctx.path,
      params: redactSensitiveData(ctx.request.body),
      user: ctx.state.user.id
    });
  }
  await next();
});

function redactSensitiveData(obj) {
  // 脱敏处理逻辑
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌