TokenBasedRememberMeServices.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. namespace Symfony\Component\Security\Http\RememberMe;
  3. use Symfony\Component\HttpFoundation\Cookie;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  7. use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
  8. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  9. use Symfony\Component\Security\Core\User\AccountInterface;
  10. /*
  11. * This file is part of the Symfony package.
  12. *
  13. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  14. *
  15. * For the full copyright and license information, please view the LICENSE
  16. * file that was distributed with this source code.
  17. */
  18. /**
  19. * Concrete implementation of the RememberMeServicesInterface providing
  20. * remember-me capabilities without requiring a TokenProvider.
  21. *
  22. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  23. */
  24. class TokenBasedRememberMeServices extends RememberMeServices
  25. {
  26. /**
  27. * {@inheritDoc}
  28. */
  29. protected function processAutoLoginCookie(array $cookieParts, Request $request)
  30. {
  31. if (count($cookieParts) !== 4) {
  32. throw new AuthenticationException('The cookie is invalid.');
  33. }
  34. list($class, $username, $expires, $hash) = $cookieParts;
  35. if (false === $username = base64_decode($username, true)) {
  36. throw new AuthenticationException('$username contains a character from outside the base64 alphabet.');
  37. }
  38. try {
  39. $user = $this->getUserProvider($class)->loadUserByUsername($username);
  40. } catch (\Exception $ex) {
  41. if (!$ex instanceof AuthenticationException) {
  42. $ex = new AuthenticationException($ex->getMessage(), null, $ex->getCode(), $ex);
  43. }
  44. throw $ex;
  45. }
  46. if (!$user instanceof AccountInterface) {
  47. throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of AccountInterface, but returned "%s".', get_class($user)));
  48. }
  49. if (true !== $this->compareHashes($hash, $this->generateCookieHash($class, $username, $expires, $user->getPassword()))) {
  50. throw new AuthenticationException('The cookie\'s hash is invalid.');
  51. }
  52. if ($expires < time()) {
  53. throw new AuthenticationException('The cookie has expired.');
  54. }
  55. return new RememberMeToken($user, $this->providerKey, $this->key);
  56. }
  57. /**
  58. * Compares two hashes using a constant-time algorithm to avoid (remote)
  59. * timing attacks.
  60. *
  61. * This is the same implementation as used in the BasePasswordEncoder.
  62. *
  63. * @param string $hash1 The first hash
  64. * @param string $hash2 The second hash
  65. *
  66. * @return Boolean true if the two hashes are the same, false otherwise
  67. */
  68. protected function compareHashes($hash1, $hash2)
  69. {
  70. if (strlen($hash1) !== $c = strlen($hash2)) {
  71. return false;
  72. }
  73. $result = 0;
  74. for ($i = 0; $i < $c; $i++) {
  75. $result |= ord($hash1[$i]) ^ ord($hash2[$i]);
  76. }
  77. return 0 === $result;
  78. }
  79. /**
  80. * {@inheritDoc}
  81. */
  82. protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
  83. {
  84. if ($token instanceof RememberMeToken) {
  85. return;
  86. }
  87. $user = $token->getUser();
  88. $expires = time() + $this->options['lifetime'];
  89. $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword());
  90. $response->headers->setCookie(
  91. new Cookie(
  92. $this->options['name'],
  93. $value,
  94. $expires,
  95. $this->options['path'],
  96. $this->options['domain'],
  97. $this->options['secure'],
  98. $this->options['httponly']
  99. )
  100. );
  101. }
  102. /**
  103. * Generates the cookie value.
  104. *
  105. * @param string $class
  106. * @param string $username The username
  107. * @param integer $expires The unixtime when the cookie expires
  108. * @param string $password The encoded password
  109. *
  110. * @throws \RuntimeException if username contains invalid chars
  111. *
  112. * @return string
  113. */
  114. protected function generateCookieValue($class, $username, $expires, $password)
  115. {
  116. return $this->encodeCookie(array(
  117. $class,
  118. base64_encode($username),
  119. $expires,
  120. $this->generateCookieHash($class, $username, $expires, $password)
  121. ));
  122. }
  123. /**
  124. * Generates a hash for the cookie to ensure it is not being tempered with
  125. *
  126. * @param string $class
  127. * @param string $username The username
  128. * @param integer $expires The unixtime when the cookie expires
  129. * @param string $password The encoded password
  130. * @throws \RuntimeException when the private key is empty
  131. * @return string
  132. */
  133. protected function generateCookieHash($class, $username, $expires, $password)
  134. {
  135. return hash('sha256', $class.$username.$expires.$password.$this->key);
  136. }
  137. }