TokenBasedRememberMeServices.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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\RememberMe;
  11. use Symfony\Component\HttpFoundation\Cookie;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  15. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  16. use Symfony\Component\Security\Core\User\UserInterface;
  17. /**
  18. * Concrete implementation of the RememberMeServicesInterface providing
  19. * remember-me capabilities without requiring a TokenProvider.
  20. *
  21. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  22. */
  23. class TokenBasedRememberMeServices extends AbstractRememberMeServices
  24. {
  25. /**
  26. * {@inheritdoc}
  27. */
  28. protected function processAutoLoginCookie(array $cookieParts, Request $request)
  29. {
  30. if (4 !== \count($cookieParts)) {
  31. throw new AuthenticationException('The cookie is invalid.');
  32. }
  33. [$class, $username, $expires, $hash] = $cookieParts;
  34. if (false === $username = base64_decode($username, true)) {
  35. throw new AuthenticationException('$username contains a character from outside the base64 alphabet.');
  36. }
  37. try {
  38. $user = $this->getUserProvider($class)->loadUserByUsername($username);
  39. } catch (\Exception $e) {
  40. if (!$e instanceof AuthenticationException) {
  41. $e = new AuthenticationException($e->getMessage(), $e->getCode(), $e);
  42. }
  43. throw $e;
  44. }
  45. if (!$user instanceof UserInterface) {
  46. throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_debug_type($user)));
  47. }
  48. if (true !== hash_equals($this->generateCookieHash($class, $username, $expires, $user->getPassword()), $hash)) {
  49. throw new AuthenticationException('The cookie\'s hash is invalid.');
  50. }
  51. if ($expires < time()) {
  52. throw new AuthenticationException('The cookie has expired.');
  53. }
  54. return $user;
  55. }
  56. /**
  57. * {@inheritdoc}
  58. */
  59. protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
  60. {
  61. $user = $token->getUser();
  62. $expires = time() + $this->options['lifetime'];
  63. $value = $this->generateCookieValue(\get_class($user), $user->getUsername(), $expires, $user->getPassword());
  64. $response->headers->setCookie(
  65. new Cookie(
  66. $this->options['name'],
  67. $value,
  68. $expires,
  69. $this->options['path'],
  70. $this->options['domain'],
  71. $this->options['secure'] ?? $request->isSecure(),
  72. $this->options['httponly'],
  73. false,
  74. $this->options['samesite']
  75. )
  76. );
  77. }
  78. /**
  79. * Generates the cookie value.
  80. *
  81. * @param int $expires The Unix timestamp when the cookie expires
  82. * @param string|null $password The encoded password
  83. *
  84. * @return string
  85. */
  86. protected function generateCookieValue(string $class, string $username, int $expires, ?string $password)
  87. {
  88. // $username is encoded because it might contain COOKIE_DELIMITER,
  89. // we assume other values don't
  90. return $this->encodeCookie([
  91. $class,
  92. base64_encode($username),
  93. $expires,
  94. $this->generateCookieHash($class, $username, $expires, $password),
  95. ]);
  96. }
  97. /**
  98. * Generates a hash for the cookie to ensure it is not being tampered with.
  99. *
  100. * @param int $expires The Unix timestamp when the cookie expires
  101. * @param string|null $password The encoded password
  102. *
  103. * @return string
  104. */
  105. protected function generateCookieHash(string $class, string $username, int $expires, ?string $password)
  106. {
  107. return hash_hmac('sha256', $class.self::COOKIE_DELIMITER.$username.self::COOKIE_DELIMITER.$expires.self::COOKIE_DELIMITER.$password, $this->getSecret());
  108. }
  109. }