Laravelでパーミッション(権限)とロール(役割)によるアクセス制限を行う
Laravelでパーミッション(権限)とロール(役割)による表示の変更、アクセス制限などを行うための設定方法をご紹介します。
動作環境
- PHP ^7.3
- Laravel 8
- Laravel-permission v4
- Laravel Enum 3.x
Laravel-permissionのインストール・設定
Laravelにパーミッション機能を付与するためにLaravel-permissionをインストールします。
$ composer require spatie/laravel-permission
config/app.php
に下記のコードを追加します。
'providers' => [
// ...
Spatie\Permission\PermissionServiceProvider::class,
];
次のコマンドを実行し設定ファイルを生成します。
$ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
次のコマンドを実行し、パーミッションとロール用のDBを登録します。
$ php artisan migrate
Laravel Enumのインストール
今回はソース上でパーミッションとロールを管理するためにlaravel-enumを用います。
下記のコマンドを用いてlaravel-enumをインストールします。
$ composer require bensampo/laravel-enum
Enumクラスを作成することにより、マジックナンバーをなくしたり、IDEによる予測変換を行うことができるようになります。
ロールクラスの作成
Enumクラスの作成
使用するロールを定義したEnumクラスを作成します。
Laravel-permissionの仕様などを加味して、enumの値は、数値ではなく英語で設定します。
<?php
namespace App\Enums;
use BenSampo\Enum\Enum;
final class RoleType extends Enum
{
const Administrator = 'administrator';
const Moderator = 'moderator';
const Subscriber = 'subscriber';
const SuperAdministrator = 'super administrator';
}
設定した値は、RoleType::Administrator
と記述することで取得する事ができます。
翻訳ファイルの作成
resources/lang/ja/enums.php
にロールの日本語名を登録していきます。
<?php
use App\Enums\RoleType;
return [
RoleType::class => [
RoleType::Administrator => '管理者',
RoleType::Moderator => 'モデレーター',
RoleType::Subscriber => '購読者',
RoleType::SuperAdministrator => 'スーパー管理者',
],
// ...
];
ロールクラスの作成
Laravel-permissionのロールクラスを継承して、Laravel Enumに対応したロールクラスを作成します。
<?php
namespace App\Models;
use Spatie\Permission\Models\Role as SpatieRole;
use App\Enums\Role as RoleEnum;
class Role extends SpatieRole
{
public function scopeNotSuperAdministrator($query)
{
return $query->where('name', '<>', RoleEnum::SuperAdministrator);
}
public function getDescriptionAttribute(): string
{
$name = $this->name;
$description = RoleEnum::getDescription($name);
return $description !== '' ? $description : $name;
}
public function isSystemDefined(): bool
{
return RoleEnum::hasValue($this->name);
}
public function isAdministrator(): bool
{
return $this->name === RoleEnum::Administrator;
}
public function isSuperAdministrator(): bool
{
return $this->name === RoleEnum::SuperAdministrator;
}
}
ロールの日本語名を表示する場合、$role->description
とすると日本語名が表示されます。
作成したクラスをLaravel-permissionに登録
config/permission.php
を変更し作成したクラスをLaravel-permissionに登録します。
<?php
return [
'models' => [
'role' => App\Models\Role::class,
// ...
],
// ...
];
パーミッションクラスの作成
Enumクラスの作成
使用するパーミッションを定義したEnumクラスを作成します。
Laravel-permissionの仕様などを加味して、enumの値は、数値ではなく英語で設定します。
<?php
namespace App\Enums;
use BenSampo\Enum\Enum;
final class PermissionType extends Enum
{
// Posts
public const ReadPosts = 'read posts';
public const EditPosts = 'edit posts';
public const DeletePosts = 'delete posts';
}
翻訳ファイルの作成
resources/lang/ja/enums.php
にパーミッションの日本語名を登録していきます。
<?php
use App\Enums\PermissionType;
return [
PermissionType::class => [
// Posts
PermissionType::ReadPosts => '投稿の閲覧',
PermissionType::EditPosts => '投稿の作成・編集',
PermissionType::DeletePosts => '投稿の削除',
],
// ...
];
パーミッションクラスの作成
Laravel-permissionのパーミッションクラスを継承して、Laravel Enumに対応したパーミッションクラスを作成します。
<?php
namespace App\Models;
use APP\Enums\Permission as PermissionEnum;
use Spatie\Permission\Models\Permission as SpatiePermission;
class Permission extends SpatiePermission
{
public function getDescriptionAttribute(): string
{
$name = $this->name;
$description = PermissionEnum::getDescription($name);
return $description !== '' ? $description : $name;
}
public function isSystemDefined(): bool
{
return PermissionEnum::hasValue($this->name);
}
}
パーミッションの日本語名を表示する場合、$permission->description
とすると日本語名が表示されます。
作成したクラスをLaravel-permissionに登録
config/permission.php
を変更し作成したクラスをLaravel-permissionに登録します。
<?php
return [
'models' => [
'permission' => App\Models\Permission::class,
// ...
],
// ...
];
Userクラスの設定
Userクラスにロールとパーミッションの機能を追加します。
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
// ...
}
パーミッションの割当
ロールへのパーミッションの割当は下記のように行います。
$role->givePermissionTo(PermissionEnums::ReadPosts);
ロールの割当
ユーザーへのロールの割当は下記のように行います。
$user->assignRole(RoleEnums::Administrator);
Seederの作成
Seederの作成し、Enumsで定義したロールとパーミッションをDBに登録します。
<?php
namespace Database\Seeders;
use App\Enums\Role as RoleEnums;
use App\Enums\Permission as PermissionEnums;
use App\Models\User;
use App\Models\Role;
use App\Models\Permission;
use Illuminate\Database\Seeder;
class RolesAndPermissionsSeeder extends Seeder
{
public function run()
{
// Reset cached roles and permissions
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// create permissions
foreach (PermissionEnums::getValues() as $permission) {
Permission::create(['name' => $permission, 'guard_name' => 'user']);
}
// create roles and assign created permissions
Role::create(['name' => RoleEnums::Moderator, 'guard_name' => 'user'])
->givePermissionTo([
PermissionEnums::ReadPosts,
PermissionEnums::EditPosts,
PermissionEnums::DeletePosts,
]);
Role::create(['name' => RoleEnums::Subscriber, 'guard_name' => 'user'])
->givePermissionTo([
PermissionEnums::ReadPosts,
]);
Role::create(['name' => RoleEnums::Administrator, 'guard_name' => 'user'])
->givePermissionTo(Permission::all());
$super_admin = Role::create(['name' => RoleEnums::SuperAdministrator, 'guard_name' => 'user'])
->givePermissionTo(Permission::all());
}
}
artisanコマンドでSeederを実行します。
$ php artisan db:seed --class=RolesAndPermissionsSeeder
Middlewareによるアクセス制限
上記の設定が完了後、Middlewareを用いることで、Routeごとにアクセスの制限を行う事ができるようになります。
例:
<?php
use App\Helpers\Middleware;
use Illuminate\Support\Facades\App;
use App\Enums\Permission;
Route::group([
'prefix' => 'posts',
'as' => 'posts.',
'middleware' => Middleware::permission(Permission::ReadPosts),
], function () {
Route::get('/', 'PostController@index')
->name('index');
Route::get('/create', 'PostController@create')
->middleware(Middleware::permission(Permission::EditPost))
->name('create');
Route::get('/{post}', 'PostController@edit')
->name('show');
Route::get('/{post}/edit', 'PostController@edit')
->middleware(Middleware::permission(Permission::EditPost))
->name('edit');
Route::patch('/{post}', 'PostController@delete')
->middleware(Middleware::permission(Permission::EditPost))
->name('update');
Route::delete('/{post}', 'PostController@delete')
->middleware(Middleware::permission(Permission::DeletePost))
->name('delete');
});
View上での表示変更
Blade上で@can
~ @endcan
を用いることで、権限ごとに表示を切り替える事もできます。
例:
@can(\App\Enums\Permission::ReadPosts)
閲覧権限あり
@else
閲覧権限なし
@endcan
@can
の他に@cannot
, @canany
, @guest
が使用できます。
スーパーユーザーの設定
Laravel-permissionでは、全ての権限を無視して実行することができるロールを設定することが可能です。
app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Modules\Admin\Enums\Role;
class AuthServiceProvider extends ServiceProvider
{
public function boot()
{
$this->registerPolicies();
// Implicitly grant "Super Admin" role all permissions
// This works in the app by using gate-related functions like auth()->user->can() and @can()
Gate::before(function ($user, $ability) {
return $user->hasRole(Role::SuperAdministrator) ? true : null;
});
}
}
終わりに
今回作成したロールクラスやパーミッションクラスは、Model
クラスを継承しているので、他のModel
クラスと同様に管理画面から管理することも可能です。