中间件

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

中间件

介绍

中间件提供了一种方便的机制来过滤进入应用程序的 HTTP 请求。例如,Laravel 包含一个验证用户身份的中间件。如果用户未能通过认证,中间件会把用户重定向到登录页面。反之,用户如果通过验证,中间件将把请求进一步转发到应用程序中。
当然,除了验证身份外,还可以编写其他的中间件来执行各种任务。例如:CORS 中间件可以负责为所有的应用返回的 responses 添加合适的响应头。日志中间件可以记录所有传入应用的请求。
Laravel 自带了一些中间件,包括身份验证、CSRF 保护等。所有的这些中间件都位于 app/Http/Middleware 目录。

定义中间件

通过运行 make:middleware Artisan 命令来创建新的中间件:

php artisan make:middleware EnsureTokenIsValid

此命令将在 app/Http/Middleware 目录中放置一个新的 EnsureTokenIsValid 类。在这个中间件中,我们只允许在提供的 token 输入与指定值匹配时访问路由。否则将重定向到 home 页面:

<?php

namespace App\Http\Middleware;

use Closure;

class EnsureTokenIsValid
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('token') !== 'my-secret-token') {
            return redirect('home');
        }

        return $next($request);
    }
}

正如你所见,如果给定的令牌与我们的秘密令牌不匹配, 中间件将返回一个 HTTP 重定向给客户端。否则这个请求将会通过,进一步传递到应用层中。要让请求继续传到到应用层中 (即允许「通过」中间件验证), 只需要将 $request 作为参数来调用函数 $next 即可。

最好将中间件想象成一系列层次,HTTP 请求必须通过它们才能进入你的应用层。每一层都会检查请求(是否符合中间件要求),而后决定通过或拒绝访问应用。

技巧:所有的中间件都是通过 服务容器, 解析的,因此,你可以在你的中间件构造函数中键入你需要的任何依赖。


前置 & 后置 中间件

中间件是在请求之前或之后执行,取决于中间件本身。例如,下面的中间件将在应用处理请求之前执行一些任务:

<?php

namespace App\Http\Middleware;

use Closure;

class BeforeMiddleware
{
    public function handle($request, Closure $next)
    {
        // Perform action

        return $next($request);
    }
}

然而,下面中间件是在应用请求之后执行一些任务:

<?php

namespace App\Http\Middleware;

use Closure;

class AfterMiddleware
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}

注册中间件

全局中间件

如果你希望中间件在应用处理每个 HTTP 请求期间运行, 只需要在 app/Http/Kernel.php 中的 $middleware 属性中列出这个中间件。

为路由分配中间件

假设你想为指定的路由分配中间件,首先应该在 app/Http/Kernel.php 文件内为该中间件分配一个键。默认情况下,该类中的 $routeMiddleware 属性下包含了 Laravel 内置的中间件。若要加入自定义的中间件,只需把它附加到列表后并为其分配一个自定义键。例如:

// Within App\Http\Kernel class...

protected $routeMiddleware = [
    '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 内核中定义了中间件后,您可以使用 middleware 方法将中间件分配给路由:

Route::get('/profile', function () {
    //
})->middleware('auth');

您还可以将多个中间件分配给路由:

Route::get('/', function () {
    //
})->middleware(['first', 'second']);

分配中间件时,您还可以传递完全限定的类名:

use App\Http\Middleware\EnsureTokenIsValid;

Route::get('/profile', function () {
    //
})->middleware(EnsureTokenIsValid::class);

将中间件分配给一组路由时,有时可能需要阻止将中间件应用于该组中的单个路由。您可以使用 withoutMiddleware 方法完成此操作:

use App\Http\Middleware\EnsureTokenIsValid;

Route::middleware([EnsureTokenIsValid::class])->group(function () {
    Route::get('/', function () {
        //
    });

    Route::get('/profile', function () {
        //
    })->withoutMiddleware([EnsureTokenIsValid::class]);
});

withoutMiddleware 方法只能删除路由中间件,不适用于全局中间件

中间件组

有时,您可能希望将多个中间件归为一个键,以使其更易于分配给路由。 您可以使用 HTTP 内核的 $middlewareGroups 属性来实现。
Laravel 开箱即用,带有 webapi 中间件组,其中包含您可能要应用于 Web UI 和 API 路由的通用中间件,请记住,这些中间件组由应用程序的 App\Providers\RouteServiceProvider 服务提供商自动应用于相应的 webapi 路由文件中的路由:

/**
 * 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\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

中间件组可以使用与单个中间件相同的语法将自身分配给路由和控制器动作。同样,中间件组使得一次将多个中间件分配给一个路由更加方便:

Route::get('/', function () {
    //
})->middleware('web');

Route::middleware(['web'])->group(function () {
    //
});

技巧:RouteServiceProvider 默认将 webapi 中间件组自动应用到 routes/web.phproutes/api.php

中间件排序

很少情况下,你可能需要中间件以特定的顺序执行,但是当它们被分配到路由时,你无法控制它们的顺序。在这种情况下,可以使用 app/Http/Kernel.php 文件中的 $middlewarePriority 属性指定中间件的优先级:

/**
 * 中间件的优先级排序列表
 *
 * 将会强制非全局中间件始终保持给定的顺序
 *
 * @var array
 */
protected $middlewarePriority = [
    \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\Session\Middleware\AuthenticateSession::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];

中间件参数

中间件还可以接收额外的参数。例如,如果你的应用程序需要在执行给定操作之前验证用户是否为给定的「角色」,你可以创建一个 EnsureUserHasRole 中间件,由它来接收「角色」名称作为附加参数。
附加的中间参数会在 $next 参数之后传递给中间件:

<?php

namespace App\Http\Middleware;

use Closure;

class EnsureUserHasRole
{
    /**
     * 处理传入的请求
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string  $role
     * @return mixed
     */
    public function handle($request, Closure $next, $role)
    {
        if (! $request->user()->hasRole($role)) {
            // 重定向...
        }

        return $next($request);
    }

}

定义路由时通过一个 : 来隔开中间件名称和参数来指定中间件参数。多个参数就使用逗号分隔:

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

Terminable 中间件

有时可能需要在 HTTP 响应之后做一些工作。 如果你在中间件上定义了一个 terminate 方法,并且你使用的是 FastCGI,那么 terminate 方法会在响应发送到浏览器之后自动调用:

<?php

namespace Illuminate\Session\Middleware;

use Closure;

class TerminatingMiddleware
{
    /**
     * 处理传入请求
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        return $next($request);
    }

    /**
     * 将响应发送到浏览器后处理任务
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function terminate($request, $response)
    {
        // ...
    }
}

terminate 方法应该同时接收请求和响应。定义了这个中间件之后,别忘了将它添加到路由列表或者 app/Http/Kernel.php 文件的全局中间件中。

当你在中间件上调用 terminate 方法的时候,Laravel 将从 服务容器 中解析出一个新的中间件实例。如果想在调用 handleterminate 方法的同时使用相同的中间件实例, 请使用容器的 singleton 方法注册中间件, 通常这应该在 AppServiceProvider.php 文件中的 register 方法中完成:

use App\Http\Middleware\TerminatingMiddleware;

/**
 * 注册任意应用服务
 *
 * @return void
 */
public function register()
{
    $this->app->singleton(TerminatingMiddleware::class);
}

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/8.5/mid...

译文地址:https://learnku.com/docs/laravel/8.5/mid...

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:4
讨论数量: 2
发起讨论 查看所有版本


沉沦落叶
中间件跨域
2 个点赞 | 1 个回复 | 代码速记 | 课程版本 8.5
沉沦落叶
address
0 个点赞 | 0 个回复 | 代码速记 | 课程版本 8.5