You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 20:17:02