Reputation: 7371
We implement AdvancedUserInterface
to manage user authentication, but for some reason Symfony security is serializing the entire User
entity instead of just the minimum required fields (e.g. id
, username
, password
).
According to the documentation, we need to specify the exact fields that should be serialized (and then the rest of the fields will be ignored).
class User implements AdvancedUserInterface, \Serializable {
/**
* @see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password
));
}
}
But although we are doing that, Symfony security is ignoring this serialize()
method and still serializing the ENTIRE entity. This breaks the sign in because the object becomes too big to serialize and store.
Upvotes: 3
Views: 4867
Reputation: 2530
Building on the answer from Tomas Tibensky, I solved the issue by adding magic serialization methods to my User object like so:
# App\Entity\User
/**
* Returns the User for serialization.
*
* @return array Returns the User as an array of data for serialization.
*/
public function __serialize(): array
{
return [
'id' => $this->id,
'userIdentifier' => $this->username,
];
}
/**
* Unserializes the User.
*
* @param array $serialized The serialized User (as an array).
*/
public function __unserialize(array $serialized): void
{
foreach ($serialized as $key => $value) {
$this->$key = $value;
}
}
Note that adding the __serialize()
and __unserialize()
magic methods is now preferable over implementing \Serializable
along with serialize()
and unserialize()
. See more in the PHP docs here.
Upvotes: 0
Reputation: 1796
The docs at https://symfony.com/doc/7.0/session.html#mariadb-mysql state that we can use MEDIUMBLOB
instead of the default BLOB
for the column sess_data
.
A BLOB column type (which is the one used by default by createTable()) stores up to 64 kb. If the user session data exceeds this, an exception may be thrown or their session will be silently reset. Consider using a MEDIUMBLOB if you need more space.
You can do that by writing a migration (for migrations see https://symfony.com/doc/current/doctrine.html).
Upvotes: 0
Reputation: 821
Here is an explanation of how it works in Symfony 5.4 and later https://symfony.com/doc/5.4/security.html#understanding-how-users-are-refreshed-from-the-session. However, they mention SerializableInterface
, without any reference and had a hard time finding anything about it. Such an interface doesn't exist (PhpStorm is not able to find it). I assumed they probably mean https://www.php.net/manual/en/class.serializable.php.
I solved this problem by implementing \Serializable
interface in my User
entity as follows:
# App\Entity\User
public function serialize()
{
return serialize([
'id' => $this->getId(),
'password' => $this->getPassword(),
'email' => $this->getEmail(),
'userIdentifier' => $this->getEmail(),
'username' => $this->getUsername(),
'salt' => $this->getSalt(),
'roles' => $this->getRoles(),
'enabled' => $this->isEnabled(),
]);
}
public function unserialize($data)
{
$unserialized = unserialize($data);
$this
->setId($unserialized['id'])
->setPassword($unserialized['password'])
->setEmail($unserialized['email'])
->setRoles($unserialized['roles'])
->setEnabled($unserialized['enabled']);
}
I no longer save unwanted relations or data. The session is now small. I also noticed a significant response time decrease = faster page load for logged-in users, especially admins.
Upvotes: 3
Reputation: 111
I realize this is an old question, but I solved a problem similar to this using the Symfony serializer with groups. You can read more about this here.
The function that encodes the groups looks like:
public static function encodeCategory($objects)
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$encoders = [new JsonEncoder()];
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
if(method_exists($object,'getName'))
return $object->getName();
},
];
$normalizers = [new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext)];
$serializer = new Serializer($normalizers,$encoders);
return $serializer->serialize($objects,'json',['groups' => 'category']);
}
A simple entities example is this:
class User
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"category"})
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
* @Groups({"category"})
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Type", inversedBy="users")
* @ORM\JoinColumn(nullable=false)
* @Groups({"category"})
*/
private $type;
}
And the Type entity:
class Type
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"category"})
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
* @Groups({"category"})
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="App\Entity\User", mappedBy="type")
*/
private $users;
}
You can extend this for as many entities as you like, or create new groups. And now use the function:
$users = $this->getDoctrine()->getRepository(User::class)->findAll();
return new JsonResponse($this->encodeCategory($users));
Before using groups, I tried the MaxDepth annotation, but it seems this has some problems.
Upvotes: 0
Reputation: 7371
Evidently Symfony security uses Symfony\Component\Security\Core\Authentication\Token\AbstractToken
and this has a custom method for serialize that adds more data into the session serialized User.
public function serialize()
{
return serialize(
array(
is_object($this->user) ? clone $this->user : $this->user,
$this->authenticated,
$this->roles,
$this->attributes,
)
);
}
This adds role
to the serialized object. But we have a custom role system that has associations with other entities (e.g. Site
) which cause the fatal bloating of the User
when it is serialized.
Upvotes: 1