在 Laravel 12 中使用 spatie/laravel-permission
进行路由权限管理,需要系统化的设计与实现。以下是详细步骤和实战代码:
1. 安装与配置
安装包
composer require spatie/laravel-permission
发布迁移文件
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
运行迁移
php artisan migrate
注册中间件(app/Http/Kernel.php
)
protected $routeMiddleware = [
// ...
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
];
2. 权限系统设计
权限规划表
权限标识 (Permission) | 描述 | 路由示例 |
---|---|---|
posts.view | 查看文章 | GET /posts |
posts.create | 创建文章 | POST /posts |
posts.edit | 编辑文章 | PUT /posts/{id} |
posts.delete | 删除文章 | DELETE /posts/{id} |
users.manage | 管理用户 | GET /admin/users |
角色规划
- Admin: 所有权限
- Editor:
posts.view
,posts.create
,posts.edit
- Viewer:
posts.view
3. 初始化权限和角色
创建权限播种器:
php artisan make:seeder PermissionSeeder
database/seeders/PermissionSeeder.php
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
public function run(): void
{
// 重置缓存
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// 创建权限
$permissions = [
'posts.view',
'posts.create',
'posts.edit',
'posts.delete',
'users.manage'
];
foreach ($permissions as $permission) {
Permission::firstOrCreate(['name' => $permission]);
}
// 创建角色并分配权限
$admin = Role::firstOrCreate(['name' => 'Admin']);
$admin->givePermissionTo(Permission::all());
$editor = Role::firstOrCreate(['name' => 'Editor']);
$editor->givePermissionTo(['posts.view', 'posts.create', 'posts.edit']);
$viewer = Role::firstOrCreate(['name' => 'Viewer']);
$viewer->givePermissionTo('posts.view');
}
运行播种器:
php artisan db:seed --class=PermissionSeeder
4. 用户分配角色
在用户模型(app/Models/User.php
)中添加 Trait:
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
}
分配角色(例如在控制器中):
$user = User::find(1);
$user->assignRole('Admin');
5. 路由权限控制
路由配置(routes/web.php
)
use App\Http\Controllers\PostController;
// 公共路由
Route::get('/', [HomeController::class, 'index']);
// 需要认证的路由组
Route::middleware(['auth'])->group(function () {
// 文章路由 - 权限控制
Route::get('/posts', [PostController::class, 'index'])
->middleware('permission:posts.view');
Route::get('/posts/create', [PostController::class, 'create'])
->middleware('permission:posts.create');
Route::post('/posts', [PostController::class, 'store'])
->middleware('permission:posts.create');
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])
->middleware('permission:posts.edit');
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('permission:posts.edit');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
->middleware('permission:posts.delete');
// 管理员路由 - 角色控制
Route::prefix('admin')->group(function () {
Route::get('/users', [UserController::class, 'index'])
->middleware('role:Admin');
});
});
高级用法:多条件检查
// 需满足角色或权限之一
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware('role_or_permission:Admin|posts.view');
// 同时检查多个权限
Route::get('/reports', [ReportController::class, 'index'])
->middleware('permission:posts.view,posts.edit');
6. 异常处理
自定义 403 页面(app/Exceptions/Handler.php
):
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
public function register(): void
{
$this->renderable(function (AccessDeniedHttpException $e, $request) {
return response()->view('errors.403', [], 403);
});
}
创建视图 resources/views/errors/403.blade.php
:
@extends('layouts.app')
@section('content')
<div class="alert alert-danger">
您无权访问此页面!
</div>
@endsection
7. 前端视图集成
在 Blade 模板中检查权限:
@can('posts.create')
<a href="{{ route('posts.create') }}" class="btn btn-primary">
创建文章
</a>
@endcan
@role('Admin')
<a href="/admin/users" class="btn btn-danger">
用户管理
</a>
@endrole
8. 实战技巧
缓存优化
# 权限变更后清除缓存
php artisan permission:cache-reset
中间件优先级
在 app/Http/Kernel.php
中确保中间件顺序:
protected $middlewarePriority = [
\App\Http\Middleware\Authenticate::class,
\Spatie\Permission\Middlewares\PermissionMiddleware::class, // 在认证之后
// ...
];
控制台命令
# 查看用户权限
php artisan permission:show-permissions {userId}
# 同步角色权限
php artisan permission:create-role Editor --permissions="posts.view,posts.create"
9. 测试验证
创建测试用例:
php artisan make:test PostPermissionTest
tests/Feature/PostPermissionTest.php
public function test_editor_can_edit_post(): void
{
$user = User::factory()->create()->assignRole('Editor');
$post = Post::factory()->create();
$response = $this->actingAs($user)
->put(route('posts.update', $post), [
'title' => 'Updated Title'
]);
$response->assertStatus(200);
$this->assertDatabaseHas('posts', ['title' => 'Updated Title']);
}
public function test_viewer_cannot_delete_post(): void
{
$user = User::factory()->create()->assignRole('Viewer');
$post = Post::factory()->create();
$response = $this->actingAs($user)
->delete(route('posts.destroy', $post));
$response->assertForbidden();
}
10. 安全增强
防止 URL 参数篡改
在控制器中二次验证:
public function edit(Post $post)
{
// 权限二次确认
if (!auth()->user()->can('posts.edit')) {
abort(403);
}
return view('posts.edit', compact('post'));
}
隐藏无权限元素
在 Blade 中使用 @can
确保前端安全:
@foreach($posts as $post)
<div>
{{ $post->title }}
@can('posts.delete')
<button @click="deletePost({{ $post->id }})">删除</button>
@endcan
</div>
@endforeach
最终目录结构
app/
├── Models/
│ └── User.php
├── Http/
│ ├── Controllers/
│ │ ├── PostController.php
│ │ └── UserController.php
│ └── Middleware/
database/
├── migrations/
│ └── [spatie_permission_tables]
└── seeders/
└── PermissionSeeder.php
routes/
└── web.php
resources/
└── views/
├── posts/
└── errors/
└── 403.blade.php
通过以上设计,可实现精细化的路由权限控制。关键要点:
- 权限颗粒化:按操作定义权限标识
- 中间件组合:灵活使用
permission
和role
- 分层防御:路由中间件 + 控制器二次验证 + 前端隐藏
- 缓存管理:权限变更后及时清除缓存
- 自动化测试:确保权限逻辑正确性