Symfony 6中如何获取指定角色对应的可用权限属性列表?
获取Symfony 6中指定角色的所有授权属性列表
针对你维护数十个Voter、难以梳理角色对应权限的问题,可以通过编写Symfony控制台命令自动扫描所有Voter并生成指定角色的授权属性列表,下面是具体实现方案:
方案一:通用控制台命令(基于反射)
这个命令会自动识别所有Voter,模拟角色权限判断,输出该角色能访问的所有属性。
1. 创建命令类
在src/Command目录下创建RolePermissionsCommand.php:
<?php namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\DependencyInjection\ContainerInterface; #[AsCommand( name: 'app:role:permissions', description: '列出指定角色拥有的所有授权属性', )] class RolePermissionsCommand extends Command { public function __construct( private ContainerInterface $container ) { parent::__construct(); } protected function configure(): void { $this ->addArgument('role', InputArgument::REQUIRED, '要查询的角色(例如:ROLE_ADMIN)') ; } protected function execute(InputInterface $input, OutputInterface $output): int { $targetRole = $input->getArgument('role'); $output->writeln(sprintf('正在查询角色 <info>%s</info> 的授权属性...', $targetRole)); // 获取所有注册的Voter服务 $voterServices = $this->container->findTaggedServiceIds('security.voter'); $authorizedAttributes = []; foreach ($voterServices as $serviceId => $tags) { /** @var VoterInterface $voter */ $voter = $this->container->get($serviceId); // 通过反射提取Voter支持的属性列表 $reflection = new \ReflectionClass($voter); $supportsMethod = $reflection->getMethod('supportsAttribute'); $methodBody = $supportsMethod->getBody(); if (preg_match('/in_array\(\s*\$attribute,\s*\[([^\]]+)\]\s*\)/', $methodBody, $matches)) { $attrStr = $matches[1]; $attributes = array_map(fn($item) => trim($item, "'\" "), explode(',', $attrStr)); foreach ($attributes as $attribute) { // 创建带有目标角色的虚拟Token $token = new AnonymousToken('dummy', 'dummy-user', [$targetRole]); try { // 模拟权限判断 $voteResult = $voter->vote($token, null, [$attribute]); if ($voteResult === VoterInterface::ACCESS_GRANTED) { $authorizedAttributes[] = $attribute; } } catch (\Exception $e) { $output->writeln(sprintf('<comment>跳过属性 %s:需要实体对象作为参数</comment>', $attribute)); } } } } if (empty($authorizedAttributes)) { $output->writeln('<error>该角色没有任何授权属性</error>'); return Command::FAILURE; } // 排序并输出结果 sort($authorizedAttributes); $output->writeln('<info>该角色拥有的授权属性:</info>'); foreach ($authorizedAttributes as $attr) { $output->writeln("- $attr"); } return Command::SUCCESS; } }
2. 使用命令
在终端执行以下命令(替换为你要查询的角色):
php bin/console app:role:permissions ROLE_ADMIN
命令输出示例:
正在查询角色 ROLE_ADMIN 的授权属性... 跳过属性 EA_TASK_EDIT:需要实体对象作为参数 跳过属性 EA_TASK_DELETE:需要实体对象作为参数 该角色拥有的授权属性: - EA_TASK_INDEX - EA_TASK_NEW - EA_USER_INDEX
方案二:优化版(自定义接口,避免反射)
如果希望更稳定、避免依赖反射,可以给所有Voter添加自定义接口,统一暴露支持的属性列表:
1. 创建自定义接口
在src/Security/Voter目录下创建SupportsAttributesListInterface.php:
<?php namespace App\Security\Voter; interface SupportsAttributesListInterface { /** * 返回当前Voter支持的所有属性 */ public function getSupportedAttributes(): array; }
2. 修改Voter实现接口
以你的TaskVoter为例,修改如下:
<?php namespace App\Security\Voter\Admin\Task; use App\Enum\Role; use App\Security\Voter\Admin\AdminCachedVoter; use App\Security\Voter\SupportsAttributesListInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class TaskVoter extends AdminCachedVoter implements SupportsAttributesListInterface { public function getSupportedAttributes(): array { return [ 'EA_TASK_INDEX', 'EA_TASK_NEW', 'EA_TASK_EDIT', 'EA_TASK_DELETE' ]; } public function supportsAttribute(string $attribute): bool { return in_array($attribute, $this->getSupportedAttributes()); } protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { // 原有逻辑保持不变 $user = $token->getUser(); if (!$user) { return false; } $roles = match ($attribute) { 'EA_TASK_INDEX', 'EA_TASK_NEW', 'EA_TASK_EDIT' => [ Role::ROLE_ADMIN, Role::ROLE_EDITOR ], 'EA_TASK_DELETE' => [ Role::ROLE_ADMIN ] }; $rolesValues = array_map(static fn(Role $role) => $role->value, $roles); return !empty(array_intersect($rolesValues, $user->getRoles())); } }
3. 修改命令适配接口
更新RolePermissionsCommand.php中的属性提取逻辑,优先使用接口方法:
// 替换原来的反射部分 $attributes = []; if ($voter instanceof \App\Security\Voter\SupportsAttributesListInterface) { $attributes = $voter->getSupportedAttributes(); } else { // 兼容未实现接口的旧Voter,使用反射 $reflection = new \ReflectionClass($voter); $supportsMethod = $reflection->getMethod('supportsAttribute'); $methodBody = $supportsMethod->getBody(); if (preg_match('/in_array\(\s*\$attribute,\s*\[([^\]]+)\]\s*\)/', $methodBody, $matches)) { $attrStr = $matches[1]; $attributes = array_map(fn($item) => trim($item, "'\" "), explode(',', $attrStr)); } }
注意事项
- 对于需要实体对象(如
EDIT/DELETE操作)的属性,命令会自动跳过,因为无法生成合法的实体实例。如果需要验证这类权限,可以扩展命令,允许传入实体ID或类名创建测试对象。 - 所有Voter都会被自动扫描,无需单独配置。
内容的提问来源于stack exchange,提问作者Bakhtiyor
相关产品推荐
相关产品推荐

