diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php index b696f9e02d91c..b62720bfd80d8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/LoginThrottlingFactory.php @@ -76,6 +76,7 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal $container->register($config['limiter'] = 'security.login_throttling.'.$firewallName.'.limiter', DefaultLoginRateLimiter::class) ->addArgument(new Reference('limiter.'.$globalId)) ->addArgument(new Reference('limiter.'.$localId)) + ->addArgument('%kernel.secret%') ; } diff --git a/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php b/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php index 2db7ee144b98a..a32d4926abc15 100644 --- a/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php +++ b/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php @@ -28,11 +28,20 @@ final class DefaultLoginRateLimiter extends AbstractRequestRateLimiter { private RateLimiterFactory $globalFactory; private RateLimiterFactory $localFactory; + private string $secret; - public function __construct(RateLimiterFactory $globalFactory, RateLimiterFactory $localFactory) + /** + * @param non-empty-string $secret A secret to use for hashing the IP address and username + */ + public function __construct(RateLimiterFactory $globalFactory, RateLimiterFactory $localFactory, #[\SensitiveParameter] string $secret = '') { + if ('' === $secret) { + trigger_deprecation('symfony/security-http', '6.4', 'Calling "%s()" with an empty secret is deprecated. A non-empty secret will be mandatory in version 7.0.', __METHOD__); + // throw new \Symfony\Component\Security\Core\Exception\InvalidArgumentException('A non-empty secret is required.'); + } $this->globalFactory = $globalFactory; $this->localFactory = $localFactory; + $this->secret = $secret; } protected function getLimiters(Request $request): array @@ -41,8 +50,13 @@ protected function getLimiters(Request $request): array $username = preg_match('//u', $username) ? mb_strtolower($username, 'UTF-8') : strtolower($username); return [ - $this->globalFactory->create($request->getClientIp()), - $this->localFactory->create($username.'-'.$request->getClientIp()), + $this->globalFactory->create($this->hash($request->getClientIp())), + $this->localFactory->create($this->hash($username.'-'.$request->getClientIp())), ]; } + + private function hash(string $data): string + { + return strtr(substr(base64_encode(hash_hmac('sha256', $data, $this->secret, true)), 0, 8), '/+', '._'); + } } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php index 248a09efba64e..450d151398f9e 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php @@ -47,7 +47,7 @@ protected function setUp(): void 'limit' => 6, 'interval' => '1 minute', ], new InMemoryStorage()); - $limiter = new DefaultLoginRateLimiter($globalLimiter, $localLimiter); + $limiter = new DefaultLoginRateLimiter($globalLimiter, $localLimiter, '$3cre7'); $this->listener = new LoginThrottlingListener($this->requestStack, $limiter); }