8

在我的 Symfony 2 应用程序中,我有 3 个不同的用户角色可以访问后端管理部分:

role_hierarchy:
    ROLE_STAFF:     ROLE_USER
    ROLE_MODERATOR: ROLE_STAFF
    ROLE_ADMIN:     ROLE_MODERATOR

对于像这样的路由http://example.org/admin/post/,我希望我的应用根据用户角色显示不同的信息,这意味着3 个控制器绑定到唯一的路由

处理此问题的最佳方法是什么?

我正在考虑一些解决方案,但似乎没有一个对我有好处:

  1. 一个控制器,在每个操作中我只测试用户角色:

    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostController extends Controller
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_STAFF")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            if ($this->get('security.context')->isGranted('ROLE_STAFF')) {
                // Do ROLE_STAFF related stuff
            } else if ($this->get('security.context')->isGranted('ROLE_MODERATOR')) {
                // Do ROLE_MODERATOR related stuff
            } else if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
                // Do ROLE_ADMIN related stuff
            }
    
            return array('posts' => $posts);
        }
    }
    

    即使这样做了,IMO 显然这不是一个好的设计。

  2. 一个 BackendController 分派给 3 个不同的控制器:

    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostBackendController extends Controller
    {
        /**
         * Lists all post entities.
         *
         * @Route("", name="admin_post_index")
         * @Template("AcmeBlogBundle:PostAdmin:index.html.twig")
         * @Secure(roles="ROLE_STAFF")
         */
        public function indexAction()
        {
            if ($this->get('security.context')->isGranted('ROLE_STAFF')) {
                $response = $this->forward('AcmeBlogBundle:PostStaff:index');
            } else if ($this->get('security.context')->isGranted('ROLE_MODERATOR')) {
                $response = $this->forward('AcmeBlogBundle:PostModerator:index');
            } else if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
                $response = $this->forward('AcmeBlogBundle:PostAdmin:index');
            }
    
            return $response;
        }
    }
    

    和一号一样。

  3. 我试图让控制器相互扩展:

    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostStaffController extends Controller
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_STAFF")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            // Do ROLE_STAFF related stuff
    
            return array('posts' => $posts);
        }
    }
    
    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostModeratorController extends PostStaffController
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_MODERATOR")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            // As PostModeratorController extends PostStaffController,
            // I can either use parent action or redefine it here
    
            return array('posts' => $posts);
        }
    }
    
    <?php
    
    /**
     * @Route("/admin/post")
     */
    class PostAdminController extends PostModeratorController
    {
        /**
         * Lists all post entities.
         *
         * @Route("/", name="post_index")
         * @Template()
         * @Secure(roles="ROLE_ADMIN")
         */
        public function indexAction()
        {
            $user = $this->get('security.context')->getToken()->getUser();
    
            // Same applies here
    
            return array('posts' => $posts);
        }
    }
    

    IMO 这是一个更好的设计,但我无法让它工作。路由系统在它匹配的第一个控制器上停止。我想让它自动成为级联风格的王者(即,如果用户是员工,则转到 PostStaffController,否则如果用户是版主,则转到 PostModeratorController,否则转到 PostAdminController)。

  4. 在我的 BlogBu​​ndle 中为 kernel.controller 添加一个监听器,它的工作与数字 2 相同吗?

我正在寻找最好的设计和更灵活的解决方案,我们有机会在未来增加更多的角色。

4

4 回答 4

1

恕我直言,您不应根据角色为同一路线触发不同的控制器。只是职责不同而已。路由用于选择控制器,角色用于特权。一年后,您将不记得诀窍,即。当您尝试添加新角色时。

当然,不同角色不同内容的问题是很常见的,所以在这种情况下我最喜欢的解决方案是:

  1. 当不同角色的控制器有很大不同时,我会在需要时使用不同的路由和重定向。
  2. 当控制器相似但内容不同时,即。不同的数据库查询条件,我使用与您类似的解决方案 2. 但不是转发,而是使用来自同一控制器的私有/受保护方法来完成作业。有一个技巧-您必须从上到下检查角色,即。首先检查 ROLE_ADMIN,下一个 ROLE_OPERATOR 和最后一个 ROLE_STAFF,因为当您的 ROLE_ADMIN 从 ROLE_STAFF 继承时,然后阻止用户捕获它。
  3. 当差异仅在于应该为不同角色显示/隐藏的某些信息块时,我会使用一个控制器并检查模板中的角色以确定哪个块呈现与否。
于 2017-08-09T08:12:04.083 回答
0

第二个解决方案的自动化版本怎么样?喜欢:

    // Roles ordered from most to least significant (ROLE_ADMIN -> ROLE_MODERATOR -> etc)
    $roles = $myUserProvider->getRoles();
    foreach ($roles as $role) {
        // add a check to test, if the function you're calling really exists
        $roleName = ucfirst(strtolower(mb_substr($role, 0, 5)));
        $response = $this->forward(sprintf('AcmeBlogBundle:Post%s:index', $roleName))

        break;
    }

    // Check that $response is not null and do something with it ...

因为我没有你的设置,所以我没有测试上面的代码。顺便说一句:发布内容的不同方法有什么区别?

于 2012-04-17T16:19:46.347 回答
0

http://symfony.com/doc/current/book/internals.html#kernel-controller-event

应该做的伎俩,并确保注入 security.context 服务

于 2012-04-18T03:04:28.737 回答
0

vendor/symfony/symfony/src/Symfony/Component/Routing/Router.php

有一个选项可以matcher_class替换config.yml.

如果您UrlMatcher继承和 overRidematchRequest将优先于路径匹配(仅限 url)。

matchRequest接受一个参数 $request (请求对象)

Request 对象应包含提供安全提供程序侦听器在路由器侦听器之前运行的用户信息,并允许您通过组合 URL 和用户角色来选择路由。路由存储在一个按名称索引的数组中,因此名称需要不同。

你可以使用像这样的名字post_index[USER] post_index[STAFF] post_index[MODERATOR]

为了生成 URL,{{ path('post_index', {...}) }}您还需要替换子类URLGenerator并将其注入到带有generator_class选项的路由器中。

于 2015-09-02T12:10:22.653 回答