Refactor to an event based authentication approach

This allows more flexibility for the authentication manager (to e.g. implement
login throttling, easier remember me, etc). It is also a known design pattern
in Symfony HttpKernel.
This commit is contained in:
Wouter de Jong 2020-02-06 15:06:07 +01:00
parent b14a5e8c52
commit 999ec2795f
26 changed files with 876 additions and 318 deletions

View File

@ -106,7 +106,7 @@ class FormLoginFactory extends AbstractFactory implements GuardFactoryInterface,
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))
->replaceArgument(1, isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null)
->replaceArgument(2, new Reference($userProviderId))
->replaceArgument(4, $options);
->replaceArgument(3, $options);
return $authenticatorId;
}

View File

@ -419,16 +419,13 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
if ($this->guardAuthenticationManagerEnabled) {
// guard authentication manager listener (must be before calling createAuthenticationListeners() to inject remember me services)
// Remember me listener (must be before calling createAuthenticationListeners() to inject remember me services)
$container
->setDefinition('security.firewall.guard.'.$id, new ChildDefinition('security.firewall.guard'))
->replaceArgument(2, new Reference('security.firewall.guard.'.$id.'.locator'))
->replaceArgument(3, $id)
->addTag('kernel.event_listener', ['event' => KernelEvents::REQUEST])
->setDefinition('security.listener.remember_me.'.$id, new ChildDefinition('security.listener.remember_me'))
->replaceArgument(0, $id)
->addTag('kernel.event_subscriber')
->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none'])
;
$listeners[] = new Reference('security.firewall.guard.'.$id);
}
// Authentication listeners
@ -438,7 +435,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$authenticationProviders = array_merge($authenticationProviders, $firewallAuthenticationProviders);
if ($this->guardAuthenticationManagerEnabled) {
// add authentication providers for this firewall to the GuardManagerListener (if guard is enabled)
// guard authentication manager listener
$container
->setDefinition('security.firewall.guard.'.$id.'.locator', new ChildDefinition('security.firewall.guard.locator'))
->setArguments([array_map(function ($id) {
@ -446,10 +443,15 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
}, $firewallAuthenticationProviders)])
->addTag('container.service_locator')
;
$container
->getDefinition('security.firewall.guard.'.$id)
->setDefinition('security.firewall.guard.'.$id, new ChildDefinition('security.firewall.guard'))
->replaceArgument(2, new Reference('security.firewall.guard.'.$id.'.locator'))
->replaceArgument(3, $id)
->addTag('kernel.event_listener', ['event' => KernelEvents::REQUEST])
;
$listeners[] = new Reference('security.firewall.guard.'.$id);
}
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\SecurityBundle\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
@ -32,9 +33,10 @@ class LazyGuardManagerListener extends GuardManagerListener
GuardAuthenticatorHandler $guardHandler,
ServiceLocator $guardLocator,
string $providerKey,
EventDispatcherInterface $eventDispatcher,
?LoggerInterface $logger = null
) {
parent::__construct($authenticationManager, $guardHandler, [], $providerKey, $logger);
parent::__construct($authenticationManager, $guardHandler, [], $providerKey, $eventDispatcher, $logger);
$this->guardLocator = $guardLocator;
}

View File

@ -12,13 +12,41 @@
class="Symfony\Bundle\SecurityBundle\EventListener\LazyGuardManagerListener"
abstract="true">
<tag name="monolog.logger" channel="security" />
<argument type="service" id="security.authentication.manager"/>
<argument type="service" id="security.authentication.guard_handler"/>
<argument type="service" id="security.authentication.manager" />
<argument type="service" id="security.authentication.guard_handler" />
<argument/> <!-- guard authenticator locator -->
<argument/> <!-- provider key -->
<argument type="service" id="event_dispatcher" />
<argument type="service" id="logger" on-invalid="null" />
</service>
<!-- Listeners -->
<service id="Symfony\Component\Security\Http\EventListener\AuthenticatingListener">
<tag name="kernel.event_subscriber" />
<argument type="service" id="security.encoder_factory" />
</service>
<service id="Symfony\Component\Security\Http\EventListener\PasswordMigratingListener">
<tag name="kernel.event_subscriber" />
<argument type="service" id="security.encoder_factory" />
</service>
<service id="Symfony\Component\Security\Http\EventListener\UserCheckerListener">
<tag name="kernel.event_subscriber" />
<argument type="service" id="Symfony\Component\Security\Core\User\UserCheckerInterface" />
</service>
<service id="security.listener.remember_me"
class="Symfony\Component\Security\Http\EventListener\RememberMeListener"
abstract="true">
<tag name="monolog.logger" channel="security" />
<argument/> <!-- provider key -->
<argument type="service" id="logger" on-invalid="null" />
</service>
<!-- Authenticators -->
<service id="security.authenticator.http_basic"
class="Symfony\Component\Security\Http\Authentication\Authenticator\HttpBasicAuthenticator"
abstract="true">
@ -34,14 +62,13 @@
<argument type="service" id="security.http_utils" />
<argument /> <!-- csrf token generator -->
<argument type="abstract">user provider</argument>
<argument type="service" id="security.encoder_factory" />
<argument type="abstract">options</argument>
</service>
<service id="security.authenticator.anonymous"
class="Symfony\Component\Security\Http\Authentication\Authenticator\AnonymousAuthenticator"
abstract="true">
<argument /> <!-- secret -->
<argument type="abstract">secret</argument>
<argument type="service" id="security.token_storage" />
</service>
</services>

View File

@ -54,7 +54,7 @@
</service>
<service id="security.authentication.manager.guard" class="Symfony\Component\Security\Http\Authentication\GuardAuthenticationManager">
<argument /> <!-- guard authenticators -->
<argument type="service" id="Symfony\Component\Security\Core\User\UserCheckerInterface" /> <!-- User Checker -->
<argument type="service" id="event_dispatcher" />
<argument>%security.authentication.manager.erase_credentials%</argument>
<call method="setEventDispatcher">
<argument type="service" id="event_dispatcher" />

View File

@ -13,9 +13,12 @@ namespace Symfony\Component\Security\Guard\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticationGuardToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken as GuardPreAuthenticationGuardToken;
@ -104,15 +107,122 @@ class GuardAuthenticationListener extends AbstractListener
$this->rememberMeServices = $rememberMeServices;
}
/**
* @param AuthenticatorInterface[] $guardAuthenticators
*/
protected function executeGuardAuthenticators(array $guardAuthenticators, RequestEvent $event): void
{
foreach ($guardAuthenticators as $key => $guardAuthenticator) {
$uniqueGuardKey = $this->providerKey.'_'.$key;;
$this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event);
if ($event->hasResponse()) {
if (null !== $this->logger) {
$this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($guardAuthenticator)]);
}
break;
}
}
}
private function executeGuardAuthenticator(string $uniqueGuardKey, AuthenticatorInterface $guardAuthenticator, RequestEvent $event)
{
$request = $event->getRequest();
try {
if (null !== $this->logger) {
$this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
// allow the authenticator to fetch authentication info from the request
$credentials = $guardAuthenticator->getCredentials($request);
if (null === $credentials) {
throw new \UnexpectedValueException(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', get_debug_type($guardAuthenticator)));
}
// create a token with the unique key, so that the provider knows which authenticator to use
$token = $this->createPreAuthenticatedToken($credentials, $uniqueGuardKey, $this->providerKey);
if (null !== $this->logger) {
$this->logger->debug('Passing guard token information to the GuardAuthenticationProvider', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
// pass the token into the AuthenticationManager system
// this indirectly calls GuardAuthenticationProvider::authenticate()
$token = $this->authenticationManager->authenticate($token);
if (null !== $this->logger) {
$this->logger->info('Guard authentication successful!', ['token' => $token, 'authenticator' => \get_class($guardAuthenticator)]);
}
// sets the token on the token storage, etc
$this->guardHandler->authenticateWithToken($token, $request, $this->providerKey);
} catch (AuthenticationException $e) {
// oh no! Authentication failed!
if (null !== $this->logger) {
$this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]);
}
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
if ($response instanceof Response) {
$event->setResponse($response);
}
return;
}
// success!
$response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey);
if ($response instanceof Response) {
if (null !== $this->logger) {
$this->logger->debug('Guard authenticator set success response.', ['response' => $response, 'authenticator' => \get_class($guardAuthenticator)]);
}
$event->setResponse($response);
} else {
if (null !== $this->logger) {
$this->logger->debug('Guard authenticator set no success response: request continues.', ['authenticator' => \get_class($guardAuthenticator)]);
}
}
// attempt to trigger the remember me functionality
$this->triggerRememberMe($guardAuthenticator, $request, $token, $response);
}
protected function triggerRememberMe($guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
{
if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) {
throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.');
}
if (null === $this->rememberMeServices) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($guardAuthenticator)]);
}
return;
}
if (!$guardAuthenticator->supportsRememberMe()) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($guardAuthenticator)]);
}
return;
}
if (!$response instanceof Response) {
throw new \LogicException(sprintf('"%s::onAuthenticationSuccess()" *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.', get_debug_type($guardAuthenticator)));
}
$this->rememberMeServices->loginSuccess($request, $response, $token);
}
protected function createPreAuthenticatedToken($credentials, string $uniqueGuardKey, string $providerKey): PreAuthenticationGuardToken
{
return new GuardPreAuthenticationGuardToken($credentials, $uniqueGuardKey, $providerKey);
}
protected function getGuardKey(string $key): string
{
// get a key that's unique to *this* guard authenticator
// this MUST be the same as GuardAuthenticationProvider
return $this->providerKey.'_'.$key;
}
}

View File

@ -11,8 +11,6 @@
namespace Symfony\Component\Security\Guard;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
/**
* An optional interface for "guard" authenticators that deal with user passwords.
*/
@ -24,6 +22,4 @@ interface PasswordAuthenticatedInterface
* @param mixed $credentials The user credentials
*/
public function getPassword($credentials): ?string;
/* public function getPasswordEncoder(): ?UserPasswordEncoderInterface; */
}

View File

@ -11,6 +11,14 @@
namespace Symfony\Component\Security\Guard\Provider;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Authentication\GuardAuthenticationManagerTrait;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
@ -22,6 +30,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
/**
* Responsible for accepting the PreAuthenticationGuardToken and calling
@ -41,6 +50,7 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface
private $providerKey;
private $userChecker;
private $passwordEncoder;
private $rememberMeServices;
/**
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener
@ -106,8 +116,48 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface
return $token instanceof GuardTokenInterface;
}
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
{
$this->rememberMeServices = $rememberMeServices;
}
protected function getGuardKey(string $key): string
{
return $this->providerKey.'_'.$key;
}
private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator, \Symfony\Component\Security\Core\Authentication\Token\PreAuthenticationGuardToken $token, string $providerKey): TokenInterface
{
// get the user from the GuardAuthenticator
$user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider);
if (null === $user) {
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($guardAuthenticator)));
}
if (!$user instanceof UserInterface) {
throw new \UnexpectedValueException(sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', get_debug_type($guardAuthenticator), get_debug_type($user)));
}
$this->userChecker->checkPreAuth($user);
if (true !== $checkCredentialsResult = $guardAuthenticator->checkCredentials($token->getCredentials(), $user)) {
if (false !== $checkCredentialsResult) {
throw new \TypeError(sprintf('"%s::checkCredentials()" must return a boolean value.', get_debug_type($guardAuthenticator)));
}
throw new BadCredentialsException(sprintf('Authentication failed because "%s::checkCredentials()" did not return true.', get_debug_type($guardAuthenticator)));
}
if ($this->userProvider instanceof PasswordUpgraderInterface && $guardAuthenticator instanceof PasswordAuthenticatedInterface && null !== $this->passwordEncoder && (null !== $password = $guardAuthenticator->getPassword($token->getCredentials())) && method_exists($this->passwordEncoder, 'needsRehash') && $this->passwordEncoder->needsRehash($user)) {
$this->userProvider->upgradePassword($user, $this->passwordEncoder->encodePassword($user, $password));
}
$this->userChecker->checkPostAuth($user);
// turn the UserInterface into a TokenInterface
$authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $providerKey);
if (!$authenticatedToken instanceof TokenInterface) {
throw new \UnexpectedValueException(sprintf('The "%s::createAuthenticatedToken()" method must return a TokenInterface. You returned "%s".', get_debug_type($guardAuthenticator), get_debug_type($authenticatedToken)));
}
return $authenticatedToken;
}
}

View File

@ -27,7 +27,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
* @final
* @experimental in 5.1
*/
class AnonymousAuthenticator implements AuthenticatorInterface
class AnonymousAuthenticator implements AuthenticatorInterface, CustomAuthenticatedInterface
{
private $secret;
private $tokenStorage;
@ -49,16 +49,17 @@ class AnonymousAuthenticator implements AuthenticatorInterface
return [];
}
public function checkCredentials($credentials, UserInterface $user): bool
{
// anonymous users do not have credentials
return true;
}
public function getUser($credentials): ?UserInterface
{
return new User('anon.', null);
}
public function checkCredentials($credentials, UserInterface $user): bool
{
return true;
}
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
{
return new AnonymousToken($this->secret, 'anon.', []);

View File

@ -70,18 +70,6 @@ interface AuthenticatorInterface
*/
public function getUser($credentials): ?UserInterface;
/**
* Returns true if the credentials are valid.
*
* If false is returned, authentication will fail. You may also throw
* an AuthenticationException if you wish to cause authentication to fail.
*
* @param mixed $credentials the value returned from getCredentials()
*
* @throws AuthenticationException
*/
public function checkCredentials($credentials, UserInterface $user): bool;
/**
* Create an authenticated token for the given user.
*

View File

@ -0,0 +1,27 @@
<?php
namespace Symfony\Component\Security\Http\Authentication\Authenticator;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* This interface should be implemented by authenticators that
* require custom (not password related) authentication.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface CustomAuthenticatedInterface
{
/**
* Returns true if the credentials are valid.
*
* If false is returned, authentication will fail. You may also throw
* an AuthenticationException if you wish to cause authentication to fail.
*
* @param mixed $credentials the value returned from getCredentials()
*
* @throws AuthenticationException
*/
public function checkCredentials($credentials, UserInterface $user): bool;
}

View File

@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
@ -23,6 +24,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
@ -34,23 +36,19 @@ use Symfony\Component\Security\Http\Util\TargetPathTrait;
* @final
* @experimental in 5.1
*/
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait, UsernamePasswordTrait {
UsernamePasswordTrait::checkCredentials as checkPassword;
}
use TargetPathTrait;
private $options;
private $httpUtils;
private $csrfTokenManager;
private $userProvider;
private $encoderFactory;
public function __construct(HttpUtils $httpUtils, ?CsrfTokenManagerInterface $csrfTokenManager, UserProviderInterface $userProvider, EncoderFactoryInterface $encoderFactory, array $options)
public function __construct(HttpUtils $httpUtils, ?CsrfTokenManagerInterface $csrfTokenManager, UserProviderInterface $userProvider, array $options)
{
$this->httpUtils = $httpUtils;
$this->csrfTokenManager = $csrfTokenManager;
$this->encoderFactory = $encoderFactory;
$this->options = array_merge([
'username_parameter' => '_username',
'password_parameter' => '_password',
@ -109,11 +107,17 @@ class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
return $credentials;
}
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function getUser($credentials): ?UserInterface
{
return $this->userProvider->loadUserByUsername($credentials['username']);
}
/* @todo How to do CSRF protection?
public function checkCredentials($credentials, UserInterface $user): bool
{
if (null !== $this->csrfTokenManager) {
@ -123,6 +127,11 @@ class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
}
return $this->checkPassword($credentials, $user);
}*/
public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface
{
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): Response

View File

@ -15,10 +15,12 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
/**
@ -28,10 +30,8 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface
* @final
* @experimental in 5.1
*/
class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface
class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface, PasswordAuthenticatedInterface
{
use UsernamePasswordTrait;
private $realmName;
private $userProvider;
private $encoderFactory;
@ -67,11 +67,21 @@ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEn
];
}
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function getUser($credentials): ?UserInterface
{
return $this->userProvider->loadUserByUsername($credentials['username']);
}
public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface
{
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
{
return null;

View File

@ -0,0 +1,24 @@
<?php
namespace Symfony\Component\Security\Http\Authentication\Authenticator;
/**
* This interface should be implemented when the authenticator
* doesn't need to check credentials (e.g. when using API tokens)
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface TokenAuthenticatedInterface
{
/**
* Extracts the token from the credentials.
*
* If you return null, the credentials will not be marked as
* valid and a BadCredentialsException is thrown.
*
* @param mixed $credentials The user credentials
*
* @return mixed|null the token - if any - or null otherwise
*/
public function getToken($credentials);
}

View File

@ -1,50 +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\Authentication\Authenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @property EncoderFactoryInterface $encoderFactory
*
* @experimental in 5.1
*/
trait UsernamePasswordTrait
{
public function checkCredentials($credentials, UserInterface $user): bool
{
if (!$this->encoderFactory instanceof EncoderFactoryInterface) {
throw new \LogicException(\get_class($this).' uses the '.__CLASS__.' trait, which requires an $encoderFactory property to be initialized with an '.EncoderFactoryInterface::class.' implementation.');
}
if ('' === $credentials['password']) {
throw new BadCredentialsException('The presented password cannot be empty.');
}
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $credentials['password'], null)) {
throw new BadCredentialsException('The presented password is invalid.');
}
return true;
}
public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface
{
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
}
}

View File

@ -12,6 +12,9 @@
namespace Symfony\Component\Security\Http\Authentication;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authentication\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticationGuardToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
@ -21,7 +24,7 @@ use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException;
use Symfony\Component\Security\Core\Exception\ProviderNotFoundException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
@ -35,18 +38,16 @@ class GuardAuthenticationManager implements AuthenticationManagerInterface
use GuardAuthenticationManagerTrait;
private $guardAuthenticators;
private $userChecker;
private $eraseCredentials;
/** @var EventDispatcherInterface */
private $eventDispatcher;
private $eraseCredentials;
/**
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener
*/
public function __construct($guardAuthenticators, UserCheckerInterface $userChecker, bool $eraseCredentials = true)
public function __construct(iterable $guardAuthenticators, EventDispatcherInterface $eventDispatcher, bool $eraseCredentials = true)
{
$this->guardAuthenticators = $guardAuthenticators;
$this->userChecker = $userChecker;
$this->eventDispatcher = $eventDispatcher;
$this->eraseCredentials = $eraseCredentials;
}
@ -100,6 +101,40 @@ class GuardAuthenticationManager implements AuthenticationManagerInterface
return $result;
}
protected function getGuardKey(string $key): string
{
// Guard authenticators in the GuardAuthenticationManager are already indexed
// by an unique key
return $key;
}
private function authenticateViaGuard(AuthenticatorInterface $authenticator, PreAuthenticationGuardToken $token, string $providerKey): TokenInterface
{
// get the user from the Authenticator
$user = $authenticator->getUser($token->getCredentials());
if (null === $user) {
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', \get_class($authenticator)));
}
if (!$user instanceof UserInterface) {
throw new \UnexpectedValueException(sprintf('The %s::getUser() method must return a UserInterface. You returned %s.', \get_class($authenticator), \is_object($user) ? \get_class($user) : \gettype($user)));
}
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, $token, $user);
$this->eventDispatcher->dispatch($event);
if (true !== $event->areCredentialsValid()) {
throw new BadCredentialsException(sprintf('Authentication failed because %s did not approve the credentials.', \get_class($authenticator)));
}
// turn the UserInterface into a TokenInterface
$authenticatedToken = $authenticator->createAuthenticatedToken($user, $providerKey);
if (!$authenticatedToken instanceof TokenInterface) {
throw new \UnexpectedValueException(sprintf('The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.', \get_class($authenticator), \is_object($authenticatedToken) ? \get_class($authenticatedToken) : \gettype($authenticatedToken)));
}
return $authenticatedToken;
}
private function handleFailure(AuthenticationException $exception, TokenInterface $token)
{
if (null !== $this->eventDispatcher) {
@ -110,11 +145,4 @@ class GuardAuthenticationManager implements AuthenticationManagerInterface
throw $exception;
}
protected function getGuardKey(string $key): string
{
// Guard authenticators in the GuardAuthenticationManager are already indexed
// by an unique key
return $key;
}
}

View File

@ -29,65 +29,6 @@ use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
*/
trait GuardAuthenticationManagerTrait
{
/**
* @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator
*/
private function authenticateViaGuard($guardAuthenticator, PreAuthenticationGuardToken $token, string $providerKey): TokenInterface
{
// get the user from the GuardAuthenticator
if ($guardAuthenticator instanceof AuthenticatorInterface) {
if (!isset($this->userProvider)) {
throw new LogicException(sprintf('%s only supports authenticators implementing "%s", update "%s" or use the legacy guard integration instead.', __CLASS__, CoreAuthenticatorInterface::class, \get_class($guardAuthenticator)));
}
$user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider);
} elseif ($guardAuthenticator instanceof CoreAuthenticatorInterface) {
$user = $guardAuthenticator->getUser($token->getCredentials());
} else {
throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.');
}
if (null === $user) {
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($guardAuthenticator)));
}
if (!$user instanceof UserInterface) {
throw new \UnexpectedValueException(sprintf('The "%s::getUser()" method must return a UserInterface. You returned "%s".', get_debug_type($guardAuthenticator), get_debug_type($user)));
}
$this->userChecker->checkPreAuth($user);
if (true !== $checkCredentialsResult = $guardAuthenticator->checkCredentials($token->getCredentials(), $user)) {
if (false !== $checkCredentialsResult) {
throw new \TypeError(sprintf('"%s::checkCredentials()" must return a boolean value.', get_debug_type($guardAuthenticator)));
}
throw new BadCredentialsException(sprintf('Authentication failed because "%s::checkCredentials()" did not return true.', get_debug_type($guardAuthenticator)));
}
if ($guardAuthenticator instanceof PasswordAuthenticatedInterface
&& null !== $password = $guardAuthenticator->getPassword($token->getCredentials())
&& null !== $passwordEncoder = $this->passwordEncoder ?? (method_exists($guardAuthenticator, 'getPasswordEncoder') ? $guardAuthenticator->getPasswordEncoder() : null)
) {
if (method_exists($passwordEncoder, 'needsRehash') && $passwordEncoder->needsRehash($user)) {
if (!isset($this->userProvider)) {
if ($guardAuthenticator instanceof PasswordUpgraderInterface) {
$guardAuthenticator->upgradePassword($user, $guardAuthenticator->getPasswordEncoder()->encodePassword($user, $password));
}
} elseif ($this->userProvider instanceof PasswordUpgraderInterface) {
$this->userProvider->upgradePassword($user, $passwordEncoder->encodePassword($user, $password));
}
}
}
$this->userChecker->checkPostAuth($user);
// turn the UserInterface into a TokenInterface
$authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $providerKey);
if (!$authenticatedToken instanceof TokenInterface) {
throw new \UnexpectedValueException(sprintf('The "%s::createAuthenticatedToken()" method must return a TokenInterface. You returned "%s".', get_debug_type($guardAuthenticator), get_debug_type($authenticatedToken)));
}
return $authenticatedToken;
}
/**
* @return CoreAuthenticatorInterface|\Symfony\Component\Security\Guard\AuthenticatorInterface|null
*/

View File

@ -0,0 +1,60 @@
<?php
namespace Symfony\Component\Security\Http\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\Authenticator\AuthenticatorInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is dispatched after an error during authentication.
*
* Listeners to this event can change state based on authentication
* failure (e.g. to implement login throttling).
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class LoginFailureEvent extends Event
{
private $exception;
private $authenticator;
private $request;
private $response;
private $providerKey;
public function __construct(AuthenticationException $exception, AuthenticatorInterface $authenticator, Request $request, ?Response $response, string $providerKey)
{
$this->exception = $exception;
$this->authenticator = $authenticator;
$this->request = $request;
$this->response = $response;
$this->providerKey = $providerKey;
}
public function getException(): AuthenticationException
{
return $this->exception;
}
public function getAuthenticator(): AuthenticatorInterface
{
return $this->authenticator;
}
public function getProviderKey(): string
{
return $this->providerKey;
}
public function getRequest(): Request
{
return $this->request;
}
public function getResponse(): ?Response
{
return $this->response;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Symfony\Component\Security\Http\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\Authenticator\AuthenticatorInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is dispatched after authentication has successfully completed.
*
* At this stage, the authenticator created an authenticated token
* and generated an authentication success response. Listeners to
* this event can do actions related to successful authentication
* (such as migrating the password).
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class LoginSuccessEvent extends Event
{
private $authenticator;
private $authenticatedToken;
private $request;
private $response;
private $providerKey;
public function __construct(AuthenticatorInterface $authenticator, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $providerKey)
{
$this->authenticator = $authenticator;
$this->authenticatedToken = $authenticatedToken;
$this->request = $request;
$this->response = $response;
$this->providerKey = $providerKey;
}
public function getAuthenticator(): AuthenticatorInterface
{
return $this->authenticator;
}
public function getAuthenticatedToken(): TokenInterface
{
return $this->authenticatedToken;
}
public function getRequest(): Request
{
return $this->request;
}
public function getResponse(): ?Response
{
return $this->response;
}
public function getProviderKey(): string
{
return $this->providerKey;
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Symfony\Component\Security\Http\Event;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authentication\Authenticator\AuthenticatorInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is dispatched when the credentials have to be checked.
*
* Listeners to this event must validate the user and the
* credentials (e.g. default listeners do password verification and
* user checking)
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class VerifyAuthenticatorCredentialsEvent extends Event
{
private $authenticator;
private $preAuthenticatedToken;
private $user;
private $credentialsValid = false;
public function __construct(AuthenticatorInterface $authenticator, TokenInterface $preAuthenticatedToken, ?UserInterface $user)
{
$this->authenticator = $authenticator;
$this->preAuthenticatedToken = $preAuthenticatedToken;
$this->user = $user;
}
public function getAuthenticator(): AuthenticatorInterface
{
return $this->authenticator;
}
public function getPreAuthenticatedToken(): TokenInterface
{
return $this->preAuthenticatedToken;
}
public function getUser(): ?UserInterface
{
return $this->user;
}
public function setCredentialsValid(bool $validated = true): void
{
$this->credentialsValid = $validated;
}
public function areCredentialsValid(): bool
{
return $this->credentialsValid;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Symfony\Component\Security\Http\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\LogicException;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Authentication\Authenticator\CustomAuthenticatedInterface;
use Symfony\Component\Security\Http\Authentication\Authenticator\TokenAuthenticatedInterface;
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
/**
* This listeners uses the interfaces of authenticators to
* determine how to check credentials.
*
* @author Wouter de Jong <wouter@driveamber.com>
*
* @final
* @experimental in 5.1
*/
class AuthenticatingListener implements EventSubscriberInterface
{
private $encoderFactory;
public function __construct(EncoderFactoryInterface $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
}
public static function getSubscribedEvents(): array
{
return [VerifyAuthenticatorCredentialsEvent::class => ['onAuthenticating', 128]];
}
public function onAuthenticating(VerifyAuthenticatorCredentialsEvent $event): void
{
$authenticator = $event->getAuthenticator();
if ($authenticator instanceof PasswordAuthenticatedInterface) {
// Use the password encoder to validate the credentials
$user = $event->getUser();
$event->setCredentialsValid($this->encoderFactory->getEncoder($user)->isPasswordValid(
$user->getPassword(),
$authenticator->getPassword($event->getPreAuthenticatedToken()->getCredentials()),
$user->getSalt()
));
return;
}
if ($authenticator instanceof TokenAuthenticatedInterface) {
if (null !== $authenticator->getToken($event->getCredentials())) {
// Token based authenticators do not have a credential validation step
$event->setCredentialsValid();
}
return;
}
if ($authenticator instanceof CustomAuthenticatedInterface) {
$event->setCredentialsValid($authenticator->checkCredentials($event->getPreAuthenticatedToken()->getCredentials(), $event->getUser()));
return;
}
throw new LogicException(sprintf('Authenticator %s does not have valid credentials. Authenticators must implement one of the authenticated interfaces (%s, %s or %s).', \get_class($authenticator), PasswordAuthenticatedInterface::class, TokenAuthenticatedInterface::class, CustomAuthenticatedInterface::class));
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Symfony\Component\Security\Http\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @final
* @experimental in 5.1
*/
class PasswordMigratingListener implements EventSubscriberInterface
{
private $encoderFactory;
public function __construct(EncoderFactoryInterface $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
}
public function onCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
{
if (!$event->areCredentialsValid()) {
// Do not migrate password that are not validated
return;
}
$authenticator = $event->getAuthenticator();
if (!$authenticator instanceof PasswordAuthenticatedInterface) {
return;
}
$token = $event->getPreAuthenticatedToken();
if (null !== $password = $authenticator->getPassword($token->getCredentials())) {
return;
}
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return;
}
$passwordEncoder = $this->encoderFactory->getEncoder($user);
if (!method_exists($passwordEncoder, 'needsRehash') || !$passwordEncoder->needsRehash($user)) {
return;
}
if (!$authenticator instanceof PasswordUpgraderInterface) {
return;
}
$authenticator->upgradePassword($user, $passwordEncoder->encodePassword($user, $password));
}
public static function getSubscribedEvents(): array
{
return [VerifyAuthenticatorCredentialsEvent::class => ['onCredentialsVerification', -128]];
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Symfony\Component\Security\Http\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Authentication\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @final
* @experimental in 5.1
*/
class RememberMeListener implements EventSubscriberInterface
{
private $providerKey;
private $logger;
/** @var RememberMeServicesInterface|null */
private $rememberMeServices;
public function __construct(string $providerKey, ?LoggerInterface $logger = null)
{
$this->providerKey = $providerKey;
$this->logger = $logger;
}
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices): void
{
$this->rememberMeServices = $rememberMeServices;
}
public function onSuccessfulLogin(LoginSuccessEvent $event): void
{
if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) {
return;
}
$this->rememberMeServices->loginSuccess($event->getRequest(), $event->getResponse(), $event->getAuthenticatedToken());
}
public function onFailedLogin(LoginFailureEvent $event): void
{
if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) {
return;
}
$this->rememberMeServices->loginFail($event->getRequest(), $event->getException());
}
private function isRememberMeEnabled(AuthenticatorInterface $authenticator, string $providerKey): bool
{
if ($providerKey !== $this->providerKey) {
// This listener is created for a different firewall.
return false;
}
if (null === $this->rememberMeServices) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($authenticator)]);
}
return false;
}
if (!$authenticator->supportsRememberMe()) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($authenticator)]);
}
return false;
}
return true;
}
public static function getSubscribedEvents(): array
{
return [
LoginSuccessEvent::class => 'onSuccessfulLogin',
LoginFailureEvent::class => 'onFailedLogin',
];
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Symfony\Component\Security\Http\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @final
* @experimental in 5.1
*/
class UserCheckerListener implements EventSubscriberInterface
{
private $userChecker;
public function __construct(UserCheckerInterface $userChecker)
{
$this->userChecker = $userChecker;
}
public function preCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
{
$this->userChecker->checkPreAuth($event->getUser());
}
public function postCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
{
$this->userChecker->checkPostAuth($event->getUser());
}
public static function getSubscribedEvents(): array
{
return [
VerifyAuthenticatorCredentialsEvent::class => [
['preCredentialsVerification', 256],
['preCredentialsVerification', 32]
],
];
}
}

View File

@ -12,12 +12,18 @@
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;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticationGuardToken;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
@ -34,19 +40,20 @@ class GuardManagerListener
private $guardHandler;
private $guardAuthenticators;
protected $providerKey;
private $eventDispatcher;
protected $logger;
private $rememberMeServices;
/**
* @param AuthenticatorInterface[] $guardAuthenticators
*/
public function __construct(AuthenticationManagerInterface $authenticationManager, GuardAuthenticatorHandler $guardHandler, iterable $guardAuthenticators, string $providerKey, ?LoggerInterface $logger = null)
public function __construct(AuthenticationManagerInterface $authenticationManager, GuardAuthenticatorHandler $guardHandler, iterable $guardAuthenticators, string $providerKey, EventDispatcherInterface $eventDispatcher, ?LoggerInterface $logger = null)
{
$this->authenticationManager = $authenticationManager;
$this->guardHandler = $guardHandler;
$this->guardAuthenticators = $guardAuthenticators;
$this->providerKey = $providerKey;
$this->logger = $logger;
$this->eventDispatcher = $eventDispatcher;
}
public function __invoke(RequestEvent $requestEvent)
@ -57,23 +64,95 @@ class GuardManagerListener
return;
}
$this->executeGuardAuthenticators($guardAuthenticators, $requestEvent);
$this->executeAuthenticators($guardAuthenticators, $requestEvent);
}
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
/**
* @param AuthenticatorInterface[] $authenticators
*/
protected function executeAuthenticators(array $authenticators, RequestEvent $event): void
{
$this->rememberMeServices = $rememberMeServices;
foreach ($authenticators as $key => $guardAuthenticator) {
$this->executeAuthenticator($key, $guardAuthenticator, $event);
if ($event->hasResponse()) {
if (null !== $this->logger) {
$this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($guardAuthenticator)]);
}
break;
}
}
}
protected function createPreAuthenticatedToken($credentials, string $uniqueGuardKey, string $providerKey): PreAuthenticationGuardToken
private function executeAuthenticator(string $uniqueAuthenticatorKey, AuthenticatorInterface $authenticator, RequestEvent $event): void
{
return new PreAuthenticationGuardToken($credentials, $uniqueGuardKey, $providerKey);
$request = $event->getRequest();
try {
if (null !== $this->logger) {
$this->logger->debug('Calling getCredentials() on authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
// allow the authenticator to fetch authentication info from the request
$credentials = $authenticator->getCredentials($request);
if (null === $credentials) {
throw new \UnexpectedValueException(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', \get_class($authenticator)));
}
// create a token with the unique key, so that the provider knows which authenticator to use
$token = $this->createPreAuthenticatedToken($credentials, $uniqueAuthenticatorKey, $this->providerKey);
if (null !== $this->logger) {
$this->logger->debug('Passing token information to the AuthenticatorManager', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
}
// pass the token into the AuthenticationManager system
// this indirectly calls AuthenticatorManager::authenticate()
$token = $this->authenticationManager->authenticate($token);
if (null !== $this->logger) {
$this->logger->info('Authenticator successful!', ['token' => $token, 'authenticator' => \get_class($authenticator)]);
}
// sets the token on the token storage, etc
$this->guardHandler->authenticateWithToken($token, $request, $this->providerKey);
} catch (AuthenticationException $e) {
// oh no! Authentication failed!
if (null !== $this->logger) {
$this->logger->info('Authenticator failed.', ['exception' => $e, 'authenticator' => \get_class($authenticator)]);
}
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $authenticator, $this->providerKey);
if ($response instanceof Response) {
$event->setResponse($response);
}
$this->eventDispatcher->dispatch(new LoginFailureEvent($e, $authenticator, $request, $response, $this->providerKey));
return;
}
// success!
$response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $authenticator, $this->providerKey);
if ($response instanceof Response) {
if (null !== $this->logger) {
$this->logger->debug('Authenticator set success response.', ['response' => $response, 'authenticator' => \get_class($authenticator)]);
}
$event->setResponse($response);
} else {
if (null !== $this->logger) {
$this->logger->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator)]);
}
}
$this->eventDispatcher->dispatch(new LoginSuccessEvent($authenticator, $token, $request, $response, $this->providerKey));
}
protected function getGuardKey(string $key): string
protected function createPreAuthenticatedToken($credentials, string $uniqueAuthenticatorKey, string $providerKey): PreAuthenticationGuardToken
{
// Guard authenticators in the GuardManagerListener are already indexed
// by an unique key
return $key;
return new PreAuthenticationGuardToken($credentials, $uniqueAuthenticatorKey, $providerKey);
}
}

View File

@ -46,134 +46,5 @@ trait GuardManagerListenerTrait
return $guardAuthenticators;
}
/**
* @param (CoreAuthenticatorInterface|AuthenticatorInterface)[] $guardAuthenticators
*/
protected function executeGuardAuthenticators(array $guardAuthenticators, RequestEvent $event): void
{
foreach ($guardAuthenticators as $key => $guardAuthenticator) {
$uniqueGuardKey = $this->getGuardKey($key);
$this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event);
if ($event->hasResponse()) {
if (null !== $this->logger) {
$this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($guardAuthenticator)]);
}
break;
}
}
}
/**
* @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator
*/
private function executeGuardAuthenticator(string $uniqueGuardKey, $guardAuthenticator, RequestEvent $event)
{
if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) {
throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.');
}
$request = $event->getRequest();
try {
if (null !== $this->logger) {
$this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
// allow the authenticator to fetch authentication info from the request
$credentials = $guardAuthenticator->getCredentials($request);
if (null === $credentials) {
throw new \UnexpectedValueException(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', get_debug_type($guardAuthenticator)));
}
// create a token with the unique key, so that the provider knows which authenticator to use
$token = $this->createPreAuthenticatedToken($credentials, $uniqueGuardKey, $this->providerKey);
if (null !== $this->logger) {
$this->logger->debug('Passing guard token information to the GuardAuthenticationProvider', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
}
// pass the token into the AuthenticationManager system
// this indirectly calls GuardAuthenticationProvider::authenticate()
$token = $this->authenticationManager->authenticate($token);
if (null !== $this->logger) {
$this->logger->info('Guard authentication successful!', ['token' => $token, 'authenticator' => \get_class($guardAuthenticator)]);
}
// sets the token on the token storage, etc
$this->guardHandler->authenticateWithToken($token, $request, $this->providerKey);
} catch (AuthenticationException $e) {
// oh no! Authentication failed!
if (null !== $this->logger) {
$this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]);
}
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
if ($response instanceof Response) {
$event->setResponse($response);
}
return;
}
// success!
$response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey);
if ($response instanceof Response) {
if (null !== $this->logger) {
$this->logger->debug('Guard authenticator set success response.', ['response' => $response, 'authenticator' => \get_class($guardAuthenticator)]);
}
$event->setResponse($response);
} else {
if (null !== $this->logger) {
$this->logger->debug('Guard authenticator set no success response: request continues.', ['authenticator' => \get_class($guardAuthenticator)]);
}
}
// attempt to trigger the remember me functionality
$this->triggerRememberMe($guardAuthenticator, $request, $token, $response);
}
/**
* Checks to see if remember me is supported in the authenticator and
* on the firewall. If it is, the RememberMeServicesInterface is notified.
*
* @param CoreAuthenticatorInterface|AuthenticatorInterface $guardAuthenticator
*/
private function triggerRememberMe($guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
{
if (!$guardAuthenticator instanceof AuthenticatorInterface && !$guardAuthenticator instanceof CoreAuthenticatorInterface) {
throw new \UnexpectedValueException('Invalid guard authenticator passed to '.__METHOD__.'. Expected AuthenticatorInterface of either Security Core or Security Guard.');
}
if (null === $this->rememberMeServices) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($guardAuthenticator)]);
}
return;
}
if (!$guardAuthenticator->supportsRememberMe()) {
if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($guardAuthenticator)]);
}
return;
}
if (!$response instanceof Response) {
throw new \LogicException(sprintf('"%s::onAuthenticationSuccess()" *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.', get_debug_type($guardAuthenticator)));
}
$this->rememberMeServices->loginSuccess($request, $response, $token);
}
abstract protected function getGuardKey(string $key): string;
abstract protected function createPreAuthenticatedToken($credentials, string $uniqueGuardKey, string $providerKey): PreAuthenticationGuardToken;
}