Laravel では、ロールとパーミッションは長年にわたって最も混乱を招くトピックの 1 つです。 ほとんどの場合、それに関するドキュメントがないためです。「ゲート」、「ポリシー」、「ガード」など、フレームワーク内の他の用語の下に同じものが「隠れて」います。 この記事では、それらすべてを「人間の言葉」で説明しようと思います。
Gate は Permission と同じ
私の意見では、最大の混乱の 1 つは「ゲート」という用語です。 開発者は、彼らが何であるかと呼ばれていれば、多くの混乱を避けることができたと思います.
ゲートは 権限、別の言葉で呼ばれるだけです。
アクセス許可を使用して実行する必要がある一般的なアクションは何ですか?
- アクセス許可を定義します。 “manage_users”
- フロントエンドの許可を確認してください。 ボタンの表示/非表示
- バックエンドの権限を確認してください。 データを更新できる/できない
ええ、「許可」という言葉を「ゲート」に置き換えれば、すべて理解できます。
簡単な Laravel の例は次のようになります。
アプリ/プロバイダー/AppServiceProvider.php:
use AppModelsUser;
use IlluminateSupportFacadesGate;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Should return TRUE or FALSE
Gate::define('manage_users', function(User $user) {
return $user->is_admin == 1;
});
}
}
リソース/ビュー/navigation.blade.php:
<ul>
<li>
<a href="{{ route('projects.index') }}">Projectsa>
li>
@can('manage_users')
<li>
<a href="{{ route('users.index') }}">Usersa>
li>
@endcan
ul>
ルート/web.php:
Route::resource('users', UserController::class)->middleware('can:manage_users');
さて、技術的には、ゲートは複数の許可を意味する可能性があることを知っています。 したがって、「manage_users」の代わりに、「admin_area」などを定義できます。 しかし、私が見たほとんどの例では、Gate は Permission の同義語です。
また、場合によっては、Bouncer パッケージのように、権限を「アビリティ」と呼びます。 また、同じことを意味します-何らかのアクションの能力/許可。 パッケージについては、この記事の後半で説明します。
ゲートの許可を確認するさまざまな方法
もう 1 つの混乱の原因は、ゲートを確認する方法/場所です。 非常に柔軟であるため、非常に異なる例を見つけることができます。 それらを実行してみましょう:
オプション 1. ルート: middleware(‘can:xxxxxx’)
これは上記の例です。 ルート/グループに直接、ミドルウェアを割り当てることができます:
Route::post('users', [UserController::class, 'store'])
->middleware('can:create_users');
オプション 2. コントローラ: can() / cannot()
Controller メソッドの最初の行では、メソッドを使用して、このようなものを見ることができます can()
金 cannot()
、ブレードのガイドラインと同じ:
public function store(Request $request)
{
if (!$request->user()->can('create_users'))
abort(403);
}
}
反対は cannot()
:
public function store(Request $request)
{
if ($request->user()->cannot('create_users'))
abort(403);
}
}
または、持っていない場合 $request
変数、使用できます auth()
ヘルパー:
public function create()
{
if (!auth()->user()->can('create_users'))
abort(403);
}
}
オプション 3. Gate::allows() または Gate::denies()
もう 1 つの方法は、Gate ファサードを使用することです。
public function store(Request $request)
{
if (!Gate::allows('create_users')) {
abort(403);
}
}
ただし、逆の方法:
public function store(Request $request)
{
if (Gate::denies('create_users')) {
abort(403);
}
}
または、ヘルパーを使用して中止するより短い方法:
public function store(Request $request)
{
abort_if(Gate::denies('create_users'), 403);
}
オプション 4. コントローラ: authorize()
より短いオプションであり、私のお気に入りのオプションは、使用することです authorize()
コントローラーで。 失敗した場合は、403 ページが自動的に返されます。
public function store(Request $request)
{
$this->authorize('create_users');
}
オプション 5. フォーム リクエスト クラス:
多くの開発者が検証ルールを定義するためだけに Form Request クラスを生成し、そのクラスの最初のメソッドを完全に無視していることに気付きました。 authorize()
.
ゲートのチェックにも使えます。 このようにして、コンサーンの分離を実現しています。これは、堅実なコードの優れたプラクティスです。そのため、専用のフォーム リクエスト クラスで検証が行われるため、コントローラーは検証を処理しません。
public function store(StoreUserRequest $request)
{
// No check is needed in the Controller method
}
そして、フォームリクエストで:
class StoreProjectRequest extends FormRequest
{
public function authorize()
{
return Gate::allows('create_users');
}
public function rules()
{
return [
// ...
];
}
}
ポリシー: モデルベースの権限セット
パーミッションを Eloquent モデルに割り当てることができる場合は、典型的な CRUD コントローラーで、それらのポリシー クラスを構築できます。
このコマンドを実行すると:
php artisan make:policy ProductPolicy --model=Product
ファイルを生成します アプリ/ポリシー/UserPolicy.php、その目的を説明するコメントがあるデフォルトのメソッド:
use AppModelsProduct;
use AppModelsUser;
class ProductPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Product $product)
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user)
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Product $product)
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Product $product)
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Product $product)
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Product $product)
{
//
}
}
これらのメソッドのそれぞれで、true/false を返す条件を定義します。 したがって、前の Gates と同じ例に従えば、次のことができます。
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
次に、Gates と非常によく似た方法でポリシーを確認できます。
public function store(Request $request)
{
$this->authorize('create', Product::class);
}
したがって、ポリシーのメソッド名とクラス名を指定します。
つまり、ポリシーは、ゲートではなく、アクセス許可をグループ化するもう 1 つの方法です。 アクションの大部分がモデルの CRUD に関するものである場合、ポリシーはおそらくゲートよりも便利で構造化されたオプションです。
役割: 権限のユニバーサル セット
もう 1 つの混乱について説明しましょう。Laravel のドキュメントには、ユーザー ロールに関するセクションがありません。 理由は簡単です。「ロール」という用語は、「管理者」や「編集者」などの名前でパーミッションをグループ化するために人為的に作られたものです。
フレームワークの観点からは、「ロール」はなく、任意の方法でグループ化できるゲート/ポリシーのみがあります。
つまり、ロールは Laravel フレームワークの外にあるエンティティであるため、ロール構造を自分で構築する必要があります。 これは全体的な認証の混乱の一部かもしれませんが、ロールの定義方法を制御する必要があるため、完全に理にかなっています。
- 1 つの役割ですか、それとも複数の役割ですか。
- ユーザーは 1 つまたは複数の役割を持つことができますか?
- システム内のロールを管理できるのは誰ですか?
- 等
したがって、Role 機能は Laravel アプリケーションの別のレイヤーです。 これは、役立つ可能性のある Laravel パッケージに到達する場所です。 ただし、パッケージなしでロールを作成することもできます。
- 「roles」DBテーブルとRole Eloquent Modelを作成する
- ユーザーからロールへの関係を追加します: 1 対多または多対多
- デフォルトのロールをシードし、既存のユーザーに割り当てます
- 登録時にデフォルトの役割を割り当てる
- 代わりにロールをチェックするようにゲート/ポリシーを変更します
最後のビットが最も重要です。
したがって、代わりに:
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
あなたは次のようなことをします:
class ProductPolicy
{
public function create(User $user)
{
return $user->role_id == Role::ADMIN;
}
ここでも、役割を確認するためのオプションがいくつかあります。 上記の例では、 belongsTo
ユーザーからロールへの関係、およびロールモデルには次のような定数もあります ADMIN = 1
、 お気に入り EDITOR = 2
、データベースへのクエリが多すぎるのを避けるためです。
ただし、柔軟にしたい場合は、毎回データベースにクエリを実行できます。
class ProductPolicy
{
public function create(User $user)
{
return $user->role->name == 'Administrator';
}
ただし、「ロール」関係を熱心にロードすることを忘れないでください。そうしないと、ここで N+1 クエリの問題に簡単に遭遇する可能性があります。
柔軟にする: DB に保存されるアクセス許可
私の個人的な経験では、すべてを一緒に構築する通常のモデルは次のとおりです。
- すべての権限と役割はデータベースに保存され、管理パネルで管理されます。
- 関係: ロールの多対多の権限、ユーザーはロール (または多対多のロール) に属します。
- 次に、AppServiceProvider で
foreach
DB からのすべての権限からループし、Gate::define()
ロールに基づいて true/false を返す、それぞれのステートメント。 - 最後に、パーミッションを確認します
@can('permission_name')
と$this->authorize('permission_name')
、上記の例のように。
$roles = Role::with('permissions')->get();
$permissionsArray = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permissions) {
$permissionsArray[$permissions->title][] = $role->id;
}
}
// Every permission may have multiple roles assigned
foreach ($permissionsArray as $title => $roles) {
Gate::define($title, function ($user) use ($roles) {
// We check if we have the needed roles among current user's roles
return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0;
});
}
つまり、ロールによるアクセスは一切チェックしません。 ロールは、アプリケーションのライフサイクル中に Gates に変換される一連のアクセス許可である、単なる「人工的な」レイヤーです。
複雑に見えますか? 心配はいりません。これが、役立つパッケージです。
役割/権限を管理するためのパッケージ
このための最も人気のあるパッケージは Spatie Laravel Permission と Bouncer です。それらについては別の長い記事があります。 記事は非常に古いですが、市場のリーダーは安定しているため、依然として同じです。
これらのパッケージが行うことは、簡単に覚えて使用できるメソッドを使用して、パーミッション管理を人間に優しい言語に抽象化するのに役立ちます。
スペース許可からのこの美しい構文を見てください:
$user->givePermissionTo('edit articles');
$user->assignRole('writer');
$role->givePermissionTo('edit articles');
$user->can('edit articles');
バウンサーは少し直感的ではないかもしれませんが、それでも非常に優れています:
Bouncer::allow($user)->to('create', Post::class);
Bouncer::allow('admin')->to('ban-users');
Bouncer::assign('admin')->to($user);
これらのパッケージの使用方法の詳細については、Github へのリンク、または上記の私の記事を参照してください。
したがって、これらのパッケージは、この記事で取り上げる認証/承認の最後の「レイヤー」です。これで全体像を把握し、使用する戦略を選択できるようになることを願っています。
PS待って、警備員はどうですか?
ああ、それら。 彼らは何年にもわたって非常に多くの混乱を引き起こしています。 多くの開発者はガードをロールと考え、「管理者」のような個別の DB テーブルを作成し、それらをガードとして割り当て始めました。 部分的には、ドキュメントで次のようなコード スニペットを見つけることができるためです。 Auth::guard('admin')->attempt($credentials))
この誤解を避けるために、警告とともにドキュメントにプル リクエストを送信しました。
公式ドキュメントには、次の段落があります。
基本的に、Laravel の認証機能は「ガード」と「プロバイダー」で構成されています。 ガードは、リクエストごとにユーザーを認証する方法を定義します。 たとえば、Laravel には、セッション ストレージと Cookie を使用して状態を維持するセッション ガードが付属しています。
したがって、ガードはロールよりもグローバルな概念です。 ガードの一例は「セッション」です。ドキュメントの後半で、JWT ガードの例が表示される場合があります。 つまり、ガードは完全な認証メカニズムであり、ほとんどの Laravel プロジェクトでは、ガードを変更する必要はなく、ガードがどのように機能するかを知る必要さえありません。 ガードは、この役割/権限のトピックの外にあります。