よく一緒に語られること多いけど認証と認可は別物だよ。
前提
- Laravel 5.4
- PHP7.1
- ロールは複数(管理者、一般ユーザー1、一般ユーザー2、一般ユーザー3・・・etc)
- ロールは特に継承関係は持ってなく単独
- Laravel のポリシーによる認可は今回は使わない
解決したいこと
- コントローラー毎にアクセス可能なロールを指定したい
- アクセス可能なロールをAND条件じゃなくてORで複数指定したい(管理者 or 一般ユーザー1ならOK等)
解決方法
- アクセス可能なロールの指定方法をパイプ区切りで複数指定する
-
AuthServiceProvider
でパイプを再帰的に処理
AuthServiceProviderを作成
$user->isなんとか()
はやっつけ
app/Providers/AuthServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
public function boot()
{
// ロール毎に Gate にクロージャーを定義
Gate::define('admin', function (\App\User $user) {
return $user->isAdmin();
});
Gate::define('general1', function (\App\User $user) {
return $user->isGeneral1();
});
Gate::define('general2', function (\App\User $user) {
return $user->isGeneral2();
});
// $ablities をパイプ区切りで複数判定
// ⇒ $abilities は 'admin|general1' といったような形式を想定
Gate::before(function (\App\User $user, $abilities, $arguments = []) {
if (strpos($abilities, '|') === false) {
return;
}
$abilities = array_filter(explode('|', $abilities));
if (! count($abilities)) {
return;
}
foreach ($abilities as $ability) {
// 再度Gate::beforeクロージャーが呼ばれるが、
// 次に渡されるときは | がないのでGate::define 側で定義したメソッドがよばれる
if ($user->can($ability, $arguments)) {
return true;
}
}
return false;
});
}
}
Gate::before
で呼び出してるクロージャーがミソで、$user->can を呼び出すとパイプで区切られた値にて再度このクロージャーが呼び出される。クロージャー内で何も返さずに return すると今度はロール毎にGateで定義したクロージャーが呼び出される。
AuthServiceProviderの登録
まぁスケルトン使ってると勝手に登録されてたりする。
AuthServiceProvider
自体も最初からあったり。
config/app.php
return [
// ~~~ 他色々 ~~~
'providers' => [
/*
* Application Service Providers...
*/
App\Providers\AuthServiceProvider::class,
],
// ~~~ 他色々 ~~~
];
コントローラーのmiddleware
各コントローラーのプロパティの文字列いじるだけで簡単に定義できるようにしたいので、基底クラス側で middleware を設定
App/Http/Controllers/Controller.php
<?php
namespace App\Http\Controllers\Controller;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public static $can = '';
public function __construct()
{
if (strlen(static::$can)) {
// @see \App\Providers\AuthServiceProvider
$this->middleware('can:' . static::$can);
}
}
}
使う
コントローラー
<?php
namespace App\Http\Controllers;
class HogeController extends Controller
{
// 管理者と一般ユーザー1ならOK
public static $can = 'admin|general1';
}
blade
@can(App\Http\Controllers\HogeController::$can)
<a href="{{ route('hoge.index') }}">ほげ</a>
@endcan
所感
これがベストプラクティスなのか分からん/(^o^)\
blade の判定部分もうちょっとすっきりさせたい・・・