在Symfony messenger异步消息处理程序上使用投票者/权限的最佳方式?

在Symfony messenger异步消息处理程序上使用投票者/权限的最佳方式?,symfony,symfony-4.4,symfony-messenger,Symfony,Symfony 4.4,Symfony Messenger,我正在开发一个安装了Symfony Messenger组件以处理异步消息的应用程序。消息处理程序需要检查某些特定用户的某些权限,例如,如果某个特定用户具有编辑权限,则该用户是否应该接收包含信息的电子邮件 为了实现这一点,我们使用Symfony投票者,但是当我们没有任何用户登录到系统时,比如控制台命令和异步消息是非常烦人的。最好的解决办法是什么?这是我的主要观点: 使用消息的安全上下文强制“登录” Pro:一种检查权限的方法,无需附加服务。选民就是服务 缺点:当我有一组用户检查时,我应该多次执

我正在开发一个安装了Symfony Messenger组件以处理异步消息的应用程序。消息处理程序需要检查某些特定用户的某些权限,例如,如果某个特定用户具有编辑权限,则该用户是否应该接收包含信息的电子邮件

为了实现这一点,我们使用Symfony投票者,但是当我们没有任何用户登录到系统时,比如控制台命令和异步消息是非常烦人的。最好的解决办法是什么?这是我的主要观点:

  • 使用消息的安全上下文强制“登录”

    • Pro:一种检查权限的方法,无需附加服务。选民就是服务
    • 缺点:当我有一组用户检查时,我应该多次执行“安全上下文登录”操作。我认为这很难
  • 设计一个域服务来处理这个问题

    • 优点:无需强制登录即可解决问题
    • 缺点:重复的代码或根据上下文(请求、控制台命令或异步队列)执行相同操作的不同方式
  • 投票者和域服务应该调用的服务

    • 缺点:我认为这会给更简单的问题增加复杂性
最好的方法是什么?除了前三点之外还有什么想法吗


非常感谢

我可能更愿意在发送消息之前检查用户的权限,但让我们想想如果情况不合适,我们该如何处理

为了检查用户权限,您需要对用户进行身份验证。但在异步使用消息或执行控制台命令的情况下,这并不简单,因为您没有实际的用户。但是,您可以将用户id与消息或控制台命令一起传递

让我分享一下我的想法,一个简单的解决方案。在Symfony Messenger中,有一个概念,允许您将元数据添加到邮件中。在我们的例子中,将用户id与消息一起传递是非常有用的,因此我们可以在消息处理过程中对用户进行身份验证

让我们创建一个自定义戳记来保存用户id。这是一个简单的PHP类,因此不需要将其注册为服务

<?php

namespace App\Messenger\Stamp;

use Symfony\Component\Messenger\Stamp\StampInterface;

class AuthenticationStamp implements StampInterface
{
    private $userId;

    public function __construct(string $userId)
    {
        $this->userId = $userId;
    }

    public function getUserId(): string
    {
        return $this->userId;
    }
}
为了对用户进行身份验证,我们需要接收和处理戳。Symfony Messenger有一个概念,所以让我们创建一个来处理工作人员发送的消息时的stamp。它将检查消息是否包含AuthenticationStamp,并在用户当前未经过身份验证时对其进行身份验证

<?php

namespace App\Messenger\Middleware;

use App\Messenger\Stamp\AuthenticationStamp;
use App\Repository\UserRepositoryInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class AuthenticationMiddleware implements MiddlewareInterface
{
    private $tokenStorage;
    private $userRepository;

    public function __construct(TokenStorageInterface $tokenStorage, UserRepositoryInterface $userRepository)
    {
        $this->tokenStorage = $tokenStorage;
        $this->userRepository = $userRepository;
    }

    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        /** @var AuthenticationStamp|null $authenticationStamp */
        if ($authenticationStamp = $envelope->last(AuthenticationStamp::class)) {
            $userId = $authenticationStamp->getUserId();

            $token = $this->tokenStorage->getToken();
            if (null === $token || $token instanceof AnonymousToken) {
                $user = $this->userRepository->find($userId);
                if ($user) {
                    $this->tokenStorage->setToken(new UsernamePasswordToken(
                        $user,
                        null,
                        'provider',
                        $user->getRoles())
                    );
                }
            }
        }

        return $stack->next()->handle($envelope, $stack);
    }
}
差不多就是这样。现在,您应该能够使用常规方法检查用户的权限,例如,投票者


至于控制台命令,我会使用身份验证服务,如果将用户id传递给命令,该服务将对用户进行身份验证。

我可能更愿意在发送消息之前检查用户的权限,但让我们想想如果情况不合适,我们该如何处理

为了检查用户权限,您需要对用户进行身份验证。但在异步使用消息或执行控制台命令的情况下,这并不简单,因为您没有实际的用户。但是,您可以将用户id与消息或控制台命令一起传递

让我分享一下我的想法,一个简单的解决方案。在Symfony Messenger中,有一个概念,允许您将元数据添加到邮件中。在我们的例子中,将用户id与消息一起传递是非常有用的,因此我们可以在消息处理过程中对用户进行身份验证

让我们创建一个自定义戳记来保存用户id。这是一个简单的PHP类,因此不需要将其注册为服务

<?php

namespace App\Messenger\Stamp;

use Symfony\Component\Messenger\Stamp\StampInterface;

class AuthenticationStamp implements StampInterface
{
    private $userId;

    public function __construct(string $userId)
    {
        $this->userId = $userId;
    }

    public function getUserId(): string
    {
        return $this->userId;
    }
}
为了对用户进行身份验证,我们需要接收和处理戳。Symfony Messenger有一个概念,所以让我们创建一个来处理工作人员发送的消息时的stamp。它将检查消息是否包含AuthenticationStamp,并在用户当前未经过身份验证时对其进行身份验证

<?php

namespace App\Messenger\Middleware;

use App\Messenger\Stamp\AuthenticationStamp;
use App\Repository\UserRepositoryInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class AuthenticationMiddleware implements MiddlewareInterface
{
    private $tokenStorage;
    private $userRepository;

    public function __construct(TokenStorageInterface $tokenStorage, UserRepositoryInterface $userRepository)
    {
        $this->tokenStorage = $tokenStorage;
        $this->userRepository = $userRepository;
    }

    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        /** @var AuthenticationStamp|null $authenticationStamp */
        if ($authenticationStamp = $envelope->last(AuthenticationStamp::class)) {
            $userId = $authenticationStamp->getUserId();

            $token = $this->tokenStorage->getToken();
            if (null === $token || $token instanceof AnonymousToken) {
                $user = $this->userRepository->find($userId);
                if ($user) {
                    $this->tokenStorage->setToken(new UsernamePasswordToken(
                        $user,
                        null,
                        'provider',
                        $user->getRoles())
                    );
                }
            }
        }

        return $stack->next()->handle($envelope, $stack);
    }
}
差不多就是这样。现在,您应该能够使用常规方法检查用户的权限,例如,投票者


至于console命令,我会选择一个身份验证服务,如果用户id被传递给命令,该服务将对用户进行身份验证。

非常感谢您提供了完整而准确的响应。到目前为止,中间件是另一种选择,对于主要情况可能是最好的。我同意你的看法,最好在将消息发送给处理程序之前检查权限。在我的具体案例中,我们使用一个对象生成一个事件,该事件必须通过电子邮件通知具有一定权限的具体用户。在这种情况下,我无法验证权限,因为没有任何上下文。我正在考虑创建令牌接口并将其直接传递给DecisionManager的可能性选民的概念旨在检查经过身份验证的用户的权限。不确定在发送电子邮件之前检查多个用户的权限是否合适。我将实施一项服务,过滤出符合电子邮件通知条件的用户。或者从存储库中获取符合条件的用户。不确定哪一个是最好的选择,因为我没有足够的应用程序上下文。非常感谢您完整准确的回答。到目前为止,中间件是另一种选择,对于主要情况可能是最好的。我同意你的看法,最好在将消息发送给处理程序之前检查权限。在我的具体案例中,我们使用一个对象生成一个事件,该事件必须通过电子邮件通知具有一定权限的具体用户。在这种情况下,我无法验证权限,因为没有任何上下文。我正在考虑创建令牌接口并将其直接传递给DecisionManager的可能性选民的概念旨在检查经过身份验证的用户的权限。不确定是否适合检查多个应用程序的权限