В Laravel роли и разрешения были одной из самых запутанных тем на протяжении многих лет. В основном из-за отсутствия документации по этому поводу: те же самые вещи «прячутся» под другими терминами фреймворка, такими как «ворота», «полисы», «охранники» и т. д. В этой статье я постараюсь объяснить их все в «человеческий язык».
Gate — это то же самое, что и разрешение
Одна из самых больших путаниц, на мой взгляд, связана с термином "ворота". Я думаю, что разработчики избежали бы путаницы, если бы их называли так, как они есть.
Шлюзы – это разрешения, просто называемые другим словом.
Какие типичные действия нам нужно выполнять с разрешениями?
- Define the permission, ex. "manage_users"
- Check the permission on the front-end, ex. show/hide the button
- Check the permission on the back-end, ex. can/can't update the data
Так что да, замените слово "разрешение" на "гейт", и вы все поймете.
Простой пример Laravel:
приложение/Providers/AppServiceProvider.php:
use App\Models\User;
use Illuminate\Support\Facades\Gate;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Should return TRUE or FALSE
Gate::define('manage_users', function(User $user) {
return $user->is_admin == 1;
});
}
}
resources/views/navigation.blade.php:
<ul>
<li>
<a href="{{ route('projects.index') }}">Projects</a>
</li>
@can('manage_users')
<li>
<a href="{{ route('users.index') }}">Users</a>
</li>
@endcan
</ul>
маршруты/web.php:
Route::resource('users', UserController::class)->middleware('can:manage_users');
Теперь я знаю, что технически Gate может означать более одного разрешения.Таким образом, вместо «manage_users» вы можете определить что-то вроде «admin_area». Но в большинстве примеров, которые я видел, ворота являются синонимом разрешения.
Кроме того, в некоторых случаях разрешения называются "возможностями", как в пакете Bouncer. То же самое и означает возможность/разрешение на какое-либо действие.Мы вернемся к пакетам позже в этой статье.
Различные способы проверки разрешения шлюза
Еще один источник путаницы — как/где проверить Врата. Это настолько гибко, что вы можете найти очень разные примеры. Давайте пробежимся по ним:
Вариант 1. Маршруты: промежуточное ПО('can:xxxxxx')
Это пример выше.Непосредственно на маршрут/группу можно назначить промежуточное ПО:
Route::post('users', [UserController::class, 'store'])
->middleware('can:create_users');
Вариант 2.
Вариант 3.Gate::allows() или Gate::denies() Еще один способ — использовать фасад Gate: Или наоборот: Или, более короткий способ прервать, с помощниками: Вариант 4. Контроллер: авторизация() Еще более короткий и мой любимый вариант — использовать Вариант 5. Класс запроса формы: Я заметил, что многие разработчики создают классы запросов формы только для того, чтобы определить правила проверки, полностью игнорируя первый метод этого класса, который называется Вы также можете использовать его для проверки шлюзов. Таким образом, вы достигаете разделения ответственности, что является хорошей практикой для надежного кода, поэтому контроллер не заботится о проверке, потому что это делается в своем специальном классе запроса формы. А затем в форме запроса: Если ваши разрешения могут быть назначены модели Eloquent, в типичном CRUD-контроллере вы можете создать вокруг них класс политики. Если мы запустим эту команду: Он создаст файл app/Policies/UserPolicy.php, с методами по умолчанию, которые имеют комментарий, поясняющий их назначение: В каждом из этих методов вы определяете условие для возврата true/false. Итак, если мы будем следовать тем же примерам, что и Гейтс ранее, мы можем сделать следующее: Затем вы можете проверить Политику очень похоже на Gates: Итак, вы указываете имя метода и имя класса политики. Другими словами, Политики — это просто еще один способ группировки разрешений, а не Gates. Если ваши действия в основном связаны с CRUD моделей, то политики, вероятно, будут более удобным и лучше структурированным вариантом, чем Gates. Давайте обсудим еще одну путаницу: в документации Laravel вы не найдете ни одного раздела о ролях пользователей.
С точки зрения фреймворка не существует "ролей", есть только шлюзы/политики, которые вы можете группировать по своему усмотрению. Другими словами, роль — это объект ВНЕ фреймворка Laravel, поэтому нам нужно самим построить структуру ролей.Это может быть частью общей путаницы с авторизацией, но в этом есть смысл, потому что мы должны контролировать, как определяются роли: Итак, функциональность роли — это еще один уровень вашего приложения Laravel. Здесь мы переходим к пакетам Laravel, которые могут помочь. Но мы также можем создавать роли без каких-либо пакетов: Последняя часть — самая важная. Итак, вместо: Вы должны сделать что-то вроде: Опять же, здесь у вас есть несколько вариантов проверки ролей. В приведенном выше примере мы предполагаем, что существует связь Но если вы предпочитаете гибкость, вы можете каждый раз запрашивать базу данных: Но не забывайте загружать «ролевые» отношения, иначе вы можете легко столкнуться с N +1 проблема с запросом здесь. По моему личному опыту, обычная модель построения всего этого вместе такова: Другими словами, мы не проверяем доступ по ролям. Роль — это всего лишь «искусственный» слой, набор разрешений, который трансформируется в Gates в течение жизненного цикла приложения. Ох уж эти. Они вызывают столько путаницы на протяжении многих лет.Многие разработчики думали, что Guards — это Роли, и начали создавать отдельные таблицы БД, такие как «administrators», а затем назначать их как Guards.Частично потому, что в документации вы можете найти фрагменты кода, такие как Итак, охранники — более глобальное понятие, чем роли.
Looks complicated?
public function store(Request $request)
{
if (!$request->user()->can('create_users'))
abort(403);
}
}
public function store(Request $request)
{
if ($request->user()->cannot('create_users'))
abort(403);
}
}
public function create()
{
if (!auth()->user()->can('create_users'))
abort(403);
}
}
public function store(Request $request)
{
if (!Gate::allows('create_users')) {
abort(403);
}
}
authorize()
в контроллерах.В случае сбоя он автоматически вернет страницу 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);
}
authorize()
.public function store(Request $request)
{
$this->authorize('create_users');
}
Политика: набор разрешений на основе модели
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 [
// ...
];
}
}
php artisan make:policy ProductPolicy --model=Product
use App\Models\Product;
use App\Models\User;
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)
{
//
}
}
class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}
Роль: универсальный набор разрешений
public function store(Request $request)
{
$this->authorize('create', Product::class);
}
belongsTo
между пользователем и ролью, а также в модели ролей есть константы, такие как < code>ADMIN = 1, как и EDITOR = 2
, чтобы избежать слишком частых запросов к базе данных.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;
}
Гибкость: разрешения сохраняются в БД
class ProductPolicy
{
public function create(User $user)
{
return $user->role->name == 'Administrator';
}
Auth::guard('admin') ->попытка($учетные данные))
foreach
loop from all permissions from DB, and run a Gate::define()
statement for each of them, returning true/false based on the role;@can('permission_name')
and $this->authorize('permission_name')
, like in the examples above.$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;
});
}
$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);