27

在我的项目中,我需要将角色层次结构存储在数据库中并动态创建新角色。在 Symfony2 中,角色层次结构security.yml默认存储在其中。我发现了什么:

有一个服务security.role_hierarchySymfony\Component\Security\Core\Role\RoleHierarchy);此服务在构造函数中接收角色数组:

public function __construct(array $hierarchy)
{
    $this->hierarchy = $hierarchy;

    $this->buildRoleMap();
}

并且$hierarchy财产是私有的。

据我了解,此参数来自\Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension::createRoleHierarchy() 使用配置中的角色的构造函数:

$container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']);

在我看来,最好的方法是从数据库中编译一组角色并将其设置为服务的参数。但我还没有明白怎么做。

我看到的第二种方法是定义我自己的RoleHierarchy继承自基类的类。但是由于在基RoleHierarchy类中该$hierarchy属性被定义为私有,所以我必须重新定义基RoleHierarchy类中的所有函数。但我认为这不是一个好的 OOP 和 Symfony 方式......

4

6 回答 6

40

解决方案很简单。首先,我创建了一个角色实体。

class Role
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $name
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="Role")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
     **/
    private $parent;

    ...
}

之后创建了一个 RoleHierarchy 服务,从 Symfony 原生服务扩展而来。我继承了构造函数,在那里添加了一个 EntityManager 并提供了一个带有新角色数组而不是旧角色数组的原始构造函数:

class RoleHierarchy extends Symfony\Component\Security\Core\Role\RoleHierarchy
{
    private $em;

    /**
     * @param array $hierarchy
     */
    public function __construct(array $hierarchy, EntityManager $em)
    {
        $this->em = $em;
        parent::__construct($this->buildRolesTree());
    }

    /**
     * Here we build an array with roles. It looks like a two-levelled tree - just 
     * like original Symfony roles are stored in security.yml
     * @return array
     */
    private function buildRolesTree()
    {
        $hierarchy = array();
        $roles = $this->em->createQuery('select r from UserBundle:Role r')->execute();
        foreach ($roles as $role) {
            /** @var $role Role */
            if ($role->getParent()) {
                if (!isset($hierarchy[$role->getParent()->getName()])) {
                    $hierarchy[$role->getParent()->getName()] = array();
                }
                $hierarchy[$role->getParent()->getName()][] = $role->getName();
            } else {
                if (!isset($hierarchy[$role->getName()])) {
                    $hierarchy[$role->getName()] = array();
                }
            }
        }
        return $hierarchy;
    }
}

...并将其重新定义为服务:

<services>
    <service id="security.role_hierarchy" class="Acme\UserBundle\Security\Role\RoleHierarchy" public="false">
        <argument>%security.role_hierarchy.roles%</argument>
        <argument type="service" id="doctrine.orm.default_entity_manager"/>
    </service>
</services>

就这样。也许,我的代码中有一些不必要的东西。也许可以写得更好。但我认为,这个主要思想现在很明显。

于 2012-08-11T18:19:30.270 回答
14

我已经做了类似 zIs 的相同操作(将 RoleHierarchy 存储在数据库中),但我无法像 zIs 那样在 Constructor 中加载完整的角色层次结构,因为我必须在kernel.request事件中加载自定义原则过滤器。构造函数将在之前被调用,kernel.request所以这对我来说是没有选择的。

因此,我检查了安全组件,发现Symfony调用了一个自定义VoterroleHierarchy根据用户角色进行检查:

namespace Symfony\Component\Security\Core\Authorization\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;

/**
 * RoleHierarchyVoter uses a RoleHierarchy to determine the roles granted to
 * the user before voting.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class RoleHierarchyVoter extends RoleVoter
{
    private $roleHierarchy;

    public function __construct(RoleHierarchyInterface $roleHierarchy, $prefix = 'ROLE_')
    {
        $this->roleHierarchy = $roleHierarchy;

        parent::__construct($prefix);
    }

    /**
     * {@inheritdoc}
     */
    protected function extractRoles(TokenInterface $token)
    {
        return $this->roleHierarchy->getReachableRoles($token->getRoles());
    }
}

getReachableRoles 方法返回用户可以成为的所有角色。例如:

           ROLE_ADMIN
         /             \
     ROLE_SUPERVISIOR  ROLE_BLA
        |               |
     ROLE_BRANCH       ROLE_BLA2
       |
     ROLE_EMP

or in Yaml:
ROLE_ADMIN:       [ ROLE_SUPERVISIOR, ROLE_BLA ]
ROLE_SUPERVISIOR: [ ROLE_BRANCH ]
ROLE_BLA:         [ ROLE_BLA2 ]

如果用户分配了 ROLE_SUPERVISOR 角色,则该方法返回角色 ROLE_SUPERVISOR、ROLE_BRANCH 和 ROLE_EMP(实现 RoleInterface 的角色对象或类)

此外,如果没有在security.yaml

private function createRoleHierarchy($config, ContainerBuilder $container)
    {
        if (!isset($config['role_hierarchy'])) {
            $container->removeDefinition('security.access.role_hierarchy_voter');

            return;
        }

        $container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']);
        $container->removeDefinition('security.access.simple_role_voter');
    }

为了解决我的问题,我创建了自己的自定义 Voter 并扩展了 RoleVoter-Class:

use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Acme\Foundation\UserBundle\Entity\Group;
use Doctrine\ORM\EntityManager;

class RoleHierarchyVoter extends RoleVoter {

    private $em;

    public function __construct(EntityManager $em, $prefix = 'ROLE_') {

        $this->em = $em;

        parent::__construct($prefix);
    }

    /**
     * {@inheritdoc}
     */
    protected function extractRoles(TokenInterface $token) {

        $group = $token->getUser()->getGroup();

        return $this->getReachableRoles($group);
    }

    public function getReachableRoles(Group $group, &$groups = array()) {

        $groups[] = $group;

        $children = $this->em->getRepository('AcmeFoundationUserBundle:Group')->createQueryBuilder('g')
                        ->where('g.parent = :group')
                        ->setParameter('group', $group->getId())
                        ->getQuery()
                        ->getResult();

        foreach($children as $child) {
            $this->getReachableRoles($child, $groups);
        }

        return $groups;
    }
}

注意事项:我的设置类似于 zls 的设置。我对角色的定义(在我的例子中,我称之为组):

Acme\Foundation\UserBundle\Entity\Group:
    type: entity
    table: sec_groups
    id: 
        id:
            type: integer
            generator: { strategy: AUTO }
    fields:
        name:
            type: string
            length: 50
        role:
            type: string
            length: 20
    manyToOne:
        parent:
            targetEntity: Group

和用户定义:

Acme\Foundation\UserBundle\Entity\User:
    type: entity
    table: sec_users
    repositoryClass: Acme\Foundation\UserBundle\Entity\UserRepository
    id:
        id:
            type: integer
            generator: { strategy: AUTO }
    fields:
        username:
            type: string
            length: 30
        salt:
            type: string
            length: 32
        password:
            type: string
            length: 100
        isActive:
            type: boolean
            column: is_active
    manyToOne:
        group:
            targetEntity: Group
            joinColumn:
                name: group_id
                referencedColumnName: id
                nullable: false

也许这对某人有帮助。

于 2013-02-07T14:01:57.910 回答
3

我开发了一个捆绑包。

你可以在https://github.com/Spomky-Labs/RoleHierarchyBundle找到它

于 2013-11-16T08:25:39.403 回答
2

我的解决方案受到 zls 提供的解决方案的启发。他的解决方案对我来说非常有效,但是角色之间的一对多关系意味着拥有一个巨大的角色树,这将变得难以维护。此外,如果两个不同的角色想要继承一个相同的角色(因为只能有一个父级),则可能会出现问题。这就是为什么我决定创建一个多对多的解决方案。我首先将其放在角色类中,而不是只有父级:

/**
 * @ORM\ManyToMany(targetEntity="Role")
 * @ORM\JoinTable(name="role_permission",
 *      joinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")},
 *      inverseJoinColumns={@ORM\JoinColumn(name="permission_id", referencedColumnName="id")}
 *      )
 */
protected $children;

之后,我像这样重写了 buildRolesTree 函数:

private function buildRolesTree()
{
    $hierarchy = array();
    $roles = $this->em->createQuery('select r, p from AltGrBaseBundle:Role r JOIN r.children p')->execute();

    foreach ($roles as $role)
    {
        /* @var $role Role */
        if (count($role->getChildren()) > 0)
        {
            $roleChildren = array();

            foreach ($role->getChildren() as $child)
            {
                /* @var $child Role */
                $roleChildren[] = $child->getRole();
            }

            $hierarchy[$role->getRole()] = $roleChildren;
        }
    }

    return $hierarchy;
}

结果是能够创建几个易于维护的树。例如,您可以拥有定义 ROLE_SUPERADMIN 角色的角色树和定义 ROLE_ADMIN 角色的完全独立的树,并在它们之间共享多个角色。虽然应该避免循环连接(角色应该布置成树,它们之间没有任何循环连接),但如果它真的发生应该没有问题。我没有对此进行测试,但是通过 buildRoleMap 代码,很明显它会转储任何重复项。这也应该意味着如果发生循环连接,它不会陷入无限循环,但这肯定需要更多测试。

我希望这对某人有所帮助。

于 2013-09-26T13:24:45.697 回答
0

由于角色层次结构不经常更改,因此这是一个快速缓存到 memcached 的类。

<?php

namespace .....;

use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Lsw\MemcacheBundle\Cache\MemcacheInterface;

/**
 * RoleHierarchy defines a role hierarchy.
 */
class RoleHierarchy implements RoleHierarchyInterface
{
    /**
     *
     * @var MemcacheInterface 
     */
    private $memcache;

    /**
     *
     * @var array 
     */
    private $hierarchy;

    /**
     *
     * @var array 
     */
    protected $map;

    /**
     * Constructor.
     *
     * @param array $hierarchy An array defining the hierarchy
     */
    public function __construct(array $hierarchy, MemcacheInterface $memcache)
    {
        $this->hierarchy = $hierarchy;

        $roleMap = $memcache->get('roleMap');

        if ($roleMap) {
            $this->map = unserialize($roleMap);
        } else {
            $this->buildRoleMap();
            // cache to memcache
            $memcache->set('roleMap', serialize($this->map));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getReachableRoles(array $roles)
    {
        $reachableRoles = $roles;
        foreach ($roles as $role) {
            if (!isset($this->map[$role->getRole()])) {
                continue;
            }

            foreach ($this->map[$role->getRole()] as $r) {
                $reachableRoles[] = new Role($r);
            }
        }

        return $reachableRoles;
    }

    protected function buildRoleMap()
    {
        $this->map = array();
        foreach ($this->hierarchy as $main => $roles) {
            $this->map[$main] = $roles;
            $visited = array();
            $additionalRoles = $roles;
            while ($role = array_shift($additionalRoles)) {
                if (!isset($this->hierarchy[$role])) {
                    continue;
                }

                $visited[] = $role;
                $this->map[$main] = array_unique(array_merge($this->map[$main], $this->hierarchy[$role]));
                $additionalRoles = array_merge($additionalRoles, array_diff($this->hierarchy[$role], $visited));
            }
        }
    }
}
于 2015-01-15T23:36:47.563 回答
-3

我希望这能帮到您。

function getRoles()
{

  //  return array(1=>'ROLE_ADMIN',2=>'ROLE_USER'); 
   return array(new UserRole($this));
}

您可以从哪里得到一个好主意, 在哪里定义安全角色?

http://php-and-symfony.matthiasnoback.nl/(2012 年 7 月 28 日)

于 2012-07-28T05:46:46.450 回答