Гейты в Laravel

Laravel Gates

Давайте разберемся, что же такое Гейты (Gates) в Laravel и как с помощью них можно управлять авторизацией пользователя.

Авторизация и Аутентификация это разные вещи. Фасад Auth предназначен для аутентификации. Он сверяет введенные учетные данные с сохранёнными. Если это не удается сделать, то показывается HTTP-код 401.

Авторизация же проверяет разрешено ли вам выполнение действия. В фильмах мы часто видим, как кто-то проникает в систему, в которой он не авторизован, используя дыры в безопасности. Если вы авторизованы, то значит вам разрешено, если нет, то показывается HTTP-код 403.

Авторизация всегда происходит после Аутентификации. Если система не может определить, кто вы, то как она сможет что-либо вам разрешать?

Как авторизоваться?

В Laravel есть два способа авторизации пользователя. Первый основан на Замыкании и называется Гейты (Gates), а второй — на Классе и называется Политика (Policy). В этой статье мы рассмотрим только Гейты. Они больше похожи на определение маршрута-замыкания.

Гейты — Авторизация на Замыканиях

Этот способ для авторизации использует Фасад Gate. Фасад проксирует вызовы на Illuminate/Auth/Access/Gate.php. Пример кода находится ниже. Вы можете добавить этот фрагмент  куда угодно. Но хорошим подходом считается хранить однотипный код в одном месте. Используем для этого метод boot в app/Providers/AuthServiceProvider.php. Для объявления критериев авторизации мы используем метод define. Он принимает два параметра:

  • name, которое позже будет использоваться как ссылка для авторизации пользователя.
  • и само замыкание.

И помните, что замыкание получает залогиненного пользователя по дефолту в первом параметре.

use Illuminate\Support\Facades\Gate;

Gate::define('create-post', function ($user) {
    return $user->id == 1;
});

В этом примере мы разрешаем создавать сообщения только пользователю с id равным 1. Кроме него больше никто создавать сообщения не сможет.

Чтобы проверить, разрешено ли текущему пользователю создавать новое сообщение, нужно выполнить следующее:

Gate::allows('create-post');

Это выражение вернёт логическое значение, означающее разрешено ли пользователю это действие. Довольно просто, не так ли? Видите переменную $user в первом параметре замыкания? По умолчанию она передается всем  заданным Гейтам и Политикам и содержит текущего залогиненного пользователя.

Вы можете задать столько Гейтов, сколько вам необходимо. Приведу несколько примеров.

Gate::define('edit-post', function ($user, $post) {
    return $user->id == $post->user_id;
});

Gate::define('delete-post', function ($user, $post) {
    return $user->id == $post->user_id;
});
Gate::allows('delete-post', Post::find(10));

Gate::allows('edit-post', $post)

Каждый раз, когда вам нужен дополнительный параметр в вашем замыкании, вы должны его передать там, где вызываете Гейт. Дополнительные параметры можно передать двумя способами. Если вам нужен только один параметр, наряду с переменной $user, то вы можете его передать как в примере с Gate::allows. Но если вам нужно более одного дополнительного параметра, то необходимо использовать массив, как показано ниже.

Gate::allows('gate-name', [$param1, $param2]);

Значения массива будут переданы как параметры в ваше замыкание после переменной $user.

Allows это единственный метод авторизации?

Нет, есть несколько методов.

  • allows — проверяет, может ли быть выполнено данное действие
  • denies — проверяет, запрещено ли данное действие.
  • check — проверяет, разрешены ли данные действие или массив действий.
  • any — проверяет, разрешено ли какое-либо из указанных действий
  • none — проверяет, запрещено ли какое-либо из указанных действий
  • authorize — проверяет, разрешено ли действие. если нет, то выбрасывает исключение Illuminate\Auth\Access\AuthorizationException
// use Illuminate\Auth\Access\Response

Gate::allows(string $ability, $arguments = []): bool
Gate::denies(string $ability, $arguments = []): bool
Gate::check(array|string $abilities, $arguments = []): bool
Gate::any(array|string $abilities, $arguments = []): bool
Gate::none(array|string $abilities, $arguments = []): bool
Gate::authorize(string $ability, $arguments = []): Response

Пользователи и Гейты

Предположим, вы хотите проверить авторизацию определенного пользователя, а не текущего. В этом случае вы можете использовать forUser($userId).

Gate::forUser(User::find(10))->allows('edit-post', Post::find(20));

Этот код проверит разрешено ли пользователю с ID 10 редактировать сообщение с ID 20.

Before и After

Если вы где-то сталкиваетесь с ситуацией, что вам нужно будет подтвердить авторизацию пользователя или пользователь должен будет иметь права для совершения определенного действия, то вы можете использовать метод before. Вы можете использовать столько колбэков before, сколько вам нужно. Но если хотя бы один из них вернёт ненулевое значение, то оно будет возвращено как результат вашей авторизационной логики и именные Гейты использоваться далее не будет. Кроме того, вы также можете добавить множественные колбэки after. Но если ваша кастомная авторизация или before не вернёт никакого значения, то after его перезапишет.

Gate::before(function ($user, $ability) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

Gate::after(function ($user, $ability, $result, $arguments) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

Гейты как Классы

Ранее мы определяли Гейт как:

Gate::define('ability', function (User $user){});

Но мы можем использовать классы так же, как и контроллеры в маршрутах web.php или api.php. Для этого мы определим Гейт следующим образом.

namespace App;

class CustomPolicy 
{
    public function editPost (User $user, Post $post) {
        return $user->id === $post->user_id;
    }
}

В файле app/Providers/AuthServiceProvider.php:

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    public function boot () {
        // для однострочного использования
        // замыкание заменяем форматом ClassName@methodName
        Gate::define('edit-post', 'App\CustomPolicy@editPost');
    }
}

Таким образом ваш провайдер остаётся чистым. А вы получаете тот же результат, что и в прошлом примере.

Вторая часть статьи об авторизации: Политики в Laravel.

Автор: Syed Sirajul Islam Anik
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.