我希望能够验证属性(角色)是否被授予在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}}被用来代替。
如果这是一个错误,我会提交报告,但也许我还在做错事?
答案 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以来已弃用。
$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)