FormLoginAuthenticator.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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\Authenticator;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  14. use Symfony\Component\HttpKernel\HttpKernelInterface;
  15. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  16. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  17. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  18. use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
  19. use Symfony\Component\Security\Core\Exception\BadCredentialsException;
  20. use Symfony\Component\Security\Core\Security;
  21. use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
  22. use Symfony\Component\Security\Core\User\UserInterface;
  23. use Symfony\Component\Security\Core\User\UserProviderInterface;
  24. use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
  25. use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
  26. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
  27. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
  28. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
  29. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  30. use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
  31. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  32. use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
  33. use Symfony\Component\Security\Http\HttpUtils;
  34. use Symfony\Component\Security\Http\ParameterBagUtils;
  35. /**
  36. * @author Wouter de Jong <wouter@wouterj.nl>
  37. * @author Fabien Potencier <fabien@symfony.com>
  38. *
  39. * @final
  40. * @experimental in 5.2
  41. */
  42. class FormLoginAuthenticator extends AbstractLoginFormAuthenticator
  43. {
  44. private $httpUtils;
  45. private $userProvider;
  46. private $successHandler;
  47. private $failureHandler;
  48. private $options;
  49. private $httpKernel;
  50. public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options)
  51. {
  52. $this->httpUtils = $httpUtils;
  53. $this->userProvider = $userProvider;
  54. $this->successHandler = $successHandler;
  55. $this->failureHandler = $failureHandler;
  56. $this->options = array_merge([
  57. 'username_parameter' => '_username',
  58. 'password_parameter' => '_password',
  59. 'check_path' => '/login_check',
  60. 'post_only' => true,
  61. 'enable_csrf' => false,
  62. 'csrf_parameter' => '_csrf_token',
  63. 'csrf_token_id' => 'authenticate',
  64. ], $options);
  65. }
  66. protected function getLoginUrl(Request $request): string
  67. {
  68. return $this->httpUtils->generateUri($request, $this->options['login_path']);
  69. }
  70. public function supports(Request $request): bool
  71. {
  72. return ($this->options['post_only'] ? $request->isMethod('POST') : true)
  73. && $this->httpUtils->checkRequestPath($request, $this->options['check_path']);
  74. }
  75. public function authenticate(Request $request): PassportInterface
  76. {
  77. $credentials = $this->getCredentials($request);
  78. $passport = new Passport(new UserBadge($credentials['username'], function ($username) {
  79. $user = $this->userProvider->loadUserByUsername($username);
  80. if (!$user instanceof UserInterface) {
  81. throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
  82. }
  83. return $user;
  84. }), new PasswordCredentials($credentials['password']), [new RememberMeBadge()]);
  85. if ($this->options['enable_csrf']) {
  86. $passport->addBadge(new CsrfTokenBadge($this->options['csrf_token_id'], $credentials['csrf_token']));
  87. }
  88. if ($this->userProvider instanceof PasswordUpgraderInterface) {
  89. $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
  90. }
  91. return $passport;
  92. }
  93. /**
  94. * @param Passport $passport
  95. */
  96. public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
  97. {
  98. return new UsernamePasswordToken($passport->getUser(), null, $firewallName, $passport->getUser()->getRoles());
  99. }
  100. public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
  101. {
  102. return $this->successHandler->onAuthenticationSuccess($request, $token);
  103. }
  104. public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
  105. {
  106. return $this->failureHandler->onAuthenticationFailure($request, $exception);
  107. }
  108. private function getCredentials(Request $request): array
  109. {
  110. $credentials = [];
  111. $credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
  112. if ($this->options['post_only']) {
  113. $credentials['username'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']);
  114. $credentials['password'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']) ?? '';
  115. } else {
  116. $credentials['username'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']);
  117. $credentials['password'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']) ?? '';
  118. }
  119. if (!\is_string($credentials['username']) && (!\is_object($credentials['username']) || !method_exists($credentials['username'], '__toString'))) {
  120. throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($credentials['username'])));
  121. }
  122. $credentials['username'] = trim($credentials['username']);
  123. if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) {
  124. throw new BadCredentialsException('Invalid username.');
  125. }
  126. $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']);
  127. return $credentials;
  128. }
  129. public function setHttpKernel(HttpKernelInterface $httpKernel): void
  130. {
  131. $this->httpKernel = $httpKernel;
  132. }
  133. public function start(Request $request, AuthenticationException $authException = null): Response
  134. {
  135. if (!$this->options['use_forward']) {
  136. return parent::start($request, $authException);
  137. }
  138. $subRequest = $this->httpUtils->createRequest($request, $this->options['login_path']);
  139. $response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
  140. if (200 === $response->getStatusCode()) {
  141. $response->setStatusCode(401);
  142. }
  143. return $response;
  144. }
  145. }