☰
Rack::Attack 示例配置实战:从零构建 IP 限流与登录暴力破解防护
2026/10/12 4:18:08 网站建设 项目流程
  • 应用安全
  • 后端

【免费下载链接】rack-attack

Rack middleware for blocking & throttling

项目地址:https://gitcode.com/gh_mirrors/ra/rack-attack
点击查看免费下载

导读

本文以 rack-attack 官方示例配置 docs/example_configuration.md 为骨架,完整讲解如何将它部署到 Rails 应用(config/initializers/rack_attack.rb),覆盖缓存存储选型、按 IP 限流、登录接口按 IP 与按邮箱双维度防爆破、以及自定义限流响应等核心能力。读完后,你将获得一份可复制、可运行、带源码级原理注释的完整防护配置,并了解每个配置项背后的实现机制,从而安全应对 95% 以上的恶意爬虫与误配置流量。

一、这份配置能做什么:核心价值与适用边界

示例配置的目标非常务实:挡住 95% 的坏请求。它针对两类最常见的滥用场景做了开箱即用的防护:

  1. 垃圾爬虫与恶意客户端:某个 IP 在短时间内向应用服务器发起大量请求,占用 CPU 与连接资源,甚至导致应用被拖垮;
  2. 登录暴力破解(Brute-Force):攻击者要么用大量邮箱/密码组合轰炸登录接口,要么使用大量不同 IP 的机器对某个特定账号轮流尝试密码。

需要正视的是,示例配置并不能阻止专业黑客——它防的是"误配置的爬虫半夜把应用搞挂"这类事故。若需要更细粒度的策略(指数退避限流、基于 Rails.cache 的动态封禁、Rails 路由级白名单等),请继续阅读 docs/advanced_configuration.md,本文第六节也会给出几条关键的进阶路线。

从代码执行顺序看,Rack::Attack 中间件对每个请求按Safelist(白名单)→ Blocklist(黑名单)→ Throttle(限流)→ Track(追踪)的优先级依次判定,见 lib/rack/attack.rb。示例配置只涉及 Throttle(限流)与自定义响应,属于最常用、风险最低的起步组合。

二、部署方式与缓存存储配置

2.1 放到哪里执行

对于 Rails 应用,把配置复制到config/initializers/rack_attack.rb即可,Rails 启动时自动加载,并经由lib/rack/attack/railtie.rb将 Rack::Attack 挂载进中间件链。Rails 应用的中间件接入是默认行为;如果是纯 Rack 应用,则需在config.ru中手动use Rack::Attack。

需要注意:默认情况下 Rack::Attack 不执行任何拦截,必须显式配置规则(即本文的 throttle 等)后防护才生效。

2.2 缓存存储:限流计数的唯一数据源

示例配置开头就保留了缓存配置入口:

class Rack::Attack # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new end

关键事实如下:

  • 默认存储:如果应用是 Rails,Rack::Attack 自动使用Rails.cache(见 lib/rack/attack/cache.rb 中Cache.default_store的实现)。

  • 存储用途边界:Rack::Attack.cache只用于限流(throttle),不用于黑名单和白名单判定。因此存储必须实现increment与write方法,与ActiveSupport::Cache::Store的接口保持一致。

  • 生产环境选型建议:示例中注释掉的ActiveSupport::Cache::MemoryStore.new仅适合开发与测试。在真实部署中,每个 Ruby 进程持有独立的 MemoryStore 状态,会按进程数成倍放大每个客户端的可用请求额度,限流形同虚设。生产环境应改用 Memcached 或 Redis 支撑的共享缓存,且推荐为 Rack::Attack 单独使用一个 Redis 数据库,避免攻击流量冲击应用自身缓存。

  • 缓存键格式:每次计数会生成形如rack::attack:<epoch>/<period>:<throttle_name>:<discriminator>的键,其中epoch = Time.now.to_i / period(见 lib/rack/attack/cache.rb)。这正是示例配置注释中"rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"的实际来源。

三、限制垃圾客户端:按 IP 全局限流

# Throttle all requests by IP (60rpm) # # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}" throttle('req/ip', limit: 300, period: 5.minutes) do |req| req.ip # unless req.path.start_with?('/assets') end

这条规则的含义是:任意单个客户端 IP 在 5 分钟(300 秒)窗口内最多允许 300 次请求,超出即返回 429。它面向所有请求路径,只要块返回的判别符(discriminator)为真值就会参与计数——这里判别符就是req.ip。

从源码看,throttle有强制的两个必填参数:limit(窗口内次数上限)与period(窗口时长),缺失任何一个都会抛出ArgumentError(见 lib/rack/attack/throttle.rb)。计数流程为:块返回判别符 → 计算当前时间窗口 → 在缓存中对name:discriminator键做原子increment→ 若计数超过 limit 则判定为限流命中(lib/rack/attack/throttle.rb)。

两个实战要点:

  1. 通过静态资源走 Rack 分发:如果你的静态资源经由 Rack(而非 nginx 等 Web 服务器直接托管)分发,这些请求也会被计入该限流,导致阈值过早触发。示例配置已给出解法——把unless req.path.start_with?('/assets')取消注释,将/assets路径排除在追踪之外(注意unless作用在整个块返回值上,即/assets请求返回nil,跳过计数)。
  2. limit/period 支持传 Proc:除了固定数值,两者都可以是接收 request 的 lambda(见 lib/rack/attack/throttle.rb),可用于"管理员 100 次/秒、普通用户 1 次/60 秒"这类动态阈值场景(README 中limit_proc/period_proc示例)。

四、登录防爆破:按 IP 与按邮箱双维度限流

4.1 按 IP 限流:挡住单点爆破

# Throttle POST requests to /login by IP address # # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" throttle('logins/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? req.ip end end

规则:单个 IP 在 20 秒内最多对/login发起 5 次 POST;只有满足路径为 /login 且为 POST的请求才返回req.ip作为判别符,其余请求块返回nil,不参与计数。它应对的是"攻击者用大量账号密码组合尝试登录"的经典场景。

注意块返回值的语义:返回判别符才计数,返回 falsy 则完全跳过(示例配置中examples/rack_attack.rb也写了同样注释)。因此条件判断写不进块时,务必保证非目标请求返回nil/false,否则会误伤正常请求。

4.2 按邮箱限流:挡住 IP 分布式爆破

# Throttle POST requests to /login by email param # # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{normalized_email}" throttle('logins/email', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? # Normalize the email, using the same logic as your authentication process, to # protect against rate limit bypasses. Return the normalized email if present, nil otherwise. req.params['email'].to_s.downcase.gsub(/\s+/, "").presence end end

规则:同一个邮箱账号在 20 秒内最多允许 5 次登录尝试,与来源 IP 无关。这专门针对"蜂群式多 IP 爆破同一账号"的攻击手法——单看 IP 每条请求都来自不同地址,但判别符换成 email 后,计数就收敛到了账号维度。

三个关键点:

  1. 归一化(Normalize)防绕过:示例代码对邮箱做了to_s.downcase.gsub(/\s+/, "")——转小写、去掉所有空白字符。理由很直接:如果认证逻辑本身认为User@Example.com与user@example.com是同一账号,而限流键不做同样归一化,攻击者就能靠大小写/空格变体刷出独立计数,绕过限流。归一化逻辑必须与你的认证流程保持一致。
  2. .presence的妙用:"".presence返回nil,因此邮箱参数为空时块整体返回nil,请求不参与计数(防止空邮箱把所有人计数到同一个键上)。
  3. 歧视性限流的权衡:该规则存在一个已知副作用——恶意用户可故意用受害者的邮箱发请求,把对方"限流封住",导致其登录被拒。示例文档坦诚说明这种情况不常见、概率低,但作为架构权衡你应该知晓。

另外,从 spec/rack_attack_throttle_spec.rb 可以看到,Rack::Attack 还内置了throttle_discriminator_normalizer(默认to_s.strip.downcase),对判别符做统一的去空白/小写处理,进一步降低同类变体绕过风险。

五、自定义限流响应:伪装 503 迷惑攻击者

默认情况下,命中限流的请求返回HTTP 429(Too Many Requests),响应体为"Retry later\n",并可通过throttled_response_retry_after_header开关附带Retry-After响应头(见 lib/rack/attack/configuration.rb 中DEFAULT_THROTTLED_RESPONDER的实现)。429 语义清晰、对搜索引擎友好,多数场景直接可用。

示例配置给出的可选方案是返回 503:

# self.throttled_responder = lambda do |env| # [ 503, # status # {}, # headers # ['']] # body # end

其动机是让攻击者误以为应用已被打挂,从而放弃攻击;或者纯粹满足你对响应格式的定制需求。自定义 responder 是一个符合 Rack 规范([status, headers, body]三元组)的 lambda。

更进阶的用法(README 与配置源码均支持):responder 内可通过request.env['rack.attack.matched']、request.env['rack.attack.match_data']拿到命中的限流名称与计数数据(discriminator、count、period、limit、epoch_time),从而构造标准RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset头,为行为良好的客户端提供限流状态指引。同时注意Rack::Attack.throttled_response=是已废弃的旧接口,新代码请统一使用throttled_responder=(废弃警告见 lib/rack/attack/configuration.rb)。

六、这份配置之外的进阶路线

示例配置是"入门即安全"的地基,官方文档 docs/advanced_configuration.md 提供了若干可直接叠加的进阶能力,建议按需选用:

进阶能力一句话说明关键实现/来源
指数退避限流用 5 层 throttle(limit 线性增长、period 指数增长)模拟退避效果见 docs/advanced_configuration.md
Request 辅助方法对Rack::Attack::Request打补丁加localhost?等方法,配合 safelist 使用lib/rack/attack/request.rb
基于 ENV 的 blocklist从环境变量解析黑名单域名/Referer,正则化后拦截见 docs/advanced_configuration.md
动态黑名单(Rails.cache)应用内通过Rails.cache.write("block <ip>", true)随时封禁/解封 IP见 docs/advanced_configuration.md
Allow2Ban / Fail2Ban累积失败次数达阈值后封禁 IP 一段时间,示例为拦截 Basic Auth 爆破lib/rack/attack/allow2ban.rb、lib/rack/attack/fail2ban.rb
按 Rails 路由匹配用Rails.application.routes.recognize_path把 URL 解析为controller#action后做 safelist见 docs/advanced_configuration.md

其中 Allow2Ban 与本文登录防护场景强相关:Rack::Attack::Allow2Ban.filter在请求尚未达到maxretry时放行但持续计数,达到阈值后立即封禁bantime时长。这与 Throttle 的"超限即拒绝"语义不同,可在"宽限期 + 硬封禁"的混合策略中与示例配置互补(对应测试见 spec/acceptance/allow2ban_spec.rb)。

七、验证与测试建议

  1. 本地验证:将配置放入config/initializers/rack_attack.rb后,可在开发环境开启缓存(Rails.cache需可用的存储后端),连续快速请求/login观察 429 返回。注意Rack::Attack.reset!会清空rack::attack前缀下的全部限流状态(要求缓存存储支持delete_matched,见 lib/rack/attack/cache.rb 与 spec/rack_attack_reset_spec.rb)。
  2. 测试隔离:测试套件中可用Rack::Attack.reset!清理计数;需要重置规则定义时使用Rack::Attack.clear_configuration(清空 safelists/blocklists/throttles/tracks 并恢复默认 responder,见 lib/rack/attack/configuration.rb)。
  3. 观察 env 标注:被限流的请求会在request.env中留下rack.attack.matched(命中的规则名)、rack.attack.match_type(:throttle)、rack.attack.match_data(计数详情)等标注,便于中间件下游或日志系统定位问题(测试断言见 spec/rack_attack_throttle_spec.rb)。

结语

本文的示例配置在"挡 95% 坏请求"与"不误伤正常用户"之间取得了恰到好处的平衡:全局限流守护应用资源,按 IP + 按邮箱的双重登录限流直击暴力破解要害,归一化与.presence两个细节则堵住了最常见的绕过路径。把它作为起点部署,再按需引入 docs/advanced_configuration.md 中的进阶策略,即可在中间件层面为你的 Rails / Rack 应用筑起一道低成本、高回报的防护层。

  • 应用安全
  • 后端

【免费下载链接】rack-attack

Rack middleware for blocking & throttling

项目地址:https://gitcode.com/gh_mirrors/ra/rack-attack
点击查看免费下载

相关推荐

上一篇:sysHAX架构深度解析:揭秘CPU+GPU异构协同加速的10个核心技术
下一篇:QEMU完全指南:从零开始掌握开源虚拟化神器

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询