Skip to content

ExpressJS - 响应限速

减缓响应速度(Slowing down responses)是一种高级的速率限制(rate-limiting)技术。它不是在请求超过特定限制时立即拒绝,而是引入一个人工延迟。这使得攻击者更难以进行暴力破解攻击(例如密码猜测)或抓取(scrape)你的 API,因为他们的脚本将被显著减速,从而大大增加攻击成本。

express-slow-down 中间件是完成这项任务的完美工具。它通常与标准速率限制器(express-rate-limit)结合使用。

使用 npm 安装该软件包。

npm install express-slow-down

让我们创建一个速度限制器,将其应用于敏感的端点(endpoint),例如登录路由。我们将允许少数请求以全速通过,然后对后续请求开始增加延迟。

import express from 'express';
import slowDown from 'express-slow-down';
const app = express();
// 配置速度限制器
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 分钟
delayAfter: 5, // 在 15 分钟内允许 5 个请求全速通过
delayMs: (hits) => hits * 100 // 在第 5 个请求之后,每个请求增加 100ms 的延迟
// (第 6 个请求 = 100ms,第 7 个 = 200ms,依此类推)
});
// 将中间件应用于特定的敏感路由
app.post('/login', speedLimiter, (req, res) => {
// 你的登录逻辑在这里...
res.send('Login attempt processed.');
});
app.get('/', (req, res) => {
res.send('This page is not slowed down.');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});

你可以使用 Postman 等 API 客户端或在终端中使用 curl 来测试。对 /login 发送前 5 次 POST 请求,响应将是即时的。你可以在浏览器的开发者工具的“网络”标签页中,查看“时间”列来观察这一点。

从第 6 个请求开始,你将注意到大约 100ms 的延迟。第 7 个请求将延迟约 200ms,依此类推。这展示了减速的实际效果。

对于生产环境,请考虑这些高级设置。

攻击者可能会故意发送大量请求来为自己制造极长的延迟,这可能不是我们期望的。你可以设置最大延迟上限。

const cappedSpeedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 5,
delayMs: (hits) => hits * 100,
maxDelayMs: 2000 // 将延迟上限设为 2 秒
});

就像 express-rate-limit 一样,默认的内存存储(in-memory store)不适用于运行在多台服务器或多个进程上的应用程序。如果一个用户先访问服务器 A 再访问服务器 B,他们的请求计数将会是独立的。为了在整个基础设施中强制执行一致的减速,请使用像 Redis 这样的共享存储。请参阅软件包文档了解存储实现。

  • 使用速率限制(express-rate-limit) 用于对所有 API 端点进行通用保护。它是一种粗略的工具,通过拒绝请求(附带 429 Too Many Requests 状态码)来保护服务器资源。它非常适合防止一般的拒绝服务(DoS)攻击。
  • 使用响应减速(express-slow-down) 用于特定、敏感的端点,攻击者可能会尝试猜测信息或暴力破解登录。它不会拒绝请求,但会因为时间成本而使攻击在经济上不可行。它为可能意外触发限制的合法用户提供了更好的用户体验,因为他们的请求仍然会被处理,只是略有延迟。

通常,最佳策略是两者结合使用:对所有路由应用通用速率限制,并对身份验证或密码重置等敏感端点应用更具体的减速策略。