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

如何为Symfony API平台配置基于独立权限的操作保护

嘿,这个需求我之前在Symfony+API平台的项目里实操过,完全能实现细粒度的权限控制,咱们一步步来搞定:

第一步:搭建实体关联(匹配你的表结构并优化灵活性)

我按照你的设想调整了关联关系,让用户可以拥有多个角色、角色可以关联多个权限,这样扩展性更强,创建三个核心实体:

User 实体

// src/Entity/User.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Put;

#[ApiResource(
    operations: [
        new GetCollection(
            accessControl: "is_granted('user_list_view')",
            securityPostDenormalizeMessage: "Sorry, you don't have permission to view users list"
        ),
        new Get(
            accessControl: "is_granted('user_list_view')",
            securityPostDenormalizeMessage: "Sorry, you don't have permission to view this user"
        ),
        new Put(
            accessControl: "is_granted('user_edit')",
            securityPostDenormalizeMessage: "Sorry, you don't have permission to edit users"
        )
    ]
)]
#[ORM\Entity]
class User implements UserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    private $userName;

    // 用户与角色多对多关联,自动生成中间表user_role
    #[ORM\ManyToMany(targetEntity: Role::class, inversedBy: 'users')]
    #[ORM\JoinTable(name: 'user_role')]
    private $roles;

    public function __construct()
    {
        $this->roles = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // 实现UserInterface核心方法,返回角色名称数组兼容Symfony安全组件
    public function getRoles(): array
    {
        $roleNames = $this->roles->map(function (Role $role) {
            return $role->getRoleName();
        })->toArray();
        // 确保每个用户至少拥有基础权限
        $roleNames[] = 'ROLE_USER';
        return array_unique($roleNames);
    }

    // 获取用户所有权限(整合角色关联的权限)
    public function getAllPrivileges(): array
    {
        $privileges = [];
        foreach ($this->roles as $role) {
            foreach ($role->getPrivileges() as $privilege) {
                $privileges[] = $privilege->getPrivName();
            }
        }
        return array_unique($privileges);
    }

    // 实现UserInterface必需的其他方法
    public function getUserIdentifier(): string
    {
        return $this->userName;
    }

    public function eraseCredentials(): void {}

    // 添加/移除角色的方法
    public function addRole(Role $role): self
    {
        if (!$this->roles->contains($role)) {
            $this->roles->add($role);
        }
        return $this;
    }

    public function removeRole(Role $role): self
    {
        $this->roles->removeElement($role);
        return $this;
    }

    // 其他字段的getter、setter...
}

Role 实体

// src/Entity/Role.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource(accessControl: "is_granted('ROLE_ADMIN')")] // 仅管理员能管理角色
#[ORM\Entity]
class Role
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $roleId;

    #[ORM\Column(type: 'string', length: 255, unique: true)]
    private $roleName; // 示例值:'ROLE_COMMERCIAL'、'ROLE_VIEWER'

    // 角色与权限多对多关联,自动生成中间表role_privilege
    #[ORM\ManyToMany(targetEntity: Privilege::class, inversedBy: 'roles')]
    #[ORM\JoinTable(name: 'role_privilege')]
    private $privileges;

    #[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'roles')]
    private $users;

    public function __construct()
    {
        $this->privileges = new \Doctrine\Common\Collections\ArrayCollection();
        $this->users = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // getter、setter方法
    public function getRoleName(): string
    {
        return $this->roleName;
    }

    public function setRoleName(string $roleName): self
    {
        $this->roleName = $roleName;
        return $this;
    }

    // 添加/移除权限的方法
    public function addPrivilege(Privilege $privilege): self
    {
        if (!$this->privileges->contains($privilege)) {
            $this->privileges->add($privilege);
        }
        return $this;
    }

    public function removePrivilege(Privilege $privilege): self
    {
        $this->privileges->removeElement($privilege);
        return $this;
    }

    // 其他字段的getter、setter...
}

Privilege 实体

// src/Entity/Privilege.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource(accessControl: "is_granted('ROLE_ADMIN')")] // 仅管理员能管理权限
#[ORM\Entity]
class Privilege
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(type: 'string', length: 255, unique: true)]
    private $privName; // 示例值:'user_list_view'、'user_edit'、'user_create'

    #[ORM\ManyToMany(targetEntity: Role::class, mappedBy: 'privileges')]
    private $roles;

    public function __construct()
    {
        $this->roles = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // getter、setter方法
    public function getPrivName(): string
    {
        return $this->privName;
    }

    public function setPrivName(string $privName): self
    {
        $this->privName = $privName;
        return $this;
    }

    // 其他字段的getter、setter...
}
第二步:自定义权限投票器(实现细粒度权限检查)

Symfony默认的is_granted()只检查角色,我们需要自定义一个Voter来验证用户是否拥有指定权限:

// src/Security/Voter/PrivilegeVoter.php
namespace App\Security\Voter;

use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

class PrivilegeVoter extends Voter
{
    // 定义支持的权限列表,后续可按需扩展
    protected function supports(string $attribute, $subject): bool
    {
        return in_array($attribute, [
            'user_list_view',
            'user_edit',
            'user_create',
            'user_delete'
        ]);
    }

    protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        // 未登录用户直接拒绝访问
        if (!$user instanceof User) {
            return false;
        }

        // 检查用户是否拥有目标权限
        return in_array($attribute, $user->getAllPrivileges());
    }
}

这个Voter会被Symfony安全组件自动识别,无需额外配置。

第三步:配置权限与角色关联(实现动态权限管理)

现在你可以通过API平台的接口完成权限配置:

  1. 创建权限:POST /privileges,传入{"privName": "user_list_view"}
  2. 创建角色:POST /roles,传入{"roleName": "ROLE_VIEWER"}
  3. 给角色分配权限:PATCH /roles/{id},传入{"privileges": ["/privileges/1"]}(假设user_list_view的ID为1)
  4. 给用户分配角色:PATCH /users/{id},传入{"roles": ["/roles/1"]}

这样,被分配ROLE_VIEWER角色的用户就能查看用户列表,但没有user_edit权限就无法编辑用户,完全符合你的需求。

可选优化:用户独立权限(跳过角色直接分配)

如果需要给单个用户单独分配权限(不通过角色),可以给User实体添加多对多关联到Privilege:

// 在User.php中添加
#[ORM\ManyToMany(targetEntity: Privilege::class)]
#[ORM\JoinTable(name: 'user_privilege')]
private $directPrivileges;

// 构造函数初始化
public function __construct()
{
    $this->roles = new \Doctrine\Common\Collections\ArrayCollection();
    $this->directPrivileges = new \Doctrine\Common\Collections\ArrayCollection();
}

// 修改getAllPrivileges方法,合并角色权限和直接权限
public function getAllPrivileges(): array
{
    $privileges = [];
    // 角色关联的权限
    foreach ($this->roles as $role) {
        foreach ($role->getPrivileges() as $privilege) {
            $privileges[] = $privilege->getPrivName();
        }
    }
    // 用户直接拥有的权限
    foreach ($this->directPrivileges as $privilege) {
        $privileges[] = $privilege->getPrivName();
    }
    return array_unique($privileges);
}

// 添加/移除直接权限的方法
public function addDirectPrivilege(Privilege $privilege): self
{
    if (!$this->directPrivileges->contains($privilege)) {
        $this->directPrivileges->add($privilege);
    }
    return $this;
}

public function removeDirectPrivilege(Privilege $privilege): self
{
    $this->directPrivileges->removeElement($privilege);
    return $this;
}

这样就实现了更灵活的权限控制,用户既可以通过角色继承权限,也能拥有独立的专属权限。


内容的提问来源于stack exchange,提问作者SAFSAF

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 12:17:45