Gates 与 Policies:细粒度权限控制
High Contrast
Dark Mode
Light Mode
Sepia
Forest
1 min read241 words

Gates 与 Policies:细粒度权限控制

认证(Authentication)确认你是谁,授权(Authorization)决定你能做什么。Gate 是简单的闭包权限检查,Policy 是面向模型的权限类——两者都是 Laravel 原生支持的权限控制机制。


Gate vs Policy:如何选择

特性 Gate Policy
定义方式 Gate::define() 闭包 专用 PHP 类
关联模型 不限 对应特定模型
适用场景 全局操作(如"是否管理员") 模型级 CRUD 权限
代码位置 AppServiceProvider::boot() app/Policies/

Gate:定义全局权限

// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
// 简单布尔 Gate
Gate::define('admin-panel', fn(User $user) => $user->is_admin);
Gate::define('manage-billing', fn(User $user) => $user->subscription_plan === 'premium');
// 超级管理员绕过所有检查(放在最前面)
Gate::before(fn(User $user) => $user->is_admin ? true : null);
// 返回 true = 允许所有操作
// 返回 null = 继续正常检查(不影响其他 Gate/Policy)
}
// 在控制器中使用 Gate
public function adminPanel(Request $request)
{
// 方法一:手动检查
if (!Gate::allows('admin-panel')) {
abort(403, '无权访问管理面板');
}
// 方法二:authorize(失败时自动抛 403)
Gate::authorize('admin-panel');
return view('admin.dashboard');
}
// 在路由中使用
Route::middleware('can:admin-panel')->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
});
// 在 Blade 模板中使用
@can('admin-panel')
<a href="/admin">管理面板</a>
@endcan

Policy:模型级权限类

php artisan make:policy TaskPolicy --model=Task
<?php
// app/Policies/TaskPolicy.php
namespace App\Policies;
use App\Models\Task;
use App\Models\User;
class TaskPolicy
{
// 在所有 Policy 方法前检查(管理员绕过)
public function before(User $user, string $ability): ?bool
{
if ($user->hasRole('admin')) {
return true;  // 管理员允许所有操作
}
return null;  // 其他用户走正常检查
}
// 查看任务列表(通常任何用户都可以查自己的)
public function viewAny(User $user): bool
{
return true;
}
// 查看单条任务
public function view(User $user, Task $task): bool
{
// 自己的任务,或者是共享给用户的团队任务
return $task->user_id === $user->id
|| ($task->team_id && $user->teams->contains($task->team_id));
}
// 创建任务
public function create(User $user): bool
{
// 免费用户最多 100 个未完成任务
if ($user->subscription_plan === 'free') {
return Task::where('user_id', $user->id)
->whereNot('status', 'done')
->count() < 100;
}
return true;
}
// 更新任务
public function update(User $user, Task $task): bool
{
return $task->user_id === $user->id;
}
// 删除任务
public function delete(User $user, Task $task): bool
{
return $task->user_id === $user->id;
}
// 恢复软删除(可自定义权限)
public function restore(User $user, Task $task): bool
{
return $task->user_id === $user->id;
}
}

注册 Policy

// Laravel 会自动发现 app/Policies/ 中按命名规范匹配的 Policy
// (Task 模型 → TaskPolicy,无需手动注册)
// 如果命名不规范,在 AppServiceProvider 中手动注册:
use Illuminate\Support\Facades\Gate;
Gate::policy(Task::class, TaskPolicy::class);

在控制器中使用 Policy

// app/Http/Controllers/TaskController.php
class TaskController extends Controller
{
// 方法一:authorize() 方法(失败时自动 403)
public function show(Task $task)
{
$this->authorize('view', $task);
return new TaskResource($task);
}
public function update(UpdateTaskRequest $request, Task $task)
{
$this->authorize('update', $task);
$task->update($request->validated());
return new TaskResource($task);
}
public function store(StoreTaskRequest $request)
{
$this->authorize('create', Task::class);  // 不传模型实例,传类名
$task = Task::create($request->validated() + ['user_id' => auth()->id()]);
return new TaskResource($task);
}
// 方法二:authorizeResource(一次性授权所有 CRUD)
public function __construct()
{
$this->authorizeResource(Task::class, 'task');
// 自动在每个方法前检查对应的 Policy 方法
}
}

在 FormRequest 中使用 Policy

// app/Http/Requests/UpdateTaskRequest.php
class UpdateTaskRequest extends FormRequest
{
public function authorize(): bool
{
// 在 FormRequest 中做 Policy 检查(比在控制器中更早)
return $this->user()->can('update', $this->route('task'));
}
}

Policy 响应(自定义错误信息)

use Illuminate\Auth\Access\Response;
public function update(User $user, Task $task): Response
{
return $task->user_id === $user->id
? Response::allow()
: Response::deny('你只能修改自己的任务。');
// 或带 HTTP 状态码:Response::denyWithStatus(404)
// 404 vs 403:如果你不想透露资源存在,用 404
}

下一节Spatie Permission:多角色权限管理——Gates 和 Policies 是 Laravel 原生的,Spatie Permission 在此基础上增加了数据库驱动的角色和权限管理——不需要改代码就能调整权限配置。