Added support for lazy firewalls

This commit is contained in:
Wouter de Jong 2020-02-09 20:58:49 +01:00
parent 7859977324
commit 1c810d5d2a
7 changed files with 87 additions and 65 deletions

View File

@ -453,7 +453,6 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
->setDefinition('security.firewall.authenticator.'.$id, new ChildDefinition('security.firewall.authenticator'))
->replaceArgument(2, new Reference('security.firewall.authenticator.'.$id.'.locator'))
->replaceArgument(3, $id)
->addTag('kernel.event_listener', ['event' => KernelEvents::REQUEST])
;
$listeners[] = new Reference('security.firewall.authenticator.'.$id);

View File

@ -26,37 +26,39 @@ use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
*/
class LazyAuthenticatorManagerListener extends AuthenticatorManagerListener
{
private $guardLocator;
private $authenticatorLocator;
public function __construct(
AuthenticationManagerInterface $authenticationManager,
AuthenticatorHandler $authenticatorHandler,
ServiceLocator $guardLocator,
ServiceLocator $authenticatorLocator,
string $providerKey,
EventDispatcherInterface $eventDispatcher,
?LoggerInterface $logger = null
) {
parent::__construct($authenticationManager, $authenticatorHandler, [], $providerKey, $eventDispatcher, $logger);
$this->guardLocator = $guardLocator;
$this->authenticatorLocator = $authenticatorLocator;
}
protected function getSupportingAuthenticators(Request $request): array
{
$guardAuthenticators = [];
foreach ($this->guardLocator->getProvidedServices() as $key => $type) {
$guardAuthenticator = $this->guardLocator->get($key);
$authenticators = [];
$lazy = true;
foreach ($this->authenticatorLocator->getProvidedServices() as $key => $type) {
$authenticator = $this->authenticatorLocator->get($key);
if (null !== $this->logger) {
$this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
$this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
if ($guardAuthenticator->supports($request)) {
$guardAuthenticators[$key] = $guardAuthenticator;
if (false !== $supports = $authenticator->supports($request)) {
$authenticators[$key] = $authenticator;
$lazy = $lazy && null === $supports;
} elseif (null !== $this->logger) {
$this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
$this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
}
return $guardAuthenticators;
return [$authenticators, $lazy];
}
}

View File

@ -80,7 +80,7 @@
class="Symfony\Component\Security\Http\Authenticator\AnonymousAuthenticator"
abstract="true">
<argument type="abstract">secret</argument>
<argument type="service" id="security.token_storage" />
<argument type="service" id="security.untracked_token_storage" />
</service>
</services>
</container>

View File

@ -34,8 +34,6 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
*/
class GuardAuthenticationListener extends AbstractListener
{
use AuthenticatorManagerListenerTrait;
private $guardHandler;
private $authenticationManager;
private $providerKey;
@ -75,7 +73,19 @@ class GuardAuthenticationListener extends AbstractListener
$this->logger->debug('Checking for guard authentication credentials.', $context);
}
$guardAuthenticators = $this->getSupportingAuthenticators($request);
$guardAuthenticators = [];
foreach ($this->authenticators as $key => $authenticator) {
if (null !== $this->logger) {
$this->logger->debug('Checking support on authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
if ($authenticator->supports($request)) {
$guardAuthenticators[$key] = $authenticator;
} elseif (null !== $this->logger) {
$this->logger->debug('Authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
}
if (!$guardAuthenticators) {
return false;
}

View File

@ -41,7 +41,8 @@ class AnonymousAuthenticator implements AuthenticatorInterface, CustomAuthentica
public function supports(Request $request): ?bool
{
// do not overwrite already stored tokens (i.e. from the session)
return null === $this->tokenStorage->getToken();
// the `null` return value indicates that this authenticator supports lazy firewalls
return null === $this->tokenStorage->getToken() ? null : false;
}
public function getCredentials(Request $request)

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
@ -30,10 +31,8 @@ use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
*
* @experimental in 5.1
*/
class AuthenticatorManagerListener
class AuthenticatorManagerListener extends AbstractListener
{
use AuthenticatorManagerListenerTrait;
private $authenticationManager;
private $authenticatorHandler;
private $authenticators;
@ -54,15 +53,58 @@ class AuthenticatorManagerListener
$this->eventDispatcher = $eventDispatcher;
}
public function __invoke(RequestEvent $requestEvent)
public function supports(Request $request): ?bool
{
$request = $requestEvent->getRequest();
$authenticators = $this->getSupportingAuthenticators($request);
if (null !== $this->logger) {
$context = ['firewall_key' => $this->providerKey];
if ($this->authenticators instanceof \Countable || \is_array($this->authenticators)) {
$context['authenticators'] = \count($this->authenticators);
}
$this->logger->debug('Checking for guard authentication credentials.', $context);
}
[$authenticators, $lazy] = $this->getSupportingAuthenticators($request);
if (!$authenticators) {
return false;
}
$request->attributes->set('_guard_authenticators', $authenticators);
return $lazy ? null : true;
}
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$authenticators = $request->attributes->get('_guard_authenticators');
$request->attributes->remove('_guard_authenticators');
if (!$authenticators) {
return;
}
$this->executeAuthenticators($authenticators, $requestEvent);
$this->executeAuthenticators($authenticators, $event);
}
protected function getSupportingAuthenticators(Request $request): array
{
$authenticators = [];
$lazy = true;
foreach ($this->authenticators as $key => $authenticator) {
if (null !== $this->logger) {
$this->logger->debug('Checking support on authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
if (false !== $supports = $authenticator->supports($request)) {
$authenticators[$key] = $authenticator;
$lazy = $lazy && null === $supports;
} elseif (null !== $this->logger) {
$this->logger->debug('Authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
}
return [$authenticators, $lazy];
}
/**
@ -71,6 +113,15 @@ class AuthenticatorManagerListener
protected function executeAuthenticators(array $authenticators, RequestEvent $event): void
{
foreach ($authenticators as $key => $authenticator) {
// recheck if the authenticator still supports the listener. support() is called
// eagerly (before token storage is initialized), whereas authenticate() is called
// lazily (after initialization). This is important for e.g. the AnonymousAuthenticator
// as its support is relying on the (initialized) token in the TokenStorage.
if (false === $authenticator->supports($event->getRequest())) {
$this->logger->debug('Skipping the "{authenticator}" authenticator as it did not support the request.', ['authenticator' => \get_class($authenticator)]);
continue;
}
$this->executeAuthenticator($key, $authenticator, $event);
if ($event->hasResponse()) {

View File

@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Ryan Weaver <ryan@knpuniversity.com>
* @author Amaury Leroux de Lens <amaury@lerouxdelens.com>
*
* @internal
*/
trait AuthenticatorManagerListenerTrait
{
protected function getSupportingAuthenticators(Request $request): array
{
$authenticators = [];
foreach ($this->authenticators as $key => $authenticator) {
if (null !== $this->logger) {
$this->logger->debug('Checking support on authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
if ($authenticator->supports($request)) {
$authenticators[$key] = $authenticator;
} elseif (null !== $this->logger) {
$this->logger->debug('Authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
}
return $authenticators;
}
}