在表单类型中使用Symfony2 UserPassword验证器

cxfofazt  于 2022-11-16  发布在  其他
关注(0)|答案(3)|浏览(176)

我尝试在表单中使用特定的验证器。
这个表单是让用户重新定义他的密码,他还必须输入他当前的密码。
在我的表单中:

use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;

表单类型如下所示:

/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('currentpassword', 'password', array('label'=>'Current password',
            'mapped' => false,
            'constraints' => new UserPassword(array('message' => 'you wot m8?')),
            'required' => true
        ))
        ->add('password', 'repeated', array(
            'first_name' => 'new',
            'second_name' => 'confirm',
            'type' => 'password',
            'required' => true
        ))
    ;
}

我知道在我的控制器中,我可以只获得数据表单,获得currentpassword值,调用security.encoder_factory等,但是那个验证器看起来很方便。
我的问题是表单总是返回一个错误(这里是:“你知道m8吗?”)就像我输入了错误的当前密码一样。
知道我做错了什么吗?

oymdgrw7

oymdgrw71#

我知道这个答案晚了几年,但当我在同一个问题上遇到困难时,我想提出我的解决方案:
问题是,在我的例子中,在用于FormMapping的$user实体和来自security.contextUser之间有一个连接。
请参见以下内容:(密码更改-控制器)

$username = $this->getUser()->getUsername();
    $user = $this->getDoctrine()->getRepository("BlueChordCmsBaseBundle:User")->findOneBy(array("username"=>$username));
    // Equal to $user = $this->getUser();

    $form = $this->createForm(new ChangePasswordType(), $user);
    //ChangePasswordType equals the one 'thesearentthedroids' posted

    $form->handleRequest($request);
    if($request->getMethod() === "POST" && $form->isValid()) {
        $manager = $this->getDoctrine()->getManager();
        $user->setPassword(password_hash($user->getPassword(), PASSWORD_BCRYPT));
        [...]
    }

    return array(...);

isValid()函数正在触发UserPassword约束验证器:

public function validate($password, Constraint $constraint)
{
    if (!$constraint instanceof UserPassword) {
        throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword');
    }

    $user = $this->tokenStorage->getToken()->getUser();

    if (!$user instanceof UserInterface) {
        throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.');
    }

    $encoder = $this->encoderFactory->getEncoder($user);

    if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
        $this->context->addViolation($constraint->message);
    }
}

感兴趣的行是:if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt()))
在我的例子中,$user->getPassword()返回了我刚刚在表单中输入的新密码作为我的新密码。**这就是为什么测试总是失败!**我不明白为什么tokenStorage中的用户和我从数据库加载的用户之间会有连接。感觉就像两个对象(MyDatabase一和tokenStorage一)共享相同的处理器地址,并且实际上是相同的...
奇怪!
我的解决方案是将ChangePasswordType中的(新)密码字段与EntityMapping分离:请参阅

->add('currentpassword', 'password', array('label'=>'Current password', 'mapped' => false, 'constraints' => new UserPassword()))
        ->add('password', 'repeated', array(
            'mapped'          => false,
            'type'            => 'password',
            'invalid_message' => 'The password fields must match.',
            'required'        => true,
            'first_options'   => array('label' => 'Password'),
            'second_options'  => array('label' => 'Repeat Password'),
            ))
        ->add('Send', 'submit')
        ->add('Reset','reset')

感兴趣的行为'mapped' => false,
这样,在表单中输入的新密码将不会自动Map到给定的$user实体。相反,您现在需要从form获取它。请参见

$form->handleRequest($request);
    if($request->getMethod() === "POST" && $form->isValid()) {
        $data = $form->getData();
        $manager = $this->getDoctrine()->getManager();
        $user->setPassword(password_hash($data->getPassword(), PASSWORD_BCRYPT));
        $manager->persist($user);
        $manager->flush();
    }

这是一个解决我不能完全理解的问题的方法。如果有人能解释一下数据库对象和security.context对象之间的联系,我很高兴听到!

iyr7buue

iyr7buue2#

我遇到了同样的问题,经过大量的研究和实际测试,这是我使用的解决方案:
1.保持用户实体不变(无更改)

(NB:如果你不把这个实体定义为模型,那么你就不能Map它,以后在表单中,除非你创建它抛出Doctrine,而这不是我们的目的)

  • 为这些字段定义'ChangePasswordType'格式(newPasword可以是RepeatedType,以进行密码确认)。必须维护Map为'true',以便自动验证oldPassword,抛出上面定义的SecurityAssert,以及稍后在控制器中捕获这些字段
  • 在控制器中(changeCurrentUserPasswordAction、...或任何Action),声明一个新的ChangePassword实体,并将其与要创建的表单相关联('ChangePasswordType)
  • 现在您可以执行并看到不能为oldPassword传递错误的密码(因为它应该等于已验证用户的实际密码)
  • 最后,在控制器中,当表单提交时,获取表单中输入的新密码的值(使用$newpass = $form-〉getData()-〉getPassword();并在刷新之前将其设置为新密码$user-〉setPassword($newpass)。

我希望这能帮助到一些人...

rsl1atfo

rsl1atfo3#

我发现自己在UserPassword上也浪费了时间,我想为那些想要“更改密码表单”的人分享一个更简单的解决方案。
其他答案已经解释了使用UserPassword时会出现什么错误,但与其添加额外的实体或在控制器中瞎折腾,不如使用一种更直接的方法来添加您自己的检查,并在无效时添加表单错误:

public function __construct(
        private UserPasswordHasherInterface $passwordHasher,
    ) {
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('currentPassword', PasswordType::class, [
                'required' => true,
                'mapped' => false,
            ])
            ->add('newPassword', RepeatedType::class, [
                'type' => PasswordType::class,
                'invalid_message' => 'The password fields must match.',
                'required' => true,
                'mapped' => false,
            ])
        ;

        $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void {
            $user = $event->getData();
            $form = $event->getForm();

            if (!$this->passwordHasher->isPasswordValid($user, $form->get('currentPassword')->getData())) {
                $form->addError(new FormError('Provided current password is invalid.'));
            } else {
                $user->setPassword($this->passwordHasher->hashPassword($user, $form->get('newPassword')->getData()));
            }
        });
    }

这应该可以让您的控制器不需要额外的检查($form->isValid()是所有需要调用的)

相关问题