{{TOC}}
{{DOCVER 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51, 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01}}
== Введение ==
В Laravel сразу после установки есть сервисы ((/docs/v5/authentication аутентификации)), а также он обеспечивает простой способ авторизации действий пользователя с определённым ресурсом. Подход Laravel к авторизации такой же простой, как и к аутентификации.
.(alert)
Авторизация была добавлена в Laravel 5.1.11, поэтому обратитесь к ((/docs/v5/upgrade руководству по обновлению)) перед добавлением этих возможностей в своё приложение.
%%(DOCNEW 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51)
Есть два основных способа авторизации действий: шлюзы и политики.
Шлюзы и политики похожи на маршруты и контроллеры. Шлюзы обеспечивают простой подход к авторизации на основе замыканий, а политики, подобно контроллерам, группируют свою логику вокруг конкретной модели или ресурса. Сначала мы рассмотрим шлюзы, а затем политики.
При создании приложения вам не надо выбирать только что-то одно: либо шлюзы, либо политики. В большинстве приложений будет использоваться смесь из шлюзов и политик, и это очень здорово! Шлюзы больше подходят для действий, не связанных с моделями и ресурсами, такими как просмотр панели управления администратора. А политики надо использовать для авторизации действий для конкретных моделей или ресурсов.
== Шлюзы ==
=== Написание шлюзов ===
Шлюзы - это замыкания, определяющие, авторизован ли пользователь на выполнение данного действия. Обычно они определяются в классе %%(t)App\Providers\AuthServiceProvider%% с помощью фасада %%(t)Gate%%. Шлюзы всегда получают объект пользователя первым аргументом и могут получать дополнительные аргументы, такие как соответствующая Eloquent-модель:
~%%
/**
* Регистрация всех сервисов аутентификации / авторизации.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
~%%
=== Авторизация действий ===
Для авторизации действий с помощью шлюзов используйте методы %%allows()%% и %%denies()%%. Помните, вам не надо передавать текущего аутентифицированного пользователя в эти методы. Laravel автоматически позаботится о передаче пользователя в замыкание шлюза:
~%%
if (Gate::allows('update-post', $post)) {
// Текущий пользователь может редактировать статью...
}
if (Gate::denies('update-post', $post)) {
// Текущий пользователь не может редактировать статью...
}
~%%
Для определения, авторизован ли конкретный пользователь на выполнение действия, используйте метод %%forUser()%% фасада %%(t)Gate%%:
~%%
if (Gate::forUser($user)->allows('update-post', $post)) {
// Пользователь может редактировать статью...
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// Пользователь не может редактировать статью...
}
~%%
== Создание политик ==
=== Генерирование политик ===
Политики - это классы, организующие логику авторизации вокруг конкретной модели или ресурса. Например, если ваше приложение - блог, у вас может быть модель %%(t)Post%% и соответствующая %%(t)PostPolicy%% для авторизации действий пользователя, таких как создание или редактирование статьи.
Вы можете сгенерировать политику с помощью ((//docs/v5/artisan artisan))-команды %%(sh)make:policy%%. Сгенерированная политика будет помещена в каталог %%(t)app/Policies%%. Если такого каталога нет в вашем приложении, Laravel создаст его:
%%(sh)
php artisan make:policy PostPolicy
~%%
Команда %%(sh)make:policy%% сгенерирует пустой класс политики. Если вы хотите создать класс уже с базовыми ((ВП:CRUD)) методами политики, укажите %%(sh)--model%% при выполнении команды:
%%(sh)
php artisan make:policy PostPolicy --model=Post
~%%
.(alert)
Все политики извлекаются через ((//docs/v5/container сервис-контейнер)) Laravel, позволяя вам указывать типы любых необходимых зависимостей в конструкторе политики, и они будут внедрены автоматически.
((#registering-policies))
=== Регистрация политик ===
Когда политика создана, её надо зарегистрировать. В Laravel-приложении по умолчанию есть %%(t)AuthServiceProvider%%, имеющий свойство %%(t)policies%%, которое связывает ваши Eloquent-модели с соответствующими им политиками. Регистрация политики позволит Laravel использовать необходимую политику для авторизации действия над данной моделью:
~%%
PostPolicy::class,
];
/**
* Регистрация всех сервисов аутентификации / авторизации приложения.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
~%%
== Написание политик ==
=== Методы политик ===
После регистрации политики вы можете добавить методы для каждого действия, которое она авторизует. Например, давайте определим метод %%update()%% в нашей %%(t)PostPolicy%%, который будет определять, может ли данный %%(t)User%% редактировать данный объект %%(t)Post%%.
Метод %%update()%% получит в качестве аргументов методы %%User()%% и %%Post()%% и должен вернуть %%true%% или %%false%%, сообщая, авторизован ли пользователь для редактирования данной статьи. В этом примере давайте проверим, совпадает ли %%(t)id%% пользователя с %%(t)user_id%% статьи:
~%%
id === $post->user_id;
}
}
~%%
При необходимости вы можете определить дополнительные методы политики для различных действий, которые она авторизует. Например, вы можете определить метод %%view()%% или %%delete()%% для авторизации различных действий со статьёй, и помните, что вы можете называть методы политики как угодно.
.(alert)
Если вы использовали параметр %%(sh)--model%% при генерировании вашей политики через консоль Artisan, она уже будет содержать методы для действий %%(t)view%%, %%(t)create%%, %%(t)update%% и %%(t)delete%%.
=== Методы без моделей ===
Некоторые методы политик принимают только текущего аутентифицированного пользователя без экземпляра модели, для которой они авторизуют. Такие ситуации наиболее распространены при авторизации действий %%(t)create%%. Например, если вы создаёте блог, вы можете проверять, авторизован ли пользователь создавать статьи в принципе.
При определении методов политик, которые не будут получать экземпляр модели, таких как метод %%create()%%, они не будут получать экземпляр модели. Вместо этого вам надо определить метод, ожидающий только аутентифицированного пользователя:
~%%
/**
* Определение, может ли данный пользователь создавать статьи.
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
~%%
=== Фильтры политик ===
Для некоторых пользователей вы можете авторизовывать все действия определённой политики. Для этого определите в политике метод %%before()%%. Этот метод будет выполнятся перед всеми остальными методами политики, давая вам возможность авторизовать действие до того, как будет вызван соответствующий метод. Эта возможность наиболее часто используется для авторизации администраторов приложения на выполнение каких-либо действий:
~%%
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
~%%
Если вы хотите запретить всю авторизацию для пользователя, вам надо вернуть %%false%% из метода %%before()%%. Если вернётся %%null%%, авторизация перейдёт к методу политики.
== Авторизация действий с помощью политик ==
=== Через модель User ===
Модель %%(t)User%%, встроенная в Laravel, содержит два полезных метода для авторизации действий: %%can()%% и %%cant()%%. Метод %%can()%% получает действие для авторизации и соответствующую модель. Например, давайте определим, авторизован ли пользователь на редактирование данной модели %%(t)Post%%:
~%%
if ($user->can('update', $post)) {
//
}
~%%
Если ((#registering-policies политика зарегистрирована)) для данной модели, метод %%can()%% автоматически вызовет соответствующую политику и вернет логический результат. Если для модели не зарегистрировано ни одной политики, метод %%can()%% попытается вызвать шлюз на основе замыкания, совпадающий с именем данного действия.
**Действия, не требующие моделей**
Помните, что некоторые действия, такие как %%(t)create%%, не требуют экземпляр модели. В таких ситуациях вы можете передать имя класса в метод %%can()%%. Имя класса будет использовано для определения того, какая политика авторизует действие:
~%%
use App\Post;
if ($user->can('create', Post::class)) {
// Выполняется метод "create" соответствующей политики...
}
~%%
=== Через посредника ===
Laravel содержит посредника, который может авторизовать действия даже до того, как входящий запрос достигнет ваших маршрутов или контроллеров. По умолчанию посреднику %%(t)Illuminate\Auth\Middleware\Authorize%% назначен ключ %%(t)can%% в вашем классе %%(t)App\Http\Kernel%%. Давайте рассмотрим пример использования посредника %%(t)can%% для авторизации пользователя на редактирование статьи блога:
~%%
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// Текущий пользователь может редактировать статью...
})->middleware('can:update,post');
~%%
В этом примере мы передаём посреднику %%(t)can%% два аргумента. Первый - имя действия для авторизации, а второе - параметр маршрута для передачи в метод политики. В данном случае, поскольку мы используем ((//docs/v5/routing##неявная неявную привязку модели)), модель %%(t)Post%% будет передана в метод политики. Если пользователь не авторизован на выполнение данного действия, посредник сгенерирует HTTP-отклик с кодом состояния %%(t)403%%.
**Действия, не требующие моделей**
И снова, некоторые действия, такие как %%(t)create%%, не требуют экземпляр модели. В таких ситуациях вы можете передать имя класса в посредник. Имя класса будет использовано для определения того, какая политика авторизует действие:
~%%
Route::post('/post', function () {
// Текущий пользователь может создавать статьи...
})->middleware('can:create,App\Post');
~%%
=== Через вспомогательные методы контроллера ===
В дополнение к полезным методам модели %%(t)User%% Laravel предоставляет полезный метод %%authorize()%% для всех ваших контроллеров, наследующих базовый класс %%(t)App\Http\Controllers\Controller%%. Подобно методу %%can()%%, этот метод принимает имя действия для авторизации и соответствующую модель. Если действие не авторизовано, метод %%authorize()%% выбросит исключение %%(t)Illuminate\Auth\Access\AuthorizationException%%, которое будет конвертировано стандартным обработчиком исключений Laravel в HTTP-отклик с кодом состояния %%(t)403%%:
~%%
authorize('update', $post);
// Текущий пользователь может редактировать статью...
}
}
~%%
**Действия, не требующие моделей**
Как было сказано ранее, некоторые действия, такие как %%(t)create%%, не требуют экземпляр модели. В таких ситуациях вы можете передать имя класса в метод %%authorize()%%. Имя класса будет использовано для определения того, какая политика авторизует действие:
~%%
/**
* Create a new blog post.
*
* @param Request $request
* @return Response
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// The current user can create blog posts...
}
~%%
=== Через шаблоны Blade ===
При написании шаблонов Blade вы можете выводить часть страницы, только если пользователь авторизован на выполнение данного действия. Например, можно показывать форму редактирования статьи, только если пользователь может редактировать статью. В данной ситуации вы можете использовать семейство директив %%@can%% и %%@cannot%%:
~%%
@can('update', $post)
@elsecan('create', $post)
@endcan
@cannot('update', $post)
@elsecannot('create', $post)
@endcannot
~%%
Эти директивы - удобный короткий вариант для написания операторов %%@if%% и %%@unless%%. Операторы %%@can%% и %%@cannot%% из приведённого примера можно перевести соответственно в следующие операторы:
~%%
@if (Auth::user()->can('update', $post))
@endif
@unless (Auth::user()->can('update', $post))
@endunless
~%%
**Действия, не требующие моделей**
Как и большинство других методов авторизации вы можете передать в директивы %%@can%% и %%@cannot%% имя класса, если действие не требует экземпляра модели:
~%%
@can('create', Post::class)
@endcan
@cannot('create', Post::class)
@endcannot
~%%
%%
%%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01)
== Определение прав ==
Простейший способ определить наличие у пользователя прав на выполнение конкретного действия - задать "право" при помощи класса %%(t)Illuminate\Auth\Access\Gate%%. Поставляемый с Laravel %%(t)AuthServiceProvider%% служит удобным местом для определения всех прав для вашего приложения. Например, давайте определим право %%(t)update-post%%, которое получает текущего %%(t)User%% и ((/docs/v5/eloquent модель)) %%(t)Post%%. Внутри нашего права мы будем проверять совпадает ли %%(t)id%% пользователя с %%(t)user_id%% статьи:
~%%
registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
}
~%%
.(tl_note)
До версии 5.2 в этом примере для сравнения использовался оператор тождественного равенства %%return $user->id === $post->user_id;%% - //прим. пер.//
Заметьте, мы не проверили данного %%(t)$user%% на %%(t)NULL%%. %%(t)Gate%% автоматически вернёт значение %%(t)false%% для **всех прав**, когда нет аутентифицированного пользователя, или конкретный пользователь не был указан с помощью метода %%forUser()%%.
=== Права на основе класса ===
В добавление к регистрации замыканий %%(t)Closures%% в качестве обратных вызовов авторизации, вы можете регистрировать методы класса, передавая строку с именем класса и метода. Когда понадобится, класс будет извлечён при помощи ((/docs/v5/container сервис-контейнера)):
~%%
$gate->define('update-post', 'Class@method');
~%%
=== Перехват проверок авторизации ===
Иногда необходимо предоставить полные права конкретному пользователю. Для таких случаев используйте метод %%before()%%, чтобы задать обратный вызов, который будет выполняться до всех остальных проверок авторизации:
~%%
$gate->before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
~%%
Если обратный вызов %%before()%% возвращает не пустой результат, то этот результат будет считаться результатом проверки.
Вы можете использовать метод %%after()%% для задания обратного вызова, который будет выполняться после каждой проверки авторизации. Но из этого метода нельзя изменить результат проверки авторизации:
~%%
$gate->after(function ($user, $ability, $result, $arguments) {
//
});
~%%
== Проверка прав ==
=== С помощью фасада Gate ===
Когда право задано, мы можем "проверить" его разными способами. Во-первых, мы можем использовать методы ((/docs/v5/facades фасада)) %%(t)Gate%% - %%check()%%, %%allows()%% и %%denies()%%. Все они получают имя права и аргументы, которые необходимо передать в обратный вызов права. Вам **не надо** передавать текущего пользователя в эти методы, поскольку %%(t)Gate%% автоматически подставит его перед аргументами, передаваемыми в обратный вызов. Поэтому при проверке права %%(t)update-post%%, которое мы определили ранее, нам надо передать только экземпляр %%(t)Post%% в метод %%denies()%%:
~%%
allows('update-post', $post)) {
//
}
~%%
**Передача нескольких аргументов**
Конечно, обратные вызовы прав могут принимать несколько аргументов:
~%%
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
~%%
Если вашему праву необходимо несколько аргументов, просто передайте массив аргументов в методы %%(t)Gate%%:
~%%
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
~%%
=== С помощью модели User ===
Альтернативный способ проверки прав - с помощью экземпляра модели %%(t)User%%. По умолчанию в Laravel модель %%(t)App\User%% использует типаж %%(t)Authorizable%%, который предоставляет два метода: %%can()%% и %%cannot()%%. Эти методы могут быть использованы так же, как методы %%allows()%% и %%denies()%% фасада %%(t)Gate%%. Тогда, используя наш предыдущий пример, мы можем изменить код вот так:
~%%
user()->cannot('update-post', $post)) {
abort(403);
}
// Обновление статьи...
}
}
~%%
Метод %%can()%% обратен методу %%cannot()%%:
~%%
if ($request->user()->can('update-post', $post)) {
// Обновление статьи...
}
~%%
=== В шаблонах Blade ===
Для удобства Laravel предоставляет Blade-директиву %%(t)@can%% для быстрой проверки наличия данного права у текущего аутентифицированного пользователя. Например:
~%%
View Post
@can('update-post', $post)
Edit Post
@endcan
~%%
Также вы можете комбинировать директиву %%(t)@can%% с директивой %%(t)@else%%:
~%%
@can('update-post', $post)
@else
@endcan
~%%
=== В запросах форм ===
Также вы можете использовать определёные в %%(t)Gate%% права в методе %%authorize()%% ((/docs/v5/validation#проверка_запроса запроса формы)). Например:
~%%
/**
* Определение авторизации пользователя для выполнения этого запроса.
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
~%%
== Политики ==
=== Создание политик ===
В больших приложениях определение всей логики авторизации в %%(t)AuthServiceProvider%% может стать громоздким, поэтому Laravel позволяет вам разделять вашу логику авторизации на классы "Политики". Политики - простые PHP-классы, которые группируют логику авторизации на основе авторизуемых ресурсов.
Сначала давайте сгенерируем политику для управления авторизацией для нашей модели %%(t)Post%%. Вы можете сгенерировать политику используя ((/docs/v5/artisan artisan-команду)) %%(sh)make:policy%%. Сгенерированная политика будет помещена в папку %%(t)app/Policies%%:
%%(sh)
php artisan make:policy PostPolicy
~%%
**Регистрация политик**
Когда мы создали политику, нам надо зарегистрировать её классом %%(t)Gate%%. %%(t)AuthServiceProvider%% содержит свойство %%(t)policies%%, которое сопоставляет различные сущности с управляющими ими политиками. Поэтому мы укажем, что политика модели %%(t)Post%% - это класс %%(t)PostPolicy%%:
~%%
PostPolicy::class,
];
/**
* Регистрация любых сервисов аутентификации/авторизации для приложения.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
~%%
=== Написание политик ===
Когда политика сгенерирована и зарегистрирована, мы можем добавлять методы для каждого права, за которое она отвечает. Например, определим метод %%update()%% в нашем %%(t)PostPolicy%%, который будет проверять может ли данный %%(t)User%% "обновлять" %%(t)Post%%:
~%%
id === $post->user_id;
}
}
~%%
При необходимости вы можете продолжить определять дополнительные методы для политики для различных прав, за которые она отвечает. Например, вы можете определить методы %%show()%%, %%destroy()%% и %%addComment()%% для авторизации различных действий с %%(t)Post%%.
.(alert)
Все политики подключаются через ((/docs/v5/container сервис-контейнер)) Laravel, а значит, вы можете указать типы любых необходимых зависимостей в конструкторе политики, и они будут внедрены автоматически.
**Перехват всех проверок**
Если вам необходимо предоставить конкретному пользователю все права политики, определите в этой политике метод %%before()%%. Этот метод будет выполняться до всех остальных проверок авторизации этой политики:
~%%
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
~%%
Если метод %%before()%% возвращает не пустой результат, то этот результат будет считаться результатом проверки.
=== Проверка политик ===
Методы политик вызываются точно так же, как обратные вызовы авторизации на основе замыканий %%(t)Closure%%. Вы можете использовать фасад %%(t)Gate%%, модель %%(t)User%%, Blade-директиву %%(t)@can%%, или вспомогательную функцию %%policy()%%.
**С помощью фасада Gate**
%%(t)Gate%% автоматически определяет какую политику использовать, исходя из классов аргументов, передаваемых в его методы. Если мы передаём экземпляр %%(t)Post%% в метод %%(t)denies%%, то %%(t)Gate%% будет использовать соответствующий %%(t)PostPolicy%% для авторизации действий:
~%%
can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
~%%
**В шаблонах Blade**
Точно так же Blade-директива %%(t)@can%% будет использовать политики, когда они доступны для данных аргументов:
~%%
@can('update', $post)
@endcan
~%%
**С помощью вспомогательной функции Policy**
Глобальная вспомогательная функция %%policy()%% может использоваться для получения класса %%(t)Policy%% для данного экземпляра класса. Например, мы можем передать экземпляр %%(t)Post%% в функцию %%policy()%% для получения экземпляра соответствующего класса %%(t)PostPolicy%%:
~%%
if (policy($post)->update($user, $post)) {
//
}
~%%
== Авторизация контроллера ==
Базовый класс Laravel %%(t)App\Http\Controllers\Controller%% по умолчанию использует типаж %%(t)AuthorizesRequests%%. Этот типаж предоставляет метод %%authorize()%%, который может быть использован для быстрой авторизации данного действия или выброса %%(t)AuthorizationException%% (!!(tl_note)%%(t)HttpException%% для версии 5.1 и ранее - //прим. пер.//!!), если действие не авторизовано.
Метод %%authorize()%% разделяет ту же подпись, что и различные другие методы авторизации, такие как %%Gate::allows%% и %%$user->can()%%. Давайте используем метод %%authorize()%% для быстрой авторизации запроса на обновление %%(t)Post%%:
~%%
authorize('update', $post);
// Обновление статьи...
}
}
~%%
Если действие авторизовано, контроллер продолжит нормально выполняться; но если метод %%authorize()%% определит, что действие не авторизовано, будет автоматически выброшено %%(t)AuthorizationException%% (!!(tl_note)%%(t)HttpException%% для версии 5.1 и ранее - //прим. пер.//!!), которое сгенерирует HTTP-ответ с кодом состояния %%(t)403 Not Authorized%%. Как видите, метод %%authorize()%% - удобный и быстрый способ авторизации действия или выброса исключения одной строчкой кода.
Типаж %%(t)AuthorizesRequests%% также предоставляет метод %%authorizeForUser()%% для авторизации действия для пользователя, который не является текущим аутентифицированным пользователем:
~%%
$this->authorizeForUser($user, 'update', $post);
~%%
**Автоматическое определение методов политики**
Часто методы политики будут соответствовать методам контроллера. Например, в приведённом выше методе %%update()%% у метода контроллера и метода политики одинаковое название %%update()%%.
Поэтому Laravel позволяет просто передать аргументы экземпляра в метод %%authorize()%%, и авторизуемое действие будет автоматически определено на основе имени вызываемой функции. В этом примере, поскольку %%authorize()%% вызывается из метода контроллера %%update()%%, то и в политике будет вызван метод %%update()%%:
~%%
/**
* Обновление данной статьи.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// Обновление статьи...
}
~%%
%%