Create a symfony2 remember me cookie manually (FOSUserBundle)

不想你离开。 提交于 2019-11-30 00:54:24
JayTaph

If you are setting the rememberme cookie directly, you have to use the following format:

base64_encode(<classname>:base64_encode(<username>):<expiry-timestamp>:<hash>)

where the hash will be:

sha256(<classname> . <username> . <expiry-timestamp> . <password> . <key>)

the key is the key you have entered in your security(.xml/.yml) in the remember_me section.

This is taken from processAutoLoginCookie() method in the Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeService.php file.

This is all done by the generateCookieValue() method in the same class.

However, I would not recommend on using doing it this way directly, but try to see if you can call the TokenBasedRememberMeService::onLoginSuccess() method, which sets this cookie for you to make the code more robust and portable.

Here is how I did it. I'm not using the FOSUserBundle and I'm using Doctrine Entity User Provider, but it should be trivial to adjust to your needs. Here is a general solution:

// after registration and persisting the user object to DB, I'm logging the user in automatically
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());

// but you can also get the token directly, if you're user is already logged in
$token = $this->container->get('security.context')->getToken();

// write cookie for persistent session storing
$providerKey = 'main'; // defined in security.yml
$securityKey = 'MySecret'; // defined in security.yml

$userProvider = new EntityUserProvider($this->getDoctrine()->getEntityManager(), 'MyCompany\MyBundle\Entity\User', 'username');

$rememberMeService = new TokenBasedRememberMeServices(array($userProvider), $securityKey, $providerKey, array(
                'path' => '/',
                'name' => 'MyRememberMeCookie',
                'domain' => null,
                'secure' => false,
                'httponly' => true,
                'lifetime' => 1209600, // 14 days
                'always_remember_me' => true,
                'remember_me_parameter' => '_remember_me')
            );

$response = new Response();
$rememberMeService->loginSuccess($request, $response, $token);

// further modify the response
// ........

return $response;

Just remember you have to set always_remember_me option to true (like I did in the code above) or have it in your $_POST parameters somehow, otherwise method isRememberMeRequested of AbstractRememberMeServices will return false and the cookie won't be stored.

You were pretty close to the correct solution though :) What you did wrong (in the 3rd attempt) is that you've changed the order of parameters here:

$persistenService = new PersistentTokenBasedRememberMeServices(array($um), $providerKey, $securityKey, array('path' => '/', 'name' => 'REMEMBERME', 'domain' => null, 'secure' => false, 'httponly' => true, 'lifetime' => 31536000, 'always_remember_me' => true, 'remember_me_parameter' => '_remember_me'));

Take a look at __construct() in AbstractRememberMeServices.php. You should pass a $securityKey as 2nd argument and $providerKey as 3rd argument, not the other way around like you did by mistake ;)

What I don't know yet, is how to get parameters from security.yml directly in the controller not to duplicate it. By using $this->container->getParameter() I can get parameters stored under parameters key in config.yml, but not the ones places higher in the configuration tree. Any thoughts on this?

For me the easiest solution was extend a BaseTokenBasedRememberMeServices and let it handle

namespace AppBundke\Security\Http;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices as BaseTokenBasedRememberMeServices;


class TokenBasedRememberMeServices extends BaseTokenBasedRememberMeServices
{
     protected $options_new = array('name' => 'REMEMBERME', 'domain' => null, 'path' => '/');

     public function __construct($userProvider, $secret, $providerKey, array $options = array(), LoggerInterface $logger = null)
     {
          return parent::__construct(array($userProvider), $secret, $providerKey, array_merge($this->options_new, $options));
     }

     public function generateCookie($user, $username, $expires, $password)
     {
        $cookie = new Cookie(
             $this->options['name'],
             parent::generateCookieValue(get_class($user), $username, $expires, $password),
             $expires,
             $this->options['path'],
             $this->options['domain'],
             $this->options['secure'],
             $this->options['httponly']
        );
    return $cookie;
    }
}

and in controller;

$user = $this->getUser();
$providerKey = $this->getParameter('fos_user.firewall_name');
$secret = $this->getParameter('secret');
$cookie_life_time = $this->getParameter('cookie_life_time');

$remember_me_service = new TokenBasedRememberMeServices($user, $secret, $providerKey );
$remember_me_cookie = $remember_me_service->generateCookie($user, $user->getUsername(),(time() + $cookie_life_time), $user->getPassword());

then response set cookie to $remember_me_cookie

I hope its works with you 2.

I had the same issue when I tried to set REMEMBERME cookie an User after a connection by token, using Guard Authentication.

In this situation I had no Response object to be able to use $response->headers->setCookie() and needs to use setcookie(). And in this situation, create a RedirectResponse is not appropriate.

This needs to be refactored but I post the raw procedural on which I based my service

$expires = time() + 2628000;
$hash = hash_hmac(
    'sha256',
     get_class($user).$user->getUsername().$expires.$user->getPassword(), 'secret in parameters.yml'
);
$value = base64_encode(implode(':', [get_class($user), base64_encode($user->getUsername()), $expires, $hash]));
setcookie(
    'REMEMBERME',
    $value,
    $expires,
    '/',
    'host',
    'ssl boolean',
    true
);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!