如何安全地使用独特性(在具有多个同时用户的站点上)



智能可以共享他们用来避免这种基本和常见并发问题的设计模式 symfony?

方案:每个用户必须具有唯一的用户名。

失败的解决方案:

  • 向用户实体添加独特性约束。
  • 遵循Symfony文档中建议的模式:使用表单组件来验证潜在的新用户。如果有效,请坚持下去。

为什么它失败了:在验证和持续存在的用户之间,用户名可以由另一个用户获取。如果是这样,学说在尝试坚持最新的用户时会引发独立的theaintaintaintViolationException。

这是我以下答案的作用:

  • 如果发生约束违规行为,它会优雅地显示给用户错误,例如验证者处理它,

  • 它可以防止非"受保护" 打破控制器逻辑的数据库更新(例如,使用Update语句或使用"未经保护" 提交表单。控制器),

  • 它是一个独立于数据库的解决方案。

这是代码,并在评论上进行了解释:

<?php
// ...
use DoctrineDBALExceptionConstraintViolationException;
use SymfonyComponentFormFormError;
use SymfonyComponentFormExtensionValidatorViolationMapperViolationMapper;
// ...
public function indexAction(Request $request)
{
    $task = new Task();
    $form = $this->createFormBuilder($task)
        ->add('name', TextType::class)
        ->add('save', SubmitType::class, array('label' => 'Create Task'))
        ->getForm();
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        $task = $form->getData();
        $em = $this->getDoctrine()->getManager();
        $em->persist($task);
        try {
            $em->flush();
            // Everything went well, do whatever you're supposed to.
            return $this->redirectToRoute('task_success');
        } catch (ConstraintViolationException $e) {
            // Reopen the entity manager so the validator can do jobs
            // that needs to be performed with the database (in example:
            // unique constraint checks)
            $em = $em->create($em->getConnection(), $em->getConfiguration());
            // Revalidate the form to see if the validator knows what
            // has thrown this constraint violation exception.
           $violations = $this->get('validator')->validate($form);
            if (empty($violations)) {
                // The validator didn't see anything wrong...
                // It can happens if you have a constraint on your table,
                // but didn't add a similar validation constraint.
                // Add an error at the root of the form.
                $form->add(new FormError('Unexpected error, please retry.'));
            } else {
                // Add errors to the form with the ViolationMapper.
                // The ViolationMapper will links error with its
                // corresponding field on the form.
                // So errors are not displayed at the root of the form,
                // just like if the form was validated natively.
                $violationMapper = new ViolationMapper();
                foreach ($violations as $violation) {
                    $violationMapper->mapViolation($violation, $form);
                }
            }
        }
    }
    return $this->render('default/new.html.twig', array(
        'form' => $form->createView(),
    ));
}

实现自己想要的一种方法是锁定Symfony LockHandler

这是一个简单的示例,使用您在问题中引用的模式:

<?php
// ...
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentFilesystemLockHandler;
use SymfonyComponentFormFormError;
public function newAction(Request $request)
{
    $task = new Task();
    $form = $this->createFormBuilder($task)
        ->add('task', TextType::class)
        ->add('dueDate', DateType::class)
        ->add('save', SubmitType::class, array('label' => 'Create Task'))
        ->getForm();
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        // locking here
        $lock = new LockHandler('task_validator.lock');
        $lock->lock();
        // since entity is validated when the form is submitted, you
        // have to call the validator manually
        $validator = $this->get('validator');
        if (empty($validator->validate($task))) {
            $task = $form->getData();
            $em = $this->getDoctrine()->getManager();
            $em->persist($task);
            $em->flush();
            // lock is released by garbage collector
            return $this->redirectToRoute('task_success');
        }
        $form->addError(new FormError('An error occured, please retry'));
        // explicit release here to avoid keeping the Lock too much time.
        $lock->release();
    }
    return $this->render('default/new.html.twig', array(
        'form' => $form->createView(),
    ));
}

nb:如果您从文档中运行应用程序,这将无法使用:

锁处理程序仅在您仅使用一台服务器时才有效。如果您有几个主机,则不得使用此助手。

您还可以覆盖EntityManager来创建一个新功能,例如validateAndFlush($entity),该功能管理LockHandler和验证过程本身。

您是否不能在数据库级别设置唯一约束。您还可以检查有关如何执行此操作的Doctrine2文档:

/**
 * @Entity
 * @Table(name="user",
 *      uniqueConstraints={@UniqueConstraint(name="username_unique", columns={"username"})},
 * )
 */
class User { 
    //...
    /**
     * @var string
     * @Column(type="string", name="username", nullable=false)
     */
    protected $username;
    //...
}

现在,您对数据库级别有唯一的限制(因此,不可能在用户表中两次插入相同的用户名)。

执行插入操作时,如果已经存在用户名( UniqueConstraintViolationException),则会得到异常。您可以捕获异常,并将有效的响应返回给客户端的有效响应,以传达该用户名已经使用(在您的数据库中)。

如果我正确理解这个问题,您为自己设定了一个很高的标准。显然,您的持久性层看未来是不可能的。因此,不可能支持一个验证器,该验证者只能仅使用您的域实体来保证插入物将成功(并且不会抛出iniquentaintVioLationException)。您需要在某个地方维护其他状态。

如果您想要一些增量的改进,则需要某种方法来在验证时保留用户名。当然,这很容易 - 您只需在某个地方创建一个列表即可跟踪"机上"用户名,并在验证过程中检查列表外检查列表。

它变得棘手的地方是设计一种理智的方法,用于修剪列表和发布用于验证的用户名,但从未在成功的注册中使用。

这是一个实现细节,您需要考虑使用用户名保留多长时间。

我头顶上的一个简单实现:使用(用户名,session_id,recard_at)在数据库中维护一个表,并在Reserved_at&lt;的情况下定期删除所有行,并定期删除所有行。:datetime。

您需要跟踪Session_ID,因为您正在为特定用户保留用户名。由于用户尚未创建帐户,因此识别它们的唯一方法是通过其会话标识符。

相关内容

  • 没有找到相关文章