damiantw / laravel-roles
Laravel 6 基于角色的授权
Requires
- php: >=7.2.0
- illuminate/auth: >=6.0.0
- illuminate/contracts: >=6.0.0
- illuminate/routing: >=6.0.0
- illuminate/support: >=6.0.0
- nesbot/carbon: ^2.0
README
目的
此包旨在为 Laravel 用户模型提供一套细粒度、定义清晰且易于访问的权限集。开箱即用的 Laravel 提供了一些非常强大的工具,用于处理用户能否完成特定操作的相关检查。通常,决定因素是简单的布尔运算结果(例如,用户 id 是否与正在编辑的 Post 的 user_id 匹配?)。用户的定义权限集可以轻松地融入这些计算中,以使用提供的 API 提供保护前哨。然后,我们只能允许拥有正确权限的用户使用 Laravel 授权工具或提供的中间件访问操作。
概念
应用程序中的每个用户都有一个定义的权限集。权限不过是一个表明允许的操作或权限级别的唯一字符串。权限存在于角色中,每个角色恰好包含一个权限值。角色可以与特定用户或任何数量的角色组相关联。角色组提供了一种方式,可以定义一组可由许多用户共享的权限。用户可以与多个角色组相关联。
用户的最终定义权限由直接与用户关联的角色的权限集和与用户的角色组关联的所有权限集合并而成。这种方法允许处理特殊场景(例如,需要为具有特定较低权限的用户提供对单个管理操作的访问权限),同时提供了便利的常用权限集。
安装
使用 Composer 安装
composer require DamianTW/laravel-roles
然后,将 ServiceProvider 添加到 config/app.php
中的包服务提供商
/* * Package Service Providers... */ DamianTW\LaravelRoles\Providers\RoleServiceProvider::class,
将 HoldsAuthorities 特性添加到用户模型
<?php namespace App; use DamianTW\LaravelRoles\Traits\HoldsAuthorities; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; use HoldsAuthorities; //...
如果您计划使用 hasAuthority 或 hasAuthorityController 中间件,您需要将它们添加到 app/Http/Kernel.php
中的 $routeMiddleware
数组
protected $routeMiddleware = [ 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'hasAuthority' => \DamianTW\LaravelRoles\Middleware\HasAuthority::class, 'hasAuthorityController' => \DamianTW\LaravelRoles\Middleware\HasAuthorityController::class // ... ];
运行 php artisan vendor:publish
将安装角色配置文件、数据库迁移、Role/RoleGroup Eloquent 模型和 RoleGroupsTableSeeder 模板到您的应用程序。最低限度,您应该使用以下命令安装迁移和 Eloquent 模型:php artisan vendor:publish --tag=migrations --tag=models
。
现在只需运行迁移即可: php artisan migrate
用法
用户的权限集与 Laravel 内置的授权工具(如 策略)配合得很好。
例如,让我们为 Post 模型创建一个策略
php artisan make:policy PostPolicy --model=Post
我们可以在策略方法中查询用户的权限集。这创建了一个前端保护,确保用户有权参与此操作。然后,我们可以提供额外的逻辑来确定此特定实例的操作是否应该被允许。
<?php namespace App\Policies; use App\User; use App\Post; use Illuminate\Auth\Access\HandlesAuthorization; class PostPolicy { use HandlesAuthorization; public function before($user, $ability) { //If the User has the SUPER_ADMIN authority they will always pass all Policy checks if ($user->hasAuthority('SUPER_ADMIN')) { return true; } } public function view(User $user, Post $post) { // To view a Post: // A User must have the authority POST_SHOW // and cannot view private Posts unless they are their own. return $user->hasAuthority('POST_SHOW') && (!$post->private || $user->id === $post->user_id); } public function create(User $user) { // To create a Post: // A User must have the authority POST_CREATE **AND** POST_STORE return $user->hasAllAuthorities(['POST_CREATE', 'POST_STORE']); } public function update(User $user, Post $post) { // To update a Post: // A user must have the authority POST_EDIT **OR** POST_UPDATE // and can only edit their own Posts return $user->hasAnyAuthority(['POST_EDIT', 'POST_UPDATE']) && $user->id === $post->user_id; } public function delete(User $user, Post $post) { // To delete a Post: // A user must have the authority POST_DESTROY // and either must be deleting their own Post or have the authority POST_CLEANER return $user->hasAuthority('POST_DESTROY') && ($user->id === $post->user_id || $user->hasAuthority('POST_CLEANER')); } }
在 AuthServiceProvider 中注册我们的策略后,我们的 PostController 可以使用 authorize() 控制器辅助函数。如果策略检查未通过,将抛出 Illuminate\Auth\Access\AuthorizationException
,这将导致默认的 Laravel 异常处理器发出 HTTP 403 状态码作为响应。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Post; class PostController extends Controller { public function create() { $this->authorize('create', Post::class); //... } public function store(Request $request) { $this->authorize('create', Post::class); //... } public function show(Post $post) { $this->authorize('view', $post); //... } public function edit(Post $post) { $this->authorize('update', $post); //... } public function update(Request $request, Post $post) { $this->authorize('update', $post); //... } public function destroy(Post $post) { $this->authorize('delete', $post); //... } }
中间件
对于只需要持有特定权限即可完成操作的情况,创建特定操作的策略可能看起来是不必要的。对于这些情况,您可以使用提供的 hasAuthority 和 hasAuthorityController 中间件。
路由中间件
可以通过应用具有权限的中间件来保护单独的路由。如果用户没有特定路由所需的权限,将抛出状态码为401的Symfony\Component\HttpKernel\Exception\HttpException
异常。
// User must have the USER_UPDATE authority to access the route Route::put('/user/{user}', 'UserController@update')->middleware('hasAuthority:USER_UPDATE'); // More then one authority can be specificed using the pipe. // If the user has the authority USER_DESTROY **OR** USER_MODERATOR they will be allowed access to the route Route::delete('/user/{user}', 'UserController@destroy')->middleware('hasAuthority:USER_DESTROY|USER_MODERATOR'); // Apply **AND** boolean logic by calling the hasAuthority middleware multiple times // Allowed if $user->hasAnyAuthority(['USER_VIEW','USER_SHOW']) **AND** $user->hasAuthority(['ADMIN']); Route::get('/user/{user}', 'UserController@show')->middleware('hasAuthority:USER_VIEW|USER_SHOW', 'hasAuthority:ADMIN'); //You can provide a second parameter to define the guard that should be used to retreive the authenticated user //The web guard will be used by default Route::put('/user/{user}', 'UserController@update')->middleware('hasAuthority:USER_UPDATE,api');
控制器中间件
您可以通过在控制器构造函数中应用hasAuthorityController中间件来轻松保护控制器中的所有操作。当用户尝试访问控制器任一路由的操作时,将检查用户角色是否符合约定CONTROLLERSUBJECT_METHOD
。如果用户没有特定路由的权限,将抛出状态码为401的Symfony\Component\HttpKernel\Exception\HttpException
异常。
以下是一个示例控制器
<?php namespace App\Http\Controllers; class PostController extends Controller { function __construct() { // Optionally provide a guard parameter to hasAuthorityController. // The web guard will be used by default // ex: $this->middleware('hasAuthorityController:api'); $this->middleware('hasAuthorityController'); } public function create(){} public function store(){} public function show(){} public function edit(){} public function update(){} public function destroy(){} }
每个路由都会进行权限检查。
API
HoldsAuthorities特质为用户模型添加了以下方法
<?php // Returns true only if $authorityStr is in the User's authority set. $user->hasAuthority($authorityStr); // Returns true only if $authorityStr1 **OR** $authorityStr2 is in the User's authority set. $user->hasAnyAuthority([$authorityStr1, $authorityStr2]); // Returns true only if $authorityStr1 **AND** $authorityStr2 **AND** $authorityStr3 are in the User's authority set. $user->hasAllAuthorities([$authorityStr1, $authorityStr2, $authorityStr3]); // Returns a Collection of all the User's authorities $user->authorities(); // Eloquent relation for User Roles. $user->roles; $user->roles(); // Eloquent relation for User RoleGroups $user->roleGroups; $user->roleGroups();
播种角色组
您可能希望为您的应用程序提供一组具有特定权限的默认角色组。这是Laravel种子功能的一个良好用例。
此包提供了一个RoleGroupsTableSeeder样板和RoleGroupSeeder Facade,可以用来清楚地定义应用程序角色组的默认权限集。
<?php use Illuminate\Database\Seeder; use DamianTW\LaravelRoles\Facades\RoleGroupSeeder; use App\RoleGroup; class RoleGroupsTableSeeder extends Seeder { public function run() { $admin = RoleGroup::firstOrCreate(['name' => 'Admin']); $user = RoleGroup::firstOrCreate(['name' => 'User']); RoleGroupSeeder::defineRoleGroupAuthorities( [ $admin->id => [ 'VIEW_USER', 'CREATE_USER', 'UPDATE_USER', 'DELETE_USER' ], // Admin RoleGroup will have authorities: VIEW_USER, CREATE_USER, UPDATE_USER, DELETE_USER $user->id => [ 'ViEW_USER' ] // User RoleGroup will only have the VIEW_USER authority ] ); } }
当我们运行php arisian db:seed --class=RoleGroupsTableSeeder
时,RoleGroupSeeder Facade将自动创建不存在权限的Roles,并同步您定义的RoleGroup权限集定义。
如果您的应用程序不允许在运行时更改RoleGroup权限集定义,则在部署程序中运行此命令可能很有用。
基于控制器的角色组播种
您还可以在角色组权限定义中将控制器类作为参数传递。RoleGroupSeeder将为每个符合约定CONTROLLERSUBJECT_METHOD
的公共非魔术方法创建权限。
以下是一个示例控制器
<?php namespace App\Http\Controllers; class PostController extends Controller { function __construct() {} public function create(){} public function store(){} public function show(){} public function edit(){} public function update(){} public function destroy(){} private function privateHelperFunction(){} }
当将PostController作为定义的一部分传递时
RoleGroupSeeder::defineRoleGroupAuthorities( [ $group->id => [ 'NON_CONTROLLER_BASED_AUTHORITY', App\Http\Controllers\PostController::class, ], ] );
将创建以下角色权限,并关联到该组
- NON_CONTROLLER_BASED_AUTHORITY
- POST_CREATE
- POST_STORE
- POST_SHOW
- POST_EDIT
- POST_UPDATE
- POST_DESTROY
Blade 指令
以下指令在Blade视图中提供,以方便和提升代码可读性。
@hasAuthority('VIEW_USER')
USER HAS AUTHORITY
@endHasAuthority
@hasAnyAuthority('VIEW_USER', 'CREATE_USER')
USER HAS AUTHORITY VIEW_USER OR CREATE_USER
@endif
{{-- @endif has the same function as @endHasAuthority. You can use either. --}}
{{-- Feel free to include conditional control structures. --}}
@hasAllAuthorities('VIEW_USER, 'CREATE_USER')
USER HAS BOTH VIEW_USER AND CREATE_USER AUTHORITIES
@else
USER DOES NOT HAVE VIEW_USER AND CREATE_USER AUTHORITIES
@endHasAuthority
免责声明
此包仅在Laravel 6.12.0上进行了测试,尽管应该与Laravel 6.*兼容。
愿望清单
缓存用户权限集hasAuthority Blade指令使用约定自动保护控制器中所有操作- 测试