15

我希望能够验证是否将属性(角色)授予UserInterface在 Symfony2 中实现的任意对象。这可能吗?

UserInterface->getRoles()不适合我的需求,因为它没有考虑角色层次结构,我宁愿不在那个部门重新发明轮子,这就是为什么我想尽可能使用 Access Decision Manager。

谢谢。

针对以下 Olivier 的解决方案,这是我的经验:

您可以将 security.context 服务与 isGranted 方法一起使用。您可以传递第二个参数,即您的对象。

$user = new Core\Model\User();
var_dump($user->getRoles(), $this->get('security.context')->isGranted('ROLE_ADMIN', $user));

输出:

array (size=1)
  0 => string 'ROLE_USER' (length=9)

boolean true

我的角色层次结构:

role_hierarchy:
    ROLE_USER:          ~
    ROLE_VERIFIED_USER: [ROLE_USER]
    ROLE_ADMIN:         [ROLE_VERIFIED_USER]
    ROLE_SUPERADMIN:    [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
    ROLE_ALLOWED_TO_SWITCH: ~

我的UserInterface->getRoles()方法:

public function getRoles()
{
    $roles = [$this->isVerified() ? 'ROLE_VERIFIED_USER' : 'ROLE_USER'];

    /**
     * @var UserSecurityRole $userSecurityRole
     */
    foreach ($this->getUserSecurityRoles() as $userSecurityRole) {
        $roles[] = $userSecurityRole->getRole();
    }

    return $roles;
}

ROLE_ADMIN必须显式分配,即使用户刚刚创建并且没有被分配除默认角色之外的任何角色,只要当前登录的用户被授予,也会isGranted('ROLE_ADMIN', $user)返回。这使我相信第二个参数 to只是被忽略了,而是使用提供的 to 。TRUEROLE_USERROLE_ADMINisGranted()TokenAccessDecisionManager->decide()SecurityContext

如果这是一个错误,我将提交报告,但也许我仍然做错了什么?

4

8 回答 8

18

您只需要AccessDecisionManager这个,不需要安全上下文,因为您不需要身份验证。

$user = new Core\Model\User();

$token = new UsernamePasswordToken($user, 'none', 'none', $user->getRoles());
$isGranted = $this->get('security.access.decision_manager')
    ->decide($token, array('ROLE_ADMIN'));

这将正确考虑角色层次结构,因为RoleHierarchyVoter默认情况下已注册

更新

正如@redalaanait 所指出的,security.access.decision_manager是一项私有服务,因此直接访问它并不是一件好事。最好使用服务别名,它允许您访问私有服务。

于 2014-03-13T13:53:33.173 回答
3

也许您可以实例化一个新的 securityContext 实例并使用它来检查用户是否被授予:

$securityContext = new \Symfony\Component\Security\Core\SecurityContext($this->get('security.authentication.manager'), $this->get('security.access.decision_manager'));
$token           = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, null, $this->container->getParameter('fos_user.firewall_name'), $user->getRoles());
$securityContext->setToken($token);
if ($securityContext->isGranted('ROLE_ADMIN')) {
    // some stuff to do
}
于 2013-06-11T15:02:07.683 回答
3

security.context自 2.6 起已弃用。

使用AuthorizationChecker

$token = new UsernamePasswordToken(
     $user,
     null,
     'secured_area',
     $user->getRoles()
);
$tokenStorage = $this->container->get('security.token_storage');
$tokenStorage->setToken($token);
$authorizationChecker = new AuthorizationChecker(
     $tokenStorage,
     $this->container->get('security.authentication.manager'),
     $this->container->get('security.access.decision_manager')
);
if (!$authorizationChecker->isGranted('ROLE_ADMIN')) {
    throw new AccessDeniedException();
}
于 2016-03-10T02:13:47.927 回答
1

RoleVoter忽略从 传递的 $object SecurityContext->isGranted()。这导致从提供的 $object (如果存在)而不是RoleHierarchyVoter从提供的角色中提取角色,所以我必须找到不同的路线。TokenUserInterface

也许有更好的方法来解决这个问题,如果有我肯定想知道,但这是我想出的解决方案:

首先,我ContainerAwareInterface在我的 User 类中实现,这样我就可以从其中访问安全组件:

final class User implements AdvancedUserInterface, ContainerAwareInterface
{
    // ...

    /**
     * @var ContainerInterface
     */
    private $container;

    // ...

    public function setContainer(ContainerInterface $container = null)
    {
        if (null === $container) {
            throw new \Exception('First argument to User->setContainer() must be an instance of ContainerInterface');
        }

        $this->container = $container;
    }

    // ...
}

然后我定义了一个hasRole()方法:

/**
 * @param string|\Symfony\Component\Security\Core\Role\RoleInterface $roleToCheck
 * @return bool
 * @throws \InvalidArgumentException
 */
public function hasRole($roleToCheck)
{
    if (!is_string($roleToCheck)) {
        if (!($roleToCheck instanceof \Symfony\Component\Security\Core\Role\RoleInterface)) {
            throw new \InvalidArgumentException('First argument expects a string or instance of RoleInterface');
        }
        $roleToCheck = $roleToCheck->getRole();
    }

    /**
     * @var \Symfony\Component\Security\Core\SecurityContext $thisSecurityContext
     */
    $thisSecurityContext = $this->container->get('security.context');
    $clientUser = $thisSecurityContext->getToken()->getUser();

    // determine if we're checking a role on the currently authenticated client user
    if ($this->equals($clientUser)) {
        // we are, so use the AccessDecisionManager and voter system instead
        return $thisSecurityContext->isGranted($roleToCheck);
    }

    /**
     * @var \Symfony\Component\Security\Core\Role\RoleHierarchy $thisRoleHierarchy
     */
    $thisRoleHierarchy = $this->container->get('security.role_hierarchy');
    $grantedRoles = $thisRoleHierarchy->getReachableRoles($this->getRoles());

    foreach ($grantedRoles as $grantedRole) {
        if ($roleToCheck === $grantedRole->getRole()) {
            return TRUE;
        }
    }

    return FALSE;
}

从控制器:

$user = new User();
$user->setContainer($this->container);

var_dump($user->hasRole('ROLE_ADMIN'));
var_dump($this->get('security.context')->isGranted('ROLE_ADMIN'));
var_dump($this->get('security.context')->isGranted('ROLE_ADMIN', $user));

$user->addUserSecurityRole('ROLE_ADMIN');
var_dump($user->hasRole('ROLE_ADMIN'));

输出:

boolean false
boolean true
boolean true

boolean true

虽然它不涉及AccessDecisionManager选民或注册选民(除非正在测试的实例是当前经过身份验证的用户),但这足以满足我的需求,因为我只需要确定给定用户是否具有特定角色。

于 2012-07-03T06:19:41.533 回答
1

我知道这篇文章已经很老了,但我最近遇到了这个问题,我根据@dr.scre 的答案创建了一个服务。

以下是我在 Symfony 5 中的做法。

<?php

declare(strict_types=1);

namespace App\Service;

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\User\UserInterface;

final class AccessDecisionMaker
{
    private AccessDecisionManagerInterface $accessDecisionManager;

    public function __construct(AccessDecisionManagerInterface $accessDecisionManager)
    {
        $this->accessDecisionManager = $accessDecisionManager;
    }

    public function isGranted(UserInterface $user, string $role): bool
    {
        $token = new UsernamePasswordToken($user, 'none', 'none', $user->getRoles());

        return $this->accessDecisionManager->decide($token, [$role]);
    }
}

现在我可以在任何我想使用的地方使用它。

<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\User;
use Symfony\Component\Security\Core\Security;

class myClass
{
    private Security $security;
    private AccessDecisionMaker $decisionMaker;

    public function __construct(Security $security, AccessDecisionMaker $decisionMaker)
    {
        $this->security      = $security;
        $this->decisionMaker = $decisionMaker;
    }

    public function someMethod(?User $user): void
    {
        $user = $user ?: $this->security->getUser();

        if ($this->decisionMaker->isGranted($user, 'ROLE_SOME_ROLE')) {
            // do something
        } else {
            // do something else
        }
    }
}
于 2020-12-19T17:47:28.940 回答
0

这看起来像一个问题:

abstract class AbstractToken implements TokenInterface

看构造函数。看起来角色是在实例化时创建的,而不是在运行时查询的。

public function __construct(array $roles = array())
{
    $this->authenticated = false;
    $this->attributes = array();

    $this->roles = array();
    foreach ($roles as $role) {
        if (is_string($role)) {
            $role = new Role($role);
        } elseif (!$role instanceof RoleInterface) {
            throw new \InvalidArgumentException(sprintf('$roles must be an array of strings, or RoleInterface instances, but got %s.', gettype($role)));
        }

        $this->roles[] = $role;
    }
}

因此,创建令牌后角色无法更改。我认为选择是写你自己的选民。我还在四处张望。

于 2013-01-03T02:38:49.233 回答
0

创建服务AccessDecisionMaker (使用 Shady 的解决方案)

<?php
namespace Bp\CommonBundle\Service;

use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\SecurityContext;

class AccessDecisionMaker
{
    /** @var Container */
    private $container;

    /** @var  SecurityContext */
    private $securityContext;

    function __construct($container)
    {
        $this->container = $container;

        if (!$this->securityContext) {
            // Ensure security context is created only once
            $this->securityContext = new SecurityContext($this->container->get(
                'security.authentication.manager'
            ), $this->container->get('security.access.decision_manager'));
        }
    }

    public function isGranted($roleToCheck, UserInterface $user)
    {
        if (!is_string($roleToCheck)) {
            if (!($roleToCheck instanceof RoleInterface)) {
                throw new \InvalidArgumentException('First argument expects a string or instance of RoleInterface');
            }
            $roleToCheck = $roleToCheck->getRole();
        }

        $token = new UsernamePasswordToken($user, null, $this->container->getParameter(
            'fos_user.firewall_name'
        ), $user->getRoles());
        $this->securityContext->setToken($token);
        if ($this->securityContext->isGranted($roleToCheck)) {
            return true;
        }

        return false;
    }

}

将此配置为服务

bp.access_decision_maker:
    class: Bp\CommonBundle\Service\AccessDecisionMaker
    arguments:  [@service_container ]

用它

$this->container->get('bp.access_decision_maker')->isGranted("ROLE_ADMIN",$user);
于 2013-11-30T20:18:28.027 回答
-3

您可以通过security.context该方法使用该服务isGranted

您可以传递第二个参数,即您的对象(参见此处)。

在控制器中:

$this->get('security.context')->isGranted('ROLE_FOOBAR', $myUser)
于 2012-07-02T07:01:04.623 回答