Added remember me functionality

This commit is contained in:
Wouter de Jong 2020-02-12 23:56:17 +01:00
parent 1c810d5d2a
commit ddf430fc1e
15 changed files with 296 additions and 105 deletions

View File

@ -42,7 +42,7 @@ class AnonymousFactory implements SecurityFactoryInterface, AuthenticatorFactory
return [$providerId, $listenerId, $defaultEntryPoint]; return [$providerId, $listenerId, $defaultEntryPoint];
} }
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{ {
if (null === $config['secret']) { if (null === $config['secret']) {
$config['secret'] = new Parameter('container.build_hash'); $config['secret'] = new Parameter('container.build_hash');

View File

@ -25,5 +25,5 @@ interface AuthenticatorFactoryInterface
* *
* @return string|string[] The authenticator service ID(s) to be used by the firewall * @return string|string[] The authenticator service ID(s) to be used by the firewall
*/ */
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId); public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId);
} }

View File

@ -97,7 +97,7 @@ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryIn
return $entryPointId; return $entryPointId;
} }
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{ {
$authenticatorId = 'security.authenticator.form_login.'.$id; $authenticatorId = 'security.authenticator.form_login.'.$id;
$defaultOptions = array_merge($this->defaultSuccessHandlerOptions, $this->options); $defaultOptions = array_merge($this->defaultSuccessHandlerOptions, $this->options);

View File

@ -46,7 +46,7 @@ class HttpBasicFactory implements SecurityFactoryInterface, AuthenticatorFactory
return [$provider, $listenerId, $entryPointId]; return [$provider, $listenerId, $entryPointId];
} }
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{ {
$authenticatorId = 'security.authenticator.http_basic.'.$id; $authenticatorId = 'security.authenticator.http_basic.'.$id;
$container $container

View File

@ -20,7 +20,7 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener; use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener;
class RememberMeFactory implements SecurityFactoryInterface class RememberMeFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
{ {
protected $options = [ protected $options = [
'name' => 'REMEMBERME', 'name' => 'REMEMBERME',
@ -46,29 +46,8 @@ class RememberMeFactory implements SecurityFactoryInterface
; ;
// remember me services // remember me services
if (isset($config['service'])) { $templateId = $this->generateRememberMeServicesTemplateId($config, $id);
$templateId = $config['service']; $rememberMeServicesId = $templateId.'.'.$id;
$rememberMeServicesId = $templateId.'.'.$id;
} elseif (isset($config['token_provider'])) {
$templateId = 'security.authentication.rememberme.services.persistent';
$rememberMeServicesId = $templateId.'.'.$id;
} else {
$templateId = 'security.authentication.rememberme.services.simplehash';
$rememberMeServicesId = $templateId.'.'.$id;
}
$rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId));
$rememberMeServices->replaceArgument(1, $config['secret']);
$rememberMeServices->replaceArgument(2, $id);
if (isset($config['token_provider'])) {
$rememberMeServices->addMethodCall('setTokenProvider', [
new Reference($config['token_provider']),
]);
}
// remember-me options
$rememberMeServices->replaceArgument(3, array_intersect_key($config, $this->options));
// attach to remember-me aware listeners // attach to remember-me aware listeners
$userProviders = []; $userProviders = [];
@ -93,17 +72,8 @@ class RememberMeFactory implements SecurityFactoryInterface
; ;
} }
} }
if ($config['user_providers']) {
$userProviders = [];
foreach ($config['user_providers'] as $providerName) {
$userProviders[] = new Reference('security.user.provider.concrete.'.$providerName);
}
}
if (0 === \count($userProviders)) {
throw new \RuntimeException('You must configure at least one remember-me aware listener (such as form-login) for each firewall that has remember-me enabled.');
}
$rememberMeServices->replaceArgument(0, new IteratorArgument(array_unique($userProviders))); $this->createRememberMeServices($container, $id, $templateId, $userProviders, $config);
// remember-me listener // remember-me listener
$listenerId = 'security.authentication.listener.rememberme.'.$id; $listenerId = 'security.authentication.listener.rememberme.'.$id;
@ -119,6 +89,42 @@ class RememberMeFactory implements SecurityFactoryInterface
return [$authProviderId, $listenerId, $defaultEntryPoint]; return [$authProviderId, $listenerId, $defaultEntryPoint];
} }
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
{
$templateId = $this->generateRememberMeServicesTemplateId($config, $id);
$rememberMeServicesId = $templateId.'.'.$id;
// create remember me services (which manage the remember me cookies)
$this->createRememberMeServices($container, $id, $templateId, [new Reference($userProviderId)], $config);
// create remember me listener (which executes the remember me services for other authenticators and logout)
$this->createRememberMeListener($container, $id, $rememberMeServicesId);
// create remember me authenticator (which re-authenticates the user based on the remember me cookie)
$authenticatorId = 'security.authenticator.remember_me.'.$id;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remember_me'))
->replaceArgument(0, new Reference($rememberMeServicesId))
->replaceArgument(3, array_intersect_key($config, $this->options))
;
foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) {
// register ContextListener
if ('security.context_listener' === substr($serviceId, 0, 25)) {
$container
->getDefinition($serviceId)
->addMethodCall('setRememberMeServices', [new Reference($rememberMeServicesId)])
;
continue;
}
throw new \LogicException(sprintf('Symfony Authenticator Security dropped support for the "security.remember_me_aware" tag, service "%s" will no longer work as expected.', $serviceId));
}
return $authenticatorId;
}
public function getPosition() public function getPosition()
{ {
return 'remember_me'; return 'remember_me';
@ -163,4 +169,63 @@ class RememberMeFactory implements SecurityFactoryInterface
} }
} }
} }
private function generateRememberMeServicesTemplateId(array $config, string $id): string
{
if (isset($config['service'])) {
return $config['service'];
}
if (isset($config['token_provider'])) {
return 'security.authentication.rememberme.services.persistent';
}
return 'security.authentication.rememberme.services.simplehash';
}
private function createRememberMeServices(ContainerBuilder $container, string $id, string $templateId, array $userProviders, array $config): void
{
$rememberMeServicesId = $templateId.'.'.$id;
$rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId));
$rememberMeServices->replaceArgument(1, $config['secret']);
$rememberMeServices->replaceArgument(2, $id);
if (isset($config['token_provider'])) {
$rememberMeServices->addMethodCall('setTokenProvider', [
new Reference($config['token_provider']),
]);
}
// remember-me options
$rememberMeServices->replaceArgument(3, array_intersect_key($config, $this->options));
if ($config['user_providers']) {
$userProviders = [];
foreach ($config['user_providers'] as $providerName) {
$userProviders[] = new Reference('security.user.provider.concrete.'.$providerName);
}
}
if (0 === \count($userProviders)) {
throw new \RuntimeException('You must configure at least one remember-me aware listener (such as form-login) for each firewall that has remember-me enabled.');
}
$rememberMeServices->replaceArgument(0, new IteratorArgument(array_unique($userProviders)));
}
private function createRememberMeListener(ContainerBuilder $container, string $id, string $rememberMeServicesId): void
{
$container
->setDefinition('security.listener.remember_me.'.$id, new ChildDefinition('security.listener.remember_me'))
->addTag('kernel.event_subscriber')
->replaceArgument(0, new Reference($rememberMeServicesId))
->replaceArgument(1, $id)
;
$container
->setDefinition('security.logout.listener.remember_me.'.$id, new Definition(RememberMeLogoutListener::class))
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$id])
->addArgument(new Reference($rememberMeServicesId));
}
} }

View File

@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
@ -34,6 +35,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder; use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder;
use Symfony\Component\Security\Core\User\ChainUserProvider;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Controller\UserValueResolver; use Symfony\Component\Security\Http\Controller\UserValueResolver;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
@ -230,9 +232,16 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
foreach ($providerIds as $userProviderId) { foreach ($providerIds as $userProviderId) {
$userProviders[] = new Reference($userProviderId); $userProviders[] = new Reference($userProviderId);
} }
$arguments[1] = new IteratorArgument($userProviders); $arguments[1] = $userProviderIteratorsArgument = new IteratorArgument($userProviders);
$contextListenerDefinition->setArguments($arguments); $contextListenerDefinition->setArguments($arguments);
if (\count($userProviders) > 1) {
$container->setDefinition('security.user_providers', new Definition(ChainUserProvider::class, [$userProviderIteratorsArgument]))
->setPublic(false);
} else {
$container->setAlias('security.user_providers', new Alias(current($providerIds)))->setPublic(false);
}
if (1 === \count($providerIds)) { if (1 === \count($providerIds)) {
$container->setAlias(UserProviderInterface::class, current($providerIds)); $container->setAlias(UserProviderInterface::class, current($providerIds));
} }
@ -423,16 +432,6 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
// Determine default entry point // Determine default entry point
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null; $configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
if ($this->authenticatorManagerEnabled) {
// Remember me listener (must be before calling createAuthenticationListeners() to inject remember me services)
$container
->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'])
;
}
// Authentication listeners // Authentication listeners
$firewallAuthenticationProviders = []; $firewallAuthenticationProviders = [];
list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId); list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId);
@ -554,7 +553,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return [$listeners, $defaultEntryPoint]; return [$listeners, $defaultEntryPoint];
} }
private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): string
{ {
if (isset($firewall[$factoryKey]['provider'])) { if (isset($firewall[$factoryKey]['provider'])) {
if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) { if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) {
@ -564,13 +563,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $providerIds[$normalizedName]; return $providerIds[$normalizedName];
} }
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) { if ('remember_me' === $factoryKey && $contextListenerId) {
if ('remember_me' === $factoryKey && $contextListenerId) { $container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
$container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']);
}
// RememberMeFactory will use the firewall secret when created
return null;
} }
if ($defaultProvider) { if ($defaultProvider) {
@ -587,6 +581,10 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $userProvider; return $userProvider;
} }
if ('remember_me' === $factoryKey || 'anonymous' === $factoryKey) {
return 'security.user_providers';
}
throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id)); throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $factoryKey, $id));
} }

View File

@ -52,7 +52,8 @@
class="Symfony\Component\Security\Http\EventListener\RememberMeListener" class="Symfony\Component\Security\Http\EventListener\RememberMeListener"
abstract="true"> abstract="true">
<tag name="monolog.logger" channel="security" /> <tag name="monolog.logger" channel="security" />
<argument/> <!-- provider key --> <argument type="abstract">remember me services</argument>
<argument type="abstract">provider key</argument>
<argument type="service" id="logger" on-invalid="null" /> <argument type="service" id="logger" on-invalid="null" />
</service> </service>
@ -82,5 +83,15 @@
<argument type="abstract">secret</argument> <argument type="abstract">secret</argument>
<argument type="service" id="security.untracked_token_storage" /> <argument type="service" id="security.untracked_token_storage" />
</service> </service>
<service id="security.authenticator.remember_me"
class="Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator"
abstract="true">
<argument type="abstract">remember me services</argument>
<argument>%kernel.secret%</argument>
<argument type="service" id="security.token_storage" />
<argument type="abstract">options</argument>
<argument type="service" id="security.authentication.session_strategy" />
</service>
</services> </services>
</container> </container>

View File

@ -25,7 +25,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface
* *
* @experimental in 5.1 * @experimental in 5.1
*/ */
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, RememberMeAuthenticatorInterface
{ {
/** /**
* Return the URL to the login page. * Return the URL to the login page.
@ -46,11 +46,6 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl
return new RedirectResponse($url); return new RedirectResponse($url);
} }
public function supportsRememberMe(): bool
{
return true;
}
/** /**
* Override to control what happens when the user hits a secure page * Override to control what happens when the user hits a secure page
* but isn't logged in yet. * but isn't logged in yet.
@ -61,4 +56,9 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl
return new RedirectResponse($url); return new RedirectResponse($url);
} }
public function supportsRememberMe(): bool
{
return true;
}
} }

View File

@ -75,9 +75,4 @@ class AnonymousAuthenticator implements AuthenticatorInterface, CustomAuthentica
{ {
return null; return null;
} }
public function supportsRememberMe(): bool
{
return false;
}
} }

View File

@ -102,18 +102,4 @@ interface AuthenticatorInterface
* will be authenticated. This makes sense, for example, with an API. * will be authenticated. This makes sense, for example, with an API.
*/ */
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response; public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response;
/**
* Does this method support remember me cookies?
*
* Remember me cookie will be set if *all* of the following are met:
* A) This method returns true
* B) The remember_me key under your firewall is configured
* C) The "remember me" functionality is activated. This is usually
* done by having a _remember_me checkbox in your form, but
* can be configured by the "always_remember_me" and "remember_me_parameter"
* parameters under the "remember_me" firewall key
* D) The onAuthenticationSuccess method returns a Response object
*/
public function supportsRememberMe(): bool;
} }

View File

@ -94,9 +94,4 @@ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEn
return $this->start($request, $exception); return $this->start($request, $exception);
} }
public function supportsRememberMe(): bool
{
return false;
}
} }

View File

@ -0,0 +1,110 @@
<?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\Authenticator\Token;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
/**
* The RememberMe *Authenticator* performs remember me authentication.
*
* This authenticator is executed whenever a user's session
* expired and a remember me cookie was found. This authenticator
* then "re-authenticates" the user using the information in the
* cookie.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Wouter de Jong <wouter@wouterj.nl>
*
* @final
*/
class RememberMeAuthenticator implements AuthenticatorInterface
{
private $rememberMeServices;
private $secret;
private $tokenStorage;
private $options;
private $sessionStrategy;
public function __construct(AbstractRememberMeServices $rememberMeServices, string $secret, TokenStorageInterface $tokenStorage, array $options, ?SessionAuthenticationStrategy $sessionStrategy = null)
{
$this->rememberMeServices = $rememberMeServices;
$this->secret = $secret;
$this->tokenStorage = $tokenStorage;
$this->options = $options;
$this->sessionStrategy = $sessionStrategy;
}
public function supports(Request $request): ?bool
{
// do not overwrite already stored tokens (i.e. from the session)
if (null !== $this->tokenStorage->getToken()) {
return false;
}
if (($cookie = $request->attributes->get(AbstractRememberMeServices::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) {
return false;
}
if (!$request->cookies->has($this->options['name'])) {
return false;
}
// the `null` return value indicates that this authenticator supports lazy firewalls
return null;
}
public function getCredentials(Request $request)
{
return [
'cookie_parts' => explode(AbstractRememberMeServices::COOKIE_DELIMITER, base64_decode($request->cookies->get($this->options['name']))),
'request' => $request,
];
}
/**
* @param array $credentials
*/
public function getUser($credentials): ?UserInterface
{
return $this->rememberMeServices->performLogin($credentials['cookie_parts'], $credentials['request']);
}
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
{
return new RememberMeToken($user, $providerKey, $this->secret);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$this->rememberMeServices->loginFail($request, $exception);
return null;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
{
if ($request->hasSession() && $request->getSession()->isStarted()) {
$this->sessionStrategy->onAuthentication($request, $token);
}
return null;
}
}

View File

@ -0,0 +1,31 @@
<?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\Authenticator;
/**
* This interface must be extended if the authenticator supports remember me functionality.
*
* Remember me cookie will be set if *all* of the following are met:
* A) SupportsRememberMe() returns true in the successful authenticator
* B) The remember_me key under your firewall is configured
* C) The "remember me" functionality is activated. This is usually
* done by having a _remember_me checkbox in your form, but
* can be configured by the "always_remember_me" and "remember_me_parameter"
* parameters under the "remember_me" firewall key
* D) The onAuthenticationSuccess method returns a Response object
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface RememberMeAuthenticatorInterface
{
public function supportsRememberMe(): bool;
}

View File

@ -5,11 +5,19 @@ namespace Symfony\Component\Security\Http\EventListener;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticatorInterface;
use Symfony\Component\Security\Http\Event\LoginFailureEvent; use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent; use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
/** /**
* The RememberMe *listener* creates and deletes remember me cookies.
*
* Upon login success or failure and support for remember me
* in the firewall and authenticator, this listener will create
* a remember me cookie.
* Upon login failure, all remember me cookies are removed.
*
* @author Wouter de Jong <wouter@wouterj.nl> * @author Wouter de Jong <wouter@wouterj.nl>
* *
* @final * @final
@ -17,23 +25,18 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
*/ */
class RememberMeListener implements EventSubscriberInterface class RememberMeListener implements EventSubscriberInterface
{ {
private $rememberMeServices;
private $providerKey; private $providerKey;
private $logger; private $logger;
/** @var RememberMeServicesInterface|null */
private $rememberMeServices;
public function __construct(string $providerKey, ?LoggerInterface $logger = null) public function __construct(RememberMeServicesInterface $rememberMeServices, string $providerKey, ?LoggerInterface $logger = null)
{ {
$this->rememberMeServices = $rememberMeServices;
$this->providerKey = $providerKey; $this->providerKey = $providerKey;
$this->logger = $logger; $this->logger = $logger;
} }
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices): void
{
$this->rememberMeServices = $rememberMeServices;
}
public function onSuccessfulLogin(LoginSuccessEvent $event): void public function onSuccessfulLogin(LoginSuccessEvent $event): void
{ {
if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) { if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) {
@ -59,15 +62,7 @@ class RememberMeListener implements EventSubscriberInterface
return false; return false;
} }
if (null === $this->rememberMeServices) { if (!$authenticator instanceof RememberMeAuthenticatorInterface || !$authenticator->supportsRememberMe()) {
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) { if (null !== $this->logger) {
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($authenticator)]); $this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($authenticator)]);
} }

View File

@ -89,6 +89,11 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface
return $this->secret; return $this->secret;
} }
public function performLogin(array $cookieParts, Request $request): UserInterface
{
return $this->processAutoLoginCookie($cookieParts, $request);
}
/** /**
* Implementation of RememberMeServicesInterface. Detects whether a remember-me * Implementation of RememberMeServicesInterface. Detects whether a remember-me
* cookie was set, decodes it, and hands it to subclasses for further processing. * cookie was set, decodes it, and hands it to subclasses for further processing.