8000 minor #15051 [RateLimiter][Security] More precisely document advanced… · SirRFI/symfony-docs@2d126db · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 2d126db

Browse files
committed
minor symfony#15051 [RateLimiter][Security] More precisely document advanced rate limiter configuration (wouterj)
This PR was squashed before being merged into the 5.2 branch. Discussion ---------- [RateLimiter][Security] More precisely document advanced rate limiter configuration These additions are based on questions I received from `@Seldaek` while he was trying out the login throttling feature on a more advanced high availability set-up. Previously, we documented the happy-path, but were not so good at documenting the more advanced settings. This PR adds more detail about locks, storage and implementing your custom login rate limiter. Commits ------- 4933d45 [RateLimiter][Security] More precisely document advanced rate limiter configuration
2 parents 6b19707 + 4933d45 commit 2d126db

File tree

4 files changed

+347
-44
lines changed

4 files changed

+347
-44
lines changed

cache.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ will create pools with service IDs that follow the pattern ``cache.[type]``.
183183
],
184184
]);
185185
186+
.. _cache-create-pools:
187+
186188
Creating Custom (Namespaced) Pools
187189
----------------------------------
188190

lock.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ processes asking for the same ``$version``::
228228
}
229229
}
230230

231+
.. _lock-named-locks:
232+
231233
Named Lock
232234
----------
233235

rate_limiter.rst

Lines changed: 208 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,74 @@ Configuration
124124
The following example creates two different rate limiters for an API service, to
125125
enforce different levels of service (free or paid):
126126

127-
.. code-block:: yaml
128-
129-
# config/packages/rate_limiter.yaml
130-
framework:
131-
rate_limiter:
132-
anonymous_api:
133-
# use 'sliding_window' if you prefer that policy
134-
policy: 'fixed_window'
135-
limit: 100
136-
interval: '60 minutes'
137-
authenticated_api:
138-
policy: 'token_bucket'
139-
limit: 5000
140-
rate: { interval: '15 minutes', amount: 500 }
127+
.. configuration-block::
128+
129+
.. code-block:: yaml
130+
131+
# config/packages/rate_limiter.yaml
132+
framework:
133+
rate_limiter:
134+
anonymous_api:
135+
# use 'sliding_window' if you prefer that policy
136+
policy: 'fixed_window'
137+
limit: 100
138+
interval: '60 minutes'
139+
authenticated_api:
140+
policy: 'token_bucket'
141+
limit: 5000
142+
rate: { interval: '15 minutes', amount: 500 }
143+
144+
.. code-block:: xml
145+
146+
<!-- config/packages/rate_limiter.xml -->
147+
<?xml version="1.0" encoding="UTF-8" ?>
148+
<container xmlns="http://symfony.com/schema/dic/services"
149+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
150+
xmlns:framework="http://symfony.com/schema/dic/symfony"
151+
xsi:schemaLocation="http://symfony.com/schema/dic/services
152+
https://symfony.com/schema/dic/services/services-1.0.xsd
153+
http://symfony.com/schema/dic/symfony
154+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
155+
156+
<framework:config>
157+
<framework:rate-limiter>
158+
<!-- policy: use 'sliding_window' if you prefer that policy -->
159+
<framework:limiter name="anonymous_api"
160+
policy="fixed_window"
161+
limit="100"
162+
interval="60 minutes"
163+
/>
164+
165+
<framework:limiter name="authenticated_api"
166+
policy="token_bucket"
167+
limit="5000"
168+
>
169+
<framework:rate interval="15 minutes"
170+
amount="500"
171+
/>
172+
</framework:limiter>
173+
</framework:rate-limiter>
174+
</framework:config>
175+
</container>
176+
177+
.. code-block:: php
178+
179+
// config/packages/rate_limiter.php
180+
$container->loadFromExtension('framework', [
181+
rate_limiter' => [
182+
'anonymous_api' => [
183+
// use 'sliding_window' if you prefer that policy
184+
'policy' => 'fixed_window',
185+
'limit' => 100,
186+
'interval' => '60 minutes',
187+
],
188+
'authenticated_api' => [
189+
'policy' => 'token_bucket',
190+
'limit' => 5000,
191+
'rate' => [ 'interval' => '15 minutes', 'amount' => 500 ],
192+
],
193+
],
194+
]);
141195
142196
.. note::
143197

@@ -302,27 +356,146 @@ the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the
302356
}
303357
}
304358

305-
Rate Limiter Storage and Locking
306-
--------------------------------
307-
308-
Rate limiters use the default cache and locking mechanisms defined in your
309-
Symfony application. If you prefer to change that, use the ``lock_factory`` and
310-
``storage_service`` options:
311-
312-
.. code-block:: yaml
313-
314-
# config/packages/rate_limiter.yaml
315-
framework:
316-
rate_limiter:
317-
anonymous_api_limiter:
318-
# ...
319-
# the value is the name of any cache pool defined in your application
320-
cache_pool: 'app.redis_cache'
321-
# or define a service implementing StorageInterface to use a different
322-
# mechanism to store the limiter information
323-
storage_service: 'App\RateLimiter\CustomRedisStorage'
324-
# the value is the name of any lock defined in your application
325-
lock_factory: 'app.rate_limiter_lock'
359+
Storing Rate Limiter State: Caching
360+
-----------------------------------
361+
362+
All rate limiter policies require to store the state of the rate limiter
363+
(e.g. how many hits were already made in the current time window). This
364+
state is stored by default using the :doc:`Cache component </cache>`.
365+
366+
The default cache pool used by all limiters is ``cache.rate_limiter``. You
367+
can modify this cache pool by :ref:`defining a "rate_limiter" pool <cache-create-pools>`.
368+
369+
You can also override the pool for a specific limiter using the ``cache_pool``
370+
option:
371+
372+
.. configuration-block::
373+
374+
.. code-block:: yaml
375+
376+
# config/packages/rate_limiter.yaml
377+
framework:
378+
rate_limiter:
379+
anonymous_api:
380+
# ...
381+
382+
# use the "cache.anonymous_rate_limiter" cache pool
383+
cache_pool: 'cache.anonymous_rate_limiter'
384+
385+
.. code-block:: xml
386+
387+
<!-- config/packages/rate_limiter.xml -->
388+
<?xml version="1.0" encoding="UTF-8" ?>
389+
<container xmlns="http://symfony.com/schema/dic/services"
390+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
391+
xmlns:framework="http://symfony.com/schema/dic/symfony"
392+
xsi:schemaLocation="http://symfony.com/schema/dic/services
393+
https://symfony.com/schema/dic/services/services-1.0.xsd
394+
http://symfony.com/schema/dic/symfony
395+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
396+
397+
<framework:config>
398+
<framework:rate-limiter>
399+
<!-- cache-pool: use the "cache.anonymous_rate_limiter" cache pool -->
400+
<framework:limiter name="anonymous_api"
401+
policy="fixed_window"
402+
limit="100"
403+
interval="60 minutes"
404+
cache-pool="cache.anonymous_rate_limiter"
405+
/>
406+
407+
<!-- ... ->
408+
</framework:rate-limiter>
409+
</framework:config>
410+
</container>
411+
412+
.. code-block:: php
413+
414+
// config/packages/rate_limiter.php
415+
$container->loadFromExtension('framework', [
416+
rate_limiter' => [
417+
'anonymous_api' => [
418+
// ...
419+
420+
// use the "cache.anonymous_rate_limiter" cache pool
421+
'cache_pool' => 'cache.anonymous_rate_limiter',
422+
],
423+
],
424+
]);
425+
426+
.. note::
427+
428+
Instead of using the Cache component, you can also implement a custom
429+
storage. Create a PHP class that implements the
430+
:class:`Symfony\\Component\\RateLimiter\\Storage\\StorageInterface` and
431+
set the ``storage_service`` setting of each limiter to the service ID
432+
of this class.
433+
434+
Using Locks to Prevent Race Conditions
435+
--------------------------------------
436+
437+
Rate limiting can be affected by race conditions, if the same limiter is
438+
applied to simultaneous requests (e.g. 3 servers of the same client call
439+
the same API). To prevent these race conditions, the rate limiting
440+
operations are protected using :doc:`locks </lock>`.
441+
442+
By default, the global lock (configured by ``framework.lock``) is used. You
443+
can use a specific :ref:`named lock <lock-named-locks>` via the
444+
``lock_factory`` option:
445+
446+
.. configuration-block::
447+
448+
.. code-block:: yaml
449+
450+
# config/packages/rate_limiter.yaml
451+
framework:
452+
rate_limiter:
453+
anonymous_api:
454+
# ...
455+
456+
# use the "lock.rate_limiter.factory" for this limiter
457+
lock_factory: 'lock.rate_limiter.factory'
458+
459+
.. code-block:: xml
460+
461+
<!-- config/packages/rate_limiter.xml -->
462+
<?xml version="1.0" encoding="UTF-8" ?>
463+
<container xmlns="http://symfony.com/schema/dic/services"
464+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
465+
xmlns:framework="http://symfony.com/schema/dic/symfony"
466+
xsi:schemaLocation="http://symfony.com/schema/dic/services
467+
https://symfony.com/schema/dic/services/services-1.0.xsd
468+
http://symfony.com/schema/dic/symfony
469+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
470+
471+
<framework:config>
472+
<framework:rate-limiter>
473+
<!-- limiter-factory: use the "lock.rate_limiter.factory" for this limiter -->
474+
<framework:limiter name="anonymous_api"
475+
policy="fixed_window"
476+
limit="100"
477+
interval="60 minutes"
478+
lock-factory="lock.rate_limiter.factory"
479+
/>
480+
481+
<!-- ... -->
482+
</framework:rate-limiter>
483+
</framework:config>
484+
</container>
485+
486+
.. code-block:: php
487+
488+
// config/packages/rate_limiter.php
489+
$container->loadFromExtension('framework', [
490+
rate_limiter' => [
491+
'anonymous_api' => [
492+
// ...
493+
494+
// use the "lock.rate_limiter.factory" for this limiter
495+
'lock_factory' => 'lock.rate_limiter.factory',
496+
],
497+
],
498+
]);
326499
327500
.. _`DoS attacks`: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
328501
.. _`Apache mod_ratelimit`: https://httpd.apache.org/docs/current/mod/mod_ratelimit.html

0 commit comments

Comments
 (0)
0