权限控制 #
一、授权概述 #
1.1 授权概念 #
授权是确定已认证用户是否有权限执行特定操作的过程。
text
┌─────────────────────────────────────────────────────┐
│ 授权机制 │
├─────────────────────────────────────────────────────┤
│ │
│ 用户请求 → 检查角色 → 检查权限 → 允许/拒绝 │
│ │
│ 授权方式: │
│ • 角色检查 (ROLE_*) │
│ • 投票器 (Voter) │
│ • 表达式 (Expression) │
│ • ACL (访问控制列表) │
│ │
└─────────────────────────────────────────────────────┘
1.2 授权组件 #
| 组件 | 说明 |
|---|---|
| AccessDecisionManager | 访问决策管理器 |
| Voter | 投票器 |
| RoleHierarchy | 角色层次 |
| ExpressionLanguage | 表达式语言 |
二、角色控制 #
2.1 角色定义 #
yaml
# config/packages/security.yaml
security:
role_hierarchy:
ROLE_USER: []
ROLE_EDITOR: [ROLE_USER]
ROLE_ADMIN: [ROLE_EDITOR, ROLE_USER]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
2.2 控制器角色检查 #
php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class AdminController extends AbstractController
{
#[Route('/admin', name: 'app_admin')]
#[IsGranted('ROLE_ADMIN')]
public function index(): Response
{
return $this->render('admin/index.html.twig');
}
#[Route('/admin/users', name: 'app_admin_users')]
#[IsGranted('ROLE_ADMIN', message: '您没有访问权限')]
public function users(): Response
{
return $this->render('admin/users.html.twig');
}
#[Route('/profile', name: 'app_profile')]
public function profile(): Response
{
$this->denyAccessUnlessGranted('ROLE_USER');
return $this->render('profile/index.html.twig');
}
}
2.3 服务中角色检查 #
php
<?php
namespace App\Service;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AdminService
{
public function __construct(
private AuthorizationCheckerInterface $authorizationChecker
) {}
public function doSomething(): void
{
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
throw new \Exception('Access denied');
}
// 执行管理员操作
}
}
2.4 模板中角色检查 #
twig
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('app_admin') }}">管理后台</a>
{% endif %}
{% if is_granted('ROLE_EDITOR') %}
<a href="{{ path('app_editor') }}">编辑内容</a>
{% endif %}
{% if is_granted('ROLE_USER') %}
<p>欢迎, {{ app.user.name }}</p>
{% endif %}
三、投票器 #
3.1 创建投票器 #
php
<?php
namespace App\Security\Voter;
use App\Entity\Post;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class PostVoter extends Voter
{
public const EDIT = 'POST_EDIT';
public const DELETE = 'POST_DELETE';
public const VIEW = 'POST_VIEW';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE, self::VIEW])
&& $subject instanceof Post;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return match ($attribute) {
self::VIEW => $this->canView($subject, $user),
self::EDIT => $this->canEdit($subject, $user),
self::DELETE => $this->canDelete($subject, $user),
default => false,
};
}
private function canView(Post $post, User $user): bool
{
if ($this->canEdit($post, $user)) {
return true;
}
return $post->isPublished();
}
private function canEdit(Post $post, User $user): bool
{
return $user === $post->getAuthor() || $this->security->isGranted('ROLE_ADMIN');
}
private function canDelete(Post $post, User $user): bool
{
return $this->canEdit($post, $user);
}
}
3.2 使用投票器 #
php
<?php
namespace App\Controller;
use App\Entity\Post;
use App\Security\Voter\PostVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class PostController extends AbstractController
{
#[Route('/post/{id}/edit', name: 'app_post_edit')]
#[IsGranted(PostVoter::EDIT, 'post')]
public function edit(Post $post): Response
{
return $this->render('post/edit.html.twig', [
'post' => $post,
]);
}
#[Route('/post/{id}/delete', name: 'app_post_delete')]
public function delete(Post $post): Response
{
$this->denyAccessUnlessGranted(PostVoter::DELETE, $post);
// 删除文章
}
}
3.3 模板中使用投票器 #
twig
{% if is_granted('POST_EDIT', post) %}
<a href="{{ path('app_post_edit', {id: post.id}) }}">编辑</a>
{% endif %}
{% if is_granted('POST_DELETE', post) %}
<a href="{{ path('app_post_delete', {id: post.id}) }}">删除</a>
{% endif %}
四、表达式安全 #
4.1 使用表达式 #
php
<?php
use Symfony\Component\Security\Http\Attribute\IsGranted;
class OrderController extends AbstractController
{
#[Route('/order/{id}', name: 'app_order_show')]
#[IsGranted(
attribute: 'ROLE_USER',
subject: 'order',
message: '您没有权限查看此订单'
)]
public function show(Order $order): Response
{
return $this->render('order/show.html.twig', [
'order' => $order,
]);
}
#[Route('/order/{id}/cancel', name: 'app_order_cancel')]
#[IsGranted(
attribute: 'order.can_cancel',
subject: 'order'
)]
public function cancel(Order $order): Response
{
// 取消订单
}
}
4.2 复杂表达式 #
php
<?php
use Symfony\Component\ExpressionLanguage\Expression;
#[Route('/special', name: 'app_special')]
#[IsGranted(
new Expression('is_granted("ROLE_ADMIN") or is_granted("ROLE_EDITOR")')
)]
public function special(): Response
{
return $this->render('special/index.html.twig');
}
#[Route('/owner/{id}', name: 'app_owner')]
#[IsGranted(
new Expression('user === subject.getOwner()'),
subject: 'entity'
)]
public function ownerAction(Entity $entity): Response
{
return $this->render('owner/index.html.twig');
}
4.3 访问控制表达式 #
yaml
# config/packages/security.yaml
security:
access_control:
- { path: ^/admin, allow_if: "is_granted('ROLE_ADMIN') and request.getClientIp() == '127.0.0.1'" }
- { path: ^/api/admin, allow_if: "is_granted('ROLE_API_ADMIN') and request.isXmlHttpRequest()" }
五、自定义投票器 #
5.1 完整投票器示例 #
php
<?php
namespace App\Security\Voter;
use App\Entity\Order;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class OrderVoter extends Voter
{
public const VIEW = 'ORDER_VIEW';
public const CANCEL = 'ORDER_CANCEL';
public const REFUND = 'ORDER_REFUND';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::VIEW, self::CANCEL, self::REFUND])
&& $subject instanceof Order;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return match ($attribute) {
self::VIEW => $this->canView($subject, $user),
self::CANCEL => $this->canCancel($subject, $user),
self::REFUND => $this->canRefund($subject, $user),
default => false,
};
}
private function canView(Order $order, User $user): bool
{
return $order->getUser() === $user || $this->isAdmin($user);
}
private function canCancel(Order $order, User $user): bool
{
if ($this->isAdmin($user)) {
return true;
}
if ($order->getUser() !== $user) {
return false;
}
return $order->getStatus() === 'pending';
}
private function canRefund(Order $order, User $user): bool
{
if (!$this->isAdmin($user)) {
return false;
}
return in_array($order->getStatus(), ['paid', 'completed']);
}
private function isAdmin(User $user): bool
{
return in_array('ROLE_ADMIN', $user->getRoles());
}
}
六、访问决策管理器 #
6.1 决策策略 #
yaml
# config/packages/security.yaml
security:
access_decision_manager:
strategy: affirmative
allow_if_all_abstain: false
allow_if_equal_granted_denied: true
6.2 策略说明 #
| 策略 | 说明 |
|---|---|
| affirmative | 任一投票器通过即允许 |
| consensus | 多数投票器通过即允许 |
| unanimous | 所有投票器通过才允许 |
七、安全上下文 #
7.1 获取当前用户 #
php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Core\Security;
class ProfileController extends AbstractController
{
public function index(Security $security): Response
{
$user = $security->getUser();
return $this->render('profile/index.html.twig', [
'user' => $user,
]);
}
}
7.2 模板中获取用户 #
twig
{% if app.user %}
<p>用户: {{ app.user.name }}</p>
<p>邮箱: {{ app.user.email }}</p>
{% for role in app.user.roles %}
<span class="badge">{{ role }}</span>
{% endfor %}
{% endif %}
八、权限缓存 #
8.1 缓存投票结果 #
php
<?php
namespace App\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\CacheableVoterInterface;
class CachedVoter implements CacheableVoterInterface
{
public function supportsAttribute(string $attribute): bool
{
return in_array($attribute, ['SOME_ATTRIBUTE']);
}
public function supportsType(string $subjectType): bool
{
return $subjectType === 'App\Entity\SomeEntity';
}
public function vote(TokenInterface $token, mixed $subject, array $attributes): int
{
// 投票逻辑
}
}
九、最佳实践 #
9.1 权限设计原则 #
text
权限设计原则:
├── 最小权限原则
├── 角色层次清晰
├── 使用投票器处理复杂逻辑
├── 避免硬编码权限
├── 权限缓存优化
└── 日志记录权限变更
9.2 安全建议 #
text
安全建议:
├── 所有敏感操作都要检查权限
├── 使用表达式处理复杂权限
├── 自定义投票器封装业务逻辑
├── 定期审计权限配置
└── 记录权限检查日志
十、总结 #
本章学习了:
- 授权机制概述
- 角色控制和层次
- 投票器创建和使用
- 表达式安全
- 自定义投票器
- 访问决策管理器
- 安全上下文
- 权限缓存
下一章将学习 事件系统。
最后更新:2026-03-28