LaravelのGate(ゲート)機能で権限(ロール)によるアクセス制限を実装する
- 公開:
- 更新:
- カテゴリ: PHP Laravel
- タグ: PHP,Laravel,5.5,5.4,5.3,Auth,Gate,5.6
LaravelなどのPHPフレームワークでWEBシステムを構築していると、管理画面機能の中で「ロール(権限)」による機能制限をつけたいといった要件は結構あったりします。
WEBシステムも、使う人が多くなればなるほど権限による機能制限は必要になってきますが、権限制限に関してはシステム全体にかかってくるもののわりに縁の下の力持ち的な役回りの為、あまり時間が割けないのでスムーズに構築してしまいたいものです。
そこで今回は、LaravelのGate(ゲート)機能を使って手早く権限(ロール)によるアクセス制限を実装します。
- アジェンダ
開発環境
- Linux CentOS 7
- Apache 2.4
- PHP 7.2/7.1
- Laravel
Laravelのバージョンについては、5.6/5.5/5.4/5.3にて動作確認済みです。
Laravelのルートディレクトリを「laravel/」とします。
システムにおける権限(ロール)の考え方
Laravelを触る前に、前提条件というか権限の扱い方や考え方を決めておいた方が実装もスムーズだと思うので、ここで紹介しておきます。
ちなみにいきなり脇道にそれますが、この手のシステム開発上では「権限」と「ロール」は同じ意味として用いられますが、ロール(role)は英語的には「役割」という意味なんだそうです。(ちなみに権限はオーソリティ(Authority))
どんな権限を持たせるか
初期の想定は「システム管理者」「管理者」「一般ユーザ」の3つとします。それぞれのロールについては以下の通りです。
- 「システム管理者」
- 我々開発者のアカウント。これ以上の権限は絶対に無いので、もちろんフル権限。
- 「管理者」
- 運用管理者アカウント。フル権限
- 「一般ユーザ」
- 作業者アカウント。作業が行えれば良いので、管理機能以外の権限。
ここで、同じフル権限なのにシステム管理者と運用管理者を分ける必要があるのかと思われがちですが、誰かの為に作るシステムであるならば、開発者が管理者のアカウントに名を連ねるのはナンセンスです(他社ならなおさら)。なのでシステムチェックの為の開発者アカウントは分けるのがマストです。そして場合にもよりますが、システム管理者のアカウントは基本、アカウント一覧には出しません(少なくとも、管理者以下には)。
レアケースですが、管理者でも使えない開発者のみの機能をそっと忍ばせる場合なんかにも使えます。
他社案件でもシステム管理者としてアカウント一覧に表示させるケースは多くあります。
権限をどう設定するか
ではこれらのロールに関して、権限をどう設定していくか。ユーザに割り当てる(ユーザ情報に格納する)ロールそれぞれを識別できる値が必要です。
まずは、名前で識別するパターンの場合。
- システム管理者 = system
- 管理者 = admin
- 一般ユーザ = user
ロール名「system」「admin」「user」での切り分けも良いですが、私の場合、参照させるなら数値の方がスムーズと考えます。
- システム管理者 = system = 1
- 管理者 = admin = 2
- 一般ユーザ = user = 3
※数値が小さいほど権限は大きくなります。
権限数値の切り方(設計)を考える
ロールを数値で管理する場合、先ほどのように必要な権限だけを上から並べてしまうと権限を追加したい場合に難しくなるので、ロール値に余裕を持たせて設計するのがベターです。
- 1:システム管理者
- 2:
- 3:
- 4:
- 5:サイト管理者
- 6:
- 7:
- 8:
- 9:
- 10:一般ユーザ
こうする事で、後で新たな権限を増やす必要がある場合に、既存ロールの間への新規追加でも対応できます。
またこの場合、
「数値が小さくなるほど権限が大きく、基本的には自身より下位権限で許可される機能については利用が可能」
という前提で権限は管理されます。
なぜこうするのかと言えば、そもそも権限管理というのは
「この権限ならできる」「この権限ではできない」
を判別する為のものです。今回の場合は3つの権限しかありませんが、もっと細かいロール設定がある場合に、「この権限以上であれば操作可能」といった事も良くあるので、許可を行う権限に対して、いちいち「○番と○番と○番は許可」といった方法ではなく、「○番以下のロールは許可」という方法でまとめる権限管理が行える利点があります。
まとめて指定できれば、許可漏れもなくなります。また、新たなロールを追加する際にも、場合によっては権限設定が不要になったりします。
ロールを数値で管理するというのは、結構利点があるのでおすすめです。
Laravelで権限によるアクセス制限を実装する
それでは実際にLaravelへ機能の実装を行っていきます。
ユーザ情報の追加
まずは、権限を管理する為、ユーザ情報に権限カラムを追加します。
laravelルートディレクトリへ移動し、以下のartisanコマンドを叩いてマイグレーションファイルを作成します。
# Laravelルートディレクトリへ移動する
cd /path/to/laravel
# artisanコマンドでマイグレーションファイルを生成する
php artisan make:migration add_column_role_users_table --table=users
# 実行結果
[demo@localhost laravel]# php artisan make:migration add_column_role_users_table --table=users
Created Migration: 2018_01_14_122201_add_column_role_users_table
マイグレーションファイルは laravel/database/migrations 配下に生成されます。
laravel
├─ database
│ ├─ migrations
│ │ ├─ 2014_10_12_000000_create_users_table.php
│ │ ├─ 2014_10_12_100000_create_password_resets_table.php
│ │ └─ 2018_01_14_122201_add_column_role_users_table.php
add_column_role_users_table.php を開き、権限カラム(role)を追加する為に以下を記述します。
- laravel/database/migrations/add_column_role_users_table.php
-
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddColumnRoleUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->tinyInteger('role')->default(0)->after('password')->index('index_role')->comment('ロール');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
}
カラム名「role」をTINYINT型でpasswordカラムの後に追加。さらにインデックスを付与する。という流れになっています。
記述が完了したらマイグレーションを実行し、テーブルにカラムを追加します。以下のartisanコマンドを叩きます。
# Laravelルートディレクトリへ移動する
cd /path/to/laravel
# artisanコマンドでマイグレーションを実行する
php artisan migrate
# 実行結果
[demo@localhost laravel]# php artisan migrate
Migrating: 2018_01_14_122201_add_column_role_users_table
Migrated: 2018_01_14_122201_add_column_role_users_table
MySQLにログインして、カラムが追加されているか確認します。
mysql> show columns from users;
+----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
| role | tinyint(4) | NO | MUL | 0 | |
| remember_token | varchar(100) | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+----------------+------------------+------+-----+---------+----------------+
roleカラムが追加されている事を確認できました。
ゲート定義
次に、権限(ロール)の設定を行います。
どうロール制限を行っていくかは数多方法がありますが、LaravelにはGate(ゲート)と呼ばれる機能があり、これを用いる事でもロール制限を実現できるので、今回はゲートを使って実装していきます。
まずはロールの定義から。AuthServiceProvider が実装ファイルになります。
- laravel/App/Providers/AuthServiceProvider.php
-
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
ここのboot()メソッドに、以下の記述を行います。
public function boot()
{
$this->registerPolicies();
// 開発者のみ許可
Gate::define('system-only', function ($user) {
return ($user->role == 1);
});
// 管理者以上(管理者&システム管理者)に許可
Gate::define('admin-higher', function ($user) {
return ($user->role > 0 && $user->role <= 5);
});
// 一般ユーザ以上(つまり全権限)に許可
Gate::define('user-higher', function ($user) {
return ($user->role > 0 && $user->role <= 10);
});
}
ここでは3つの権限制限を設定しています。
- 開発者のみ許可
- ロール値が「1」のユーザのみ。
- 管理者以上
- ロール値が「1~5」のユーザのみ。
- 一般ユーザ以上
- ロール値が「1~10」のユーザ(現状全ユーザ)。
ルーティング
それでは設定したゲート定義をもとに、これから制限をかけていきます。制限はルーティングでかけます。権限の無いユーザに関してはアクセスを許さない。という断固たる姿勢で臨みたいと思います。
- laravel/routes/web.php
-
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
// 全ユーザ
Route::group(['middleware' => ['auth', 'can:user-higher']], function () {
// ユーザ一覧
Route::get('/account', 'AccountController@index')->name('account.index');
});
// 管理者以上
Route::group(['middleware' => ['auth', 'can:admin-higher']], function () {
// ユーザ登録
Route::get('/account/regist', 'AccountController@regist')->name('account.regist');
Route::post('/account/regist', 'AccountController@createData')->name('account.regist');
// ユーザ編集
Route::get('/account/edit/{user_id}', 'AccountController@edit')->name('account.edit');
Route::post('/account/edit/{user_id}', 'AccountController@updateData')->name('account.edit');
// ユーザ削除
Route::post('/account/delete/{user_id}', 'AccountController@deleteData');
});
// システム管理者のみ
Route::group(['middleware' => ['auth', 'can:system-only']], function () {
});
ミドルウェアとしてゲートを指定し、権限制限を行っています。ログインを伴うものなので、Auth+ゲートでミドルウェアグループを形成し、その中に、ロールに該当する機能を配置しています。
上記の場合、ユーザ一覧ページ関してはログインした全てのユーザに公開し、ユーザの新規登録・編集・削除については管理者権限以上の権限を持つユーザのみに制限されます。
これで、一般権限のユーザではユーザ情報の操作が行えなくなりました。
もし制限されているユーザが該当機能にアクセスしようとした場合、Laravelは403ステータスコードのHTTPレスポンスを返します。
403エラーページの作成
Laravelはデフォルトでは403ページがありませんので、ロール制限がかかった場合にはエラー画面が表示されてしまいます。
それを回避する為に、403ページを作成します。
ちなみに403とは、「Forbidden」を意味し、アクセス権限がない場合などにも使われるHTTPステータスコードです。
下記階層に403.blade.phpを作成し、以下を記述します。
- laravel/resources/views/errors/403.blade.php
-
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>403 Forbidden</title>
<style>
.error-wrap {
padding: 5px 20px;
border: 1px solid #dcdcdc;
display: inline-block;
box-shadow: 0px 0px 8px #dcdcdc;
}
h1 { font-size: 18px; }
p { margin-left: 10px; }
</style>
</head>
<body>
<div class="error-wrap">
<section>
<h1>403 Forbidden</h1>
<p>You do not have access.</p>
</section>
</div>
</body>
</html>
こうする事で、ロール制限に引っかかった場合に、403ページが表示されます。
権限によるHTMLの出し分け
Gate機能によってロールを設定していると、Bladeテンプレート、いわゆるビューの部分でもそれによって出し分けが出来ます。
例えばナビゲーションなど、以下のように@canディレクティブを用いて記述する事で、該当権限のユーザにのみ表示する事が出来ます。
<nav>
<ul>
@can('system-only') {{-- システム管理者権限のみに表示される --}}
<li><a href="">機能1</a></li>
@elsecan('admin-higher') {{-- 管理者権限以上に表示される --}}
<li><a href="">機能2</a></li>
<li><a href="">機能3</a></li>
@elsecan('user-higher') {{-- 一般権限以上に表示される --}}
<li><a href="">機能4</a></li>
<li><a href="">機能4</a></li>
<li><a href="">機能4</a></li>
<li><a href="">機能5</a></li>
@endcan
</ul>
</nav>
まとめ
作業は以上で完了です。
ロールでのアクセス制限機能は色々な方法で実装できるので今回の方法が全てではありませんが、少ない手順とコードで実装できるのでおすすめです。ぜひ試してみてください。