首页 > 解决方案 > Symfony 4 投票者注释(@IsGranted)

问题描述

我正在尝试使用 Symfony Voters 和 Controller Annotation 来允许或限制对 Symfony 4 应用程序中某些操作的访问。

例如,我的前端提供删除“帖子”的功能,但前提是用户为该帖子设置了“DELETE_POST”属性。

前端向我的 symfony 端点发送一个 HTTP“DELETE”操作,在 URL 中传递帖子的 id(即/api/post/delete/19)。

我正在尝试使用@IsGranted注释,如此所述。

这是我的 symfony 端点:

/**
 * @Route("/delete/{id}")
 * @Method("DELETE")
 * @IsGranted("DELETE_POST", subject="post")
 */
public function deletePost($post)  {
    ... some logic to delete post
    return new Response("Deleting " . $post->getId());
}

这是我的选民:

class PostVoter extends Voter {

    private $attributes = array(
        "VIEW_POST", "EDIT_POST", "DELETE_POST", "CREATE_POST"
    );

    protected function supports($attribute, $subject) { 
        return in_array($attribute, $this->attributes, true)  &&  $subject instanceof Post;
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {
        ... logic to figure out if user has permissions.

        return $check;

    }
}

我遇到的问题是我的前端只是将资源 ID 发送到我的端点。@IsGrantedSymfony 然后通过调用 Voters 并传入属性"DELETE_POST"和帖子 ID来解析注释。

问题是,$post 只是一个帖子 ID,而不是一个实际的 Post 对象。因此,当 Voter 到达时,$subject instanceof Post它会返回false

我尝试Post通过将方法签名更改为public function deletePost(Post $post). 当然这不起作用,因为 javascript 在 URL 中发送一个 id,而不是 Post 对象。

(顺便说一句:我知道这种类型的注射应该适用于 Doctrine,但我没有使用 Doctrine)。

我的问题是我如何@IsGranted理解“帖子”应该是一个帖子对象?有没有办法告诉它从传入的 id 中查找 Post 并基于它进行评估?或者甚至遵从另一个控制器方法来确定subject="post"应该代表什么?

谢谢。

更新

感谢@NicolasB,我添加了一个 ParamConverter:

class PostConverter implements ParamConverterInterface {
    private $dao;

    public function __construct(MySqlPostDAO $dao) {
        $this->dao = $dao;
    }

    public function apply(Request $request, ParamConverter $configuration)  {
        $name = $configuration->getName();            
        $object = $this->dao->getById($request->get("id"));

        if (!$object)  {
            throw new NotFoundHttpException("Post not found!");
        }

        $request->attributes->set($name, $object);

        return true;
    }

    public function supports(ParamConverter $configuration) {
        if ($configuration->getClass() === "App\\Model\\Objects\\Post")  {
            return true;
        }

        return false;
    }
}

这似乎按预期工作。我什至不必使用 @ParamConverter 注释来使其工作。我必须对控制器进行的唯一其他更改是更改我的路由的方法签名public function deletePost(Post $post)(正如我之前尝试过的那样 - 但现在由于我的PostConverter.

我最后的两个问题是:

  1. 我应该在supports()方法中检查什么?我目前只是检查班级是否匹配。我是否也应该检查一下$configuration->getName() == "id",以确保我使用的是正确的字段?

  2. 我怎样才能使它更通用?我是否正确假设任何时候你在控制器方法中注入一个实体,Symfony 会supports在所有实现的东西上调用该方法ParamConverterInterface

谢谢。

标签: phpsymfonyannotationsauthorization

解决方案


如果您使用 Doctrine 会发生什么,您需要对$post变量进行类型提示。完成之后,Doctrine 的ParamConverter会处理剩下的事情。目前,Symfony 不知道如何将您的idurl 占位符与您的$post参数相关联,因为它不知道 Entity$post指的是哪个。通过使用类似的类型提示public function deletePost(Post $post)并使用 ParamConverter,Symfony 会知道它$post指的是具有来自 url占位符Post的 id 的实体。id

从文档:

通常,您希望 show() 有一个 $id 参数。相反,通过创建一个新参数 ($post) 并使用 Post 类(它是一个 Doctrine 实体)对其进行类型提示,ParamConverter 会自动查询 $id 属性与 {id} 值匹配的对象。如果找不到帖子,它也会显示 404 页面。

选民也将知道$post它是什么以及如何处理它。


现在,由于您没有使用 Doctrine,因此默认情况下您没有 ParamConverter,正如我们刚刚看到的,这是这里的关键元素。所以你要做的只是定义你自己的 ParamConverter。

Symfony 文档的这一页将告诉你更多关于如何做到这一点,尤其是最后一节“创建转换器”。您必须告诉它如何使用模型的逻辑将字符串"id"转换为对象。Post首先,您可以使其非常特定于Post对象(并且您可能希望使用该converter="name"选项在注释中显式引用该 ParamConverter)。稍后,一旦你有了一个工作版本,你可以让它更通用。


推荐阅读