Laravelでパーミッション(権限)とロール(役割)によるアクセス制限を行う

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クラスと同様に管理画面から管理することも可能です。

参考サイト