Also use authentication failure/success handlers in FormLoginAuthenticator

This commit is contained in:
Wouter de Jong 2020-04-06 14:00:37 +02:00
parent 0fe5083a3e
commit 9ea32c4ed3
6 changed files with 36 additions and 49 deletions

View File

@ -30,6 +30,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface
'check_path' => '/login_check', 'check_path' => '/login_check',
'use_forward' => false, 'use_forward' => false,
'require_previous_session' => false, 'require_previous_session' => false,
'login_path' => '/login',
]; ];
protected $defaultSuccessHandlerOptions = [ protected $defaultSuccessHandlerOptions = [

View File

@ -100,12 +100,13 @@ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryIn
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); $options = array_intersect_key($config, $this->options);
$options = array_merge($defaultOptions, array_intersect_key($config, $defaultOptions));
$container $container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login')) ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login'))
->replaceArgument(1, new Reference($userProviderId)) ->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(2, $options); ->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $id, $config)))
->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $id, $config)))
->replaceArgument(4, $options);
return $authenticatorId; return $authenticatorId;
} }

View File

@ -95,6 +95,8 @@
abstract="true"> abstract="true">
<argument type="service" id="security.http_utils" /> <argument type="service" id="security.http_utils" />
<argument type="abstract">user provider</argument> <argument type="abstract">user provider</argument>
<argument type="abstract">authentication success handler</argument>
<argument type="abstract">authentication failure handler</argument>
<argument type="abstract">options</argument> <argument type="abstract">options</argument>
</service> </service>

View File

@ -30,7 +30,7 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl
/** /**
* Return the URL to the login page. * Return the URL to the login page.
*/ */
abstract protected function getLoginUrl(): string; abstract protected function getLoginUrl(Request $request): string;
/** /**
* Override to change what happens after a bad username/password is submitted. * Override to change what happens after a bad username/password is submitted.
@ -41,7 +41,7 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
} }
$url = $this->getLoginUrl(); $url = $this->getLoginUrl($request);
return new RedirectResponse($url); return new RedirectResponse($url);
} }
@ -52,7 +52,7 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl
*/ */
public function start(Request $request, AuthenticationException $authException = null): Response public function start(Request $request, AuthenticationException $authException = null): Response
{ {
$url = $this->getLoginUrl(); $url = $this->getLoginUrl($request);
return new RedirectResponse($url); return new RedirectResponse($url);
} }

View File

@ -16,13 +16,15 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\ParameterBagUtils; use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
/** /**
* @author Wouter de Jong <wouter@wouterj.nl> * @author Wouter de Jong <wouter@wouterj.nl>
@ -33,34 +35,32 @@ use Symfony\Component\Security\Http\Util\TargetPathTrait;
*/ */
class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements PasswordAuthenticatedInterface, CsrfProtectedAuthenticatorInterface class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements PasswordAuthenticatedInterface, CsrfProtectedAuthenticatorInterface
{ {
use TargetPathTrait;
private $options;
private $httpUtils; private $httpUtils;
private $userProvider; private $userProvider;
private $successHandler;
private $failureHandler;
private $options;
public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, array $options) public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options)
{ {
$this->httpUtils = $httpUtils; $this->httpUtils = $httpUtils;
$this->userProvider = $userProvider;
$this->successHandler = $successHandler;
$this->failureHandler = $failureHandler;
$this->options = array_merge([ $this->options = array_merge([
'username_parameter' => '_username', 'username_parameter' => '_username',
'password_parameter' => '_password', 'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token', 'check_path' => '/login_check',
'csrf_token_id' => 'authenticate',
'post_only' => true, 'post_only' => true,
'always_use_default_target_path' => false, 'csrf_parameter' => '_csrf_token',
'default_target_path' => '/', 'csrf_token_id' => 'authenticate',
'login_path' => '/login',
'target_path_parameter' => '_target_path',
'use_referer' => false,
], $options); ], $options);
$this->userProvider = $userProvider;
} }
protected function getLoginUrl(): string protected function getLoginUrl(Request $request): string
{ {
return $this->options['login_path']; return $this->httpUtils->generateUri($request, $this->options['login_path']);
} }
public function supports(Request $request): bool public function supports(Request $request): bool
@ -122,36 +122,13 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles()); return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
} }
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): Response public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
{ {
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request, $providerKey)); return $this->successHandler->onAuthenticationSuccess($request, $token);
} }
private function determineTargetUrl(Request $request, string $providerKey) public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{ {
if ($this->options['always_use_default_target_path']) { return $this->failureHandler->onAuthenticationFailure($request, $exception);
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'];
} }
} }

View File

@ -17,17 +17,23 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\HttpUtils;
class FormLoginAuthenticatorTest extends TestCase class FormLoginAuthenticatorTest extends TestCase
{ {
private $userProvider; private $userProvider;
private $successHandler;
private $failureHandler;
private $authenticator; private $authenticator;
protected function setUp(): void protected function setUp(): void
{ {
$this->userProvider = $this->createMock(UserProviderInterface::class); $this->userProvider = $this->createMock(UserProviderInterface::class);
$this->successHandler = $this->createMock(AuthenticationSuccessHandlerInterface::class);
$this->failureHandler = $this->createMock(AuthenticationFailureHandlerInterface::class);
} }
/** /**
@ -123,7 +129,7 @@ class FormLoginAuthenticatorTest extends TestCase
private function setUpAuthenticator(array $options = []) private function setUpAuthenticator(array $options = [])
{ {
$this->authenticator = new FormLoginAuthenticator(new HttpUtils(), $this->userProvider, $options); $this->authenticator = new FormLoginAuthenticator(new HttpUtils(), $this->userProvider, $this->successHandler, $this->failureHandler, $options);
} }
private function createSession() private function createSession()