LaravelでACL(Access Control List)を利用してアプリケーションのリソースに関してユーザのアクセスを制限したい場合があります。特にアプリケーション導入時にはアクセス制限を気にしなかった人達もアプリケーションが大きくなるにつれてアクセス制限を行いたいという要望が必ず出てきます。そんな時、Saptie laravel-permissionパッケージを使えばRole(ロール), Permission(パーミッション)をユーザに付与することでアクセス制限を簡単に実現することができます。

laravel-permissionのインストール

laravel-permissionパッケージのインストールを行います。公式のドキュメントに設定方法が記述されているので、その手順の指示に従って行なっていきます。

ドキュメントではLaravelの過去バージョンのインストール方法が記載されているかもしれませんがhttps://github.com/spatie/laravel-permission/blob/main/CHANGELOG.mdを確認することでサポートされているLaravelのバージョンを確認することができます。例えばLaravel11の場合は6.4.0 – 2024-02-28でサポートされています。新しいバージョンにも対応しているので安心して利用することができます。
fukidashi

composerコマンドを使ってlaravel-permissionパッケージのインストールを行います。


 $ composer require spatie/laravel-permission

インストール後は/config/app.phpファイルにサービスプロバーダーの登録を行います。


'providers' => [
    // ...
    Spatie\Permission\PermissionServiceProvider::class,
];
SeviceProviderは自動で登録されるのでconfig/app.phpに登録しなくても動作します。登録されているSeriveProviderはbootstrap/cache/packages.phpに記述されているので確認することができます。
fukidashi

laravel-permissionに必要となるテーブルを作成するためのmigrationファイルの作成とconfigファイルであるpersmission.phpファイルの作成w行います。migrationファイルとpermission.phpファイルを作成するコマンドが準備されているので、下記のコマンドを実行します。


 $ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

   INFO  Publishing assets.  

  Copying file [vendor/spatie/laravel-permission/config/permission.php] to [config/permission.php]  DONE
  Copying file [vendor/spatie/laravel-permission/database/migrations/create_permission_tables.php.stub] to [database/migrations/2024_03_11_111912_create_permission_tables.php] 

コマンドが完了するとdatabase\migrationsフォルダにcreate_permission_tables.phpという名前のmigrationファイルが作成されます。configディレクトリにpermission.phpファイルが作成されます。

migrationファイルを見ることで作成されるテーブルがどのような構成になっているかを確認することができます。

php artisan migrateコマンドでテーブルの作成を行います。


 $ php artisan migrate

   INFO  Running migrations.

  2024_03_11_111912_create_permission_tables ........... 930ms DONE

最後にUserモデルにHasRoles traitを追加します。Userモデルに設定されているTraitはLaravelのバージョンや設定に異なるのでNotificable以外のTraitも設定されている場合があるので他のTraitを誤って削除しないように注意してください。


namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use Notifiable,HasRoles;

ここまででインストールと初期設定は完了です。

Permissionを設定する

Permission(パーミッション)を通してユーザが実行する処理に対して許可を与えることができます。例えばブログの記事を作成したユーザのみに更新する権限を与えたい場合は更新を許可するPermissionをユーザに与えることで更新することが可能となります。

作成した記事の更新に関するPermission “edit posts”を作成して、ユーザに”edit post” Permissionを付与してアクセスの制限を行います。

Permissionの設定によるアクセス制限
Permissionの設定によるアクセス制限

ここではPermission “edit posts”のみ扱いますが、1つのユーザに複数のPermissionを付与することも可能です。

Permissionを作成する

createメソッドを使ってPermission ‘edit posts’を作成します。


use Spatie\Permission\Models\Permission;

Permission::create(['name' => 'edit posts']);

作成したPermission “edit posts”をユーザID1を持つユーザに付与します。


$user = \App\User::find(1);

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

Bladeファイルで設定を行う

BladeファイルでPermissionの設定を行う場合は、@canディレクティブを利用します。

@canメソッドに”edit posts”を指定することで、”edit posts”のPermissionを持つユーザのみ更新ボタンが表示され、持たないユーザには更新ボタンが表示されません。


<td>
	@can('edit posts')
	<a href="/posts/{{ $post->id }}/edit" class="btn btn-success">更新</a>
	@endcan
</td>

ユーザID1のユーザがアクセスすると更新ボタンが表示。

"edit posts"のパーミッションを持つユーザ
“edit posts”のパーミッションを持つユーザ

‘edit posts’パーミッションを持たないユーザID3のユーザがアクセスすると更新ボタンは表示されません。

"edit posts"のパーミッションを持たないユーザ
“edit posts”のパーミッションを持たないユーザ

Contoller(コントロラー)で制御

コントロラーでPermissionを設定したい場合はcanメソッドを利用することでアクセスを制限することができます。”edit posts”を付与されたユーザのみcanメソッドのif文でtrueが戻り、更新画面が表示されます。”edit posts”を付与されていないユーザには別の処理、もしくはメッセージでアクセス権限がないことを伝える必要があります。


public function edit(Post $post){

    $user = \App\User::find(1);

    if($user->can('edit posts')){

    	return view('posts.edit',compact('post'));

    }else{
     // 'edit posts'パーミッションを持たない場合の処理
    }

}
コントローラーの場合は更新ボタンを押した後の処理でアクセス制限を行っています。
fukidashi

middleware(ミドルウェア)で制御

middleware(ミドルウェア)でアクセス制限を行うことができますが、app/Http/Kernel.phpへのミドルウェアの追加が必要になります。


protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
     ・
     ・
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
];}

web.phpルーティングファイルでミドルウェアを設定します。下記では”edit posts”のPermissionを持っていないユーザはpostsへアクセスすることができません。


Route::resource('/posts','PostController')->middleware(['permission:edit posts']);
“edit posts”のPermissionを付与されていないユーザは更新だけではなくpostsの一覧も表示されません。resourceメソッドではなくget, postメソッドを個別に設定すればそれぞれの処理に対してアクセス制限を行うことができます。
fukidashi

下記のようにgroupメソッドを利用することもできます。


Route::group(['middleware' => ['permission:edit posts']], function () {
	Route::get('/posts','PostController@index');
});

上記ではgetメソッドのみに対してアクセス制限を行っています。

Permissionのメソッド

Permissionには作成、付与、確認などいくつかのメソッドが準備されています。準備されているメソッドについて説明を行っていきます。

Permissionの作成

createメソッドを使ってPermissionを作成することができます。


use Spatie\Permission\Models\Permission;

$permission = Permission::create(['name' => 'edit articles']);

ユーザへのPermissionの付与方法

ユーザへPermissionを付与する時、1度に1つだけではなく1度に複数付与することができます。

1つのPermissionを付与する場合


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

複数のPermissionを1度に付与する場合


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

複数のPermissionを1度に付与する場合(配列利用)


$user->givePermissionTo(['edit articles', 'delete articles']);

設定されているPermissionを一度削除して、指定してPermissionを設定する場合はsyncPermissionsメソッドを利用します。


$user->syncPermissions(['edit articles', 'delete articles']);

付与したPermissionの削除方法

付与したPermissionを削除するためには、revokePermissionToメソッドを利用します。


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

付与されたPermissionの確認方法

ユーザに付与されているPermissionを確認することができます。戻り値はtrue, falseになります。


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

Permissionの名前ではなくidで指定することもできます。作成したPermissionの情報は、permissionsテーブルの中に保存されているので、テーブルでidを確認することも可能です。


sqlite> select * from permissions;
id|name|guard_name|created_at|updated_at
1|edit posts|web|2019-07-23 01:42:55|2019-07-23 01:42:55
2|publish posts|web|2019-07-23 03:05:08|2019-07-23 03:05:08

上記ではid 1が”edit posts”, id 2が”publish posts”です。”edit posts”を付与したい場合は、idで下記のように付与することができます。


$user->hasPermissionTo('1');
$user->hasPermissionTo(Permission::find(1)->id);

指定した複数のPermissionの中からどれか1つでもPermissionを持っているかチェックする場合はhasAnyPermissionメソッドが使えます。指定するPermissionは名前とPermissionのidを混合しても行うことができます。


$user->hasAllPermissions(['edit articles', 2, 'unpublish articles']);

指定してすべてのPermissionを持っているかチェックをするためには、hasAllPermissionsメソッドが使えます。


$user->hasAllPermissions(['edit articles', 2, 'unpublish articles']);

Roleの設定について

ここまではPermissionを直接ユーザに付与してアクセス制限を行いました。ここからはRoleにPermissionを付与し、そのRoleをユーザへ付与しアクセス制限を行います。

PermissionとRoleの関係
PermissionとRoleの関係

Roleを作成する

createメソッドを使ってRole “writer”を作成します。


use Spatie\Permission\Models\Role;

$role = Role::create(['name' => 'writer']);

作成したRoleをユーザID2を持つユーザに付与します。


$user = \App\User::find(2);

$user->assignRole('writer');

Roleを使ってアクセス制限

Roleの作成とユーザへのRole付与は完了しましたが、RoleへのPermissionの付与は行なっていません。

PermissionがなくてもRoleのみを使ってアクセス制限を行うことができます。

BladeファイルでRoleを使ってアクセス制限を行いたい場合は、@roleディレクティブを使用することができます。


<td>
	@can('writer')
	<a href="/posts/{{ $post->id }}/edit" class="btn btn-success">更新</a>
	@endcan
</td>

“writer”のRoleを持つユーザのみ更新ボタンが表示され、持たないユーザには更新ボタンは表示されません。@roleと@canとはディレクティブが異なりますが、PermissionとRoleでほぼ同じ方法でアクセス制限を行うことができます。

RoleにPermissionを付与

Roleのみを使用しても@roleディレクティブを利用すればアクセス制限が行えることがわかりましたが、より柔軟にアクセス制限を行うためにRoleにPermissionを付与してアクセス制限を行います。

新たに”editor”というRoleを作成します。


$use Spatie\Permission\Models\Role;

$role = Role::create(['name' => 'editor']);

“editor”のRoleを作成したことでシステムには、”writer”と”editor”の2つのロールが存在します。”writer”には、”create posts”, “edit posts”の2つのPermission、editorには”edit posts”のみPermissionを付与します。

PermissonとRoleの関係
PermissonとRoleの関係
  • writer ・・・”create posts”, “edit posts”
  • editor・・・”edit posts”

“create posts”はブログの記事を作成する許可、”edit posts”は作成されている記事を更新する許可を与えるPermissionです。

データベース内に作成されているpermissionsテーブルとrolesテーブルを使ってそれぞれに割り当てられているidを確認します。

Roleのidは名前がわかっていればRole::where(‘name’,’writer’)->first()->idでも確認することができます。
fukidashi

permissionsテーブルには、id 1に”edit posts”, id 2に”create posts”が登録されています。


sqlite> select * from permissions;
id|name|guard_name|created_at
1|edit posts|web|2019-07-23 01:42:55
2|create posts|web|2019-07-23 03:05:08

rolesテーブルには、id 1にwriter, id 2にeditorが登録されています。


sqlite> select * from roles;
id|name|guard_name|created_at
1|writer|web|2019-07-23 03:42:52
2|editor|web|2019-07-23 04:15:48

givePermissionToメソッドを使用してRoleにPermissionを付与します。


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

// Permissionをテーブルから取得
$create_permission = Permission::find(2);

$edit_permission = Permission::find(1);

// Roleをテーブルから取得
$role_write = Role::find(1);

$role_editor  = Role::find(2);

// RoleにPermissionを付与
$role_editor->givePermissionTo($edit_permission);

$role_writer->givePermissionTo($create_permission);

$role_writer->givePermissionTo($edit_permission);

Roleをユーザに付与

設定したRoleはユーザに付与します。ユーザへの付与は、assignRoleメソッドを利用します。ユーザID1を持つユーザに”editor”のRole、ユーザID2を持つユーザに”writer”のRoleを付与します。


$user = User::find(1);

$user->assignRole('writer');

$user = User::find(2);

$user->assignRole('editor');
RoleとPermissionとUserの関係
RoleとPermissionとUserの関係

RoleのPermissionを利用してアクセス制限

Roleに付与したPermissionを使ってアクセスの制限を行います。

“create posts”のPermissionを持っていれば、ブログの記事作成ボタンが表示されるように@canディレクティブを使用して設定を行います。


@can('create posts')

<p><a class="btn btn-primary" href="/posts/create">ブログ記事作成</a></p>

@endcan

“edit posts”のPermissionを持っていれば、更新ボタンが表示されるように@canディレクティブを使用して設定を行います。


<td>
	@can('edit posts')
	<a href="/posts/{{ $post->id }}/edit" class="btn btn-success">更新</a>
	@endcan
</td>

“writer” Roleを持っているユーザでアクセスすると作成ボタンも更新ボタンも表示されます。”writer” Roleは”create posts”と”edit posts”のPermissionを持っています。

writeロールを持つユーザ
writeロールを持つユーザ

“editor”Roleを持っているユーザでアクセスすると更新ボタンのみ表示されます。”editor”Roleは”edit posts”のPermissionを持っています。

“create posts”のPermissionを持っていないため、ブログ記事作成ボタンが表示されません。

editorロールを持つユーザでアクセス
editorロールを持つユーザでアクセス

どちらのRoleも持っていないユーザは、作成ボタンも更新ボタンも表示されません。

ロールを持たないユーザでアクセス
ロールを持たないユーザでアクセス

このようにRoleに付与されているPermissionによってアクセス制限ができることが確認できました。

コントローラーやmiddlewareでのアクセス制限はPermissionで行った設定によって行うことができます。

Roleの役割

複数のRole、Permissionを設定していくと下記のように段々と複雑になってきます。

複数のRole、Permission, Userの関係
複数のRole、Permission, Userの関係

もしRoleがなければPermissionをユーザに付与しなければいけないため、下記のようになります。

Roleがない状態
Roleがない状態

ここに1名のユーザが新しく登録された場合を考えましょう。そのユーザにB, C, DのPermissionを渡したい場合は、Roleがあれば、YのRoleを付与することでユーザにB, C, DのPermissionを付与することができます。Roleがなければ、B, C, DのPermission設定が必要になり、複雑化することがわかります。

Roleありでユーザ追加
Roleありでユーザ追加
Roleなしでユーザ追加
Roleなしでユーザ追加

RoleがあることでPermissionの管理が楽になることがわかります。

Roleのメソッド

RoleにもPermissionと同様に作成、付与、確認などいくつかのメソッドが準備されています。どのメソッドも使用する可能性があるため下記で説明を行なっています。

Roleの作成

createメソッドを使ってRoleを作成することができます。


$use Spatie\Permission\Models\Role;

$role = Role::create(['name' => 'editor']);

RoleへのPermissionの付与、削除、確認方法

RoleにPermissionを付与するためには、givePermissionToメソッドを使用します。


$role->givePermissionTo('edit posts');

Roleに付与したPermissonを削除する場合は、revokePermissionToメソッドを利用します。


$role->revokePermissionTo('edit posts');

Roleに付与されているPermissionを確認したい場合は、hasPermissionToメソッドを利用します。


$role-> hasPermissionTo('edit posts');

ユーザへのRoleの付与、削除、確認方法

ユーザにRoleを付与する場合は下記のように一度に複数のRoleを付与することができます。付与はassignRoleメソッドを利用します。


$user->assignRole('writer');

// 一度に複数のRoleを設定
$user->assignRole('writer', 'editor');
// 配列を使用してもRoleの設定が可能
$user->assignRole(['writer', 'editor']);

付与したRoleを削除するのは、removeRoleメソッドを利用します。


$user->removeRole('writer');

syncRolesメソッドを利用すれば現在設定されるRoleを一度削除し、指定したRoleを付与することができます。


$user->syncRoles(['writer', 'editor']);

ユーザがRoleを付与されているか確認する時は、hasRoleメソッドを利用します。


$user->hasRole('writer');

指定した複数のRoleの中からどれか1つ付与されているかチェックする場合は、hasAnyRoleメソッドが使えます。また指定したすべてのRoleが付与されているか確認する時は、hasAllRolesメソッドを利用します。


$user->$user->hasAnyRole(['writer', 'editor']);
$user->$user->hasAllRoles(['writer', 'editor']);

permissionには直接ユーザに付与する方法とRoleを経由してユーザに付与する方法があることを説明しました。どちらの方法で付与するか確認することも可能です。直接の場合は、getDirectPermissionsメソッド、Role経由の場合は、getPermissionsViaRolesメソッドを利用します。両方を一括で確認したい場合は、getAllPermissionsメソッドを利用します。


// 直接Permissionを付与したもの
$user->getDirectPermissions()
// Role経由でPermissionを付与したもの
$user->getPermissionsViaRoles();
// ユーザに付与されているPermission
$user->getAllPermissions();