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 配合使用可提供更友好的限流体验。
记住:限流只是安全策略的一环,应结合认证、授权、输入验证等措施构建完整的防护体系。
选择它当你希望在不直接拒绝请求的情况下减缓恶意流量。它通过增加响应延迟来惩罚频繁请求者,适合需要保持服务可用性但希望 discouraging 滥用的场景。通常与 express-rate-limit 配合使用。
仅建议在维护旧项目时使用,该包已不再积极维护。它专为防止暴力破解设计,支持指数退避策略,但缺乏现代存储后端支持。如果是新项目,请避免使用并考虑迁移到 rate-limiter-flexible。
适用于需要简单 Redis 集成的传统 Express 项目。它的配置相对直接,但功能较为基础,缺乏灵活的键控策略。如果你的项目已经依赖它且运行稳定,可以继续使用,但新项目建议评估更现代的替代方案。
选择它如果你需要简单、轻量且广泛采用的 Express 专用限流中间件。它支持内存和 Redis 存储,配置简单,适合大多数标准 API 限流场景。社区活跃,文档完善,是 Express 生态中的默认选择。
选择它如果你需要跨框架支持(不仅限于 Express)、细粒度的限流策略或多种存储后端(Redis、Memcached、数据库等)。它提供最灵活的配置选项,适合复杂的企业级应用和微服务架构。
Basic rate-limiting middleware for Express that slows down responses rather than blocking them outright. Use to slow repeated requests to public APIs and/or endpoints such as password reset.
Plays nice with (and built on top of) Express Rate Limit
The default memory store does not share state with any other processes or servers. It's sufficient for basic abuse prevention, but an external store will provide more consistency.
express-slow-down uses express-rate-limit's stores
Note: when using express-slow-down and express-rate-limit with an external store, you'll need to create two instances of the store and provide different prefixes so that they don't double-count requests.
From the npm registry:
# Using npm
> npm install express-slow-down
# Using yarn or pnpm
> yarn/pnpm add express-slow-down
From Github Releases:
# Using npm
> npm install https://github.com/express-rate-limit/express-slow-down/releases/download/v{version}/express-slow-down.tgz
# Using yarn or pnpm
> yarn/pnpm add https://github.com/express-rate-limit/express-slow-down/releases/download/v{version}/express-slow-down.tgz
Replace {version} with the version of the package that you want to your, e.g.:
2.0.0.
This library is provided in ESM as well as CJS forms, and works with both Javascript and Typescript projects.
This package requires you to use Node 16 or above.
Import it in a CommonJS project (type: commonjs or no type field in
package.json) as follows:
const { slowDown } = require('express-slow-down')
Import it in a ESM project (type: module in package.json) as follows:
import { slowDown } from 'express-slow-down'
To use it in an API-only server where the speed-limiter should be applied to all requests:
import { slowDown } from 'express-slow-down'
const limiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 5, // Allow 5 requests per 15 minutes.
delayMs: (hits) => hits * 100, // Add 100 ms of delay to every request after the 5th one.
/**
* So:
*
* - requests 1-5 are not delayed.
* - request 6 is delayed by 600ms
* - request 7 is delayed by 700ms
* - request 8 is delayed by 800ms
*
* and so on. After 15 minutes, the delay is reset to 0.
*/
})
// Apply the delay middleware to all requests.
app.use(limiter)
To use it in a 'regular' web server (e.g. anything that uses
express.static()), where the rate-limiter should only apply to certain
requests:
import { slowDown } from 'express-slow-down'
const apiLimiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 1, // Allow only one request to go at full-speed.
delayMs: (hits) => hits * hits * 1000, // 2nd request has a 4 second delay, 3rd is 9 seconds, 4th is 16, etc.
})
// Apply the delay middleware to API calls only.
app.use('/api', apiLimiter)
To use a custom store:
import { slowDown } from 'express-slow-down'
import { MemcachedStore } from 'rate-limit-memcached'
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 1, // Allow only one request to go at full-speed.
delayMs: (hits) => hits * hits * 1000, // Add exponential delay after 1 request.
store: new MemcachedStore({
/* ... */
}), // Use the external store
})
// Apply the rate limiting middleware to all requests.
app.use(speedLimiter)
Note: most stores will require additional configuration, such as custom prefixes, when using multiple instances. The default built-in memory store is an exception to this rule.
windowMs
number
Time frame for which requests are checked/remembered.
Note that some stores have to be passed the value manually, while others infer it from the options passed to this middleware.
Defaults to 60000 ms (= 1 minute).
delayAfter
number|function
The max number of requests allowed during windowMs before the middleware
starts delaying responses. Can be the limit itself as a number or a (sync/async)
function that accepts the Express req and res objects and then returns a
number.
Defaults to 1.
An example of using a function:
const isPremium = async (user) => {
// ...
}
const limiter = slowDown({
// ...
delayAfter: async (req, res) => {
if (await isPremium(req.user)) return 10
else return 1
},
})
delayMs
number | function
The delay to apply to each request once the limit is reached. Can be the delay
itself (in milliseconds) as a number or a (sync/async) function that accepts a
number (number of requests in the current window), the Express req and res
objects and then returns a number.
By default, it increases the delay by 1 second for every request over the limit:
const limiter = slowDown({
// ...
delayMs: (used) => (used - delayAfter) * 1000,
})
maxDelayMs
number | function
The absolute maximum value for delayMs. After many consecutive attempts, the
delay will always be this value. This option should be used especially when your
application is running behind a load balancer or reverse proxy that has a
request timeout. Can be the number itself (in milliseconds) or a (sync/async)
function that accepts the Express req and res objects and then returns a
number.
Defaults to Infinity.
For example, for the following configuration:
const limiter = slowDown({
// ...
delayAfter: 1,
delayMs: (hits) => hits * 1000,
maxDelayMs: 4000,
})
The first request will have no delay. The second will have a 2 second delay, the 3rd will have a 3 second delay, but the fourth, fifth, sixth, seventh and so on requests will all have a 4 second delay.
express-rate-limitBecause
express-rate-limit
is used internally, additional options that it supports may be passed in. Some
of them are listed below; see express-rate-limit's
documentation
for the complete list.
Note: The
limit(max) option is not supported (usedelayAfterinstead), nor is thehandleroption.
standardHeaderslegacyHeadersrequestPropertyNameskipFailedRequestsskipSuccessfulRequestskeyGeneratoripv6SubnetskiprequestWasSuccessfulvalidatestoreA req.slowDown property is added to all requests with the limit, used, and
remaining number of requests and, if the store provides it, a resetTime Date
object. It also has the delay property, which is the amount of delay imposed
on current request (milliseconds). These may be used in your application code to
take additional actions or inform the user of their status.
Note that used includes the current request, so it should always be > 0.
The property name can be configured with the configuration option
requestPropertyName.
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