diff --git a/backend/server.js b/backend/server.js index 3d20dee..a526e50 100644 --- a/backend/server.js +++ b/backend/server.js @@ -436,6 +436,46 @@ const shareLimiter = new RateLimiter({ blockDuration: 20 * 60 * 1000 }); +// 创建验证码获取限流器(30次请求/10分钟,封锁30分钟) +const captchaLimiter = new RateLimiter({ + maxAttempts: 30, + windowMs: 10 * 60 * 1000, + blockDuration: 30 * 60 * 1000 +}); + +// 验证码最小请求间隔控制 +const CAPTCHA_MIN_INTERVAL = 3000; // 3秒 +const captchaLastRequest = new TTLCache(15 * 60 * 1000); // 15分钟自动清理 + +// 验证码防刷中间件 +function captchaRateLimitMiddleware(req, res, next) { + const clientKey = `captcha:${captchaLimiter.getClientKey(req)}`; + const now = Date.now(); + + // 最小时间间隔限制 + const lastRequest = captchaLastRequest.get(clientKey); + if (lastRequest && (now - lastRequest) < CAPTCHA_MIN_INTERVAL) { + return res.status(429).json({ + success: false, + message: '验证码请求过于频繁,请稍后再试' + }); + } + captchaLastRequest.set(clientKey, now, 15 * 60 * 1000); + + // 窗口内总次数限流 + const result = captchaLimiter.recordFailure(clientKey); + if (result.blocked) { + return res.status(429).json({ + success: false, + message: `验证码请求过多,请在 ${result.waitMinutes} 分钟后再试`, + blocked: true, + resetTime: result.resetTime + }); + } + + next(); +} + // 登录防爆破中间件 function loginRateLimitMiddleware(req, res, next) { const clientIP = loginLimiter.getClientKey(req); @@ -601,17 +641,17 @@ app.get('/api/health', (req, res) => { }); // 生成验证码API -app.get('/api/captcha', (req, res) => { +app.get('/api/captcha', captchaRateLimitMiddleware, (req, res) => { try { const captcha = svgCaptcha.create({ - size: 4, // 验证码长度 - noise: 2, // 干扰线条数 + size: 6, // 验证码长度 + noise: 3, // 干扰线条数 color: true, // 使用彩色 - background: '#f0f0f0', // 背景色 - width: 120, - height: 40, - fontSize: 50, - charPreset: '0123456789' // 只使用数字 + background: '#f7f7f7', // 背景色 + width: 140, + height: 44, + fontSize: 52, + charPreset: 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // 去掉易混淆字符,字母+数字 }); // 将验证码文本存储在session中