简体   繁体   中英

Symfony 2.8 session lost after login_check redirect

I am currently porting my old custom framework to a Symfony-based custom framework using Symfony's components. So far everything is going smoothly, except for the login part. Here are a few details about the project:

  • I'm using Symfony Security Component v2.8
  • My sessions are being stored in a database using PDOSessionHandler
  • I'm using Guard to authenticate my users.

The problem arises when a user tries to login to an admin area using a form. After the form submission, the user is forwarded to the login_check route where all credentials are successfully checked. The user role ROLE_ADMIN is set and finally the user is redirected to a secure page, but then gets redirected automatically back to the login. The order of events is like so:

login -> login_check -> admin -> login

I have done some debugging by setting breakpoints in ContextListener::OnKernelResponse and found out that a token is never saved in the session,because the method returns here:

if (!$event->getRequest()->hasSession()) {
    return;
   }

Also, I am able to see a session being added to the database table and the session id remains constant throughout the redirect. In the end I am bounced back to the login page and my user is set to .anon Somewhere between /login_check and /admin my token is lost.

I have run out of ideas on how to debug this. I am pasting some code to help get an idea of my setup, but I think these are fine.

My firewall configuration is looking like this

return[
'security'=>[
    //Providers
    'providers'=>[
        'user' => array(
            'id' => 'security.user.provider.default',
        ),
    ],

    //Encoders
    'encoders'=>[
        'Library\\Security\\Users\\User::class' => array('algorithm' => 'bcrypt', 'cost'=> 15)
    ],
    'firewalls'=>
        [
            'backend'=>array(
                'security'       =>true,
                'anonymous'      => true,
                'pattern'        => '^/',
                'guard'          => array(
                    'authenticators'  => array(
                        'security.guard.form.authenticator',
                        'security.authenticator.token'
                    ),
                    'entry_point'=>'security.guard.form.authenticator'
                ),
            ),


        ],
    'access_control'=>array(
        array('path' => '^/admin', 'roles' => ['ROLE_ADMIN']),
        array('path' => '^/api', 'roles' => ['ROLE_API']),
        array('path' => '^/pos', 'roles' => ['ROLE_POS']),
        array('path' => '^/dashboard', 'roles' => ['ROLE_SUPER_ADMIN']),
        array('path' => '^/login', 'roles' => ['IS_AUTHENTICATED_ANONYMOUSLY']),
        array('path' => '/', 'roles' => ['IS_AUTHENTICATED_ANONYMOUSLY']),
    )

]];

My UserInterface :

class User implements UserInterface, EquatableInterface{


 private $username;
    private $password;
    private $salt;
    private $roles;

public function __construct($username, $password, $salt, array $roles)
{
    $this->username = $username;
    $this->password = $password;
    $this->salt = $salt;
    $this->roles = $roles;
}

public function getRoles()
{
    return $this->roles;
}

public function getPassword()
{
    return $this->password;
}

public function getSalt()
{
    return $this->salt;
}

public function getUsername()
{
    return $this->username;
}

public function eraseCredentials()
{
}

public function isEqualTo(UserInterface $user)
{

    if (!$user instanceof DefaultUserProvider) {
        return false;
    }

    if ($this->password !== $user->getPassword()) {
        return false;
    }

    if ($this->salt !== $user->getSalt()) {
        return false;
    }

    if ($this->username !== $user->getUsername()) {
        return false;
    }

    return true;
}}

My UserProvider

namespace Library\Security\UserProviders;

use Library\Nosh\Project\Project;
    use Library\Security\Users\User;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    use PDO;
    use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;

    class DefaultUserProvider implements UserProviderInterface{
        private $db;

    private $project;

    public function __construct(\PDO $db, Project $project)
    {
        $this->db = $db;

        $this->project=$project;
    }

    public function loadUserByUsername($username)
    {
        $projectId = $this->project->id();

        $statement = $this->db->prepare("SELECT * FROM users WHERE :userLogin IN (user_login, user_email) AND project_id=:project_id  AND user_active=:user_active");

        $statement->bindParam(':userLogin', $username, PDO::PARAM_STR);
        $statement->bindValue(':user_active', 1, PDO::PARAM_INT);
        $statement->bindValue(':project_id', $projectId, PDO::PARAM_INT);
        $statement->execute();

        if (!$user = $statement->fetch()) {
            throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
        }

        $roles = explode(',', $user['user_roles']);

        return new User($user['user_login'], $user['user_password'],$salt='',$roles);
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return $class ===  'Library\\Security\\Users\\User';
    }

}

I was able to solve my own problem after several days of debugging. The reason I had no session is because I had failed to implement a SessionListener to store the session into the request. This should not be an issue for anyone using the Symfony Framework or Silex. It was only an issue for me, because I am actually creating something from scratch.

For anyone wondering how to do this, here are the necessary steps:

  • Create a class which extends Symfony\\Component\\HttpKernel\\EventListener\\SessionListener
  • Implement the method getSession()
  • Make sure you add the class to the dispatcher with addSubscriber()

See my example below:

SessionListener

use Symfony\Component\HttpKernel\EventListener\SessionListener as AbstractSessionListener;

class SessionListener extends AbstractSessionListener {

    private $container;

    public function __construct(Container $container)
    {
        $this->container=$container;
    }

    protected function getSession()
    {
        if (!$this->container->has('session')) {
            return;
        }
        return $this->container->get('session');
    }
}

SessionServiceProvider

use Core\Container;
use Interfaces\EventListenerProviderInterface;
use Interfaces\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class SessionServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface {

    protected $options=[
        'cookie_lifetime'=>2592000,//1 month
        'gc_probability'=>1,
        'gc_divisor'=>1000,
        'gc_maxlifetime'=>2592000
    ];

    public function register(Container $container){


        switch($container->getParameter('session_driver')){
            case 'database':
                $storage = new NativeSessionStorage($this->options, new PdoSessionHandler($container->get('db')));

                break;

            case 'file':
                $storage = new NativeSessionStorage($this->options, new NativeFileSessionHandler($container->getParameter('session_dir')));
                break;

            default:
                $storage = new NativeSessionStorage($this->options, new NativeFileSessionHandler($container->getParameter('session_dir')));
                break;
        }

        $container->register('session',Session::class)->setArguments([$storage]);


    }

    public function subscribe(Container $container, EventDispatcherInterface $dispatcher)
    {
        $dispatcher->addSubscriber(new SessionListener($container));
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM