maba/gentle-force-bundle

Symfony 扩展包,集成了 gentle-force 库,用于限制暴力破解尝试和普通请求,基于 Redis 的漏桶/令牌桶算法

安装次数: 410,284

依赖者: 1

建议者: 0

安全性: 0

星标: 54

关注者: 6

分支: 14

开放问题: 14

类型:symfony-bundle

0.6.3 2021-08-26 20:03 UTC

This package is auto-updated.

Last update: 2024-08-27 02:31:45 UTC


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_keyidentifiers

identifiers用于指定用于限制的请求中的项目。ipusername标识符默认可用,您还可以注册其他标识符

如果指定了多个标识符,则所有标识符都必须匹配,才能减少可用限制。

请注意,如果至少有一个标识符不可用,则不会应用限制。因此,如果通过[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_headerunlock_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)、parametersoptions(允许配置与 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

运行测试

Travis status

composer update
vendor/bin/phpunit

贡献

请随意创建问题并提供拉取请求。

您可以使用此命令修复任何代码风格问题

vendor/bin/php-cs-fixer fix --config=.php_cs