8000 [RateLimiter][Security] More precisely document advanced rate limiter… · SirRFI/symfony-docs@4933d45 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4933d45

Browse files
wouterjjaviereguiluz
authored andcommitted
[RateLimiter][Security] More precisely document advanced rate limiter configuration
1 parent 481b1b2 commit 4933d45

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

@@ -300,27 +354,146 @@ the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the
300354
}
301355
}
302356

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

0 commit comments

Comments
 (0)
0