繁体   English   中英

重定向到页面时,会话过期

[英]Session expires when redirect to a page

在我的应用程序中,一旦用户登录,便会转到主页,在其中可以查看其详细信息。 有一个“编辑配置文件”按钮,在该按钮中,用户将被带到可以编辑数据的页面。 一旦编辑成功,他将被重定向回主页。 但是在这里,它被重定向到登录页面。 我认为该会话意外过期。 如何克服这个问题?

//这是我的更新信息控制器

/**
 * @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;
}

}

假设您正在使用默认的投票者和实体安全用户提供程序。

这应该适用于Symfony 3.4+,但是知道您使用的是哪个版本的Symfony,将可以使用其他方法。

在每个请求结束时(除非您的防火墙是无状态的),您的User对象都被序列化到会话。 在下一个请求的开始,它会反序列化,然后传递给您的用户提供程序以“刷新”它(例如,对于新用户的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对象(导致无效的Entity状态或调用File::__toString并更改图片)。 您应该为图像上传使用单独的属性,并在视图中手动绘制当前图像,或者考虑在Form中而不是在控制器中使用数据转换器来处理状态更改。 参见: https : //symfony.com/doc/current/form/data_transformers.html

将您当前的passwordimage表单字段替换为plainPassworduploadImage字段。

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的直接User实体来管理数据,以防止出现无效的实体状态。

然后在用户实体中创建属性和getter / setter方法,以存储表单值。

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;
      }

      //...
}

由于您使用的是实体管理器和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,则需要实现\\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));
    }
}

使用临时字段以预编码/预散列的形式保存纯文本密码(请参阅: https ://symfony.com/doc/4.0/doctrine/registration_form.html#registration-password-max-该字段称为plainPassword或类似)。

我怀疑设置空密码时会出现一些意外行为,这可能会使会话缓存无效(symfony存储一些用户数据来确定是否必须从数据库中重新加载用户,并且如果相关数据已更改,则用户可能会注销)。 仅重定向绝对不应注销用户。

希望这足够了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM