中间件安全注意事项
中间件安全的重要性
Koa2中间件作为请求处理的核心环节,安全漏洞可能导致数据泄露、服务瘫痪等严重后果。中间件执行顺序直接影响安全策略的有效性,错误配置可能绕过关键安全检查。
输入验证与过滤
所有进入中间件的参数必须进行严格验证。使用koa-parameter
或joi
进行结构化验证:
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();
});
特别注意:
- 对
ctx.request.body
进行深度过滤 - 处理文件上传时验证MIME类型
- 数字参数必须校验范围
身份认证中间件
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
上一篇:中间件组合与复用技巧