如何为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平台的接口完成权限配置:
- 创建权限:POST
/privileges,传入{"privName": "user_list_view"} - 创建角色:POST
/roles,传入{"roleName": "ROLE_VIEWER"} - 给角色分配权限:PATCH
/roles/{id},传入{"privileges": ["/privileges/1"]}(假设user_list_view的ID为1) - 给用户分配角色: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
相关产品推荐
相关产品推荐

