vendor/symfony/security-http/Firewall/AbstractAuthenticationListener.php line 34

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Http\Firewall;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Event\RequestEvent;
  15. use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
  16. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  17. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  18. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  19. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  20. use Symfony\Component\Security\Core\Exception\SessionUnavailableException;
  21. use Symfony\Component\Security\Core\Security;
  22. use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
  23. use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
  24. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  25. use Symfony\Component\Security\Http\HttpUtils;
  26. use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
  27. use Symfony\Component\Security\Http\SecurityEvents;
  28. use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
  29. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  30. trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AbstractAuthenticationListener::class);
  31. /**
  32. * The AbstractAuthenticationListener is the preferred base class for all
  33. * browser-/HTTP-based authentication requests.
  34. *
  35. * Subclasses likely have to implement the following:
  36. * - an TokenInterface to hold authentication related data
  37. * - an AuthenticationProvider to perform the actual authentication of the
  38. * token, retrieve the UserInterface implementation from a database, and
  39. * perform the specific account checks using the UserChecker
  40. *
  41. * By default, this listener only is active for a specific path, e.g.
  42. * /login_check. If you want to change this behavior, you can overwrite the
  43. * requiresAuthentication() method.
  44. *
  45. * @author Fabien Potencier <fabien@symfony.com>
  46. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  47. *
  48. * @deprecated since Symfony 5.3, use the new authenticator system instead
  49. */
  50. abstract class AbstractAuthenticationListener extends AbstractListener
  51. {
  52. protected $options;
  53. protected $logger;
  54. protected $authenticationManager;
  55. protected $providerKey;
  56. protected $httpUtils;
  57. private $tokenStorage;
  58. private $sessionStrategy;
  59. private $dispatcher;
  60. private $successHandler;
  61. private $failureHandler;
  62. private $rememberMeServices;
  63. /**
  64. * @throws \InvalidArgumentException
  65. */
  66. public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = [], ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null)
  67. {
  68. if (empty($providerKey)) {
  69. throw new \InvalidArgumentException('$providerKey must not be empty.');
  70. }
  71. $this->tokenStorage = $tokenStorage;
  72. $this->authenticationManager = $authenticationManager;
  73. $this->sessionStrategy = $sessionStrategy;
  74. $this->providerKey = $providerKey;
  75. $this->successHandler = $successHandler;
  76. $this->failureHandler = $failureHandler;
  77. $this->options = array_merge([
  78. 'check_path' => '/login_check',
  79. 'login_path' => '/login',
  80. 'always_use_default_target_path' => false,
  81. 'default_target_path' => '/',
  82. 'target_path_parameter' => '_target_path',
  83. 'use_referer' => false,
  84. 'failure_path' => null,
  85. 'failure_forward' => false,
  86. 'require_previous_session' => true,
  87. ], $options);
  88. $this->logger = $logger;
  89. $this->dispatcher = $dispatcher;
  90. $this->httpUtils = $httpUtils;
  91. }
  92. /**
  93. * Sets the RememberMeServices implementation to use.
  94. */
  95. public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
  96. {
  97. $this->rememberMeServices = $rememberMeServices;
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. public function supports(Request $request): ?bool
  103. {
  104. return $this->requiresAuthentication($request);
  105. }
  106. /**
  107. * Handles form based authentication.
  108. *
  109. * @throws \RuntimeException
  110. * @throws SessionUnavailableException
  111. */
  112. public function authenticate(RequestEvent $event)
  113. {
  114. $request = $event->getRequest();
  115. if (!$request->hasSession()) {
  116. throw new \RuntimeException('This authentication method requires a session.');
  117. }
  118. try {
  119. if ($this->options['require_previous_session'] && !$request->hasPreviousSession()) {
  120. throw new SessionUnavailableException('Your session has timed out, or you have disabled cookies.');
  121. }
  122. $previousToken = $this->tokenStorage->getToken();
  123. if (null === $returnValue = $this->attemptAuthentication($request)) {
  124. return;
  125. }
  126. if ($returnValue instanceof TokenInterface) {
  127. $this->migrateSession($request, $returnValue, $previousToken);
  128. $response = $this->onSuccess($request, $returnValue);
  129. } elseif ($returnValue instanceof Response) {
  130. $response = $returnValue;
  131. } else {
  132. throw new \RuntimeException('attemptAuthentication() must either return a Response, an implementation of TokenInterface, or null.');
  133. }
  134. } catch (AuthenticationException $e) {
  135. $response = $this->onFailure($request, $e);
  136. }
  137. $event->setResponse($response);
  138. }
  139. /**
  140. * Whether this request requires authentication.
  141. *
  142. * The default implementation only processes requests to a specific path,
  143. * but a subclass could change this to only authenticate requests where a
  144. * certain parameters is present.
  145. *
  146. * @return bool
  147. */
  148. protected function requiresAuthentication(Request $request)
  149. {
  150. return $this->httpUtils->checkRequestPath($request, $this->options['check_path']);
  151. }
  152. /**
  153. * Performs authentication.
  154. *
  155. * @return TokenInterface|Response|null The authenticated token, null if full authentication is not possible, or a Response
  156. *
  157. * @throws AuthenticationException if the authentication fails
  158. */
  159. abstract protected function attemptAuthentication(Request $request);
  160. private function onFailure(Request $request, AuthenticationException $failed): Response
  161. {
  162. if (null !== $this->logger) {
  163. $this->logger->info('Authentication request failed.', ['exception' => $failed]);
  164. }
  165. $token = $this->tokenStorage->getToken();
  166. if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) {
  167. $this->tokenStorage->setToken(null);
  168. }
  169. $response = $this->failureHandler->onAuthenticationFailure($request, $failed);
  170. if (!$response instanceof Response) {
  171. throw new \RuntimeException('Authentication Failure Handler did not return a Response.');
  172. }
  173. return $response;
  174. }
  175. private function onSuccess(Request $request, TokenInterface $token): Response
  176. {
  177. if (null !== $this->logger) {
  178. // @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
  179. $this->logger->info('User has been authenticated successfully.', ['username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]);
  180. }
  181. $this->tokenStorage->setToken($token);
  182. $session = $request->getSession();
  183. $session->remove(Security::AUTHENTICATION_ERROR);
  184. $session->remove(Security::LAST_USERNAME);
  185. if (null !== $this->dispatcher) {
  186. $loginEvent = new InteractiveLoginEvent($request, $token);
  187. $this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
  188. }
  189. $response = $this->successHandler->onAuthenticationSuccess($request, $token);
  190. if (!$response instanceof Response) {
  191. throw new \RuntimeException('Authentication Success Handler did not return a Response.');
  192. }
  193. if (null !== $this->rememberMeServices) {
  194. $this->rememberMeServices->loginSuccess($request, $response, $token);
  195. }
  196. return $response;
  197. }
  198. private function migrateSession(Request $request, TokenInterface $token, ?TokenInterface $previousToken)
  199. {
  200. if ($previousToken) {
  201. $user = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername();
  202. $previousUser = method_exists($previousToken, 'getUserIdentifier') ? $previousToken->getUserIdentifier() : $previousToken->getUsername();
  203. if ('' !== ($user ?? '') && $user === $previousUser) {
  204. return;
  205. }
  206. }
  207. $this->sessionStrategy->onAuthentication($request, $token);
  208. }
  209. }