Все политики реализованы через сервис контейнер, что позволяет вписывать любые зависимости
в конструктор политики и проводить автоматическое внедрение.
Авторизация
Содержание:
- Введение
- Шлюзы
- Создание политики
- Написание политики
- Действия авторизации с использование политики доступа
Введение
Дополнительно к сервисам аутентификации из коробки Laravel также предлагает простой способ авторизации действий пользователей относительно данного ресурса. Как и в случае с аутентификацией, подход Ларавел к авторизации простой, и есть два пути для действий авторизации: шлюзы и политики.
Воспринимайте шлюзы и политики как маршруты и контроллеры. Шлюзы предоставляют простой подход авторизации, основанный на Замыкании, в то время как политики, как контроллеры группируют логику вокруг отдельной модели или ресурса. Предлагаю рассмотреть шлюзы, после чего обратиться к политикам.
Вам не нужно выбирать между использованием только шлюзов или только полити при построении приложения. Большинство приложений используют и политику, и шлюзы. Шлюзы не связаны с моделью или ресурсами такими как просмотр панели администратора. Напротив, политику необходимо использовать в случае когда вам необходимо привязать действие к конкретной модели или ресурсу.
Шлюзы
Написание шлюзов
Шлюзы — это замыкание, которое определяет, что пользователь авторизован для выполнения
определённого действия и обычно определяется в App\Providers\AuthServiceProvider
классе используя фасад Gate
.
Шлюзы всегда получают объект пользователя в качесве первого аргумента и могут получать
другие аргументы, такие как соответствующая модель Eloquent:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('edit-settings', function ($user) {
return $user->isAdmin;
});
Gate::define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
Шлюзы можно также определять используя Class@method
стиль вызова строки, как для контроллера:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'App\Policies\PostPolicy@update');
}
Действия авторизации через шлюзы
Для авторизации действий используя шлюзы вам необходимо исопльзовать методы allows
или denies
. Заметьте, что вам не нужно передать текущего аутентифицированного
пользователя в эти методы. Ларавел автоматически позаботится о передаче пользователя:
if (Gate::allows('edit-settings')) {
// The current user can edit settings
}
if (Gate::allows('update-post', $post)) {
// The current user can update the post...
}
if (Gate::denies('update-post', $post)) {
// The current user can't update the post...
}
Вы можете использовать метод forUser
для фасада Gate
,
если хотите определить, что конкретный пользователь может выполнить это действие:
if (Gate::forUser($user)->allows('update-post', $post)) {
// The user can update the post...
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// The user can't update the post...
}
Вы можете авторизовать несколько действий одномоментно используя методы
any
или none
:
if (Gate::any(['update-post', 'delete-post'], $post)) {
// The user can update or delete the post
}
if (Gate::none(['update-post', 'delete-post'], $post)) {
// The user cannot update or delete the post
}
Авторизация или выбрасывание исключений
Если вы хотите попытаться авторизовать действие и автоматически выкинуть
Illuminate\Auth\Access\AuthorizationException
, если пользователю
не разрешено проводить такое действие, вы можете использовать метод Gate::authorize
.
Экземпляр AuthorizationException
автоматически конвертируется в
403
HTTP ответ:
Gate::authorize('update-post', $post);
// The action is authorized...
Обеспечение дополнительного контекста
Методы шлюзов для возможностей авторизации (allows
, denies
,
check
, any
, none
, authorize
,
can
, cannot
) и директивы авторизации
Blade (@can
,
@cannot
, @canany
) могут получить массив в качестве
второго аргумента. Массив аргументов, передаваемый в качестве аргументов в фасад
Gate
, может быть использован для дополнлительного контекста в момент
принятия решений авторизации:
Gate::define('create-post', function ($user, $category, $extraFlag) {
return $category->group > 3 && $extraFlag === true;
});
if (Gate::check('create-post', [$category, $extraFlag])) {
// The user can create the post...
}
Ответы шлюза
Мы разобрали случай, когда шлюз возвращает простые логические ответы. Порой, вы можете захотеть
вернуть более детализированный ответ, который включает в себя сообщение ошибки. Чтобы это сделать,
вы можете вернуть Illuminate\Auth\Access\Response
из вашего шлюза:
use Illuminate\Support\Facades\Gate;
use Illuminate\Auth\Access\Response;
Gate::define('edit-settings', function ($user) {
return $user->isAdmin
? Response::allow()
: Response::deny('You must be a super administrator.');
});
При возврате ответа авторизации из вашего шлюза, метод Gate::allows
будет возвращать по-прежнему простое логическое значение, но вы можете использовать
метод Gate::inspect
для получения поного ответа:
$response = Gate::inspect('edit-settings', $post);
if ($response->allowed()) {
// The action is authorized...
} else {
echo $response->message();
}
Конечно, при использовании метода Gate::authorize
, чтобы выкинуть
AuthorizationException
, если действие не авторизовано, сообщение
ошибки, предоставленное ответом авторизации, будет распространяться и на
HTTP ответ:
Gate::authorize('edit-settings', $post);
// The action is authorized...
Перехват проверки шлюза
Иногда, вы можете захотеть предоставить все возможности определённому пользователю.
Вы можете использовать метод before
для определения обратной связи,
которая запускается до момента других проверок авторизации:
Gate::before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
Если before
возвращает результат отличный от null,
такой ответ и будет считаться результатом проверки.
Вы можете использовать метод after
для определения обратной связи,
которая будет запущена после проверок авторизации:
Gate::after(function ($user, $ability, $result, $arguments) {
if ($user->isSuperAdmin()) {
return true;
}
});
Похоже на проверку before
, если after
возвращает ненулевой результат,
его и будем считать результатом проверки.
Создание политики
Генерация политики
Политики — это классы, которые организуют логику авторизации вокруг определённой модели или ресурса.
Например, если ваше приложение — это блог, у вас может быть модель Post
и соответствующая политика PostPolicy
для авторизации действий пользователя
таких как создание или обновление заметки.
Вы можете сгененрировать политику используя команду Артизан make:policy
.
Файл готовой политики будет располагаться в папке app/Policies
.
Если такой папки у вас пока нет, Ларавел создаст за вас и эту папку тоже:
php artisan make:policy PostPolicy
Команда make:policy
сгенерирует пустой класс политики. Если вы хотите сгенерировать
класс с включённыеми базовыми методами "CRUD", тогда при выполнении команды необходимо добавить
--model
и название модели:
php artisan make:policy PostPolicy --model=Post
Регистрация политики
Теперь, когда у нас появиласьа политика, её необходимо зарегистрировать.
AuthServiceProvider
, включённый в новую установку Laravel, содержит
свойство policies
, которое должно сопоставить модель Eloquent с
соответсвующей политикой. Регистрация политики научит Laravel, какую конкретно политику
нужно применить для действий данной модели:
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
Автопоиск политики
Вместо ручной регистрации моделий политики, Ларавел может проводить автопоиск политик.
Для этого модели и политики должны выполнять правила имён. Политики должны находится в
папке Policies
, на том же уровне вложенности, что и модели. Поэтому, например,
модели располагаются в папке app
, в то время как политики находятся в папке
app/Policies
. Дополнительно, имена политики должны соответсвовать имени модели
и иметь суффикс Policy
. Поэтому, модель User
соответсвует классу
UserPolicy
.
Если вы хотите сделать собственную логику автоматического поиска, вы можете зарегистрировать
пользовательский вызов используя метод Gate::guessPolicyNamesUsing
.
Обычно этот метод должен вызываться из метода boot
вашего
AuthServiceProvider
:
use Illuminate\Support\Facades\Gate;
Gate::guessPolicyNamesUsing(function ($modelClass) {
// return policy class name...
});
Написание политики
Методы политики
После регистрации политики, вы можете добавлять методы для каждого действия. Например,
давайте определим метод update
для нашей PostPolicy
,
который определяет что данный User
может обновить экземпляр Post
.
Метод update
будет получать экземпляры User
и Post
в качестве аргументов и должен вернуть true
или false
в качестве
индикатора того, что пользователь может обновить данный Post
. Поэтому, для этого
примера давайте проведём верификацию того, что идентификатор пользователя id
совпадает с user_id
для заметки:
<?php
namespace App\Policies;
use App\User;
use App\Post;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
Вы можете продолжить определять дополнительные методы для политики, в соответствии
с нуждами различных действий. Например, вы можете определить методы view
или delete
для авторизации различных Post
действий. Вы свободно
можете давать любые имена методам политики.
You may continue to define additional methods on the policy
as needed for the various actions it authorizes.
For example, you might define view
or delete
methods to authorize various
Post
actions, but remember you are free to give your policy methods any name you like.
Ответы политики
Мы только что разобрали методы, которые возвращают простые логические значения.
Но иногда, вы можете захотеть вернуть более детализированный ответ, который включает
сообщение ошибки. Чтобы это сделать, вы можете вернуть Illuminate\Auth\Access\Response
из метода политики:
use Illuminate\Auth\Access\Response;
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id
? Response::allow()
: Response::deny('You do not own this post.');
}
Метод Gate::allows
возвращает простое логическое значение. Однако, вы можете
использовать метод Gate::inspect
для получения полного ответа авторизации
через шлюзы:
$response = Gate::inspect('update', $post);
if ($response->allowed()) {
// The action is authorized...
} else {
echo $response->message();
}
Конечно, при использовании метод Gate::authorize
, чтобы выкинуть
AuthorizationException
, если действие не авторизовано, сообщение
ошибки предоставленное ответом авторизации будет распространено на HTTP ответ:
Gate::authorize('update', $post);
// The action is authorized...
Методы без моделей
Некоторые методы политики получают текущего аутентифицированного пользователя и не получают
модель авторизации. Эта ситуация часто встречается для действия create
. Например,
если вы создаёте блог, вы можете захотеть проверить, что пользователь вообще авторизован
для создания каких-либо заметок.
При определении методов политики, которые не будут получать экземпляр модели,
такие как create
, вы должны определять метод таким образом,
что он ожидает аутентифицированного пользователя:
/**
* Determine if the given user can create posts.
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
Гости
По умолчанию все шлюзы и политики автоматически возвращают false
,
если входящий запрос HTTP не был инициирован авторизованным пользователем.
Однако, вы можете разрешить эти проверки авторизации, чтобы пропустить через
политики и шлюзы путём объявления необязательного вписывания или поддержки
null
как базового значения для определения аргумента пользователя:
<?php
namespace App\Policies;
use App\User;
use App\Post;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(?User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
Фильтры политики
Для определённых пользователей, вы можете захотеть авторизовать все действия
внутри данной политики. Чтобы осуществить задуманное, определите метод
before
для политики. Этот метод будет выполнять до выполнения
других методов политики, и даст возможность авторизовать действие до
фактического вызова предполагаемого метода политики. Такая возможность может
использоваться для того, чтобы разрешить администраторам проводить любые
действия:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
Если вы хотите отклонить все авторизации для пользователя, вы должны вернуть
false
из метода before
. Если вернётся null
,
авторизацию пропустят через метод полититки.
Действия авторизации с использование политики доступа
Через модель User
Модель User
, которая включена в базовое приложение Laravel, включает
два полезных метода для авторизации действий: can
и cant
.
Метод can
получает действие, которое вы бы хотели авторизовать и
релевантную модель. Например, давайте определим, что пользователь авторизован для
обновления данной модели Post
:
if ($user->can('update', $post)) {
//
}
Если политика зарегистрирована для данной модели,
метод can
будет автоматически вызывать соответствующую политику для
и возвращать логический результат. Если политика не зарегистрирована для модели,
метод can
попытается вызвать основанный на Замыкании шлюз, который
соответсвует данному имени действия.
Действия не требующие моделей
Стоит напомнить, что некоторые действия, такие как create
, могут не требовать
экземпляр модели. В этой ситуации, вы можете передать имя класса в метод can
.
Такое имя класса будет использоваться, чтобы определить, какую политику использовать
при авторизации действия:
use App\Post;
if ($user->can('create', Post::class)) {
// Executes the "create" method on the relevant policy...
}
Через средний слой
Ларавел включает посредника, который может авторизовать действия даже до момента,
когда входящий запрос достигнет ваших маршрутов или контроллеров. По умолчанию,
посреднику Illuminate\Auth\Middleware\Authorize
присваивается ключ
can
в вашем классе App\Http\Kernel
. Давайте исследуем пример
использования посредника can
для авторизации того, что пользователь
может обновить заметку блога:
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
В этом примере, мы передаём посреднику can
два аргумента. Первый — это имя действия,
которое мы бы хотели авторизовать, второй аргумент — это параметр маршрута, который мы бы хотели
передать в метод политики. В этом случае, в силу того, что мы используем
неявную привязку модели,
Post
будет передоваться в метод политики. Если пользователь не авторизован
для выполнения данного действия, посредником будет сгенерирован HTTP ответ
с 403
статус кодом.
Действия, которые не трубуют моделей
Опять же, некоторые действия, такие как create
, могут не требовать экземпляр модели.
В этой ситуации вы можете передавать имя класса в посредник, которое будет использовано ддя
определения нужно политики для авторизации действия:
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
Через помощников контроллера
В дополнение к полезным методом для модели User
, Laravel поставляет метод
authorize
для любого из ваших контроллеров, который расширяет базовый класс
App\Http\Controllers\Controller
. Как и метод can
, этот метод
принимает имя действия, которое вы хотите авторизовать и релевантную модель. Если действие
не авторизовано, тогда метод authorize
выбросит
Illuminate\Auth\Access\AuthorizationException
, который базовый обработчик
исключений Ларавел конвертирует в HTTP ответ с 403
статус кодом:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given blog post.
*
* @param Request $request
* @param Post $post
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// The current user can update the blog post...
}
}
Действия, которые не трубуют моделей
Как и обсуждалось ранее, некоторые действия, такие как create
, могут не требовать
экземпляр модели. В этой ситуации, вы должны передать имя класса в метод authorize
.
Имя класса будет использовано для определения политики для авторизации действия:
/**
* Create a new blog post.
*
* @param Request $request
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// The current user can create blog posts...
}
Авторизация ресурса контроллера
Если вы используете
контроллеры ресурсов,
вы можете использовать метод authorizeResource
в конструкторе контроллера.
Этот метод прикрепит соответсвующие определения посредника can
к методам
контроллера ресурсов.
Метод authorizeResource
принимает имя класса модели в качестве первого аргумента,
и имя маршрута / параметра запроса, который содержит идентификатор модели в качестве
второго аргумента:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
public function __construct()
{
$this->authorizeResource(Post::class, 'post');
}
}
Следующие методы контроллера будут связаны с их соответсвующими методами политики:
Метод контроллера | Метод политики |
---|---|
index | viewAny |
show | view |
create | create |
store | create |
edit | update |
update | update |
destroy | delete |
Через шаблоны Blade
При написании шаблонов Blade, вы можете захотеть отобразить часть страницы,
если пользователь авторизован для выполнения данного действия. Например, вы можете
захотеть показать форму обновления заметки блога только в том случае, если пользователь
может обновить эту заметку. В этой ситуации, вы можете использовать директивы
@can
и @cannot
:
@can('update', $post)
<!-- The Current User Can Update The Post -->
@elsecan('create', App\Post::class)
<!-- The Current User Can Create New Post -->
@endcan
@cannot('update', $post)
<!-- The Current User Can't Update The Post -->
@elsecannot('create', App\Post::class)
<!-- The Current User Can't Create New Post -->
@endcannot
Эти директивы являются удобными ярлыками для записи операторов
@if
и @unless
. Операторы
@can
и @cannot
выше соответственно
переводим на следующие утверждения:
@if (Auth::user()->can('update', $post))
<!-- The Current User Can Update The Post -->
@endif
@unless (Auth::user()->can('update', $post))
<!-- The Current User Can't Update The Post -->
@endunless
Вы также можете определить, что пользователь имеет любую возможность авторизации
из списка способностей. Чтобы добиться такого эффекта, используйте директиву
@canany
:
@canany(['update', 'view', 'delete'], $post)
// The current user can update, view, or delete the post
@elsecanany(['create'], \App\Post::class)
// The current user can create a post
@endcanany
Действия которые не трубуют моделей
Как и в случае с остальными методами авторизации, вы можете передавать
имя класса в директивы @can
и @cannot
,
если действие не требует модели:
@can('create', App\Post::class)
<!-- The Current User Can Create Posts -->
@endcan
@cannot('create', App\Post::class)
<!-- The Current User Can't Create Posts -->
@endcannot
Предоставление Дополнительного Контекста
При авторизации действий с использованием политик, выможете передавать массив в качестве
второго аргумента для большого числа функций авторизации и помощников. Первый элемент
в массиве будет использован для определения какую политику стоит применить, в то время как
остальные элементы массива могут использоваться для создания дополнительного контекста.
Например, рассмотрим следующее определение метода PostPolicy
,
которое содержит дополнительный параметр $category
:
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @param int $category
* @return bool
*/
public function update(User $user, Post $post, int $category)
{
return $user->id === $post->user_id &&
$category > 3;
}
При попытке определить, что аутентифицированный пользователь может обновить данную заметку, мы можем использовать этот метод политики таким образом:
/**
* Update the given blog post.
*
* @param Request $request
* @param Post $post
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', [$post, $request->input('category')]);
// The current user can update the blog post...
}