-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
Symfony version(s) affected: 5.3.9
Description
After upgrading my application to Symfony 5.3 I tried to enable the new security system. Everything seems to work well. However, I discovered that a fresh user object is loaded by the UserProvider on every request (although it is still beeing loaded from the session).
I'm using REMOTE_USER authentication and a custom User and UserProvider implementation.
How to reproduce
I created a small dummy project to reproduce the problem: https://github.com/stlrnz/test-new-symfony-security
As you can see, there is nothing special in my implementation/configuration:
<?php
declare(strict_types=1);
namespace App\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
private $username;
public function __construct(string $username)
{
$this->username = $username;
}
public function getRoles()
{
return ['ROLE_USER'];
}
public function getPassword()
{
return null;
}
public function getSalt()
{
return null;
}
public function eraseCredentials()
{
}
public function getUsername()
{
return $this->username;
}
public function getUserIdentifier()
{
return $this->getUsername();
}
}
<?php
declare(strict_types=1);
namespace App\Security;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
final class UserProvider implements UserProviderInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function refreshUser(UserInterface $user)
{
$this->logger->debug('Refreshing user ' . $user->getUserIdentifier());
return $user;
}
public function supportsClass(string $class)
{
return $class === User::class;
}
public function loadUserByUsername(string $username)
{
$this->logger->debug('Loading user ' . $username);
return new User($username);
}
public function loadUserByIdentifier(string $username)
{
return $this->loadUserByUsername($username);
}
}
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
custom_remote_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
remote_user:
provider: custom_remote_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
Additional context
As you can see the UserProvider writes two log messages for debugging.
When using the new security system the user is loaded on the first request:
Loading user stlrnz
and refreshed and loaded on the following:
Refreshing user stlrnz
Loading user stlrnz
When using the old system by configuring
security:
enable_authenticator_manager: false
the user is still loaded on the first request
Loading user stlrnz
and refreshed on the following (no loading as expected).
Refreshing user stlrnz
Im my real application, loading a user is a very complex operation (requires some webservice calls etc.). And therfore it should not be done on every request. Is there a way to achive this?
I tried to understand why this happens in the new system. It seems that the Authenticator always triggers the load of the user through the passport to create an Authenticated Token.
symfony/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php
Line 105 in 9f43121
return new PreAuthenticatedToken($passport->getUser(), null, $firewallName, $passport->getUser()->getRoles()); |