Added FormLogin and Anonymous authenticators

This commit is contained in:
Wouter de Jong 2019-12-13 17:31:14 +01:00
parent 9b7fddd10c
commit a172bacaa6
9 changed files with 262 additions and 6 deletions

View File

@ -19,7 +19,7 @@ use Symfony\Component\DependencyInjection\Parameter;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class AnonymousFactory implements SecurityFactoryInterface
class AnonymousFactory implements SecurityFactoryInterface, GuardFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
@ -42,6 +42,20 @@ class AnonymousFactory implements SecurityFactoryInterface
return [$providerId, $listenerId, $defaultEntryPoint];
}
public function createGuard(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string
{
if (null === $config['secret']) {
$config['secret'] = new Parameter('container.build_hash');
}
$authenticatorId = 'security.authenticator.anonymous.'.$id;
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.anonymous'))
->replaceArgument(0, $config['secret']);
return $authenticatorId;
}
public function getPosition()
{
return 'anonymous';

View File

@ -22,7 +22,7 @@ use Symfony\Component\DependencyInjection\Reference;
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class FormLoginFactory extends AbstractFactory
class FormLoginFactory extends AbstractFactory implements GuardFactoryInterface
{
public function __construct()
{
@ -96,4 +96,17 @@ class FormLoginFactory extends AbstractFactory
return $entryPointId;
}
public function createGuard(ContainerBuilder $container, string $id, array $config, ?string $userProviderId): string
{
$authenticatorId = 'security.authenticator.form_login.'.$id;
$defaultOptions = array_merge($this->defaultSuccessHandlerOptions, $this->options);
$options = array_merge($defaultOptions, array_intersect_key($config, $defaultOptions));
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))
->replaceArgument(1, isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null)
->replaceArgument(3, $options);
return $authenticatorId;
}
}

View File

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

View File

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

View File

@ -138,7 +138,9 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);
$this->guardAuthenticationManagerEnabled = $config['guard_authentication_manager'];
if ($this->guardAuthenticationManagerEnabled = $config['guard_authentication_manager']) {
$loader->load('authenticators.xml');
}
$this->createFirewalls($config, $container);
$this->createAuthorization($config, $container);

View File

@ -12,5 +12,20 @@
<argument type="service" id="security.encoder_factory" />
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="security.authenticator.form_login"
class="Symfony\Component\Security\Core\Authentication\Authenticator\FormLoginAuthenticator"
abstract="true">
<argument type="service" id="security.http_utils" />
<argument /> <!-- csrf token generator -->
<argument type="service" id="security.encoder_factory" />
<argument type="abstract">options</argument>
</service>
<service id="security.authenticator.anonymous"
class="Symfony\Component\Security\Core\Authentication\Authenticator\AnonymousAuthenticator"
abstract="true">
<argument /> <!-- secret -->
</service>
</services>
</container>

View File

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

View File

@ -0,0 +1,70 @@
<?php
namespace Symfony\Component\Security\Core\Authentication\Authenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class AnonymousAuthenticator implements AuthenticatorInterface
{
private $secret;
public function __construct(string $secret)
{
$this->secret = $secret;
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new Response(null, Response::HTTP_UNAUTHORIZED);
}
public function supports(Request $request): ?bool
{
return true;
}
public function getCredentials(Request $request)
{
return [];
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return new User('anon.', null);
}
public function checkCredentials($credentials, UserInterface $user)
{
return true;
}
public function createAuthenticatedToken(UserInterface $user, string $providerKey)
{
return new AnonymousToken($this->secret, 'anon.', []);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
}
public function supportsRememberMe(): bool
{
return false;
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace Symfony\Component\Security\Core\Authentication\Authenticator;
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\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
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\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait, UsernamePasswordTrait, UserProviderTrait {
UsernamePasswordTrait::checkCredentials as checkPassword;
}
private $options;
private $httpUtils;
private $csrfTokenManager;
private $encoderFactory;
public function __construct(HttpUtils $httpUtils, ?CsrfTokenManagerInterface $csrfTokenManager, EncoderFactoryInterface $encoderFactory, array $options)
{
$this->httpUtils = $httpUtils;
$this->csrfTokenManager = $csrfTokenManager;
$this->encoderFactory = $encoderFactory;
$this->options = array_merge([
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'csrf_token_id' => 'authenticate',
'post_only' => true,
'always_use_default_target_path' => false,
'default_target_path' => '/',
'login_path' => '/login',
'target_path_parameter' => '_target_path',
'use_referer' => false,
], $options);
}
protected function getLoginUrl(): string
{
return $this->options['login_path'];
}
public function supports(Request $request): bool
{
return ($this->options['post_only'] ? $request->isMethod('POST') : true)
&& $this->httpUtils->checkRequestPath($request, $this->options['check_path']);
}
public function getCredentials(Request $request): array
{
$credentials = [];
if (null !== $this->csrfTokenManager) {
$credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
}
if ($this->options['post_only']) {
$credentials['username'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']);
$credentials['password'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']);
} else {
$credentials['username'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']);
$credentials['password'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']);
}
if (!\is_string($credentials['username']) && (!\is_object($credentials['username']) || !method_exists($credentials['username'], '__toString'))) {
throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($credentials['username'])));
}
$credentials['username'] = trim($credentials['username']);
if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.');
}
$request->getSession()->set(Security::LAST_USERNAME, $username);
return $credentials;
}
public function checkCredentials($credentials, UserInterface $user): bool
{
if (null !== $this->csrfTokenManager) {
if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $credentials['csrf_token']))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
return $this->checkPassword($credentials, $user);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request, $providerKey));
}
private function determineTargetUrl(Request $request, string $providerKey)
{
if ($this->options['always_use_default_target_path']) {
return $this->options['default_target_path'];
}
if ($targetUrl = ParameterBagUtils::getRequestParameterValue($request, $this->options['target_path_parameter'])) {
return $targetUrl;
}
if ($targetUrl = $this->getTargetPath($request->getSession(), $providerKey)) {
$this->removeTargetPath($request->getSession(), $providerKey);
return $targetUrl;
}
if ($this->options['use_referer'] && $targetUrl = $request->headers->get('Referer')) {
if (false !== $pos = strpos($targetUrl, '?')) {
$targetUrl = substr($targetUrl, 0, $pos);
}
if ($targetUrl && $targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
return $targetUrl;
}
}
return $this->options['default_target_path'];
}
}