ブログ
これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。
Laravel ルーティングにアクセスコントロール ゲートとポリシー
ゲートとポリシー
reffect.co.jp/laravel
laravel.com
qiita.com 1
qiita.com 2
ゲート
ゲートを作成
Gate::define
を使用して isAdmin
というゲートを作成する
app\Providers\AuthServiceProvider.php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
// isAdminゲートを作成
Gate::define('isAdmin', function ($user) {
return $user->role == 'admin';
});
}
}
ゲートをコンロトーラで使用
app\Http\Controllers\CheckController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Post;
use Illuminate\Support\Facades\Gate;
class CheckController extends Controller
{
public function index(Request $request, Post $post)
{
/*
* `Gate::authorize`を使用して`isAdmin`というゲートを使用する
*
* パスしなかった場合は`403 unauthorized`を返す
*/
Gate::authorize('isAdmin');
/*
* `Gate::allows`を使用して`isAdmin`というゲートを使用する
*
* パスしなかった場合は`false`を返す
*/
$flag = Gate::allows('isAdmin');
/*
* `Gate::denies`を使用して`isAdmin`というゲートを使用する
*
* allowsとは反対にパスしなかった場合は`true`を返す
*/
$flag = Gate::denies('isAdmin');
/*
* `Gate::forUser`を使用して`isAdmin`というゲートをログインしているユーザ以外で使う場合
*
*/
// 特定のユーザを取得
$other_user = User::find(1);
$flag = Gate::forUser($other_user)->allows('isAdmin');
}
}
ゲートをルートで使用
middleware
で can
を使って isAdmin
を指定する
routes\web.php
Route::group(['middleware' => 'can:isAdmin'], function () {
Route::get('/check/{post?}', [CheckController::class, 'index'])->name('check');
});
ポリシー
Policies
を使用して index
アクションに使用するゲートを index
として作成
Postを作成した本人以外削除できないいうゲートにする
ポリシー作成
Post
モデル用に PostPolicy
を作成する
php artisan make:policy PostPolicy
app\Policies\PostPolicy.php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function delete(User $user, Post $post)
{
return $user->id == $post->user_id;
}
}
ポリシーを登録
App\Models\Post
: App\Policies\PostPolicy
のように Post
ぶぶんがいっちする場合は
自動的に登録されるみたい
app\Providers\AuthServiceProvider.php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
// 追加 ↓
use App\Models\Post;
use App\Policies\PostPolicy;
// 追加 ↑
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 追加 ↓
Post::class => PostPolicy::class,
// 追加 ↑
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
ポリシーをコンロトーラで使用
authorize('delete', $post)
, $user->can('view', $post)
, $user->cannot('view',$post)
の第2引数は登録したModelインスタンスを渡す
ここでは Post
モデルのインスタンス $post
を渡している
app\Http\Controllers\CheckController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Post;
use Illuminate\Support\Facades\Gate;
class CheckController extends Controller
{
public function destroy(Request $request, Post $post)
{
/*
* `authorize`を使用して`delete`というゲートを使用する
* 第2引数はポリシーを登録したモデルインスタンス
*/
// パスしなかった場合は`403 unauthorized`を返す
$this->authorize('delete', $post);
/*
* `can`、`cannot`を使用して`delete`というゲートを使用する
* 第2引数はポリシーを登録したモデルインスタンス
* auth()->user()や$request->user()などでユーザインスタンスを取得する必要がある
*/
// ユーザ取得
$user = $request->user();
// パスしなかった場合は`false`を返す
$flag = $user->can('delete', $post);
// `can`とは逆にパスしなかった場合は`true`を返す
$flag = $user->cannot('delete', $post);
}
}
ポリシーをルートで使用
middleware
で can
を使って delete
を指定する
ポリシーを登録したモデルインスタンスを渡す必要があるので
can:delete, post
のようにモデルバインディングでpostインスタンスも渡す
routes\web.php
Route::group(['middleware' => 'can:delete,post'], function () {
Route::get('/check/{post?}', [CheckController::class, 'index'])->name('check');
});
Policyのメソッド
※ Policyわかりにくいので使いたくないきがする。。
php artisan make:policy PostPolicy –model
このように –model
オプションをつけると viewAny
, view
, create
, update
, delete
, restore
, forceDelete
のアクションが作成される
これらのメソッドは、コントローラーのメソッドと関連をもち以下のようになている
コントローラーメソッドは、対応するポリシーメソッドにマップされまる
リクエストが特定のコントローラーメソッドにルーティングされると、コントローラーメソッドが実行される前に、対応するポリシーメソッドが自動的に呼び出される
Controller Method | Policy Method |
---|---|
index | viewAny |
show | view |
create | create |
store | create |
edit | update |
update | update |
destroy | delete |
以下はfunctionが destroy
なので delete
を省略しても上記対応表にある通り delete
が実行される
//...
public function destroy(Request $request, Post $post)
{
// destroyアクションの中なのでdeleteを省略できる
// $this->authorize('delete', $post);
$this->authorize($post);
}
//...
ポリシーについての個人的意見と対応
- モデルやリソースに対する認可はわかりにくいのでゲートのみ使うほうがわかりやすい
- ルーティングでのミドルウェアの設定がわかりにくくなる
- モデルバインディングが必要になる
- ポリシーになにが設定されているのかわかりにくい
- 大規模で複雑なアクセスコントロールの必要がある場合は良いと思う
上記理由から小規模プロジェクトには ゲートを使用して、クラスとして設定する方法が適しているのかと思うので、 ゲートのみを使用した対応策を考える
方法
モデル、リソースに基づく認可、 権限に基づく認可のどちらも書くことができるので柔軟に対応できる
テスト用のコントローラ
app\Http\Controllers\PostController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Post;
class PostController extends Controller
{
// 一覧
public function index(Request $request)
{
dump('index');
dump(Post::all());
}
// 閲覧
public function show(Request $request, Post $post)
{
dump('show');
dump($post->toArray());
}
// 作成
public function store(Request $request, Post $post)
{
dump('store');
dump($post->toArray());
}
// 編集
public function update(Request $request, Post $post)
{
dump('update');
dump($post->toArray());
}
// 削除
public function destroy(Request $request, Post $post)
{
dump('destroy');
dump($post->toArray());
}
}
ゲートクラスを作成
app\Gates\PostGate.php
namespace App\Gates;
use App\Models\User;
use App\Models\Post;
class PostGate
{
// adminとeditorが作成と編集可能
public function create(User $user): bool
{
return in_array($user->role, ['admin', 'editor'], true);
}
// adminと作成者が削除可能
public function destroy(User $user, Post $post): bool
{
return $post->user_id === $user->id || $user->role === 'admin';
}
}
ゲートを登録
app\Providers\AuthServiceProvider.php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\User;
// 作成したクラスを読み込む
use App\Gates\PostGate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
// super admin 権限
Gate::define('superAdmin', function(User $user){
return $user->role === 'super-admin';
});
// post gate
// 作成(store)と 編集(update)
Gate::define('postCreate', [PostGate::class, 'create']);
// 削除(destroy)
Gate::define('postDestroy', [PostGate::class, 'destroy']);
}
}
ルートで使用
routes\web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
// super admin だけ見れる
Route::group(['middleware' => ['can:superAdmin']], function () {
Route::get('/check', [PostController::class, 'index'])->name('post.index');
});
// 全部見れる
Route::get('/check/{post}', [PostController::class, 'show'])->name('post.show');
// 作成(store)と 編集(update)
Route::group(['middleware' => ['can:postCreate']], function () {
Route::post('/post/{post}', [PostController::class, 'store'])->name('post.store');
Route::put('/post/{post}', [PostController::class, 'update'])->name('post.update');
});
// 削除(destroy)
Route::group(['middleware' => ['can:postDestroy,post']], function () {
Route::delete('/post/{post}', [PostController::class, 'destroy'])->name('post.destroy');
});
コメントはありません。