maba / gentle-force-bundle
Symfony 扩展包,集成了 gentle-force 库,用于限制暴力破解尝试和普通请求,基于 Redis 的漏桶/令牌桶算法
Requires
- php: ^7.1 | ^8.0
- maba/dependency-injection-extra: ^0.1.1|^1.0
- maba/gentle-force: ^0.2.1|^0.3
- psr/log: ^1.0
- symfony/framework-bundle: ^4.3|^5.0
Requires (Dev)
- doctrine/cache: ^1.6|^2.0
- friendsofphp/php-cs-fixer: ^3.0
- google/recaptcha: ~1.1.3
- phpunit/phpunit: ^7.5|^8.5|^9.5
- sebastian/comparator: ^3.0 |^4.0
- symfony/asset: ^4.3|^5.0
- symfony/dependency-injection: ^4.3|^5.0
- symfony/error-handler: ^4.3|^5.0
- symfony/security-bundle: ^4.3|^5.0
- symfony/templating: ^4.3|^5.0
- symfony/twig-bundle: ^4.3|^5.0
- symfony/yaml: ^4.3|^5.0
Suggests
- google/recaptcha: Needed to use 'recaptcha_headers' or 'recaptcha_template' strategy
- symfony/monolog-bundle: Needed to use 'log' strategy, log connection failures and blocked requests
- symfony/security-bundle: Needed to use 'username' identifier
- symfony/twig-bundle: Needed to use 'recaptcha_template' strategy
README
这是一个用于限制暴力破解尝试(如无效凭证)和普通请求的 symfony 扩展包。
它将独立库 gentle-force 集成到 Symfony 框架中。
特性
- 可用于限制暴力破解尝试;
- 可用于请求速率限制;
- 使用漏桶/令牌桶算法。这意味着用户不需要等待下一小时或一天,随着时间的推移,额外的尝试是可能的。这也意味着请求不会在每小时开始时大批量到达;
- 处理竞争条件。这对于暴力破解限制很重要。例如,如果有1000个请求同时发出以检查同一用户的密码,则只有配置的数量允许尝试;
- 可以为单个用例配置多个限制(例如,每分钟最多100个请求和每小时200个请求);
- 不假设其用途和位置 - 它可以用用户标识符、API令牌、IP地址或其他任何数据来分组使用;
- 提供与 Google reCAPTCHA 的集成。
安装
composer require maba/gentle-force-bundle
在 AppKernel.php
中注册扩展包
new \Maba\Bundle\GentleForceBundle\MabaGentleForceBundle(),
如果使用 reCAPTCHA
composer require google/recaptcha
在 app/routing.yml
中导入路由
gentle_force: resource: '@MabaGentleForceBundle/Resources/config/routing.xml'
使用
通常,只需在 config.yml
文件中配置监听器即可。
您还可以注入限制服务并集成您自己的逻辑 - 请参阅下文的 高级使用。
配置
示例
按 IP 地址和用户限制 API 请求的示例配置
maba_gentle_force: limits: api_request: # Allow 10 requests each minute. # User can "save up" hour of usage if not using API. # This means up to 610 requests at once, after that - # 10 requests per minute, which could again save-up up to 610. - max_usages: 10 period: 1m bucketed_period: 1h listeners: - path: ^/api/ # automatically limit matching requests limits_key: api_request identifiers: [ip] # limit by IP address - path: ^/api limits_key: api_request # additionally limit by username, if available identifiers: [username]
限制登录表单失败的示例配置
maba_gentle_force: limits: credentials_error: # Allow 3 errors per hour, # 2 additional errors if no errors were made during last hour: - max_usages: 3 period: 1h bucketed_usages: 2 # Allow 10 errors per day: - max_usages: 10 period: 1d listeners: - path: ^/login # match only POST requests to /login* methods: [POST] limits_key: credentials_error identifiers: [ip] strategy: recaptcha_page # only status 302 is successful in our case # response code 200 usually displays error message, # while we redirect after success success_statuses: ['302'] strategies: recaptcha_template: template: custom.html.twig # optional - overwrite template recaptcha: site_key: my_recaptcha_site_key # get this at google.com/recaptcha secret: my_recaptcha_secret # this also
限制
限制由具体用例定义。它可能是您的 API 请求、凭据失败、密码重置尝试、注册电子邮件订阅、检查用户名是否可用等。
使用任何唯一的键来标识限制配置 - 之后使用相同的限制键来计算是否达到具体的限制。
每个限制配置可以定义多个限制。如果您希望对更频繁的失败或请求有更大的间隔阻止,这很有用。例如,您可以为同一用例的分钟、日和周设置不同的限制。如果达到定义的任何限制,则请求将被阻止。
您可以为限制配置以下键
max_usages
;period
。以秒为单位衡量,您可以使用后缀s
(秒)、m
(分钟)、h
(小时)、d
(日)或w
(周);bucketed_usages
。可选的,在max_usages
之上提供额外的使用。不影响额外令牌的速度(见 令牌桶);bucketed_period
。可选的,与bucketed_usages
相互排斥。在未使用的情况下,向max_usages
之上添加额外使用的周期。后缀可用。
监听器
每个配置的监听器可能会阻塞请求。
过滤
要应用限制的请求的过滤,请使用以下键
path
。正则表达式匹配请求路径;methods
。请求方法列表;hosts
。主机列表。
限制
您必须始终为每个监听器配置用于限制请求的limits_key
和identifiers
。
identifiers
用于指定用于限制的请求中的项目。ip
和username
标识符默认可用,您还可以注册其他标识符。
如果指定了多个标识符,则所有标识符都必须匹配,才能减少可用限制。
请注意,如果至少有一个标识符不可用,则不会应用限制。因此,如果通过[ip, username]
限制,则未经授权的请求将不会受到任何限制。
处理成功请求
对于暴力尝试,捆绑需要检查请求是否成功。按照设计,捆绑在检查是否一切正常之前,预先检查并增加使用计数。因此,如果请求有效,则必须减少此计数。
要配置被视为成功响应的内容,请使用以下之一
success_statuses
。提供表示成功响应的HTTP响应状态列表;failure_statuses
。与success_statuses
相同,但相反 - 其他所有内容都视为成功;success_matcher
。用于识别响应是否成功的服务ID。服务必须实现SuccessMatcherInterface
。
如果您省略所有三个,则所有请求都视为失败 - 即,功能处理基本请求限制。
定义达到限制的策略
使用strategy
键来识别如果达到此监听器的限制时应使用的策略。有关更多信息,请参阅下面的策略。
策略
以下策略可用
headers
。返回预配置的带有429 Too Many Requests
状态码的响应。这是默认的;log
。不修改响应,仅记录失败。在配置测试阶段可用;recaptcha_headers
。与headers
相同,但添加recaptcha站点密钥。可以由JavaScript代码用于启动recaptcha小部件;recaptcha_template
。返回带有recaptcha小部件的HTML响应。在成功提交recaptcha后,当前页面将被刷新。
您可以配置并使用自己的策略 - 只需提供服务ID而不是预配置的键。策略必须实现StrategyInterface
,并且可选地实现ResponseModifyingStrategyInterface
以修改成功的响应。
头部
配置选项
content
。要返回的响应内容;content_type
。响应的内容类型;wait_for_header
。在速率超过响应中使用的头部名称。此响应头定义在重复请求之前应等待的最小时间(以秒为单位);requests_available_header
。在成功的响应中使用的头部名称,用于标识此时可用的请求数量。
日志
您可以为日志配置level
(默认为error
)。
Recaptcha
对于recaptcha_headers
,您可以配置site_key_header
和unlock_url_header
以指定在速率超过响应中使用的头部名称,以提供配置的recaptcha站点密钥和解锁绝对URL。
对于recaptcha_template
,您可以配置template
以用于生成响应。有关传递的数据的更多信息,请参阅捆绑包内的模板。此策略需要TwigBundle
才能工作。
对于这两种策略,您必须安装recaptcha(请参阅安装)并配置recaptcha站点数据(请参阅配置示例)。
当导入路由时,maba_gentle_force_unlock_recaptcha
路由可用(POST
方法)。使用 application/x-www-form-urlencoded
编码在 g-recaptcha-response
字段中传递 recaptcha 响应。空的 200
响应表示速率限制已重置。发生错误时,将返回带有 JSON 内容的 400
响应,errors
键将包含来自 recaptcha 服务的错误数组。有关更多信息,请参阅 RecaptchaUnlockController
和 twig 模板中的 JavaScript 代码。
Redis
要配置 Redis 客户端,可以使用 host
(默认为 localhost
)、parameters
和 options
(允许配置与 Redis 哨兵的连接)或 service_id
来提供自定义的 Predis\Redis
服务。
您可以配置 prefix
以为所有创建的键添加额外的前缀。
如果您希望避免在 Redis 连接失败时完全使用速率限制,但仍然像往常一样处理请求,则将 failure_strategy
配置为 ignore
。在连接失败的情况下,您将得到错误记录,而不是导致 500
响应的未处理的异常。
完整配置参考
maba_gentle_force: redis: host: ~ parameters: [] options: replication: ~ service: ~ parameters: password: ~ service_id: ~ prefix: ~ failure_strategy: fail limits: my_limit_name: - max_usages: ~ period: ~ bucketed_usages: ~ bucketed_period: ~ strategies: default: headers headers: wait_for_header: null requests_available_header: null content: 'Too many requests' content_type: 'text/plain; charset=UTF-8' log: level: error recaptcha_headers: site_key_header: null unlock_url_header: null recaptcha_template: template: null listeners: - path: ^/ limits_key: ~ identifiers: [] strategy: ~ success_matcher: ~ success_statuses: [] failure_statuses: [] methods: [] hosts: [] recaptcha: site_key: ~ secret: ~ listener_priorities: default: 1000 post_authentication: 0
附加标识符
您可以为您的监听器提供附加标识符进行配置。
您需要创建一个实现 IdentifierProviderInterface
的服务,并将其标记为 maba_gentle_force.identifier_provider
(在 identifierType
属性中提供名称)。
例如
<?php namespace Acme; use Symfony\Component\HttpFoundation\Request; use Maba\Bundle\GentleForceBundle\Service\IdentifierProvider\IdentifierProviderInterface; class UserAgentProvider implements IdentifierProviderInterface { public function getIdentifier(Request $request) { return $request->headers->get('User-Agent'); } }
<service class="Acme\UserAgentProvider"> <tag name="maba_gentle_force.identifier_provider" identifierType="user_agent"/> </service>
# ... listeners: - limits_key: api_request # limit by IP and User-Agent combination identifiers: [ip, user_agent]
高级使用
速率限制
/** @var Maba\GentleForce\Throttler $throttler */ $throttler = $container->get('maba_gentle_force.throttler'); try { $result = $throttler->checkAndIncrease('api_request', $request->getClientIp()); $response->headers->set('Requests-Available', $result->getUsagesAvailable()); } catch (RateLimitReachedException $exception) { return new Response('', 429, ['Wait-For' => $exception->getWaitForInSeconds()]); }
暴力限制
try { // we must increase error count in-advance before even checking credentials // this avoids race-conditions with lots of requests $credentialsResult = $throttler->checkAndIncrease('credentials_error', $username); } catch (RateLimitReachedException $exception) { $error = sprintf('Too much password tries for user. Please try after %s seconds', $exception->getWaitForInSeconds()); return $this->showError($error); } $credentialsValid = $credentialsManager->checkCredentials($username, $password); if ($credentialsValid) { // as we've increased error count in advance, we need to decrease it if everything went fine $credentialsResult->decrease(); // log user into system }
手动重置特定限制
有两个命令可以在需要时手动重置限制。
maba:gentle-force:reset
命令交互式地请求所需的监听器配置和每个标识符(如用户名、IP 等)。
maba:gentle-force:reset-limit
命令接受两个参数 - 限制键和要重置的标识符。这可以在使用高级使用并知道要使用的具体标识符时使用。
语义版本
此捆绑包遵循 语义版本。
此捆绑包的公共 API(换句话说,如果您想轻松更新到新版本,则应仅使用这些功能)
- 只有未标记为
public="false"
的服务 - 只有标记有
@api
的类、接口和类方法 - 控制台命令
- 支持的 DIC 标签
- 配置参考
- 路由键和具有路由的控制器
例如,如果只有类方法标记了 @api
,则不应扩展该类,因为构造函数可能在任何版本中更改。
有关 API 中可以更改的内容以及不能更改的内容的基本信息,请参阅 Symfony BC 规则。请注意,在此捆绑包中,一切默认为 @internal
。
运行测试
composer update vendor/bin/phpunit
贡献
请随意创建问题并提供拉取请求。
您可以使用此命令修复任何代码风格问题
vendor/bin/php-cs-fixer fix --config=.php_cs