Middleware
Introduction
中间件提供了一种方便的机制来检查和过滤进入应用程序的 HTTP 请求。例如,Laravel 包含一个中间件,用于验证您的应用程序的用户是否已通过身份验证。如果用户未通过身份验证,中间件会将用户重定向到应用程序的登录屏幕。但是,如果用户通过身份验证,中间件将允许请求进一步进入应用程序。
可以编写额外的中间件来执行除身份验证之外的各种任务。例如,日志记录中间件可能会记录所有传入应用程序的请求。 Laravel 框架中包含了几个中间件,包括用于身份验证和 CSRF 保护的中间件。所有这些中间件都位于app/Http/Middleware
目录。
定义中间件
要创建新的中间件,请使用make:middleware
工匠命令:
php artisan make:middleware EnsureTokenIsValid
此命令将放置一个新的EnsureTokenIsValid
你的班级app/Http/Middleware
目录。在此中间件中,我们将仅在提供的情况下允许访问路由token
输入匹配指定值。否则,我们会将用户重定向回home
网址:
<?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 redirect('home');
}
return $next($request);
}
}
如您所见,如果给定token
与我们的秘密令牌不匹配,中间件将向客户端返回 HTTP 重定向;否则,请求将进一步传递到应用程序中。要将请求更深入地传递到应用程序(允许中间件“传递”),您应该调用$next
回调与$request
.
最好将中间件设想为 HTTP 请求在到达您的应用程序之前必须通过的一系列“层”。每一层都可以检查请求,甚至可以完全拒绝它。
Note
所有中间件都通过服务容器,因此您可以在中间件的构造函数中对所需的任何依赖项进行类型提示。
中间件和响应
当然,中间件可以在将请求更深入地传递到应用程序之前或之后执行任务。例如,以下中间件将执行一些任务before 该请求由应用程序处理:
<?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);
}
}
然而,这个中间件会执行它的任务after 该请求由应用程序处理:
<?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;
}
}
注册中间件
全局中间件
如果您希望中间件在对您的应用程序的每个 HTTP 请求期间运行,请在$middleware
你的财产app/Http/Kernel.php
班级。
为路由分配中间件
如果你想将中间件分配给特定的路由,你可以调用middleware
定义路由时的方法:
use App\Http\Middleware\Authenticate;
Route::get('/profile', function () {
// ...
})->middleware(Authenticate::class);
您可以通过将中间件名称数组传递给路由来为路由分配多个中间件middleware
方法:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
为方便起见,您可以在应用程序的app/Http/Kernel.php
文件。默认情况下,$middlewareAliases
此类的属性包含 Laravel 附带的中间件的条目。您可以将自己的中间件添加到此列表并为其分配您选择的别名:
// Within App\Http\Kernel class...
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
一旦在 HTTP 内核中定义了中间件别名,您就可以在将中间件分配给路由时使用该别名:
Route::get('/profile', function () {
// ...
})->middleware('auth');
排除中间件
将中间件分配给一组路由时,您有时可能需要防止将中间件应用于组内的单个路由。您可以使用withoutMiddleware
方法:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
您还可以从整个系统中排除一组给定的中间件group 路线定义:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
这withoutMiddleware
方法只能移除路由中间件,不适用于全局中间件.
中间件组
有时您可能希望将多个中间件分组在一个键下,以便更容易地将它们分配给路由。您可以使用$middlewareGroups
HTTP 内核的属性。
Laravel 包含预定义的web
和api
包含您可能希望应用于 Web 和 API 路由的通用中间件的中间件组。请记住,这些中间件组由您的应用程序的自动应用App\Providers\RouteServiceProvider
服务提供商到您相应的路线web
和api
路由文件:
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
可以使用与单个中间件相同的语法将中间件组分配给路由和控制器操作。同样,中间件组可以更方便地一次将多个中间件分配给一个路由:
Route::get('/', function () {
// ...
})->middleware('web');
Route::middleware(['web'])->group(function () {
// ...
});
Note
开箱即用,web
和api
中间件组会自动应用于您的应用程序的相应routes/web.php
和routes/api.php
文件由App\Providers\RouteServiceProvider
.
排序中间件
极少数情况下,您可能需要中间件以特定顺序执行,但在将它们分配给路由时无法控制它们的顺序。在这种情况下,您可以使用$middlewarePriority
你的财产app/Http/Kernel.php
文件。默认情况下,您的 HTTP 内核中可能不存在此属性。如果不存在,您可以在下面复制其默认定义:
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var string[]
*/
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::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
{
/**
* 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);
}
}
可以在定义路由时指定中间件参数,方法是用中间件名称和参数分隔:
.多个参数应以逗号分隔:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor');
可终止中间件
有时中间件可能需要在 HTTP 响应发送到浏览器后做一些工作。如果你定义一个terminate
你的中间件上的方法和你的网络服务器正在使用 FastCGI,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
方法应该同时接收请求和响应。一旦你定义了一个可终止的中间件,你应该将它添加到路由或全局中间件的列表中app/Http/Kernel.php
文件。
当调用terminate
方法,Laravel 将从中解析一个新的中间件实例服务容器.如果你想在handle
和terminate
调用方法,使用容器的方法向容器注册中间件singleton
方法。通常这应该在register
你的方法AppServiceProvider
:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}