TokenBasedRememberMeServices.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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 (count($cookieParts) !== 4) {
  31. throw new AuthenticationException('The cookie is invalid.');
  32. }
  33. list($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 $ex) {
  40. if (!$ex instanceof AuthenticationException) {
  41. $ex = new AuthenticationException($ex->getMessage(), $ex->getCode(), $ex);
  42. }
  43. throw $ex;
  44. }
  45. if (!$user instanceof UserInterface) {
  46. throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_class($user)));
  47. }
  48. if (true !== $this->compareHashes($hash, $this->generateCookieHash($class, $username, $expires, $user->getPassword()))) {
  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. * Compares two hashes using a constant-time algorithm to avoid (remote)
  58. * timing attacks.
  59. *
  60. * This is the same implementation as used in the BasePasswordEncoder.
  61. *
  62. * @param string $hash1 The first hash
  63. * @param string $hash2 The second hash
  64. *
  65. * @return Boolean true if the two hashes are the same, false otherwise
  66. */
  67. private function compareHashes($hash1, $hash2)
  68. {
  69. if (strlen($hash1) !== $c = strlen($hash2)) {
  70. return false;
  71. }
  72. $result = 0;
  73. for ($i = 0; $i < $c; $i++) {
  74. $result |= ord($hash1[$i]) ^ ord($hash2[$i]);
  75. }
  76. return 0 === $result;
  77. }
  78. /**
  79. * {@inheritDoc}
  80. */
  81. protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
  82. {
  83. $user = $token->getUser();
  84. $expires = time() + $this->options['lifetime'];
  85. $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword());
  86. $response->headers->setCookie(
  87. new Cookie(
  88. $this->options['name'],
  89. $value,
  90. $expires,
  91. $this->options['path'],
  92. $this->options['domain'],
  93. $this->options['secure'],
  94. $this->options['httponly']
  95. )
  96. );
  97. }
  98. /**
  99. * Generates the cookie value.
  100. *
  101. * @param string $class
  102. * @param string $username The username
  103. * @param integer $expires The unixtime when the cookie expires
  104. * @param string $password The encoded password
  105. *
  106. * @throws \RuntimeException if username contains invalid chars
  107. *
  108. * @return string
  109. */
  110. protected function generateCookieValue($class, $username, $expires, $password)
  111. {
  112. return $this->encodeCookie(array(
  113. $class,
  114. base64_encode($username),
  115. $expires,
  116. $this->generateCookieHash($class, $username, $expires, $password)
  117. ));
  118. }
  119. /**
  120. * Generates a hash for the cookie to ensure it is not being tempered with
  121. *
  122. * @param string $class
  123. * @param string $username The username
  124. * @param integer $expires The unixtime when the cookie expires
  125. * @param string $password The encoded password
  126. *
  127. * @throws \RuntimeException when the private key is empty
  128. *
  129. * @return string
  130. */
  131. protected function generateCookieHash($class, $username, $expires, $password)
  132. {
  133. return hash('sha256', $class.$username.$expires.$password.$this->getKey());
  134. }
  135. }