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:
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:
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.