Предполагается, что вы используете поставщика безопасности по умолчанию для избирателя и пользователя.
Это должно применяться к Symfony 3.4+, но знание, какую версию Symfony вы используете, предоставит другие подходы.
В конце каждого запроса (если ваш брандмауэр не имеет состояния), ваш
Пользовательский объект сериализуется в сеанс. В начале следующего
запрос, он десериализуется и затем передается вашему провайдеру
"обновить" его (например, запросы Doctrine для нового пользователя).
Затем два объекта пользователя (оригинал из сеанса и
обновленный объект пользователя) сравниваются, чтобы увидеть, равны ли они. От
по умолчанию базовый класс AbstractToken сравнивает возвращаемые значения
методы getPassword (), getSalt () и getUsername (). Если какой-либо из
они разные, ваш пользователь выйдет из системы. Это безопасность
меры, чтобы убедиться, что злоумышленники могут быть де-аутентифицированы, если
изменения основных данных пользователя.
Однако в некоторых случаях этот процесс может вызвать неожиданное
проблемы с аутентификацией. Если у вас есть проблемы с аутентификацией, это
может быть, что вы успешно проходите проверку подлинности, но вы сразу
потерять аутентификацию после первого перенаправления.
Источник: https://symfony.com/doc/current/security/user_provider.html#understanding-how-users-are-refreshed-from-the-session
Кажется, проблема вызвана
$user->setPassword($passwordEncoder->encodePassword($user,$user->getPassword()));
, который сгенерирует новый хешированный пароль из предоставленного пароля, аннулируя пользовательское состояние, даже если оно идентично.
Вам нужно будет сохранить простой текстовый пароль пользователя и подтвердить его, если он изменился, и применить изменения пароля только в случае его изменения.
Кроме того, настройка image
формы недопустима, поскольку для User::$image
требуется строка, но форма будет загружать объект File
(вызывая недопустимое состояние объекта или вызывая File::__toString
и изменяя изображение). Вам следует использовать отдельное свойство для загрузки изображения и вручную нарисовать текущее изображение в своем представлении или рассмотреть возможность использования преобразователя данных в вашей форме, а не в контроллере для обработки изменения состояния. Смотри: https://symfony.com/doc/current/form/data_transformers.html
Замените текущие поля формы password
и image
полями plainPassword
и uploadImage
.
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('plainPassword',RepeatedType::class,[
'type' => PasswordType::class,
'invalid_message' => 'The password fields must match.',
'required' => false,
'options' => ['attr' => ['class' => 'password-field']],
'first_options' => ['label' => 'Password','attr'=>['placeholder'=>"Password"]],
'second_options' => ['label' => 'Confirm Password','attr'=>['placeholder'=>"Confirm Password"]],
])
->add('uploadImage',FileType::class,['label'=>'Photo','required'=>false,'attr'=>['hidden'=>"hidden", 'accept'=>"image/jpeg, image/png"]]);
//...
}
Вам также следует серьезно рассмотреть возможность использования DTO
вместо прямой сущности пользователя из Doctrine для управления вашими данными, чтобы предотвратить недопустимое состояние сущности.
Затем создайте свойства и методы получения / установки в вашей пользовательской сущности, чтобы сохранить значения формы.
class User implements UserInterface
{
/**
* @var string
*/
private $plainPassword = '';
/**
* @var File|null
*/
private $uploadImage;
public function getPlainPassword(): string
{
return $this->plainPassword;
}
public function setPlainPassword(string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
$this->plainPassword = null;
}
public function getUploadImage(): ?File
{
return $this->uploadImage;
}
public function setUploadImage(?File $file): void
{
$this->uploadImage = $file;
}
//...
}
Поскольку вы используете Entity Manager и поле RegisterType
, вы можете удалить запросы на обновление вручную. Поскольку $form->handleRequest()
будет применять изменения непосредственно к объекту User. Я также предлагаю использовать Paramconverter, чтобы воспользоваться преимуществами внедрения зависимостей для объекта User.
/**
* @Route("/{user}/update", name="update", requirements={ "user":"\d+" }, methods={"GET","POST"})
* @param User $user
* @param Request $request
* @param UserPasswordEncoderInterface $passwordEncoder
* @param UserInterface $loggedUser
* @return Response
*/
public function updateUser(User $user, Request $request, UserPasswordEncoderInterface $passwordEncoder, UrlGeneratorInterface $urlGenerator): Response
{
$loggedinUser = $this->getUser(); //helper from ControllerTrait
if ($loggedinUser && loggedinUser->getId() === $user->getId()) {
$form = $this->createForm(RegisterType::class,$user, [
'validation_groups' => ['update'],
]);
$currentImage = $user->getImage();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($file = $user->getUploadImage()) {
//this logic should be moved to the Form using a data transformer
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move(
$this->getParameter('uploads_dir'), $fileName
);
$user->setImage($fileName);
}
if ('' !== $user->getPlainPassword() && !$passwordEncoder->isPasswordValid($user->getPassword(), $user->getPlainPassword())) {
//change password only when changed
$user->setPassword($passwordEncoder->encodePassword($user, $user->getPlainPassword()));
$user->eraseCredentials();
}
$em = $this->getDoctrine()->getManager();
$em->flush();
return new RedirectResponse($urlGenerator->generate('home'));
}
return $this->render('register/update.html.twig', [
'form'=>$form->createView(),
]);
}
return new RedirectResponse($urlGenerator->generate('home'));
}
Если вы используете Symfony <4.1, вам необходимо реализовать <code>\Serializable и добавить методы serialize
и unserialize
в свой класс User, в противном случае весь ваш объект User будет сериализован и станет недействительным при любом изменении.
class User implements UserInterface, \Serializable
{
//...
/** @see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
//$this->roles //(optional)
));
}
/** @see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
//$this->roles //(optional)
) = unserialize($serialized, array('allowed_classes' => false));
}
}