[擴展推薦] spatie/Laravel-permission Laravel 應用中的角色和權限控制

文章轉發自專業的Laravel開發者社區,原始連接:learnku.com/laravel/t/8…php

將用戶與角色進行關聯

這個包容許你在數據庫中管理用戶的權限和角色。laravel

當你安裝了擴展包以後你就能夠這樣作:git

// 給用戶添加一個權限
$user->givePermissionTo('edit articles');

// 經過角色添加權限。
$user->assignRole('writer');

// 給角色添加一個權限
$role->givePermissionTo('edit articles');
複製代碼

若是你給單個用戶添加了多個守衛(guard),擴展包也能夠處理的很好,每個分配給用戶的守衛都有它本身的權限和角色,閱讀 using multiple guards章節能夠看見更多的信息。github

由於全部的權限將註冊在Laravel's gate上,因此你能夠調用 Laravel 默認的 'can' 方法來測試用戶是否有權限:web

$user->can('edit articles');
複製代碼

Spatie 是一個位於 Antwerp, Belgium的web設計機構。你能夠在咱們的官網找到全部的開源項目。redis

安裝

Laravel

這個包能夠在 Laravel 5.4或更高版本中使用,若是你使用的是舊版本的Laravel,能夠切換到 這個包的 v1 分支 去使用。數據庫

你能夠經過 composer 去安裝這個包:bootstrap

composer require spatie/laravel-permission
複製代碼

在 Laravel 5.5 中 service provider 會自動註冊,舊版本的Laravel中你須要像如下這樣自行添加到 config/app.php 中:緩存

'providers' => [
    // ...
    Spatie\Permission\PermissionServiceProvider::class,
];
複製代碼

你可使用如下命令發佈 migration 安全

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
複製代碼

若是你在 User 模型中使用 UUIDs 或 GUIDs 你能夠修改 create_permission_tables.php migration 並替換 $table->morphs('model')

$table->uuid('model_id');
$table->string('model_type');
複製代碼

migration發佈後,您能夠經過運行如下命令來建立角色和權限表:

php artisan migrate
複製代碼

你能夠運行如下命令生成配置文件:

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
複製代碼

當發佈了配置文件後,就能夠看到 config/permission.php 中包括:

return [

    'models' => [

        /* * When using the "HasRoles" trait from this package, we need to know which * Eloquent model should be used to retrieve your permissions. Of course, it * is often just the "Permission" model but you may use whatever you like. * * The model you want to use as a Permission model needs to implement the * `Spatie\Permission\Contracts\Permission` contract. */

        'permission' => Spatie\Permission\Models\Permission::class,

        /* * When using the "HasRoles" trait from this package, we need to know which * Eloquent model should be used to retrieve your roles. Of course, it * is often just the "Role" model but you may use whatever you like. * * The model you want to use as a Role model needs to implement the * `Spatie\Permission\Contracts\Role` contract. */

        'role' => Spatie\Permission\Models\Role::class,

    ],

    'table_names' => [

        /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your roles. We have chosen a basic * default value but you may easily change it to any table you like. */

        'roles' => 'roles',

        /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your permissions. We have chosen a basic * default value but you may easily change it to any table you like. */

        'permissions' => 'permissions',

        /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your models permissions. We have chosen a * basic default value but you may easily change it to any table you like. */

        'model_has_permissions' => 'model_has_permissions',

        /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your models roles. We have chosen a * basic default value but you may easily change it to any table you like. */

        'model_has_roles' => 'model_has_roles',

        /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your roles permissions. We have chosen a * basic default value but you may easily change it to any table you like. */

        'role_has_permissions' => 'role_has_permissions',
    ],

    /* * By default all permissions will be cached for 24 hours unless a permission or * role is updated. Then the cache will be flushed immediately. */

    'cache_expiration_time' => 60 * 24,
    
    /* * When set to true, the required permission/role names are added to the exception * message. This could be considered an information leak in some contexts, so * the default setting is false here for optimum safety. */

    'display_permission_in_exception' => false,
];
複製代碼

Lumen

經過 Composer 安裝:

composer require spatie/laravel-permission
複製代碼

複製必須的文件:

cp vendor/spatie/laravel-permission/config/permission.php config/permission.php
cp vendor/spatie/laravel-permission/database/migrations/create_permission_tables.php.stub database/migrations/2018_01_01_000000_create_permission_tables.php
複製代碼

另一個配置文件 config/auth.php, 你能夠從 Laravel 倉庫獲取, 或者直接執行下面的命令獲取:

curl -Ls https://raw.githubusercontent.com/laravel/lumen-framework/5.5/config/auth.php -o config/auth.php
複製代碼

如今,執行遷移:

php artisan migrate
複製代碼

接下來, 在 bootstrap/app.php 中, 註冊中間件:

$app->routeMiddleware([
    'auth'       => App\Http\Middleware\Authenticate::class,
    'permission' => Spatie\Permission\Middlewares\PermissionMiddleware::class,
    'role'       => Spatie\Permission\Middlewares\RoleMiddleware::class,
]);
複製代碼

一樣的 , 註冊配置和服務提供者:

$app->configure('permission');
$app->register(Spatie\Permission\PermissionServiceProvider::class);
複製代碼

使用

首先,添加 Spatie\Permission\Traits\HasRoles trait 到 User 模型:

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable {
    use HasRoles;

    // ...
}
複製代碼
  • 請注意,若是你須要在另外一個模型,例如 Page 中添加 HasRoles trait 你還須要添加 protected $guard_name = 'web'; 到這個模型中,不然會報錯。
use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasRoles;

class Page extends Model {
   use HasRoles;

   protected $guard_name = 'web'; // or whatever guard you want to use

   // ...
}
複製代碼

這個包容許用戶與權限和角色相關聯。每一個角色都與多個權限相關聯。 RolePermission 都是 Eloquent 模型,它們建立的時候須要傳入 name 這個參數,就像下面這樣:

use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

$role = Role::create(['name' => 'writer']);
$permission = Permission::create(['name' => 'edit articles']);
複製代碼

可使用下面其中一個方法將權限分配給角色:

$role->givePermissionTo($permission);
$permission->assignRole($role);
複製代碼

可使用下面其中一種方法將多個權限同步賦予到一個角色:

$role->syncPermissions($permissions);
$permission->syncRoles($roles);
複製代碼

可使用如下其中一種方法經過角色去刪除權限:

$role->revokePermissionTo($permission);
$permission->removeRole($role);
複製代碼

若是你使用多守衛的話, guard_name 必需要設置,在 使用多守衛 段落中有說起到。

HasRoles trait 具備 Eloquent 模型關係功能,能夠經過關係去直接訪問或用做基本的查詢:

// 獲取直接分配給用戶的全部的權限
$permissions = $user->permissions;

// 返回全部用戶經過賦予角色所繼承的權限
$permissions = $user->getAllPermissions();

// 獲取全部已定義的角色的集合
$roles = $user->getRoleNames(); // 返回一個集合
複製代碼

HasRoles trait 在你的模型上還增長了 role scope 能讓你檢索特定角色或者特定權限的用戶:

$users = User::role('writer')->get(); // 返回角色是 'writer' 的用戶
複製代碼

role scope 接收一個字符串, \Spatie\Permission\Models\Role 對象或者 \Illuminate\Support\Collection 對象。

這個 trait 還增長了 scope 讓你只能獲取到具備某個權限的用戶。

$users = User::permission('edit articles')->get(); // 只返回有 'edit articles' 權限的用戶 (繼承角色得來的或者是直接分配的)
複製代碼

role scope 接收一個字符串, \Spatie\Permission\Models\Permission 對象或者 \Illuminate\Support\Collection 對象。

使用 "直接" 權限 (能夠參考下面 roles 和 permissions 的使用)

分配權限給任何一個用戶:

$user->givePermissionTo('edit articles');

// You can also give multiple permission at once
$user->givePermissionTo('edit articles', 'delete articles');

// You may also pass an array
$user->givePermissionTo(['edit articles', 'delete articles']);
複製代碼

撤銷用戶的某個權限:

$user->revokePermissionTo('edit articles');
複製代碼

在一次操做中撤銷或者新增權限:

$user->syncPermissions(['edit articles', 'delete articles']);
複製代碼

你能夠判斷某個用戶是否具備這個權限:

$user->hasPermissionTo('edit articles');
複製代碼

判斷用戶是否具備多個權限:

$user->hasAnyPermission(['edit articles', 'publish articles', 'unpublish articles']);
複製代碼

保存了權限後它將會註冊在 Illuminate\Auth\Access\Gate 類的默認守衛中。因此你可使用 Laravel 的默認 can 方法來判斷用戶是否有某個權限:

$user->can('edit articles');
複製代碼

經過角色使用權限

角色能夠被分配給任意用戶:

$user->assignRole('writer');

// You can also assign multiple roles at once
$user->assignRole('writer', 'admin');
// or as an array
$user->assignRole(['writer', 'admin']);
複製代碼

角色能夠從一個用戶身上移除:

$user->removeRole('writer');
複製代碼

角色也能夠被同步:

// All current roles will be removed from the user and replaced by the array given
$user->syncRoles(['writer', 'admin']);
複製代碼

你能夠判斷一個用戶是否包含某個角色:

$user->hasRole('writer');
複製代碼

你也能夠判斷一個用戶是否包含給定角色列表中的一個:

$user->hasAnyRole(Role::all());
複製代碼

你也能夠判斷一個用戶是否包含全部給定的角色:

$user->hasAllRoles(Role::all());
複製代碼

assignRole, hasRole, hasAnyRole, hasAllRolesremoveRole 這些函數能夠接受一個字符串, 一個 \Spatie\Permission\Models\Role 對象 或者 一個 \Illuminate\Support\Collection 對象做爲參數。

權限能夠被分配給一個角色:

$role->givePermissionTo('edit articles');
複製代碼

你能夠判斷一個角色是否包含某個權限:

$role->hasPermissionTo('edit articles');
複製代碼

權限也能夠從一個角色身上移除:

$role->revokePermissionTo('edit articles');
複製代碼

givePermissionTorevokePermissionTo 函數能夠接受一個字符串或者一個 Spatie\Permission\Models\Permission 對象做爲參數。

權限是從角色中自動繼承的. 另外, 我的權限也能夠分配給用戶. 例如:

$role = Role::findByName('writer');
$role->givePermissionTo('edit articles');

$user->assignRole('writer');

$user->givePermissionTo('delete articles');
複製代碼

在上面的例子中,角色被賦予了編輯文章的權限,而且該角色被分配給了用戶。 如今,用戶能夠編輯、刪除文章。「刪除」權限是直接分配給用戶的直接權限。 當咱們調用 $user->hasDirectPermission('delete articles') 它會返回 true, 而 false 對應的是 $user->hasDirectPermission('edit articles').

若是爲應用程序中的角色和用戶設置權限,並但願限制或者更改用戶角色的繼承權限(僅容許更改用戶的直接權限),則這個方法就會很是有用。

你能夠列出這些權限:

// Direct permissions
$user->getDirectPermissions() // Or $user->permissions;

// Permissions inherited from the user's roles
$user->getPermissionsViaRoles();

// All permissions which apply on the user (inherited and direct)
$user->getAllPermissions();
複製代碼

全部的響應都是 Spatie\Permission\Models\Permission 這個對象的集合.

若是咱們按照以前的例子, 第一個響應將是一個具備 delete article 權限的集合,第二個響應會是一個具備 edit article權限的集合,而第三個響應將會包含以前兩者的集合。

使用 Blade 語法

這個包還增長了 Blade 語法來驗證當前登陸的用戶是否具備所有或某個給定的角色。

你能夠經過傳入第二個參數 guard 來進行檢索。

Blade 語法 Role

測試一個特定的角色:

@role('writer')
    我是一個 writer!
@else
   我不是一個 writer...
@endrole
複製代碼

至關於

@hasrole('writer')
    我是一個 writer!
@else
   我不是一個 writer...
@endhasrole
複製代碼

測試傳入的列表中的任一角色:

@hasanyrole($collectionOfRoles)
    我有一個或多個這裏的權限
@else
    這些權限我都沒有
@endhasanyrole
// or
@hasanyrole('writer|admin')
    我是一個 writer 或者 admin
@else
    我既不是 writer 也不是 admin
@endhasanyrole
複製代碼

測試是否具備全部角色

@hasallroles($collectionOfRoles)
    這些角色我都是
@else
    這些角色我都不是
@endhasallroles
// or
@hasallroles('writer|admin')
    我既是 writer 也是 admin
@else
    我不是 writer 也不是 admin
@endhasallroles
複製代碼

Blade 模板 與 Permissions 權限

本擴展沒有提供權限相關的特殊的 Blade directives,而是使用 Laravel 自帶的 @can directive 來校驗用戶權限。舉個栗子:

@can('edit articles')
  //
@endcan
複製代碼

再舉個栗子:

@if(auth()->user()->can('edit articles') && $some_other_condition)
  //
@endif
複製代碼

使用多個 guards

使用 Laravel 默認的 auth 配置時,以上例子的全部的方法均可以正常使用,不須要作額外的配置。 當有多個 guards 時,校驗權限和角色時有點像命名空間。意味着每一個 guard 都有與之用戶模型相關的的角色和權限。

經過多重守衛來使用權限和角色

新增的權限和角色會默認使用系統默認的 guard (config('auth.defaults.guard')) 。新增權限和角色的時候,能夠經過 model 的屬性 guard_name 來指定 guard:

// Create a superadmin role for the admin users
$role = Role::create(['guard_name' => 'admin', 'name' => 'superadmin']);

// Define a `publish articles` permission for the admin users belonging to the admin guard
$permission = Permission::create(['guard_name' => 'admin', 'name' => 'publish articles']);

// Define a *different* `publish articles` permission for the regular users belonging to the web guard
$permission = Permission::create(['guard_name' => 'web', 'name' => 'publish articles']);
複製代碼

校驗用戶是否有指定 guard 的權限:

$user->hasPermissionTo('publish articles', 'admin');
複製代碼

用戶受權和角色賦予

你可使用上文所述的方法 using permissions via roles 給用戶受權和賦予角色。僅須要確保權限或者角色的 guard_name 與用戶的 guard 是同樣的,不然將會拋出一個GuardDoesNotMatch 異常

blade directives 中使用 multiple guards

全部的 blade directivesusing blade directives 對於多個 guards都是一樣適用,只須要將 guard_name 做爲第二個參數便可。舉個栗子:

@role('super-admin', 'admin')
    I am a super-admin!
@else
    I am not a super-admin...
@endrole
複製代碼

使用中間件

這個包包含 RoleMiddlewarePermissionMiddleware 倆箇中間件。 你能夠把他們添加到 app/Http/Kernel.php 文件中。

protected $routeMiddleware = [
    // ...
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
];
複製代碼

你可使用中間件保護你的路由:

Route::group(['middleware' => ['role:super-admin']], function () {
    //
});

Route::group(['middleware' => ['permission:publish articles']], function () {
    //
});

Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () {
    //
});
複製代碼

另外,你能夠經過 | (pipe) 字符把多個角色或者權限區分開:

Route::group(['middleware' => ['role:super-admin|writer']], function () {
    //
});

Route::group(['middleware' => ['permission:publish articles|edit articles']], function () {
    //
});
複製代碼

一樣, 你也能夠經過在構造函數中設置須要的中間件的方式保護你的控制器:

public function __construct() {
    $this->middleware(['role:super-admin','permission:publish articles|edit articles']);
}
複製代碼

捕獲角色和權限失敗

若是你想重寫默認的 403 響應, 你能夠經過應用的異常捕獲機制捕獲 UnauthorizedException 異常:

public function render($request, Exception $exception) {
    if ($exception instanceof \Spatie\Permission\Exceptions\UnauthorizedException) {
        // Code here ...
    }

    return parent::render($request, $exception);
}

複製代碼

使用 artisan 命令

你能夠經過控制檯使用 artisan 命令建立角色和權限。

php artisan permission:create-role writer
複製代碼
php artisan permission:create-permission "edit articles"
複製代碼

當你爲特定的守衛建立角色和權限時,你能夠將守衛名字做爲第二個參數:

php artisan permission:create-role writer web
複製代碼
php artisan permission:create-permission "edit articles" web
複製代碼

單元測試

在你應用的測試中,若是你沒有在 setUp() 測試方法中填充角色和權限的數據做爲測試的一部分,你極可能會遇到先有雞仍是先有蛋的問題,角色和權限還沒註冊到門臉上 (由於你測試它們是在門臉註冊以後的),解決這個問題很簡單:只須要像如下這樣在你的測試裏面的添加 setUp() 方法來從新註冊權限:

public function setUp() {
        // 首先像正常同樣調用如下父類的setUp()方法
        parent::setUp();

        // 如今從新註冊角色和權限
        $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();
    }
複製代碼

數據庫填充

關於數據庫填充有兩點須要注意

  1. 最好在填充數據以前更新如下 spatie.permission.cache ,以免緩存衝突的錯誤,這能夠用一條 Artisan 命令解決 (能夠參考後面故障排除:緩存部分) 或者直接在一個 seeder 類中清除緩存 (能夠看看下面的例子)。

  2. 這裏是一個數據填充示例,它先清除緩存,再建立權限,而後爲角色分配權限:

    use Illuminate\Database\Seeder;
    use Spatie\Permission\Models\Role;
    use Spatie\Permission\Models\Permission;
    
    class RolesAndPermissionsSeeder extends Seeder {
        public function run() {
        	// 重置角色和權限的緩存
            app()['cache']->forget('spatie.permission.cache');
    
            // 建立權限
            Permission::create(['name' => 'edit articles']);
            Permission::create(['name' => 'delete articles']);
            Permission::create(['name' => 'publish articles']);
            Permission::create(['name' => 'unpublish articles']);
    
            // 建立角色並賦予已建立的權限
            $role = Role::create(['name' => 'writer']);
            $role->givePermissionTo('edit articles');
            $role->givePermissionTo('delete articles');
    
            $role = Role::create(['name' => 'admin']);
            $role->givePermissionTo('publish articles');
            $role->givePermissionTo('unpublish articles');
        }
    }
    
    複製代碼

擴展

若是你想擴展示有的 Role 或者 Permission 模型,請注意:

  • 你的 Role 模型須要繼承
  • Spatie\Permission\Models\Role 模型
  • 你的 Permission 模型須要繼承 Spatie\Permission\Models\Permission 模型

若是你須要替換現有的 Role 或者 Permission 模型,請記住:

  • 你的 Role 模型須要實現
  • Spatie\Permission\Contracts\Role
  • 你的 Permission 須要實現 Spatie\Permission\Contracts\Permission

在這兩種狀況下, 不管是擴展仍是替換, 你須要在配置中指明你的新模型。 爲此,你必須在發佈配置後,更新在配置文件中的 models.rolemodels.permission 這兩個值。經過以下命令:

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
複製代碼

緩存

角色和權限數據被存在緩存中以加快性能。

當您使用提供的方法來操做角色和權限時,緩存會自動重置:

$user->assignRole('writer');
$user->removeRole('writer');
$user->syncRoles(params);
$role->givePermissionTo('edit articles');
$role->revokePermissionTo('edit articles');
$role->syncPermissions(params);
$permission->assignRole('writer');
$permission->removeRole('writer');
$permission->syncRoles(params);
複製代碼

然而,若是你直接經過數據庫操做權限、角色數據,而不是調用咱們提供的方法,應用程序不會出現你作的更改,除非你手動重置緩存。

手動重置緩存

如需手動重置此擴展包的緩存,運行命令:

php artisan cache:forget spatie.permission.cache
複製代碼

緩存標識符

建議:若是你正在使用諸如 redismemcached 等緩存服務,或者還有其餘的網站在你的服務器上運行,你可能會遇到緩存衝突,那麼設置本身的緩存前綴是深謀遠慮的。 在 / config / cache.php 文件中爲每一個應用程序設置獨有的標識符。 這將阻止其餘應用程序意外使用或更改您的緩存數據。

須要用戶界面?

這個擴展包沒有用戶界面,你須要本身建立。 請參考 Caleb Oki這篇教程

測試

composer test
複製代碼

更新日誌

請參閱 更新日誌 以瞭解更多最近更新的信息

貢獻代碼

詳情請參閱 貢獻代碼

安全

若是您發現任何與安全相關的問題, 請發送郵件至 freek@spatie.be,而不是使用問題跟蹤器。

明信片

你能夠無償使用此擴展包,但若是你在生產環境中使用,咱們將很是感謝你給咱們發送一張明信片,告訴咱們正在使用哪個擴展包。

咱們的地址是: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium.

咱們把全部收到的明信片發佈到了公司官網

鳴謝

這個擴展包很大程度上基於 Jeffrey Way 很是酷的 Laracasts 關於 權限和角色的課程。這是他的源碼地址 in this repo on GitHub.

特別要感謝 Alex Vanderbist ,他對v2版本有過很大幫助, 還有 Chris Brown ,他長期幫助咱們維護着這個擴展包。

資源

選擇

Povilas Korop 寫的文章 Laravel新聞的一篇文章裏作了對比。他用laravel-permission和Joseph SilberBouncer對比, 在咱們的書裏也是一個優秀的包。

支持咱們

Spatie是一個web設計機構,在比利時的安特衛普。你能夠看到咱們的開源項目的概覽 在咱們的網站上

你的業務依賴於咱們的貢獻嗎? 在Patreon資助咱們。這些錢將用於維護老產品,開發新產品。

許可

麻省理工學院許可證 (MIT). 有關更多信息,請參閱 許可證文件

相關文章
相關標籤/搜索