Epic Stack 中的速率限制(Rate Limiting):基于 express-rate-limit 的分级限流实战指南

发布时间:2026/9/18 2:30:08
Epic Stack 中的速率限制(Rate Limiting):基于 express-rate-limit 的分级限流实战指南 Epic Stack 中的速率限制Rate Limiting基于 express-rate-limit 的分级限流实战指南【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack导读本文基于 Epic Stack 项目中的架构决策记录ADRdocs/decisions/025-rate-limiting.md深入讲解该项目如何借助express-rate-limit构建分层限流体系既包含面向全部流量的通用限流也包含针对登录、注册等高风险端点的更强限制。读完本文你将掌握 Epic Stack 的限流实现原理、三级限流阈值的具体配置、多实例部署下的分布式限流演进路径以及开发与测试环境下如何规避限流干扰。背景为什么要做速率限制暴力破解与邮件滥用是真实威胁攻击者Adversary常常通过不断猜测用户密码来尝试入侵账户这就是经典的暴力破解攻击brute force attack。此外恶意用户可能会反复请求/signup或/settings/profile/change-email这类端点从而触发应用向大量人群发送邮件。频繁的邮件发送会降低你在邮件服务商处的信誉可能导致邮件被标记为垃圾邮件。限流是通用的防御手段一种常见的缓解手段就是速率限制rate limiting只允许某个 IP 地址在给定时间窗口内发起一定数量的请求。对 Web 应用而言用户执行的 GET 请求数量通常远多于 POST 请求因此一刀切的限流并不合理——某些端点和请求方法需要比其他端点更强的限制。注意限流并不能完全消除骚扰邮件问题CSRF Token 在减少这类问题方面效果更好但它能极大地缓解问题。Epic Stack 同时内置了 CSRF 防护相关讨论可参阅 docs/security.md 中的 CSRF 小节。决策采用 express-rate-limit 并内置分级限流根据 ADR 的决策章节Epic Stack 确定了两条核心原则使用express-rate-limit作为限流库默认使用其**内置内存存储in-memory storage**机制。这一方案对大多数应用已经足够且实现最简单对于需要更强保护的用户演进到基于 Redis 的方案并不需要太多额外工作。分级限流策略对非 GET 请求整体施加更强的限流对更容易被滥用的特定端点施加更严格的限流。在 package.json 中可以确认该依赖已锁定为express-rate-limit: ^8.2.1与 Express 5express: ^5.2.1一同使用。源码实现三级限流在 server/index.ts 中的落地限流配置全部位于 server/index.ts 中这是 Epic Stack 的服务端入口文件生产环境运行 React Router 构建产物开发环境加载 Vite 中间件。整体实现由默认配置、三个限流实例、路径分派中间件三部分构成。默认配置与测试豁免机制// server/index.ts import rateLimit, { ipKeyGenerator } from express-rate-limit // When running tests or running in development, we want to effectively disable // rate limiting because playwright tests are very fast and we dont want to // have to wait for the rate limit to reset between tests. const maxMultiple !IS_PROD || process.env.PLAYWRIGHT_TEST_BASE_URL ? 10_000 : 1 const rateLimitDefault { windowMs: 60 * 1000, limit: 1000 * maxMultiple, standardHeaders: true, legacyHeaders: false, validate: { trustProxy: false }, // Malicious users can spoof their IP address which means we should not default // to trusting req.ip when hosted on Fly.io. However, users cannot spoof Fly-Client-Ip. // When sitting behind a CDN such as cloudflare, replace fly-client-ip with the CDN // specific header such as cf-connecting-ip keyGenerator: (req: express.Request) { const ip req.ip ?? req.socket?.remoteAddress return req.get(fly-client-ip) ?? ipKeyGenerator(ip ?? 0.0.0.0) }, }几个关键参数的含义windowMs: 60 * 1000限流时间窗口为 1 分钟limit: 1000 * maxMultiple默认上限为每分钟 1000 次请求standardHeaders: true/legacyHeaders: false采用新版标准化的RateLimit-*响应头对应draft-7规范而不是旧版的X-RateLimit-*validate: { trustProxy: false }显式关闭对trust proxy的默认校验避免在代理链配置不完整时误判maxMultiple是测试豁免的关键当处于非生产环境IS_PROD为 false或设置了PLAYWRIGHT_TEST_BASE_URL时maxMultiple为 10_000即所有限流阈值放大 10000 倍实际等效于禁用限流。这是因为 Playwright 端到端测试执行速度极快若不做此处理测试之间会因等待限流窗口重置而阻塞。三级限流实例通用 / 强 / 最强const strongestRateLimit rateLimit({ ...rateLimitDefault, windowMs: 60 * 1000, limit: 10 * maxMultiple, // 生产环境每分钟 10 次 }) const strongRateLimit rateLimit({ ...rateLimitDefault, windowMs: 60 * 1000, limit: 100 * maxMultiple, // 生产环境每分钟 100 次 }) const generalRateLimit rateLimit(rateLimitDefault) // 生产环境每分钟 1000 次三个实例共享同一份默认配置仅limit不同。生产环境maxMultiple 1下的阈值梯度为限流级别变量名时间窗口生产环境上限适用场景通用限流generalRateLimit60 秒1000 次GET/HEAD 等常规读请求强限流strongRateLimit60 秒100 次非 GET/HEAD 的一般写请求最强限流strongestRateLimit60 秒10 次高危端点登录、注册、验证等IP 识别为何优先信任fly-client-ipkeyGenerator是限流按谁计数的核心。这里有个安全细节值得注意恶意用户可以伪造req.ip通过伪造X-Forwarded-For等头因此在 Fly.io 上不能默认信任req.ip但用户无法伪造Fly-Client-Id/Fly-Client-Ip头Fly 的代理会在入口处覆写这些头因此代码优先取req.get(fly-client-ip)兜底逻辑若取不到则回退到req.ip ?? req.socket?.remoteAddress最后再用ipKeyGenerator处理0.0.0.0之类的缺省值部署在 Cloudflare 等 CDN 之后时官方注释建议把fly-client-ip替换为对应 CDN 的专用头如 Cloudflare 的cf-connecting-ip。这正是 ADR 中生产环境多实例 负载均衡场景下的落地细节trust proxy被设为 true见server/index.ts中的app.set(trust proxy, true)因为 Fly 是我们的代理但限流的keyGenerator并不盲信req.ip。路径分派非 GET 一律加强verify 特殊对待app.use((req, res, next) { const strongPaths [ /login, /signup, /verify, /admin, /onboarding, /reset-password, /settings/profile, /resources/login, /resources/verify, ] if (req.method ! GET req.method ! HEAD) { if (strongPaths.some((p) req.path.includes(p))) { return strongestRateLimit(req, res, next) } return strongRateLimit(req, res, next) } // the verify route is a special case because its a GET route that // can have a token in the query string if (req.path.includes(/verify)) { return strongestRateLimit(req, res, next) } return generalRateLimit(req, res, next) })分派逻辑清晰且覆盖面广非 GET/HEAD 请求如果路径命中strongPaths中的任何一个/login、/signup、/verify、/admin、/onboarding、/reset-password、/settings/profile、/resources/login、/resources/verify则使用strongestRateLimit每分钟 10 次其余写请求统一走strongRateLimit每分钟 100 次。GET/HEAD 请求的特殊分支/verify是个特例因为它是 GET 路由但查询字符串中可能携带验证 Token容易被枚举或滥用所以即使是 GET 也套用最强限流。其余所有请求常规 GET/HEAD走generalRateLimit每分钟 1000 次。注意req.path.includes(p)用的是包含匹配而非精确匹配因此/settings/profile/change-email邮件滥用的目标端点之一也会命中/settings/profile前缀从而被最强限流保护——这与 ADR 中提到的邮件滥用威胁直接对应。这些路由对应的应用层入口可以在 app/routes 中印证例如登录页在 login.tsx注册在 signup.tsx验证页在 verify.tsx管理员区在 admin。多实例与分布式限流从内存存储到 RedisADR 明确指出一个关键挑战在生产环境中应用通常以多个实例运行在负载均衡器Epic Stack 的场景是 Fly之后。此时必须保证限流是跨所有实例全局生效的而不是各自实例独立计数——否则攻击者只要把请求分散到不同实例就能轻松绕开单实例的阈值。express-rate-limit通过**共享存储shared storage**机制解决这一问题常见方案是 Redis 或 memcached。Epic Stack 的默认选择是库内置的内存存储因为对大多数应用已经足够是最简单的实现方式演进到 Redis 是express-rate-limit的内置能力迁移成本低。server/index.ts 中没有任何 Redis 连接代码印证了默认内存存储的决策docs/security.md 的 Rate Limiting 一节也确认了这一点并指出将存储外部化为 Redis 是 express-rate-limit 的内置特性。若你的应用需要全局一致限流只需为限流实例传入store选项指向 Redis 存储即可其余分派逻辑无需改动。后果与权衡共享 IP 与阈值激进问题ADR 如实记录了该方案的两点代价这也是使用限流时必须知晓的边界共享 IP 误伤从共享 IP 地址例如公司/校园网络访问应用的用户可能比预期更早触发限流。这是 Epic Stack 目前愿意接受的权衡。默认阈值可能过激默认限流级别对某些人的使用场景可能过于激进导致困惑。因此项目明确要求把这些潜在问题文档化帮助使用者意识到问题并知道如何调整。实践中的调整入口就是 server/index.ts 顶部的rateLimitDefault与三个限流实例的limit值调大limit放宽限制调小limit收紧限制若只想针对个别端点调整可以在分派中间件中为特定路径单独注册新的rateLimit实例。调整后建议跑一遍测试套件验证行为测试环境中限流被放大 10000 倍一般不会干扰断言。与其他安全机制的协同在 Epic Stack 的整体安全体系中限流并非孤立存在。从 docs/security.md 可以看到它与其他机制的分工CSRF 防护使用remix-utils的 CSRF 工具配合 Honeypot 隐藏字段从表单来源可信度上阻断自动化滥用——ADR 明确指出 CSRF Token 在减少骚扰邮件方面比限流效果更好HTTPS 强制跳转server/index.ts中通过X-Forwarded-Proto检测 HTTP 请求并 301 跳转至 HTTPSHelmet 安全头禁用x-powered-by、设置 CSP默认 report-only等会话安全会话 Cookie 配置httpOnly、secure、sameSite: lax详见 app/utils/session.server.ts。限流负责的是流量层面的滥用抑制与上述请求内容层面的防护互补共同构成纵深防御。结语Epic Stack 的速率限制设计体现了典型的分层防御思维用express-rate-limit提供基础设施用通用 / 强 / 最强三级阈值平衡可用性与安全性用fly-client-ip解决代理环境下的 IP 识别难题并为多实例部署预留了平滑迁移到 Redis 存储的路径。对基于此模板构建应用的开发者而言理解 server/index.ts 中的限流分派逻辑并根据自身业务调整limit阈值是上线前必做的安全功课。【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询