中間件為檢查和過濾進(jìn)入應(yīng)用程序的 HTTP 請求提供了一種方便的機(jī)制。比如 Laravel 提供了一個(gè)中間件,用于驗(yàn)證用戶是否通過身份驗(yàn)證,如果身份驗(yàn)證失敗,中間件則會(huì)將重定向到登錄頁,反之中間件將允許請求繼續(xù)進(jìn)入后面的業(yè)務(wù)邏輯。
除了身份驗(yàn)證,中間件還可以用于各種業(yè)務(wù)場景。比如,日志記錄中間件可以記錄所有傳入應(yīng)用程序的請求。 Laravel 自身也包含多種中間件,其中包括用于 身份驗(yàn)證 和 CSRF 保護(hù)的中間件;通常用戶定義的中間件都位于應(yīng)用程序的 app/Http/Middleware 目錄中。
一、定義Laravel中間件
可以使用 make:middleware Artisan 命令,來創(chuàng)建一條中間件:
php artisan make:middleware EnsureTokenIsValid
該命令將在 app/Http/Middleware 目錄中放置一個(gè)新的 EnsureTokenIsValid 類。在此中間件中,如果提供的 token 輸入與指定值匹配,將僅允許訪問路由。否則會(huì)將用戶重定向回 home URI:
<?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 重定向;否則,請求將進(jìn)一步傳遞到應(yīng)用程序中。要將請求更深入地傳遞到應(yīng)用程序(進(jìn)一步傳遞),應(yīng)該使用 $request 調(diào)用 $next 回調(diào)。
最好將中間件設(shè)想為一系列「層」,HTTP 請求在到達(dá)應(yīng)用程序之前必須經(jīng)過的部分,每一層都可以檢查請求,也可以完全拒絕它。
[! 注意]所有中間件都通過服務(wù)容器解析,因此您可以在中間件的構(gòu)造函數(shù)中鍵入提示所需的任何依賴項(xiàng)。
中間件響應(yīng)
中間件可以選擇在請求 之前 或 之后 執(zhí)行任務(wù)。
在應(yīng)用程序 處理請求之前 執(zhí)行任務(wù):
<?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 { // Perform action return $next($request); } }
在應(yīng)用程序 處理請求之后 執(zhí)行任務(wù):
<?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); // Perform action return $response; } }
二、注冊Laravel中間件
1、全局中間件
如果希望中間件在應(yīng)用程序的每個(gè) HTTP 請求期間運(yùn)行,可以將其附加到應(yīng)用程序的 bootstrap/app.php 文件中的全局中間件堆棧中:
use App\Http\Middleware\EnsureTokenIsValid; ->withMiddleware(function (Middleware $middleware) { $middleware->append(EnsureTokenIsValid::class); })
提供給 withMiddleware 閉包的 $middleware 對象是 Illuminate\Foundation\Configuration\Middleware 的實(shí)例,負(fù)責(zé)管理分配給應(yīng)用程序路由的中間件。 append 方法將中間件添加到全局中間件列表的末尾。如果想將中間件添加到列表的開頭,則應(yīng)使用 prepend 方法。
2、手動(dòng)管理全局中間件
如果想手動(dòng)管理 Laravel 的全局中間件堆棧,可以向 use 方法提供 Laravel 的默認(rèn)全局中間件堆棧。然后,可以根據(jù)需要調(diào)整默認(rèn)的中間件堆棧:
->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, ]); })
3、路由綁定
如果想將中間件分配給特定的路由,可以在定義路由時(shí)調(diào)用 middleware 方法:
use App\Http\Middleware\EnsureTokenIsValid; Route::get('/profile', function () { // ... })->middleware(EnsureTokenIsValid::class);
可以通過將中間件名稱數(shù)組傳遞給 middleware 方法來將多個(gè)中間件分配給路由:
Route::get('/', function () { // ... })->middleware([First::class, Second::class]);
4、禁用中間件
將中間件分配給一組路由時(shí),有時(shí)可能需要阻止中間件應(yīng)用于組內(nèi)的單個(gè)路由,可以使用 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 方法只能移除路由中間件,不適用于全局中間件.
5、中間件分組
有時(shí)可能希望將多個(gè)中間件分組在一個(gè)鍵下,以便更輕松地將它們分配給路由。可以使用應(yīng)用程序的 bootstrap/app.php 文件中的 appendToGroup 方法來完成此操作:
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, ]); })
中間件組可以使用與單個(gè)中間件相同的語法分配給路由和控制器操作:
Route::get('/', function () { // ... })->middleware('group-name'); Route::middleware(['group-name'])->group(function () { // ... });
6、Laravel 默認(rèn)中間件組
Laravel 包含預(yù)定義的 web 和 api 中間件組,其中包含可能想要應(yīng)用于 Web 和 API 路由的常見中間件。請記住,Laravel 會(huì)自動(dòng)將這些中間件組應(yīng)用到相應(yīng)的 routes/web.php 和 routes/api.php 文件:
(1)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
(2)api 中間件組
Illuminate\Routing\Middleware\SubstituteBindings
如果想將中間件附加或添加到這些組中,則可以在應(yīng)用程序的 bootstrap/app.php 文件中使用 web 和 api 方法。 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 的默認(rèn)中間件組條目之一替換為自己的自定義中間件:
use App\Http\Middleware\StartCustomSession; use Illuminate\Session\Middleware\StartSession; $middleware->web(replace: [ StartSession::class => StartCustomSession::class, ]);
也可以完全刪除中間件:
$middleware->web(remove: [ StartSession::class, ]);
7、手動(dòng)管理 Laravel 默認(rèn)中間件組
如果想手動(dòng)管理 Laravel 默認(rèn) web 和 api 中間件組中的所有中間件,可以完全重新定義這些組。下面的示例將定義 web 和 api 中間件組及其默認(rèn)中間件,允許根據(jù)需要自定義它們:
->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, ]); })
[! 注意]默認(rèn)情況下 web 和 api 中間件組會(huì)通過 bootstrap/app.php 和 routes/api.php 文件。
8、中間件別名
可以在應(yīng)用程序的 bootstrap/app.php 文件中為中間件分配別名。中間件別名允許為給定的中間件類定義一個(gè)短別名,這對于具有長類名的中間件特別有用:
use App\Http\Middleware\EnsureUserIsSubscribed; ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'subscribed' => EnsureUserIsSubscribed::class ]); })
一旦在應(yīng)用程序的 bootstrap/app.php 文件中定義了中間件別名,就可以在將中間件分配給路由時(shí)使用該別名:
Route::get('/profile', function () { // ... })->middleware('subscribed');
為了方便起見,Laravel 的一些內(nèi)置中間件默認(rèn)是別名的。例如, auth 中間件是 Illuminate\Auth\Middleware\Authenticate 中間件的別名。以下是默認(rèn)中間件別名的列表:
別名 | 中間件 |
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 or Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified | Illuminate\Auth\Middleware\EnsureEmailIsVerified |
9、中間件排序
極少數(shù)情況下,可能需要中間件按特定順序執(zhí)行,但在將它們分配給路由時(shí)無法控制它們的順序。在這些情況下,可以使用應(yīng)用程序的 bootstrap/app.php 文件中的 priority 方法指定中間件優(yōu)先級(jí):
->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, ]); })
三、Laravel中間件參數(shù)
中間件還可以接收附加參數(shù)。例如,如果應(yīng)用程序需要在執(zhí)行給定操作之前驗(yàn)證經(jīng)過身份驗(yàn)證的用戶是否具有給定的 “角色”,可以創(chuàng)建一個(gè) EnsureUserHasRole 中間件來接收角色名稱作為附加參數(shù)。
其他中間件參數(shù)將在 $next 參數(shù)之后傳遞給中間件:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class EnsureUserHasRole { /** * Handle an incoming request. * * @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)) { // Redirect... } return $next($request); } }
定義路由時(shí)可以通過使用 : 分隔中間件名稱和參數(shù)來指定中間件參數(shù):
Route::put('/post/{id}', function (string $id) { // ... })->middleware('role:editor');
多個(gè)參數(shù)可以用逗號(hào)分隔:
Route::put('/post/{id}', function (string $id) { // ... })->middleware('role:editor,publisher');
四、可終止Laravel中間件
有時(shí),中間件可能需要在 HTTP 響應(yīng)發(fā)送到瀏覽器后執(zhí)行一些工作。如果在中間件上定義了 terminate 方法,并且 Web 服務(wù)器使用 FastCGI,則在響應(yīng)發(fā)送到瀏覽器后將自動(dòng)調(diào)用 terminate 方法:
<?php namespace Illuminate\Session\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class TerminatingMiddleware { /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { return $next($request); } /** * Handle tasks after the response has been sent to the browser. */ public function terminate(Request $request, Response $response): void { // ... } }
terminate 方法應(yīng)該接收請求和響應(yīng)。定義可終止中間件后,您應(yīng)該將其添加到應(yīng)用程序的 bootstrap/app.php 文件中的路由或全局中間件列表中。
當(dāng)在中間件上調(diào)用 terminate 方法時(shí),Laravel 將從服務(wù)容器中解析中間件的新實(shí)例。如果您想在調(diào)用 handle 和 terminate 方法時(shí)使用相同的中間件實(shí)例,請使用容器的 singleton 方法向容器注冊中間件。通常,這應(yīng)該在 AppServiceProvider 的 register 方法中完成:
use App\Http\Middleware\TerminatingMiddleware; /** * Register any application services. */ public function register(): void { $this->app->singleton(TerminatingMiddleware::class); }
————————————————
原文作者:Laravel China 社區(qū)文檔:《Laravel 11 中文文檔(11.x)》
轉(zhuǎn)自鏈接:https://learnku.com/docs/laravel/11.x/middlewaremd/16658
版權(quán)聲明:翻譯文檔著作權(quán)歸譯者和 LearnKu 社區(qū)所有。轉(zhuǎn)載請保留原文鏈接