express-brute、express-limiter、express-rate-limit、express-slow-down 和 rate-limiter-flexible 都是用于保护 Node.js Express 应用免受暴力破解、API 滥用和 DDoS 攻击的中间件。它们通过限制特定 IP 或用户在一定时间内的请求频率来工作,但实现机制、存储后端支持和灵活性各不相同。express-rate-limit 是目前最流行且维护活跃的基础限流方案;rate-limiter-flexible 提供了最强大的跨框架支持和细粒度控制;express-brute 专注于暴力破解防护但已停止维护;express-limiter 是较早期的中间件实现;而 express-slow-down 则采用渐进式延迟策略而非直接拒绝请求。
在构建生产级 Express 应用时,保护 API 免受滥用和暴力破解是基本安全要求。本文深入对比五个主流限流中间件,从架构设计、存储支持、配置灵活性到实际代码实现,帮助你在技术选型时做出明智决策。
不同包对"如何处理超限请求"有不同哲学。
express-rate-limit 采用标准的令牌桶或固定窗口算法,超限后直接返回 429 状态码。
// express-rate-limit: 直接拒绝超限请求
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个 IP 最多 100 个请求
message: '请求过多,请稍后再试'
});
app.use('/api/', limiter);
express-slow-down 不直接拒绝,而是随着请求频率增加逐渐延长响应时间。
// express-slow-down: 渐进式延迟
import slowDown from 'express-slow-down';
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 50, // 50 个请求后开始延迟
delayMs: (hits) => Math.min(hits * 100, 60000) // 每次增加 100ms,最多 60 秒
});
app.use('/api/', speedLimiter);
express-brute 专为登录等敏感端点设计,使用指数退避算法防止暴力破解。
// express-brute: 指数退避防护
import ExpressBrute from 'express-brute';
import MemStore from 'express-brute-mem-store';
const store = new MemStore();
const brute = new ExpressBrute(store);
app.post('/login', brute.prevent, (req, res) => {
// 登录逻辑
res.send('登录成功');
});
express-limiter 提供基础的 Redis 驱动限流,配置相对简单直接。
// express-limiter: Redis 基础限流
import limiter from 'express-limiter';
limiter(app, {
lookup: 'connection.remoteAddress',
total: 100,
expire: 1000 * 60 * 60,
redis: redisClient
});
rate-limiter-flexible 提供最细粒度的控制,支持按用户、IP、端点等多维度组合策略。
// rate-limiter-flexible: 细粒度策略
import { RateLimiterRedis } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'middleware',
points: 10,
duration: 1
});
app.use('/api/', (req, res, next) => {
rateLimiter.consume(req.ip)
.then(() => next())
.catch(() => res.status(429).send('Too Many Requests'));
});
存储选择直接影响限流在集群环境下的有效性。
| 包名 | 内存存储 | Redis | Memcached | 数据库 | 其他 |
|---|---|---|---|---|---|
express-brute | ✅ | ✅ (需额外包) | ❌ | ❌ | MongoDB (需额外包) |
express-limiter | ❌ | ✅ | ❌ | ❌ | ❌ |
express-rate-limit | ✅ | ✅ | ❌ | ❌ | 自定义存储 |
express-slow-down | ✅ | ✅ | ❌ | ❌ | 依赖 express-rate-limit 存储 |
rate-limiter-flexible | ✅ | ✅ | ✅ | ✅ | 15+ 种存储 |
express-rate-limit 内置内存存储,也支持 Redis 通过 rate-limit-redis 包。
// express-rate-limit: Redis 存储配置
import RedisStore from 'rate-limit-redis';
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redisClient.call(...args),
}),
windowMs: 15 * 60 * 1000,
max: 100
});
rate-limiter-flexible 支持最广泛的存储后端,包括 Redis、Memcached、MySQL、PostgreSQL、MongoDB 等。
// rate-limiter-flexible: Memcached 存储
import { RateLimiterMemcached } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterMemcached({
storeClient: memcachedClient,
points: 10,
duration: 1
});
express-brute 需要为不同存储安装额外的 store 包。
// express-brute: MongoDB 存储
import ExpressBrute from 'express-brute';
import MongoDBStore from 'express-brute-mongodb-store';
const store = new MongoDBStore(mongoConnection);
const brute = new ExpressBrute(store);
如何识别"谁"在发起请求决定了限流的精确度。
express-rate-limit 默认按 IP,但支持自定义键生成函数。
// express-rate-limit: 自定义键控
const limiter = rateLimit({
keyGenerator: (req) => {
return req.user ? req.user.id : req.ip;
},
max: 100
});
express-limiter 通过 lookup 配置指定键来源。
// express-limiter: 多键控支持
limiter(app, {
lookup: ['connection.remoteAddress', 'user.id'],
total: 100,
expire: 3600000
});
rate-limiter-flexible 允许在 consume 时动态指定键,最灵活。
// rate-limiter-flexible: 动态键控
app.post('/api/action', async (req, res) => {
const key = req.user ? `user:${req.user.id}` : `ip:${req.ip}`;
try {
await rateLimiter.consume(key);
res.send('操作成功');
} catch (rejRes) {
res.status(429).send('操作过于频繁');
}
});
express-brute 默认按 IP,可通过中间件参数覆盖。
// express-brute: 按用户限流
app.post('/login', brute.prevent, (req, res, next) => {
// 验证逻辑
if (valid) {
brute.reset(req, next); // 成功后重置计数
}
});
express-slow-down 继承 express-rate-limit 的键控逻辑。
// express-slow-down: 与 rate-limit 相同键控
const speedLimiter = slowDown({
keyGenerator: (req) => req.user ? req.user.id : req.ip,
delayAfter: 50,
delayMs: 500
});
rate-limiter-flexible 支持最复杂场景,如不同端点不同限制、用户等级差异化限流。
// rate-limiter-flexible: 多级限流策略
const regularLimiter = new RateLimiterRedis({ points: 10, duration: 1 });
const premiumLimiter = new RateLimiterRedis({ points: 100, duration: 1 });
app.use('/api/', (req, res, next) => {
const limiter = req.user?.isPremium ? premiumLimiter : regularLimiter;
limiter.consume(req.ip).then(() => next()).catch(() => res.status(429).send());
});
express-rate-limit 支持请求头返回限流信息,便于客户端调整。
// express-rate-limit: 返回限流头
const limiter = rateLimit({
standardHeaders: true, // 返回 RateLimit-* 头
legacyHeaders: false,
max: 100
});
express-slow-down 可配置延迟计算函数,实现非线性惩罚。
// express-slow-down: 非线性延迟
const speedLimiter = slowDown({
delayMs: (hits) => Math.pow(hits, 2) * 100 // 指数增长延迟
});
express-brute 支持请求成功后自动重置计数,适合登录场景。
// express-brute: 成功重置
app.post('/login', brute.prevent, (req, res) => {
authenticate(req.body, (err, user) => {
if (user) {
brute.reset(req); // 登录成功重置尝试次数
res.send('欢迎');
}
});
});
express-limiter 配置相对固定,适合简单场景。
// express-limiter: 基础配置
limiter(app, {
total: 100,
expire: 3600000,
lookup: 'connection.remoteAddress'
});
重要警告:express-brute 已不再积极维护,最后更新距今较久。虽然功能稳定,但存在潜在安全风险,新项目中应避免使用。
express-limiter 维护频率较低,功能基础,适合简单 Redis 限流需求。
express-rate-limit 是目前 Express 生态中最活跃维护的限流包,社区支持好,文档完善。
express-slow-down 由 express-rate-limit 同一作者维护,两者配合使用效果最佳。
rate-limiter-flexible 维护活跃,功能最强大,适合复杂企业级应用,但学习曲线稍陡。
| 特性 | express-brute | express-limiter | express-rate-limit | express-slow-down | rate-limiter-flexible |
|---|---|---|---|---|---|
| 维护状态 | ⚠️ 停止维护 | 🟡 低频维护 | 🟢 活跃维护 | 🟢 活跃维护 | 🟢 活跃维护 |
| 存储支持 | 有限 | Redis 仅 | 内存 + Redis | 内存 + Redis | 15+ 种后端 |
| 配置灵活性 | 中 | 低 | 高 | 高 | 极高 |
| 跨框架支持 | Express 仅 | Express 仅 | Express 仅 | Express 仅 | 多框架支持 |
| 暴力破解防护 | ✅ 专为设计 | ❌ | ❌ | ❌ | ✅ 可配置 |
| 渐进延迟 | ❌ | ❌ | ❌ | ✅ 核心功能 | ✅ 可配置 |
| 学习曲线 | 低 | 低 | 低 | 低 | 中 |
推荐:express-rate-limit + Redis
大多数 API 只需要基础的请求频率限制,express-rate-limit 配置简单,社区支持好,足够满足需求。
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
const apiLimiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true
});
app.use('/api/', apiLimiter);
推荐:rate-limiter-flexible(新项目)或 express-brute(旧项目维护)
登录端点需要更严格的防护和指数退避策略。
import { RateLimiterRedis } from 'rate-limiter-flexible';
const loginLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'login',
points: 5, // 5 次尝试
duration: 60 * 15 // 15 分钟内
});
app.post('/login', async (req, res) => {
try {
await loginLimiter.consume(req.ip);
// 验证逻辑
} catch (rejRes) {
res.status(429).send('尝试次数过多');
}
});
推荐:rate-limiter-flexible
如果你的应用不仅使用 Express,还涉及 Koa、Fastify 或其他框架,统一使用 rate-limiter-flexible 可以保持限流策略一致。
推荐:express-rate-limit + express-slow-down 组合
先使用 express-slow-down 减缓频繁请求者,达到硬性限制后再用 express-rate-limit 拒绝。
import slowDown from 'express-slow-down';
import rateLimit from 'express-rate-limit';
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 50,
delayMs: 500
});
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api/', speedLimiter, rateLimiter);
推荐:rate-limiter-flexible
需要根据用户订阅等级动态调整限流策略。
const limiters = {
free: new RateLimiterRedis({ points: 10, duration: 1 }),
pro: new RateLimiterRedis({ points: 100, duration: 1 }),
enterprise: new RateLimiterRedis({ points: 1000, duration: 1 })
};
app.use('/api/', (req, res, next) => {
const tier = req.user?.subscription || 'free';
limiters[tier].consume(req.user.id)
.then(() => next())
.catch(() => res.status(429).send('超出配额'));
});
对于新项目,优先选择 express-rate-limit(简单场景)或 rate-limiter-flexible(复杂场景)。两者维护活跃,文档完善,社区支持好。
对于旧项目维护,如果已使用 express-brute 或 express-limiter 且运行稳定,可暂时保留,但建议制定迁移计划。
express-slow-down 是很好的补充工具,与 express-rate-limit 配合使用可提供更友好的限流体验。
记住:限流只是安全策略的一环,应结合认证、授权、输入验证等措施构建完整的防护体系。
选择它如果你需要简单、轻量且广泛采用的 Express 专用限流中间件。它支持内存和 Redis 存储,配置简单,适合大多数标准 API 限流场景。社区活跃,文档完善,是 Express 生态中的默认选择。
选择它如果你需要跨框架支持(不仅限于 Express)、细粒度的限流策略或多种存储后端(Redis、Memcached、数据库等)。它提供最灵活的配置选项,适合复杂的企业级应用和微服务架构。
仅建议在维护旧项目时使用,该包已不再积极维护。它专为防止暴力破解设计,支持指数退避策略,但缺乏现代存储后端支持。如果是新项目,请避免使用并考虑迁移到 rate-limiter-flexible。
适用于需要简单 Redis 集成的传统 Express 项目。它的配置相对直接,但功能较为基础,缺乏灵活的键控策略。如果你的项目已经依赖它且运行稳定,可以继续使用,但新项目建议评估更现代的替代方案。
选择它当你希望在不直接拒绝请求的情况下减缓恶意流量。它通过增加响应延迟来惩罚频繁请求者,适合需要保持服务可用性但希望 discouraging 滥用的场景。通常与 express-rate-limit 配合使用。
express-rate-limit Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset. Plays nice with express-slow-down and ratelimit-header-parser.
The full documentation is available on-line.
import { rateLimit } from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
standardHeaders: 'draft-8', // draft-6: `RateLimit-*` headers; draft-7 & draft-8: combined `RateLimit` header
legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
ipv6Subnet: 56, // Set to 60 or 64 to be less aggressive, or 52 or 48 to be more aggressive
// store: ... , // Redis, Memcached, etc. See below.
})
// Apply the rate limiting middleware to all requests.
app.use(limiter)
The rate limiter comes with a built-in memory store, and supports a variety of external data stores.
All function options may be async. Click the name for additional info and default values.
| Option | Type | Remarks |
|---|---|---|
windowMs | number | How long to remember requests for, in milliseconds. |
limit | number | function | How many requests to allow. |
message | string | json | function | Response to return after limit is reached. |
statusCode | number | HTTP status code after limit is reached (default is 429). |
handler | function | Function to run after limit is reached (overrides message and statusCode settings, if set). |
legacyHeaders | boolean | Enable the X-Rate-Limit header. |
standardHeaders | 'draft-6' | 'draft-7' | 'draft-8' | Enable the Ratelimit header. |
identifier | string | function | Name associated with the quota policy enforced by this rate limiter. |
store | Store | Use a custom store to share hit counts across multiple nodes. |
passOnStoreError | boolean | Allow (true) or block (false, default) traffic if the store becomes unavailable. |
keyGenerator | function | Identify users (defaults to IP address). |
ipv6Subnet | number (32-64) | function | false | How many bits of IPv6 addresses to use in default keyGenerator |
requestPropertyName | string | Add rate limit info to the req object. |
skip | function | Return true to bypass the limiter for the given request. |
skipSuccessfulRequests | boolean | Uncount 1xx/2xx/3xx responses. |
skipFailedRequests | boolean | Uncount 4xx/5xx responses. |
requestWasSuccessful | function | Used by skipSuccessfulRequests and skipFailedRequests. |
validate | boolean | object | Enable or disable built-in validation checks. |
Sponsored by Zuplo a fully-managed API Gateway for developers. Add dynamic rate-limiting, authentication and more to any API in minutes. Learn more at zuplo.com
Thanks to Mintlify for hosting the documentation at express-rate-limit.mintlify.app
Finally, thank you to everyone who's contributed to this project in any way! 🫶
If you encounter a bug or want to see something added/changed, please go ahead and open an issue! If you need help with something, feel free to start a discussion!
If you wish to contribute to the library, thanks! First, please read the contributing guide. Then you can pick up any issue and fix/implement it!
MIT © Nathan Friedly, Vedant K