Посредники (middleware)
Введение
Посредник обеспечивает удобный механизм для проверки и фильтрации HTTP-запросов, поступающих в ваше приложение. Например, в Laravel уже содержится посредник, проверяющий аутентификацию пользователя вашего приложения. Если пользователь не аутентифицирован, то посредник перенаправит пользователя на экран входа в ваше приложение. Однако, если пользователь аутентифицирован, то посредник позволит запросу продолжить работу в приложении.
Посредник может быть написан для выполнения различных задач помимо аутентификации. Например, посредник для ведения журналов может регистрировать все входящие запросы к вашему приложению. В Laravel включено множество посредников, включая псредники для аутентификации и защиты CSRF; однако все определяемые пользователями посредники обычно находится в каталоге app/Http/Middleware
вашего приложения.
Определение посредника
Чтобы создать нового посредника, используйте команду make:middleware
Artisan:
php artisan make:middleware EnsureTokenIsValid
Эта команда поместит новый класс посредника в каталог app/Http/Middleware
вашего приложения. В этом посреднике мы будем разрешать доступ к маршруту только в том случае, если значение входящего token
соответствует указанному. В противном случае мы перенаправим пользователя по маршруту /home
:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Обработка входящего запроса.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('/home');
}
return $next($request);
}
}
Как видите, если переданный token
не совпадает с нашим секретным токеном, то посредник вернет клиенту HTTP-перенаправление; в противном случае запрос будет передан в приложение. Чтобы передать запрос дальше в приложение (позволяя «пройти» посредника), вы должны вызвать замыкание $next
с параметром $request
.
Лучше всего представить себе посредников как серию «слоев» для HTTP-запроса, которые необходимо пройти, прежде чем запрос попадет в ваше приложение. Каждый слой может рассмотреть запрос и даже полностью отклонить его.
Все посредники извлекаются из контейнера служб, поэтому вы можете объявить необходимые вам зависимости в конструкторе посредника.
Посредники и ответы
Конечно, посредник может выполнять задачи до или после передачи запроса в приложение. Например, следующий посредник будет выполнять некоторую задачу до того, как запрос будет обработан приложением:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BeforeMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// Выполнить действие
return $next($request);
}
}
Однако, этот посредник будет выполнять свою задачу после обработки входящего запроса приложением:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AfterMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Выполнить действие
return $response;
}
}
Регистрация посредника
Глобальный стек HTTP-посредников
Если вы хотите, чтобы посредник запускался при каждом HTTP-запросе к вашему приложению, вы можете добавить его в глобальный стек посредников в файле bootstrap/app.php
вашего приложения:
use App\Http\Middleware\EnsureTokenIsValid;
->withMiddleware(function (Middleware $middleware) {
$middleware->append(EnsureTokenIsValid::class);
})
Объект $middleware
, предоставляемый замыканию withMiddleware
, является экземпляром Illuminate\Foundation\Configuration\Middleware
и отвечает за управление посредником, назначенным маршрутам вашего приложения. Метод append
добавляет посредника в конец глобального списка посредников. Если вы хотите добавить посредника в начало списка, вам следует использовать метод prepend
.
Ручное управление глобальным стеком посредников
Если вы хотите управлять глобальным стеком посредников Laravel вручную, вы можете предоставить глобальный стек посредников Laravel по умолчанию для метода use
. Затем вы можете при необходимости настроить стек посредников по умолчанию:
->withMiddleware(function (Middleware $middleware) {
$middleware->use([
// \Illuminate\Http\Middleware\TrustHosts::class,
\Illuminate\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
]);
})
Назначение посредников маршрутам
Если вы хотите назначить посредника (middleware) для определенных маршрутов, вы можете использовать метод middleware
при определении маршрута:
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
Вы также можете назначить несколько middleware для маршрута, передав массив имен в метод middleware
:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Исключение посредников
При назначении посредника группе маршрутов, иногда может потребоваться запретить применение посредника к одному из маршрутов в группе. Вы можете сделать это с помощью метода withoutMiddleware
:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
Вы также можете исключить данный набор посредников из всей группы маршрутов:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
Метод withoutMiddleware
удаляет только посредника маршрутизации и не применим к глобальному посреднику.
Группы посредников
По желанию можно сгруппировать несколько посредников под одним ключом, чтобы упростить их назначение маршрутам. Вы можете сделать это, используя метод appendToGroup
в файле bootstrap/app.php
вашего приложения:
use App\Http\Middleware\First;
use App\Http\Middleware\Second;
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup('group-name', [
First::class,
Second::class,
]);
$middleware->prependToGroup('group-name', [
First::class,
Second::class,
]);
})
Группы посредников могут быть назначены маршрутам и действиям контроллера, используя тот же синтаксис, что и с индивидуальным посредником:
Route::get('/', function () {
// ...
})->middleware('group-name');
Route::middleware(['group-name'])->group(function () {
// ...
});
Группы посредников Laravel по умолчанию
Laravel включает в себя предопределенные группы посредников web
и api
, которые содержат общие посредники, которое вы, возможно, захотите применить к своим веб и API маршрутам. Помните, что Laravel автоматически применяет эти группы посредников к соответствующим файлам routes/web.php
и routes/api.php
:
Группа посредников web |
---|
Illuminate\Cookie\Middleware\EncryptCookies |
Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse |
Illuminate\Session\Middleware\StartSession |
Illuminate\View\Middleware\ShareErrorsFromSession |
Illuminate\Foundation\Http\Middleware\ValidateCsrfToken |
Illuminate\Routing\Middleware\SubstituteBindings |
Группа посредников api |
---|
Illuminate\Routing\Middleware\SubstituteBindings |
Если вы хотите добавить или добавить посредника к этим группам, вы можете использовать методы web
и api
в файле bootstrap/app.php
вашего приложения. Методы web
и api
являются удобной альтернативой методу appendToGroup
:
use App\Http\Middleware\EnsureTokenIsValid;
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
EnsureUserIsSubscribed::class,
]);
$middleware->api(prepend: [
EnsureTokenIsValid::class,
]);
})
Вы даже можете заменить одну из записей группы посредников Laravel по умолчанию на собственного посредника:
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;
$middleware->web(replace: [
StartSession::class => StartCustomSession::class,
]);
Или вы можете полностью удалить посредника:
$middleware->web(remove: [
StartSession::class,
]);
Ручное управление группами посредников Laravel по умолчанию
Если вы хотите вручную управлять всеми посредниками в группах посредников Laravel по умолчанию web
и api
, вы можете полностью переопределить эти группы. В приведенном ниже примере будут определены группы посредников web
и api
с их посредниками по умолчанию, что позволит вам настроить их по мере необходимости:
->withMiddleware(function (Middleware $middleware) {
$middleware->group('web', [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
]);
$middleware->group('api', [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
// 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
})
По умолчанию группы посредников
web
иapi
автоматически применяются к соответствующим файламroutes/web.php
иroutes/api.php
вашего приложения с помощью файлаbootstrap/app.php
.
Псевдонимы посредников
Вы можете назначить псевдонимы посредникам в файле bootstrap/app.php
вашего приложения. Псевдонимы посредников позволяют определить короткий псевдоним для данного класса посредника, что может быть особенно полезно для посредника с длинными именами классов:
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class
]);
})
После того как псевдоним посредника определен в файле bootstrap/app.php
вашего приложения, вы можете использовать его при назначении посредника маршрутам:
Route::get('/profile', function () {
// ...
})->middleware('subscribed');
Для удобства некоторые встроенные посредники Laravel по умолчанию имеют псевдонимы. Например, посредник auth
является псевдонимом посредника Illuminate\Auth\Middleware\Authenticate
. Ниже приведен список псевдонимов посредников по умолчанию:
Псевдоним | Посредник |
---|---|
auth |
Illuminate\Auth\Middleware\Authenticate |
auth.basic |
Illuminate\Auth\Middleware\AuthenticateWithBasicAuth |
auth.session |
Illuminate\Session\Middleware\AuthenticateSession |
cache.headers |
Illuminate\Http\Middleware\SetCacheHeaders |
can |
Illuminate\Auth\Middleware\Authorize |
guest |
Illuminate\Auth\Middleware\RedirectIfAuthenticated |
password.confirm |
Illuminate\Auth\Middleware\RequirePassword |
precognitive |
Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests |
signed |
Illuminate\Routing\Middleware\ValidateSignature |
subscribed |
\Spark\Http\Middleware\VerifyBillableIsSubscribed |
throttle |
Illuminate\Routing\Middleware\ThrottleRequests или Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified |
Illuminate\Auth\Middleware\EnsureEmailIsVerified |
Сортировка посредников
В редких случаях вам может потребоваться, чтобы ваши посредники выполнялись в определенном порядке, но вы не можете контролировать их порядок, когда они назначены маршруту. В этом случае вы можете указать приоритет посредников, используя метод priority
в файле bootstrap/app.php
вашего приложения:
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
Параметры посредника
Посредник также может получать дополнительные параметры. Например, если вашему приложению необходимо проверить, что аутентифицированный пользователь имеет конкретную «роль» перед выполнением им конкретного действия, то вы можете создать посредника, например, EnsureUserHasRole
, который получит имя роли в качестве дополнительного аргумента.
Дополнительные параметры посредника будут переданы после аргумента $next
:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* Обработка входящего запроса.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
// Перенаправление ...
}
return $next($request);
}
}
Параметры посредника можно указать при определении маршрута, разделив имя посредника и параметры символом :
.
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor');
Несколько параметров следует разделять запятыми:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor,publisher');
Завершающий посредник
Иногда посреднику может потребоваться выполнить некоторую работу после отправки HTTP-ответа в браузер. Если вы определите метод terminate
в своем посреднике и при условии, что ваш веб-сервер использует FastCGI, то метод terminate
будет автоматически вызван после отправки ответа в браузер:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* Обработка входящего запроса.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Обработать задачи после отправки ответа в браузер.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
Метод terminate
должен получать и запрос, и ответ. После того как вы определили завершающий посредник, вы должны добавить его в список маршрутов или глобальный стек посредников в файле bootstrap/app.php
вашего приложения.
При вызове метода terminate
посредника, Laravel извлечет новый экземпляр посредника из контейнера служб. Если вы хотите использовать один и тот же экземпляр посредника при вызове методов handle
и terminate
, то зарегистрируйте посредника в контейнере, используя метод контейнера singleton
. Обычно это должно быть сделано в методе register
вашего AppServiceProvider
:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Регистрация любых служб приложения.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}