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

时间:2014-02-26 13:33:12

标签: php validation symfony passwords formbuilder

我试图在表单中使用特定的验证器。

该表单供用户重新定义密码,他还必须输入当前密码。 为此,我使用symfony

中的内置验证器

以我的形式:

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?')就像我输入了错误的当前密码一样。

知道我做错了吗?

2 个答案:

答案 0 :(得分:3)

我知道这个答案会迟到几年,但是当我遇到同样的问题时,我想提出我的解决方案:

问题在于,我在用于FormMapping的$user实体与User中出现的security.context之间存在连接。

请参阅以下内容:(PasswordChange - Controller)

    $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 Constraint Validator:

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 one和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,

这样,表单中输入的新密码将不会自动映射到给定的$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对象之间的联系,我很高兴听到它!

答案 1 :(得分:0)

我遇到了同样的问题,经过大量的研究和实践测试,这是我使用的解决方案:

  1. 保持用户实体不变(不变)
  2. 定义一个新实体(ChangePassword),该实体具有2个字段,作为'Form'文件夹中'Model'目录中的MODEL,如symfony docs中所述:[https://symfony.com/doc/current/reference/constraints/UserPassword.html][1]
    • oldPassword字段:添加一个约束:@SecurityAssert \ UserPassword(message =“ old pass错误.....”)
    • 字段:newPassword
      (注意:如果您没有将此实体定义为模型,那么您将无法在以后的形式中映射它,除非您创建该实体时会抛出“教义”,这不是我们的目的)
  3. 为这些字段定义格式“ ChangePasswordType”(newPasword可以为RepeatedType以进行密码确认)。必须保留为“ true”的映射,以自动验证oldPassword,抛出上面定义的SecurityAssert,并稍后在控制器中捕获这些字段

  4. 在控制器中(changeCurrentUserPasswordAction,...或其他任何操作),声明一个新的ChangePassword实体并将其与要创建的表单关联('ChangePasswordType)

  5. 现在您可以执行并看到无法为oldPassword传递错误的密码(因为该密码应与已验证用户的实际密码相同)
  6. 最后,在控制器中,提交表单后,获取在表单中输入的新密码的值(例如,使用$ newpass = $ form-> getData()-> getPassword();)并进行编码并在刷新前将其设置为新密码$ user-> setPassword($ newpass)。

我希望这可以帮助某人...