跳轉至內容

中介軟體

簡介

中介軟體提供了一種方便的機制,用於檢查和過濾進入應用程式的 HTTP 請求。例如,Laravel 包含一個驗證應用程式使用者是否已透過身份驗證的中介軟體。如果使用者未透過身份驗證,中介軟體會將使用者重定向到應用程式的登入介面。然而,如果使用者已透過身份驗證,中介軟體將允許請求繼續進入應用程式。

除了身份驗證之外,還可以編寫其他中介軟體來執行各種任務。例如,日誌中介軟體可能會記錄所有進入應用程式的請求。Laravel 內建了多種中介軟體,包括用於身份驗證和 CSRF 保護的中介軟體;不過,所有使用者定義的中介軟體通常都位於應用程式的 app/Http/Middleware 目錄中。

定義中介軟體

要建立新的中介軟體,請使用 make:middleware Artisan 命令

1php artisan make:middleware EnsureTokenIsValid

此命令將在你的 app/Http/Middleware 目錄中建立一個新的 EnsureTokenIsValid 類。在這個中介軟體中,我們僅當提供的 token 輸入與指定值匹配時才允許訪問該路由。否則,我們將把使用者重定向回 /home URI。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class EnsureTokenIsValid
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15 */
16 public function handle(Request $request, Closure $next): Response
17 {
18 if ($request->input('token') !== 'my-secret-token') {
19 return redirect('/home');
20 }
21 
22 return $next($request);
23 }
24}

正如你所見,如果給定的 token 與我們的金鑰不匹配,中介軟體將向客戶端返回 HTTP 重定向;否則,請求將被傳遞到應用程式的更深層。要將請求進一步傳入應用程式(允許中介軟體“透過”),你應該使用 $request 呼叫 $next 回撥。

最好將中介軟體想象成一系列 HTTP 請求在到達應用程式之前必須透過的“層”。每一層都可以檢查請求,甚至完全拒絕它。

所有中介軟體都是透過 服務容器 解析的,因此你可以在中介軟體的建構函式中對任何所需的依賴項進行型別提示。

中介軟體與響應

當然,中介軟體可以在將請求傳遞到應用程式更深層之前或之後執行任務。例如,以下中介軟體會在請求被應用程式處理之前執行某些任務:

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class BeforeMiddleware
10{
11 public function handle(Request $request, Closure $next): Response
12 {
13 // Perform action
14 
15 return $next($request);
16 }
17}

然而,這個中介軟體會在請求被應用程式處理之後執行其任務:

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class AfterMiddleware
10{
11 public function handle(Request $request, Closure $next): Response
12 {
13 $response = $next($request);
14 
15 // Perform action
16 
17 return $response;
18 }
19}

註冊中介軟體

全域性中介軟體

如果你希望中介軟體在應用程式的每個 HTTP 請求期間執行,可以將它附加到應用程式 bootstrap/app.php 檔案中的全域性中介軟體堆疊中:

1use App\Http\Middleware\EnsureTokenIsValid;
2 
3->withMiddleware(function (Middleware $middleware): void {
4 $middleware->append(EnsureTokenIsValid::class);
5})

提供給 withMiddleware 閉包的 $middleware 物件是 Illuminate\Foundation\Configuration\Middleware 的一個例項,負責管理分配給應用程式路由的中介軟體。append 方法將中介軟體新增到全域性中介軟體列表的末尾。如果你想將中介軟體新增到列表的開頭,應該使用 prepend 方法。

手動管理 Laravel 的預設全域性中介軟體

如果你想手動管理 Laravel 的全域性中介軟體堆疊,可以將 Laravel 的預設全域性中介軟體堆疊提供給 use 方法。然後,你可以根據需要調整預設的中介軟體堆疊:

1->withMiddleware(function (Middleware $middleware): void {
2 $middleware->use([
3 \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
4 // \Illuminate\Http\Middleware\TrustHosts::class,
5 \Illuminate\Http\Middleware\TrustProxies::class,
6 \Illuminate\Http\Middleware\HandleCors::class,
7 \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
8 \Illuminate\Http\Middleware\ValidatePostSize::class,
9 \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
10 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
11 ]);
12})

為路由分配中介軟體

如果你想為特定路由分配中介軟體,可以在定義路由時呼叫 middleware 方法:

1use App\Http\Middleware\EnsureTokenIsValid;
2 
3Route::get('/profile', function () {
4 // ...
5})->middleware(EnsureTokenIsValid::class);

你可以透過向 middleware 方法傳遞中介軟體名稱陣列,為路由分配多箇中間件:

1Route::get('/', function () {
2 // ...
3})->middleware([First::class, Second::class]);

排除中介軟體

當為一組路由分配中介軟體時,有時你可能需要阻止中介軟體應用於組內的某個特定路由。你可以使用 withoutMiddleware 方法來實現這一點:

1use App\Http\Middleware\EnsureTokenIsValid;
2 
3Route::middleware([EnsureTokenIsValid::class])->group(function () {
4 Route::get('/', function () {
5 // ...
6 });
7 
8 Route::get('/profile', function () {
9 // ...
10 })->withoutMiddleware([EnsureTokenIsValid::class]);
11});

你還可以從整個 路由組 定義中排除一組給定的中介軟體:

1use App\Http\Middleware\EnsureTokenIsValid;
2 
3Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
4 Route::get('/profile', function () {
5 // ...
6 });
7});

withoutMiddleware 方法只能移除路由中介軟體,不適用於 全域性中介軟體

中介軟體組

有時你可能希望將多箇中間件組合在一個鍵下,以便更輕鬆地將它們分配給路由。你可以在應用程式的 bootstrap/app.php 檔案中使用 appendToGroup 方法來實現:

1use App\Http\Middleware\First;
2use App\Http\Middleware\Second;
3 
4->withMiddleware(function (Middleware $middleware): void {
5 $middleware->appendToGroup('group-name', [
6 First::class,
7 Second::class,
8 ]);
9 
10 $middleware->prependToGroup('group-name', [
11 First::class,
12 Second::class,
13 ]);
14})

中介軟體組可以使用與單箇中間件相同的語法分配給路由和控制器操作:

1Route::get('/', function () {
2 // ...
3})->middleware('group-name');
4 
5Route::middleware(['group-name'])->group(function () {
6 // ...
7});

Laravel 的預設中介軟體組

Laravel 包含預定義的 webapi 中介軟體組,其中包含你可能想要應用於 Web 和 API 路由的通用中介軟體。請記住,Laravel 會自動將這些中介軟體組應用於相應的 routes/web.phproutes/api.php 檔案。

web 中介軟體組
Illuminate\Cookie\Middleware\EncryptCookies
Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse
Illuminate\Session\Middleware\StartSession
Illuminate\View\Middleware\ShareErrorsFromSession
Illuminate\Foundation\Http\Middleware\PreventRequestForgery
Illuminate\Routing\Middleware\SubstituteBindings
api 中介軟體組
Illuminate\Routing\Middleware\SubstituteBindings

如果你想向這些組新增或預置中介軟體,可以在應用程式的 bootstrap/app.php 檔案中使用 webapi 方法。webapi 方法是 appendToGroup 方法的便捷替代方案:

1use App\Http\Middleware\EnsureTokenIsValid;
2use App\Http\Middleware\EnsureUserIsSubscribed;
3 
4->withMiddleware(function (Middleware $middleware): void {
5 $middleware->web(append: [
6 EnsureUserIsSubscribed::class,
7 ]);
8 
9 $middleware->api(prepend: [
10 EnsureTokenIsValid::class,
11 ]);
12})

你甚至可以用你自己的自定義中介軟體替換 Laravel 預設中介軟體組中的某一項:

1use App\Http\Middleware\StartCustomSession;
2use Illuminate\Session\Middleware\StartSession;
3 
4$middleware->web(replace: [
5 StartSession::class => StartCustomSession::class,
6]);

或者,你也可以完全移除某個中介軟體:

1$middleware->web(remove: [
2 StartSession::class,
3]);

手動管理 Laravel 的預設中介軟體組

如果你想手動管理 Laravel 預設 webapi 中介軟體組中的所有中介軟體,可以完全重新定義這些組。下面的示例將定義帶有預設中介軟體的 webapi 中介軟體組,讓你根據需要自定義它們:

1->withMiddleware(function (Middleware $middleware): void {
2 $middleware->group('web', [
3 \Illuminate\Cookie\Middleware\EncryptCookies::class,
4 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
5 \Illuminate\Session\Middleware\StartSession::class,
6 \Illuminate\View\Middleware\ShareErrorsFromSession::class,
7 \Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
8 \Illuminate\Routing\Middleware\SubstituteBindings::class,
9 // \Illuminate\Session\Middleware\AuthenticateSession::class,
10 ]);
11 
12 $middleware->group('api', [
13 // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
14 // 'throttle:api',
15 \Illuminate\Routing\Middleware\SubstituteBindings::class,
16 ]);
17})

預設情況下,webapi 中介軟體組透過 bootstrap/app.php 檔案自動應用於應用程式對應的 routes/web.phproutes/api.php 檔案。

中介軟體別名

你可以在應用程式的 bootstrap/app.php 檔案中為中介軟體分配別名。中介軟體別名允許你為給定的中介軟體類定義一個簡短的別名,這對於類名較長的中介軟體特別有用:

1use App\Http\Middleware\EnsureUserIsSubscribed;
2 
3->withMiddleware(function (Middleware $middleware): void {
4 $middleware->alias([
5 'subscribed' => EnsureUserIsSubscribed::class
6 ]);
7})

一旦在應用程式的 bootstrap/app.php 檔案中定義了中介軟體別名,你就可以在將中介軟體分配給路由時使用該別名:

1Route::get('/profile', function () {
2 // ...
3})->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\ThrottleRequestsIlluminate\Routing\Middleware\ThrottleRequestsWithRedis
verified Illuminate\Auth\Middleware\EnsureEmailIsVerified

中介軟體排序

在極少數情況下,你可能需要中介軟體按特定順序執行,但無法控制它們在分配給路由時的順序。在這種情況下,你可以透過應用程式 bootstrap/app.php 檔案中的 priority 方法指定中介軟體的優先順序:

1->withMiddleware(function (Middleware $middleware): void {
2 $middleware->priority([
3 \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
4 \Illuminate\Cookie\Middleware\EncryptCookies::class,
5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
6 \Illuminate\Session\Middleware\StartSession::class,
7 \Illuminate\View\Middleware\ShareErrorsFromSession::class,
8 \Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
9 \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
10 \Illuminate\Routing\Middleware\ThrottleRequests::class,
11 \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
12 \Illuminate\Routing\Middleware\SubstituteBindings::class,
13 \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
14 \Illuminate\Auth\Middleware\Authorize::class,
15 ]);
16})

中介軟體引數

中介軟體還可以接收額外的引數。例如,如果你的應用程式需要在執行特定操作之前驗證已透過身份驗證的使用者是否擁有特定的“角色”,你可以建立一個接收角色名稱作為額外引數的 EnsureUserHasRole 中介軟體。

額外的中介軟體引數將在 $next 引數之後傳遞給中介軟體:

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class EnsureUserHasRole
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15 */
16 public function handle(Request $request, Closure $next, string $role): Response
17 {
18 if (! $request->user()->hasRole($role)) {
19 // Redirect...
20 }
21 
22 return $next($request);
23 }
24}

在定義路由時,可以透過用 : 分隔中介軟體名稱和引數來指定中介軟體引數:

1use App\Http\Middleware\EnsureUserHasRole;
2 
3Route::put('/post/{id}', function (string $id) {
4 // ...
5})->middleware(EnsureUserHasRole::class.':editor');

多個引數可以用逗號分隔:

1Route::put('/post/{id}', function (string $id) {
2 // ...
3})->middleware(EnsureUserHasRole::class.':editor,publisher');

可終止的中介軟體

有時,中介軟體可能需要在 HTTP 響應傳送到瀏覽器後執行某些工作。如果你在中介軟體中定義了 terminate 方法,並且你的 Web 伺服器正在使用 FastCGI,那麼 terminate 方法將在響應傳送到瀏覽器後自動被呼叫。

1<?php
2 
3namespace Illuminate\Session\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class TerminatingMiddleware
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15 */
16 public function handle(Request $request, Closure $next): Response
17 {
18 return $next($request);
19 }
20 
21 /**
22 * Handle tasks after the response has been sent to the browser.
23 */
24 public function terminate(Request $request, Response $response): void
25 {
26 // ...
27 }
28}

terminate 方法應接收請求和響應。定義了可終止中介軟體後,你應該將其新增到應用程式 bootstrap/app.php 檔案中的路由或全域性中介軟體列表中。

當在中介軟體上呼叫 terminate 方法時,Laravel 會從 服務容器 中解析出一個全新的中介軟體例項。如果你希望在呼叫 handleterminate 方法時使用同一個中介軟體例項,請使用容器的 singleton 方法在容器中註冊該中介軟體。這通常應該在 AppServiceProviderregister 方法中完成:

1use App\Http\Middleware\TerminatingMiddleware;
2 
3/**
4 * Register any application services.
5 */
6public function register(): void
7{
8 $this->app->singleton(TerminatingMiddleware::class);
9}