【问题标题】:Symfony 5 Form mutates user in sessionSymfony 5 Form 在会话中改变用户
【发布时间】:2020-07-10 04:41:16
【问题描述】:

问题:我遇到了这个问题,如果用户向我的用户表单提交了一封已经使用过的电子邮件,他们会收到一条错误消息(就像他们应该看到的那样)。如果刷新页面或导航到用户注销的站点上的另一个页面,他们收到错误后,我认为用户在收到带有错误的表单后已经注销,但页面仍然呈现为用户已登录。

用户实体没有用户名字段并使用电子邮件作为视觉标识符。

总结:在用户表单失败后,uniqueEntity 验证用户在下一次请求时被注销,

问题:有什么办法可以防止这种行为吗?

更新:当您尝试更新电子邮件时,无论它是否有效,该表单似乎都在调用用户实体的设置器。存储在会话中的用户发生了变异,然后在下一次请求后,它与数据库中的用户不匹配,需要再次进行身份验证。这是一个只有在将当前用户的对象放入表单时才会出现的错误。问题还是一样。

在这里,我将包含重现此问题所需的所有代码。

用户类:

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 *
 * @UniqueEntity(
 *     fields={"email"},
 *     message="general.unique_email"
 * )
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

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

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

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

    /**
     * @ORM\Column(type="date")
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\LessThan(
     *     value="-16 years",
     *     message="general.min_age"
     * )
     */
    private $birthDate;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Length(
     *     min="10",
     *     minMessage="general.characters_min",
     *     max="15",
     *     maxMessage="general.characters_max",
     * )
     */
    private $phone;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Length(
     *     min="2",
     *     minMessage="general.characters_min",
     *     max="30",
     *     maxMessage="general.characters_max",
     * )
     */
    private $firstName;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Assert\Length(
     *     min="2",
     *     minMessage="general.characters_min",
     *     max="20",
     *     maxMessage="general.characters_max",
     * )
     */
    private $prepositions;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank(message="general.not_blank")
     * @Assert\Length(
     *     min="2",
     *     minMessage="general.characters_min",
     *     max="30",
     *     maxMessage="general.characters_max",
     * )
     */
    private $lastName;

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

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

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

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

        return $this;
    }

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

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

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

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(?string $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(): void
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function getBirthDate(): ?DateTimeInterface
    {
        return $this->birthDate;
    }

    public function setBirthDate(?DateTimeInterface $birthDate): self
    {
        $this->birthDate = $birthDate;

        return $this;
    }

    public function getPhone(): ?string
    {
        return $this->phone;
    }

    public function setPhone(?string $phone): self
    {
        $this->phone = $phone;

        return $this;
    }

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

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

        return $this;
    }

    public function getPrepositions(): ?string
    {
        return $this->prepositions;
    }

    public function setPrepositions(?string $prepositions): self
    {
        $this->prepositions = $prepositions;

        return $this;
    }

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

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

        return $this;
    }

    public function getGender(): ?bool
    {
        return $this->gender;
    }

    public function setGender(?bool $gender): self
    {
        $this->gender = $gender;

        return $this;
    }
}

表单类:

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class, [
                'label' => 'user.email',
            ])
            ->add('firstName', TextType::class, [
                'label' => 'user.first_name',
            ])
            ->add('prepositions', TextType::class, [
                'label' => 'user.prepositions',
                'required' => false,
            ])
            ->add('lastName', TextType::class, [
                'label' => 'user.last_name'
            ])
            ->add('phone', TextType::class, [
                'label' => 'user.phone',
            ])
            ->add('birthDate', BirthdayType::class, [
                'label' => 'user.birth_date',
                'widget' => 'single_text',
                'placeholder' => [
                    'day' => 'general.day',
                    'month' => 'general.mon th',
                    'year' => 'general.year',
                ]
            ])
            ->add('gender', ChoiceType::class, [
                'label' => 'user.gender.label',
                'choices' => [
                    'user.gender.male' => true,
                    'user.gender.female' => false,
                    'user.gender.other' => null,
                ]
            ]);
    }

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

控制器:

    /**
     * @IsGranted("ROLE_USER")
     * @Route("/account", name="account")
     */
    public function account(Request $request, UserPasswordEncoderInterface $passwordEncoder, EntityManagerInterface $em): Response
    {
        /** @var User $user */
        $user = $this->getUser();
        $userForm = $this->createForm(UserType::class, $user);
        $userForm->handleRequest($request);

        if ($userForm->isSubmitted() && $userForm->isValid()) {
            $this->addFlash('success', 'applied user data');
            $em->flush();

            return $this->redirectToRoute('app_security_account');
        }

        $passwordForm = $this->createForm(ChangePasswordType::class);
        $passwordForm->handleRequest($request);

        if ($passwordForm->isSubmitted() && $passwordForm->isValid()) {
            /** @var ChangePassword $changePassword */
            $changePassword = $passwordForm->getData();
            $this->getUser()->setPassword($passwordEncoder->encodePassword($this->getUser(), $changePassword->getNewPassword()));
            $em->flush();
            $this->addFlash('success', 'changed password');

            return $this->redirectToRoute('app_security_account');
        }

        return $this->render('page/security/account.html.twig', [
            'title' => 'My Site | Account',
                'userForm' => $userForm->createView(),
                'passwordForm' => $passwordForm->createView(),
        ]);
    }

树枝模板:

{% extends 'content_base.html.twig' %}

{% block content_container %}
    <h1>{{ 'user.header'|trans([], 'form') }}</h1>
    {{ form_start(userForm, {"novalidate": "novalidate"}) }}
        {{ form_widget(userForm) }}
        <button class="btn btn-warning btn-block" type="submit">Submit</button>
    {{ form_end(userForm) }}
    <h1>{{ 'password.header'|trans([], 'form') }}</h1>
    {{ form_start(passwordForm, {"novalidate": "novalidate"}) }}
        {{ form_widget(passwordForm) }}
        <button class="btn btn-warning btn-block" type="submit">Submit</button>
    {{ form_end(passwordForm) }}
{% endblock %}

Security.yaml:

security:
    encoders:
        App\Entity\User:
            algorithm: auto
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
        # used to reload user from session & other features (e.g. switch_user)
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: lazy
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            logout:
                path: app_security_logout
                # where to redirect after logout
                target: app_security_login
            remember_me:
                secret: '%kernel.secret%'
                lifetime: 604800 # 1 week in seconds
                path: /

【问题讨论】:

  • 我不会以与其他所有内容相同的形式更改电子邮件地址。此外,我可能不会在表单中使用实时用户对象。至少您应该在表单中的电子邮件字段中添加约束(阅读表单约束)。

标签: php forms symfony validation


【解决方案1】:

由于安全性和将实体绑定到表单的继承设计缺陷,没有真正的修复可以让我保持相同的设计,所以我必须弄清楚如何处理这个问题。我认为解决方案是制作一个自定义表单来处理电子邮件,这样它就不会影响当前实体。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多