目录

Laravel 11.x 中间件


原文


介绍

中间件为检查和过滤进入应用程序的HTTP请求提供了一种便捷的机制。

定义中间件

make:middleware命令用于创建新的中间件。

1
php artisan make:middleware EnsureTokenIsValid

此命令将在app/Http/Middleware目录中放置一个新EnsureTokenIsValid类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureTokenIsValid
{
    /**
     * Handle an incoming request.
     *
     * @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 response('This action is unauthorized.', 401);
        }
        return $next($request);
    }
}

中间件和响应

中间件可以在将请求传递到应用程序更深处之前或之后执行任务。

例如,以下中间件会在请求被应用程序处理之前执行某些任务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class BeforeMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        // Perform action
        
        return $next($request);
    }
}

以下中间件将在应用程序处理请求后执行其任务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AfterMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}

注册中间件

全局中间件

如果需要中间件在每个HTTP请求到达应用程序时都运行,那么可以将它附加到应用程序的全局中间件中,在应用程序的bootstrap/app.php文件中进行配置

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

withMiddleware 闭包中提供的 $middleware 对象是 Illuminate\Foundation\Configuration\Middleware 的实例,负责管理分配给应用程序路由的中间件。append 方法将中间件添加到全局中间件列表的末尾。

如果需要将中间件添加到列表的开头,应该使用 prepend 方法。

手动管理Laravel默认全局中间件

可以使用use方法对Laravel提供的默认全局中间件进行手动管理,根据所需调整中间件使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
->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方法将中间件分配给路由

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

也可以将中间件名称数组传递给middleware方法,将多个中间件分配给路由:

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

排除中间件

当将中间件分配给一组路由时,需要防止某个中间件应用于该组中的某个单独路由。

可以使用 withoutMiddleware 方法来实现。

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

还可以从整个路由组定义中排除给定的一组中间件

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

注意:withoutMiddleware 方法只能移除路由中的中间件,不能用于全局中间件。

中间件组

将多个中间件分组到一个键下,以便更轻松分配给路由,可以使用应用程序bootstrap/app.php 文件中的方法 appendToGroup 完成此操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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,
    ]);
})

中间件组可以使用与单个中间件相同的语法分配给路由和控制器动作

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

Laravel默认的中间件组

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

如果向这些组附加或前置中间件,可以在应用程序的 bootstrap/app.php 文件中使用 webapi 方法。webapi 方法是向组添加中间件的便捷替代方法,相当于 appendToGroup 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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的默认中间件条目之一替换为自定义中间件:

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

也可以完全删除中间件:

1
2
3
$middleware->web(remove: [
    StartSession::class,
]);
手动管理Laravel的默认中间件组
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
->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,
    ]);
})

中间件别名

可以在应用程序的 bootstrap/app.php 文件中为中间件分配别名。

中间件别名允许您为给定的中间件类定义一个简短的别名,这对于具有较长类名的中间件特别有用

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

一旦在应用程序的 bootstrap/app.php 文件中定义了中间件别名,就可以在将中间件分配给路由时使用这个别名

1
2
3
Route::get('/profile', function () {
    // ...
})->middleware('subscribed');

Laravel 内置中间件默认使用的别名

AliasMiddleware
authIlluminate\Auth\Middleware\Authenticate
auth.basicIlluminate\Auth\Middleware\AuthenticateWithBasicAuth
auth.sessionIlluminate\Session\Middleware\AuthenticateSession
cache.headersIlluminate\Http\Middleware\SetCacheHeaders
canIlluminate\Auth\Middleware\Authorize
guestIlluminate\Auth\Middleware\RedirectIfAuthenticated
password.confirmIlluminate\Auth\Middleware\RequirePassword
precognitiveIlluminate\Foundation\Http\Middleware\HandlePrecognitiveRequests
signedIlluminate\Routing\Middleware\ValidateSignature
subscribed\Spark\Http\Middleware\VerifyBillableIsSubscribed
throttleIlluminate\Routing\Middleware\ThrottleRequests or Illuminate\Routing\Middleware\ThrottleRequestsWithRedis Illuminate\Routing\Middleware\ThrottleRequestsIlluminate\Routing\Middleware\ThrottleRequestsWithRedis
verifiedIlluminate\Auth\Middleware\EnsureEmailIsVerified

中间件优先级

以使用应用程序 bootstrap/app.php 文件中的方法 priority 指定中间件优先级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
->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,
    ]);
})

中间件参数

中间件还可以接收其他参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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);
    }
 
}

在定义路由时,可以通过将中间件名称和参数以:分隔来指定中间件参数:

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

多个参数可以用逗号进行分隔:

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

终止中间件

有时候,中间件可能需要在HTTP响应已发送到浏览器后进行一些工作。如果在中间件上定义了 terminate 方法,并且 Web 服务器使用 FastCGI,那么 terminate 方法将会在响应发送到浏览器后自动被调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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 方法应接收请求和响应两个参数。一旦定义了一个终止中间件,需要将其添加到路由列表或全局中间件中,即在应用程序的 bootstrap/app.php 文件中。

在调用中间件的 terminate 方法时,Laravel 将会从服务容器中解析一个新的中间件实例。

如果需要在调用 handleterminate 方法时使用同一个中间件实例,可以使用容器的 singleton 方法将中间件注册为单例。

通常情况下,这需要在 AppServiceProviderregister 方法中完成。

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