如何在Symfony2中使用AccessDecisionManager来授权任意用户?

时间:2012-07-02 05:43:53

标签: php symfony access-control

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

UserInterface->getRoles()不适合我的需要,因为它没有考虑角色层次结构,我宁愿不重新发明该部门的轮子,这就是为什么我想使用访问决策经理,如果可能的话。

感谢。

回应下面的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)以外的任何角色,TRUE也会返回ROLE_USER,< em>只要当前登录的用户被授予ROLE_ADMIN 。这使我相信isGranted()的第二个参数被忽略,并且Token提供给AccessDecisionManager->decide()的{​​{1}}被用来代替。

如果这是一个错误,我会提交报告,但也许我还在做错事?

8 个答案:

答案 0 :(得分:16)

您只需要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

<强>更新

如@redalaana所述, security.access.decision_manager 是一项私人服务,因此直接访问它并不是一件好事。 最好使用service aliasing,它允许您访问私人服务。

答案 1 :(得分: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
}

答案 2 :(得分:2)

  

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();
}

答案 3 :(得分:1)

RoleVoter忽略从SecurityContext->isGranted()传来的$对象。这导致RoleHierarchyVoter提取角色来自Token而不是提供的UserInterface $对象(如果存在),因此我必须找到不同的路径。

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

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

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或注册选民(除非被测试的实例是当前经过身份验证的用户),但它足以满足我的需求,因为我只需要确定给定用户是否有特别的角色。

答案 4 :(得分: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;
    }
}

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

答案 5 :(得分: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);

答案 6 :(得分:0)

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

这是我在 Symfony 5 中所做的。

#exel database
import xlrd, xlwt
from xlutils.copy import copy
import datetime

class DataBase:
    def __init__(self, filename):
        self.filename = filename
        self.loadinfo = None
        self.file = None
        self.load()

    def load(self):
        self.file = open(self.filename, "r")
        self.loadinfo = {}

        for line in self.file:
            invoice, purpose, carrier, cost, local, outcal, Driver, Dispatcher = line.strip().split(";")
            self.loadinfo[invoice] = (invoice, purpose, carrier, cost, local, outcal, Driver, Dispatcher)

        self.file.close()

    def top_right(self, invoice, purpose):
        pass

    def add_loadinfo(self, invoice, purpose, carrier, cost, local, outcal, Driver, Dispatcher):
        self.loadinfo[invoice.strip()] = (invoice.strip(), purpose.strip(), carrier.strip(), cost.strip(), local.strip(), outcal.strip(), Driver.strip(), Dispatcher.strip(), DataBase.get_date())
        self.save()

    
    def save(self):
        with open(self.filename, "w") as f:
            for loadinfo in self.loadinfo:
                f.write(loadinfo + ";" + self.loadinfo[invoice][0] + ";" + self.loadinfo[invoice][1] + ";" + self.loadinfo[invoice][2] + "\n")

    @staticmethod
    def get_date():
        return str(datetime.datetime.now()).split(" ")[0]
        

#code to put it into excel

# read_book = xlrd.open_workbook("Invoice.xls", formatting_info=True) #Make Readable Copy
# write_book = copy(read_book) #Make Writeable Copy

# write_sheet1 = write_book.get_sheet(1) #Get sheet 1 in writeable copy
# write_sheet1.write(1, 11, self.invoice.text) #Write 'test' to cell (1, 11)

# write_sheet2 = write_book.get_sheet(2) #Get sheet 2 in writeable copy
# write_sheet2.write(3, 14, '135') #Write '135' to cell (3, 14)

# write_book.save("New/File/Path") #Save the newly written copy. Enter the same as the old path to write over

现在我可以随时随地使用它。

<?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]);
    }
}

答案 7 :(得分:-3)

您可以使用security.context方法使用isGranted服务。

您可以传递第二个参数,该参数是您的对象(请参阅here)。

在控制器中:

$this->get('security.context')->isGranted('ROLE_FOOBAR', $myUser)