Сессия истекает при перенаправлении на страницу - PullRequest
0 голосов
/ 21 июня 2019

В моем приложении после входа в систему пользователь переходит на домашнюю страницу, где он может просматривать свои данные.Есть кнопка «Редактировать профиль», где пользователь попадет на страницу, где он сможет редактировать данные.После успешного редактирования он перенаправляется обратно на домашнюю страницу.Но здесь он перенаправлен на страницу входа.Я думаю, что сессия неожиданно истекла.Как решить эту проблему?

// Это мой контроллер информации обновления

/**
 * @Route("/update/{id}", name="update")
 * @param $id
 * @param Request $request
 * @param UserPasswordEncoderInterface $passwordEncoder
 * @param UserInterface $loggedUser
 * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
 */

public function updateUser($id,Request $request, UserPasswordEncoderInterface $passwordEncoder, UrlGeneratorInterface $urlGenerator){

    $loggedUser = $this->get('security.token_storage')->getToken()->getUser()->getId();

    if ($id == $loggedUser){
        $em = $this->getDoctrine()->getManager();
        $conn =$em->getConnection();
        $user = $em->find(User::class,$id);

        $form = $this->createForm(RegisterType::class,$user, [
            'validation_groups' => ['update'],
        ]);


        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()) {

            $file = $request->files->get('register')['image'];
            if($file){
                $fileName = md5(uniqid()).'.'.$file->guessExtension();
                $file->move(
                    $this->getParameter('uploads_dir'), $fileName
                );
                $user->setImage($fileName);
            }

            if($user->getPassword() !="") {
                $user->setPassword($passwordEncoder->encodePassword($user,$user->getPassword()));

                $sql = '
                    UPDATE user
                    SET first_name = :firstName, last_name = :lastName, id_number = :idNumber, phone_number = :phoneNumber, address = :address, password = :password
                    WHERE id= :id
    ';
                $stmt = $conn->prepare($sql);
                $stmt->execute(['firstName' => $user->getFirstName(),
                    'lastName' => $user->getLastName(),
                    'idNumber' => $user->getIdNumber(),
                    'phoneNumber' => $user->getPhoneNumber(),
                    'address' => $user->getAddress(),
                    'password' => $user->getPassword(),
                    'id' => $id]);
            } else {
                $sql = '
                    UPDATE user
                    SET first_name = :firstName, last_name = :lastName, id_number = :idNumber, phone_number = :phoneNumber, address = :address
                    WHERE id= :id
    ';
                $stmt = $conn->prepare($sql);
                $stmt->execute(['firstName' => $user->getFirstName(),
                    'lastName' => $user->getLastName(),
                    'idNumber' => $user->getIdNumber(),
                    'phoneNumber' => $user->getPhoneNumber(),
                    'address' => $user->getAddress(),
                    'id' => $id]);
            }



            return new RedirectResponse($urlGenerator->generate('home'));
        }
    } else {
        return new RedirectResponse($urlGenerator->generate('home'));
    }

    return $this->render('register/update.html.twig', [
        'form'=>$form->createView(),
    ]);
}

// Это форма RegisterType

class RegisterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email',EmailType::class,[
                'label'=>'Email',
                'required' => false,
                'attr'=>['placeholder'=>"Email"]
            ])
            ->add('password',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('firstName',TextType::class,['label'=>'First Name',  'attr'=>['placeholder'=>"First Name"]])
            ->add('lastName',TextType::class,['label'=>'Last Name','attr'=>['placeholder'=>"Last Name"]])
            ->add('address',TextareaType::class,['required' => false,'label'=>'Address','attr'=>['placeholder'=>"Address"]])
            ->add('idNumber',TextType::class,['label'=>'NIC Number','attr'=>['placeholder'=>"NIC Number"]])
            ->add('phoneNumber',TelType::class,['label'=>'Phone Number','attr'=>['placeholder'=>"Phone Number"]])
            ->add('image',FileType::class,['label'=>'Photo','required'=>false,'attr'=>['hidden'=>"hidden", 'accept'=>"image/jpeg, image/png"]])
            ->add('save',SubmitType::class,[
                'label'=>'Register',
                'attr' => [
                    'class'=>"btn btn-outline-success float-right"
                ]
            ])

        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}

// Это мой пользовательКласс

class User implements UserInterface{

/**
 * @ORM\Id()
 * @ORM\GeneratedValue()
 * @ORM\Column(type="integer")
 */
private $id;

/**
     * @ORM\Column(type="string", length=180, unique=true)
 * @Assert\Email()
 * @Assert\NotBlank()
 */
private $email;

/**
 * @ORM\Column(type="json")
 */
private $roles = [];

/**
 * @var string The hashed password
 * @ORM\Column(type="string")
 * @Assert\NotBlank()
 */
private $password;

/**
 * @ORM\Column(type="string",length=255)
 * @Assert\NotBlank(groups={"update"})
 *
 */
private $firstName;

/**
 * @ORM\Column(type="string",length=255)
 * @Assert\NotBlank(groups={"update"})
 */
private $lastName;

/**
 * @ORM\Column(type="string",length=255,nullable=true)
 *
 */
private $image;

/**
 * @ORM\Column(type="string", nullable=true)
 */
private $address;

/**
 * @ORM\Column(type="string",length=10)
 * @Assert\Length("10",groups={"update"})
 */
private $phoneNumber;

/**
 * @ORM\Column(type="string",length=10)
 * @Assert\NotBlank(groups={"update"})
 * @Assert\Length("10",groups={"update"})
 */
private $idNumber;

/**
 * @ORM\OneToMany(targetEntity="App\Entity\Vehicle", mappedBy="user")
 */
private $vehicle;

/**
 * @ORM\OneToOne(targetEntity="App\Entity\Account", inversedBy="user")
 */
private $account;


public function __construct()
{
    $this->vehicle = new ArrayCollection();
}

public function getId(): ?int
{
    return $this->id;
}

public function getEmail()
{
    return $this->email;
}

public function setEmail( $email): self
{
    $this->email = $email;

    return $this;
}

/**
 * A visual identifier that represents this user.
 *
 * @see UserInterface
 */
public function getUsername()
{
    return (string) $this->email;
}

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

public function setRoles(array $roles): self
{
    $this->roles = $roles;

    return $this;
}

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

public function setPassword($password): self
{
    $this->password = $password;

    return $this;
}

/**
 * @see UserInterface
 */
public function getSalt()
{
    // not needed when using the "bcrypt" algorithm in security.yaml
}

/**
 * @see UserInterface
 */
public function eraseCredentials()
{
    // If you store any temporary, sensitive data on the user, clear it here
    // $this->plainPassword = null;
}

public function getFirstName()
{
    return $this->firstName;
}

public function setFirstName( $firstName): self
{
    $this->firstName = $firstName;

    return $this;
}

public function getLastName()
{
    return $this->lastName;
}

public function setLastName( $lastName): self
{
    $this->lastName = $lastName;

    return $this;
}

public function getImage(): ?string
{
    return $this->image;
}

public function setImage(string $image): self
{
    $this->image = $image;

    return $this;
}

public function getAddress()
{
    return $this->address;
}

public function setAddress( $address): self
{
    $this->address = $address;

    return $this;
}

public function getIdNumber()
{
    return $this->idNumber;
}

public function setIdNumber( $idNumber): self
{
    $this->idNumber = $idNumber;

    return $this;
}

public function getPhoneNumber()
{
    return $this->phoneNumber;
}

public function setPhoneNumber( $phoneNumber): self
{
    $this->phoneNumber = $phoneNumber;

    return $this;
}

/**
 * @return Collection|Vehicle[]
 */
public function getVehicle(): Collection
{
    return $this->vehicle;
}

public function addVehicle(Vehicle $vehicle): self
{
    if (!$this->vehicle->contains($vehicle)) {
        $this->vehicle[] = $vehicle;
        $vehicle->setUser($this);
    }

    return $this;
}

public function removeVehicle(Vehicle $vehicle): self
{
    if ($this->vehicle->contains($vehicle)) {
        $this->vehicle->removeElement($vehicle);
        // set the owning side to null (unless already changed)
        if ($vehicle->getUser() === $this) {
            $vehicle->setUser(null);
        }
    }

    return $this;
}

public function getAccount(): ?Account
{
    return $this->account;
}

public function setAccount(?Account $account): self
{
    $this->account = $account;

    return $this;
}

}

Ответы [ 2 ]

1 голос
/ 23 июня 2019

Предполагается, что вы используете поставщика безопасности по умолчанию для избирателя и пользователя.

Это должно применяться к 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));
    }
}
0 голосов
/ 23 июня 2019

Используйте временное поле для хранения незашифрованного пароля в форме предварительного кодирования / предварительного хеширования (см .: https://symfony.com/doc/4.0/doctrine/registration_form.html#registration-password-max - поле называется plainPassword или аналогичным).

Я подозреваю, что при установке пустого пароля происходит непредвиденное поведение, которое может сделать недействительным кэш сеанса (symfony хранит некоторые пользовательские данные, чтобы определить, нужно ли перезагрузить пользователя из базы данных и изменились ли соответствующие данные, пользовательможет выйти из системы).Одно только перенаправление не должно определенно выходить из системы.

Надеюсь, этого будет достаточно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...