问题:我遇到了这个问题,如果用户向我的用户表单提交了一封已经使用的电子邮件,他们会收到一条错误消息(就像他们应该看到的那样(。在他们收到错误后,如果页面被刷新或导航到网站上的另一个页面,用户就会注销,我认为用户在收到带有错误的表单后已经注销了,但页面仍然像用户登录一样呈现
用户实体没有用户名字段,而是使用电子邮件作为视觉标识符。
摘要:用户表单uniqueEntity验证失败后,用户在下一个请求中注销,
问题:有什么方法可以防止这种行为吗?
更新:当您尝试更新电子邮件时,无论它是否有效,表单似乎都在调用用户实体的setter。存储在会话中的用户发生了变异,然后在下一个请求之后,它与数据库中的用户不匹配,需要再次进行身份验证。这是一个只有当当前用户的对象被放入表单时才会出现的错误。问题保持不变。
在这里,我将包括复制这个问题所需的所有代码。
用户类别:
/**
* @ORMEntity(repositoryClass="AppRepositoryUserRepository")
*
* @UniqueEntity(
* fields={"email"},
* message="general.unique_email"
* )
*/
class User implements UserInterface
{
/**
* @ORMId()
* @ORMGeneratedValue()
* @ORMColumn(type="integer")
*/
private $id;
/**
* @ORMColumn(type="string", length=180, unique=true)
* @AssertNotBlank(message="general.not_blank")
* @AssertEmail
*/
private $email;
/**
* @ORMColumn(type="json")
*/
private $roles = [];
/**
* @var string The hashed password
* @ORMColumn(type="string")
*/
private $password;
/**
* @ORMColumn(type="date")
* @AssertNotBlank(message="general.not_blank")
* @AssertLessThan(
* value="-16 years",
* message="general.min_age"
* )
*/
private $birthDate;
/**
* @ORMColumn(type="string", length=255)
* @AssertNotBlank(message="general.not_blank")
* @AssertLength(
* min="10",
* minMessage="general.characters_min",
* max="15",
* maxMessage="general.characters_max",
* )
*/
private $phone;
/**
* @ORMColumn(type="string", length=255)
* @AssertNotBlank(message="general.not_blank")
* @AssertLength(
* min="2",
* minMessage="general.characters_min",
* max="30",
* maxMessage="general.characters_max",
* )
*/
private $firstName;
/**
* @ORMColumn(type="string", length=255, nullable=true)
* @AssertLength(
* min="2",
* minMessage="general.characters_min",
* max="20",
* maxMessage="general.characters_max",
* )
*/
private $prepositions;
/**
* @ORMColumn(type="string", length=255)
* @AssertNotBlank(message="general.not_blank")
* @AssertLength(
* min="2",
* minMessage="general.characters_min",
* max="30",
* maxMessage="general.characters_max",
* )
*/
private $lastName;
/**
* @ORMColumn(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:
AppEntityUser:
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: AppEntityUser
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:
- AppSecurityLoginFormAuthenticator
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: /
由于安全性和将实体绑定到表单的继承设计缺陷,没有真正的解决方案可以让我保持相同的设计,所以我必须弄清楚如何处理这个问题。我认为解决方案是制作一个自定义表单来处理电子邮件,这样它就不会影响当前实体。