本文目录导读:

在 Laravel 中,策略(Policy) 和 门面(Gate) 是授权系统的两大核心组件,它们紧密配合,共同完成权限控制,可以理解为:
- 策略 是具体实现授权逻辑的“操作手册”(针对某个模型)。
- 门面 是调用授权检查的“总入口”(
Gatefacade 或Authfacade)。
下面详细介绍它们的配合方式。
核心思想
- 策略(Policy):定义某个模型(如
Post、User)的各种操作权限(如view,create,update,delete)。 - 门面(Gate):提供统一的接口(如
can、allows、denies)来调用这些策略,Gate 会自动根据模型类找到对应的策略。
配合流程:
调用 Gate → Gate 查找 Policy → Policy 的方法执行检查 → 返回 bool
创建和注册策略
1 生成策略
php artisan make:policy PostPolicy --model=Post
2 定义策略方法
// app/Policies/PostPolicy.php
class PostPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true; // 所有用户可查看列表
}
public function update(User $user, Post $post)
{
return $user->id === $post->user_id; // 只有作者可更新
}
public function delete(User $user, Post $post)
{
// 更复杂的逻辑
return $user->id === $post->user_id || $user->isAdmin();
}
}
3 注册策略
在 AuthServiceProvider 的 $policies 属性中注册:
// app/Providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Post::class => PostPolicy::class,
];
public function boot()
{
$this->registerPolicies();
}
}
使用门面调用策略
有四种主要的调用方式:
1 使用 Gate Facade
use Illuminate\Support\Facades\Gate;
if (Gate::allows('update', $post)) {
// 允许更新
}
if (Gate::denies('update', $post)) {
// 拒绝更新
}
// 或指定用户(如管理员检查其他用户)
if (Gate::forUser($otherUser)->allows('update', $post)) { ... }
2 在 Controller 中辅助方法
public function update(Request $request, Post $post)
{
$this->authorize('update', $post); // 自动抛出 403 异常
// 更新逻辑...
}
3 在 Blade 视图中
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">编辑</a>
@endcan
@cannot('delete', $post)
<span>无权删除</span>
@endcannot
4 在模型实例上使用 can() 方法
if ($user->can('update', $post)) {
// ...
}
门面如何找到策略?
Gate 使用约定来自动匹配:
- 你传入模型的类名(
Post::class)或实例($post)。 - Gate 根据
AuthServiceProvider中注册的$policies数组,找到对应的PostPolicy。 - 从策略中调用与方法同名的方法(如
update→update())。 - 如果没有注册策略,Gate 会尝试查找
Policy命名规范(App\Policies\ModelNamePolicy)。
注意:如果没有显式注册,Gate 也能通过约定自动发现策略(Laravel 8+)。
更高级的配合方式
1 在 Gate 中直接定义逻辑(小型应用)
可以在 AuthServiceProvider::boot() 中直接使用 Gate::define:
public function boot()
{
$this->registerPolicies();
// 额外定义的授权逻辑(优先级低于策略)
Gate::define('admin-action', function ($user) {
return $user->isAdmin();
});
}
在 Blade 中使用:
@can('admin-action')
<!-- 管理员专用按钮 -->
@endcan
2 使用策略的 before 方法(超级管理员绕过)
class PostPolicy
{
public function before(User $user, $ability) // ability 表示操作名称
{
if ($user->isSuperAdmin()) {
return true; // 超级管理员通过所有检查
}
}
// ... 其他方法
}
注意:before 返回 true 直接通过,返回 false 会继续执行具体方法。
完整示例
场景:博客文章权限控制
策略(PostPolicy):
class PostPolicy
{
public function view(User $user, Post $post) { ... }
public function create(User $user) { ... }
public function update(User $user, Post $post) { ... }
public function delete(User $user, Post $post) { ... }
}
Controller 使用:
class PostController extends Controller
{
public function store(Request $request)
{
$this->authorize('create', Post::class); // 不需要模型实例
// 创建逻辑
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// 更新逻辑
}
}
Blade 视图:
@can('update', $post)
<form action="/posts/{{ $post->id }}" method="POST">
@csrf @method('PUT')
<button>更新</button>
</form>
@endcan
API 路由(使用 Gate 门面手动检查):
Route::put('/posts/{post}', function (Post $post) {
if (Gate::allows('update', $post)) {
// 更新逻辑
}
abort(403);
});
总结配合关系
| 组件 | 职责 | 对应代码 |
|---|---|---|
| Policy | 定义针对某个模型的授权规则 | update(User $user, Post $post) |
| Gate | 提供统一授权检查接口 | Gate::allows('update', $post) |
| Controller | 使用 $this->authorize() 简化调用 |
自动调用 Gate |
| Blade | 使用 @can 指令展示/隐藏界面元素 |
@can('update', $post) |
| User Model | 使用 $user->can() 在代码中检查 |
$user->can('update', $post) |
最佳实践:
- 模型专属权限用 Policy(代码清晰、可维护性强)。
- 通用权限(如
admin-action)用 Gate::define 直接定义。 - Controller 里优先使用
$this->authorize()而非手动Gate::allows。 - 视图层用 Blade 指令,避免在模板中写复杂 PHP 逻辑。