news 2026/4/15 16:13:43

深入理解 Laravel Middleware:完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 Laravel Middleware:完整指南

什么是中间件?

中间件提供了一种便捷的机制来检查和过滤进入应用的 HTTP 请求。你可以把中间件理解为 HTTP 请求在到达应用核心之前必须经过的一道道关卡。

比如,Laravel 内置了一个用于验证用户身份的中间件。如果用户未登录,中间件会把他们重定向到登录页。如果已登录,中间件就放行,让请求继续往下走。

除了身份验证,中间件还有很多其他用途:

请求日志:跟踪所有传入请求以进行调试和分析

CSRF 保护:确保请求合法且安全

数据验证:在数据到达控制器之前进行验证

速率限制:通过限制请求频率来防止滥用

CORS 处理:管理跨域资源共享策略

API Token 验证:认证 API 请求

基于角色的访问控制:根据用户角色限制访问

请求/响应修改:在处理或发送之前转换数据

中间件的工作原理:请求生命周期

理解中间件在 Laravel 请求生命周期中的位置很重要。当一个 HTTP 请求进来时,它会经历这样的流程:

请求从 public/index.php 进入

Laravel 启动应用并加载服务提供者

请求经过全局中间件栈

路由器匹配对应的路由

执行该路由的中间件

请求到达控制器或路由处理器

响应原路返回,再次经过中间件

最终返回给客户端

这种管道式架构让每个中间件都可以在请求到达应用逻辑之前对其进行检查、修改,甚至直接拦截。

创建自定义中间件

在 Laravel 12 中,用 Artisan 命令创建中间件很简单。我们来一步步看如何创建自定义中间件。

步骤 1:生成 Middleware

使用 make:middleware Artisan 命令创建一个新的 Middleware 类:

php artisan make:middleware EnsureTokenIsValid

这个命令会在 app/Http/Middleware 目录下生成一个新文件,里面已经写好了基本的中间件结构。

步骤 2:编写中间件逻辑

打开刚生成的 EnsureTokenIsValid.php,你会看到一个带 handle 方法的模板:

<?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')->with('error', 'Invalid token provided');

}

return $next($request);

}

}

handle 方法有两个参数:

$request:当前的 HTTP 请求

$next:下一个中间件的闭包

如果想让请求通过,就调用 $next($request)。如果要拦截请求,就直接返回响应或重定向。

步骤 3:前置和后置中间件

中间件可以在处理请求前后都执行操作。

前置中间件(在请求到达控制器之前执行):

<?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

{

// 在请求被处理之前执行操作

Log::info('Request received: ' . $request->path());

return $next($request);

}

}

后置中间件(在请求处理完成后执行):

<?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);

// 在请求被处理之后执行操作

Log::info('Response sent: ' . $response->getStatusCode());

return $response;

}

}

注册中间件

Laravel 12 有个重大变化:中间件注册不再用 app/Http/Kernel.php,而是移到了 bootstrap/app.php。这样做集中了配置,也更好理解。

Bootstrap/App.php 的新结构

Laravel 12 中 bootstrap/app.php 的基本结构长这样:

<?php

use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))

->withRouting(

web: __DIR__.'/../routes/web.php',

api: __DIR__.'/../routes/api.php',

commands: __DIR__.'/../routes/console.php',

health: '/up',

)

->withMiddleware(function ($middleware) {

// 在这里注册 Middleware

})

->withExceptions(function ($exceptions) {

//

})

->create();

全局中间件

全局中间件会在每个 HTTP 请求上运行。注册方式是在 withMiddleware 闭包中用 append 或 prepend:

->withMiddleware(function ($middleware) {

$middleware->append(\App\Http\Middleware\EnsureTokenIsValid::class);

})

append 会把中间件加到栈的末尾,prepend 则加到开头:

->withMiddleware(function ($middleware) {

$middleware->prepend(\App\Http\Middleware\LogRequests::class);

$middleware->append(\App\Http\Middleware\CompressResponse::class);

})

路由中间件

路由中间件只作用于指定的路由或路由组。注册时需要给中间件起个别名,用 alias 方法:

->withMiddleware(function ($middleware) {

$middleware->alias([

'admin' => \App\Http\Middleware\CheckAdmin::class,

'verified.email' => \App\Http\Middleware\EnsureEmailIsVerified::class,

'check.token' => \App\Http\Middleware\EnsureTokenIsValid::class,

]);

})

中间件组

中间件组可以把多个中间件打包在一起,方便批量应用:

->withMiddleware(function ($middleware) {

$middleware->appendToGroup('api', [

\App\Http\Middleware\EncryptCookies::class,

\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

]);

})

Laravel 12 已经内置了 web 和 api 组。你可以向它们添加自己的中间件,也可以创建新的组。

应用中间件到路由

注册好中间件后,有多种方式可以应用到路由上。

单个路由

用 middleware 方法给单个路由加中间件:

use Illuminate\Support\Facades\Route;

Route::get('/dashboard', function () {

// Dashboard 逻辑

})->middleware('auth');

多个中间件

传一个数组就可以同时应用多个:

Route::get('/admin/settings', function () {

// 管理员设置

})->middleware(['auth', 'admin', 'verified.email']);

路由组

给一组路由加中间件:

Route::middleware(['auth', 'verified.email'])->group(function () {

Route::get('/profile', [ProfileController::class, 'show']);

Route::put('/profile', [ProfileController::class, 'update']);

Route::delete('/profile', [ProfileController::class, 'destroy']);

});

在控制器中使用

也可以直接在控制器构造函数中指定:

<?php

namespace App\Http\Controllers;

class AdminController extends Controller

{

public function __construct()

{

$this->middleware('auth');

$this->middleware('admin')->only(['destroy', 'create']);

$this->middleware('log.request')->except(['index']);

}

}

中间件参数

中间件可以接收参数,这样能让一个中间件更灵活、更好复用。比如做权限控制或功能开关时就特别有用。

创建带参数的中间件

下面是一个检查用户角色的例子:

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

class CheckRole

{

public function handle(Request $request, Closure $next, string $role): Response

{

if (! $request->user() || ! $request->user()->hasRole($role)) {

abort(403, 'Unauthorized action.');

}

return $next($request);

}

}

使用带参数的中间件

用冒号把参数传给中间件:

Route::get('/admin/dashboard', function () {

// 仅限管理员

})->middleware('role:admin');

Route::get('/moderator/panel', function () {

// 仅限版主

})->middleware('role:moderator');

也可以传多个参数,用逗号隔开:

Route::get('/content/edit', function () {

// 编辑内容

})->middleware('role:admin,editor');

中间件会把它们当作独立的参数接收:

public function handle(Request $request, Closure $next, string ...$roles): Response

{

if (! $request->user() || ! $request->user()->hasAnyRole($roles)) {

abort(403, 'Unauthorized action.');

}

return $next($request);

}

依赖注入

Laravel 的服务容器会解析所有中间件,所以你可以在构造函数中直接注入需要的依赖:

<?php

namespace App\Http\Middleware;

use App\Services\TokenService;

use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

class ValidateApiToken

{

protected $tokenService;

public function __construct(TokenService $tokenService)

{

$this->tokenService = $tokenService;

}

public function handle(Request $request, Closure $next): Response

{

$token = $request->header('X-API-Token');

if (! $this->tokenService->isValid($token)) {

return response()->json(['error' => 'Invalid API token'], 401);

}

return $next($request);

}

}

可终止中间件

有时候你想在响应发送给用户之后再做一些事情。这时可以实现 terminate 方法:

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

class TerminableMiddleware

{

public function handle(Request $request, Closure $next): Response

{

return $next($request);

}

public function terminate(Request $request, Response $response): void

{

// 在响应发送后执行任务

// 这不会延迟对用户的响应

Log::info('Response completed', [

'status' => $response->getStatusCode(),

'path' => $request->path(),

]);

}

}

terminate 方法会在响应发出后才执行,所以不会影响用户体验。适合用来记日志、同步数据或做一些清理工作。

实际案例

看几个在生产环境中常用的中间件实现。

API 限流中间件

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Cache;

use Symfony\Component\HttpFoundation\Response;

class RateLimitApi

{

public function handle(Request $request, Closure $next, int $maxAttempts = 60): Response

{

$key = 'rate-limit:' . $request->ip();

$attempts = Cache::get($key, 0);

if ($attempts >= $maxAttempts) {

return response()->json([

'error' => 'Too many requests. Please try again later.'

], 429);

}

Cache::put($key, $attempts + 1, now()->addMinute());

$response = $next($request);

$response->headers->set('X-RateLimit-Limit', $maxAttempts);

$response->headers->set('X-RateLimit-Remaining', $maxAttempts - $attempts - 1);

return $response;

}

}

请求日志中间件

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\Log;

use Symfony\Component\HttpFoundation\Response;

class LogRequests

{

public function handle(Request $request, Closure $next): Response

{

$startTime = microtime(true);

$response = $next($request);

$duration = microtime(true) - $startTime;

Log::info('HTTP Request', [

'method' => $request->method(),

'url' => $request->fullUrl(),

'ip' => $request->ip(),

'user_id' => $request->user()?->id,

'status' => $response->getStatusCode(),

'duration' => round($duration * 1000, 2) . 'ms',

]);

return $response;

}

}

强制 HTTPS 中间件

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

class ForceHttps

{

public function handle(Request $request, Closure $next): Response

{

if (! $request->secure() && app()->environment('production')) {

return redirect()->secure($request->getRequestUri(), 301);

}

return $next($request);

}

}

输入清理中间件

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

class SanitizeInput

{

public function handle(Request $request, Closure $next): Response

{

$input = $request->all();

array_walk_recursive($input, function (&$value) {

if (is_string($value)) {

$value = strip_tags($value);

$value = trim($value);

}

});

$request->merge($input);

return $next($request);

}

}

语言切换中间件

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Illuminate\Support\Facades\App;

use Symfony\Component\HttpFoundation\Response;

class SetLocale

{

public function handle(Request $request, Closure $next): Response

{

$locale = $request->segment(1);

$availableLocales = ['en', 'es', 'fr', 'de'];

if (in_array($locale, $availableLocales)) {

App::setLocale($locale);

}

return $next($request);

}

}

Laravel 内置的中间件

Laravel 12 内置了一些强大的中间件来处理常见任务:

身份验证

Authenticate:确保用户已登录

RedirectIfAuthenticated:已登录用户访问登录页时自动跳转

CSRF 保护

VerifyCsrfToken:防止 POST、PUT、PATCH 和 DELETE 请求的跨站请求伪造攻击

Session 管理

StartSession:处理 Session 数据

ShareErrorsFromSession:把验证错误传递给视图

Cookie 加密

EncryptCookies:自动加密解密 Cookie

AddQueuedCookiesToResponse:把队列中的 Cookie 加入响应

安全相关

HandleCors:处理跨域请求 (CORS)

TrustProxies:配置可信代理,用于负载均衡场景

维护模式

PreventRequestsDuringMaintenance:维护模式下阻止请求

最佳实践

跟着这些建议做,可以让你的中间件更好维护、更安全、性能也更好:

保持简单专注

一个中间件只做一件事。如果一个中间件做的事情太多,就该拆分了:

// 坏例子:一个中间件做太多事

class HandleRequestMiddleware

{

public function handle($request, $next)

{

// 认证

// 验证

// 日志

// 转换数据

// 等等...

}

}

// 好例子:拆分成多个中间件

class AuthenticateMiddleware { }

class ValidateRequestMiddleware { }

class LogRequestMiddleware { }

class TransformRequestMiddleware { }

用参数提高灵活性

让中间件接受参数,能提高复用性:

// 不要创建 CheckAdminMiddleware、CheckModeratorMiddleware 等

// 写一个灵活的就够了

class CheckRole

{

public function handle($request, $next, ...$roles)

{

if (! $request->user()->hasAnyRole($roles)) {

abort(403);

}

return $next($request);

}

}

// 使用方式

Route::get('/admin', fn() => '...')->middleware('role:admin');

Route::get('/content', fn() => '...')->middleware('role:admin,editor');

注意执行顺序

中间件的顺序很重要。认证中间件应该放在需要用户信息的中间件之前:

Route::middleware(['auth', 'verified', 'role:admin'])->group(function () {

// 这里的路由

});

不要滥用

不是所有逻辑都适合放在中间件里。中间件适合处理多个路由的通用逻辑。如果是特定路由的逻辑,考虑用:

控制器方法

表单请求验证类

服务类

路由模型绑定

优化性能

中间件逻辑要尽量轻量。如果有耗时操作:

用缓存避免重复计算

把耗时任务放到队列

用可终止中间件做响应后处理

public function handle($request, $next)

{

// 只做快速检查

if (Cache::has('user-banned:' . $request->user()->id)) {

abort(403);

}

return $next($request);

}

public function terminate($request, $response)

{

// 耗时操作放到响应后

SomeHeavyJob::dispatch($request->user());

}

处理好异常

中间件里要做好异常处理,避免应用崩溃:

public function handle($request, $next)

{

try {

// 验证 Token

$token = $request->header('X-API-Token');

$this->tokenService->validate($token);

} catch (InvalidTokenException $e) {

return response()->json(['error' => 'Invalid token'], 401);

}

return $next($request);

}

写测试

给中间件写测试,确保它们正常工作:

<?php

namespace Tests\Feature;

use Tests\TestCase;

class CheckAdminMiddlewareTest extends TestCase

{

public function test_non_admin_cannot_access_admin_routes()

{

$user = User::factory()->create(['role' => 'user']);

$response = $this->actingAs($user)->get('/admin/dashboard');

$response->assertStatus(403);

}

public function test_admin_can_access_admin_routes()

{

$admin = User::factory()->create(['role' => 'admin']);

$response = $this->actingAs($admin)->get('/admin/dashboard');

$response->assertStatus(200);

}

}

加上类型声明

用上 PHP 的类型系统,IDE 也能更好地提示:

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

public function handle(Request $request, Closure $next): Response

{

// 实现

}

注释要清楚

如果中间件有参数,记得加注释说明:

/**

* 检查用户是否具有所需角色。

*

* @param \Illuminate\Http\Request $request

* @param \Closure $next

* @param string ...$roles 所需角色(admin、editor、moderator)

* @return \Symfony\Component\HttpFoundation\Response

*/

public function handle(Request $request, Closure $next, string ...$roles): Response

{

// 实现

}

安全相关

中间件在应用安全中很关键。注意这几点:

别忘了 CSRF 保护

Laravel 的 VerifyCsrfToken 中间件要加入 web 组,用来保护所有修改数据的请求:

->withMiddleware(function ($middleware) {

$middleware->appendToGroup('web', [

\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,

]);

})

API Token 验证

API 路由要做好 Token 验证:

public function handle($request, $next)

{

$token = $request->bearerToken();

if (! $token || ! $this->isValidToken($token)) {

return response()->json(['error' => 'Unauthorized'], 401);

}

return $next($request);

}

限流保护

加上限流,防止被恶意攻击,特别是 API 和登录接口:

Route::middleware(['throttle:60,1'])->group(function () {

// 每分钟最多 60 次请求

});

输入清理

虽然 Laravel 已经有 CSRF 和 SQL 注入防护,但在中间件里再加一层清理也不错:

public function handle($request, $next)

{

$input = $request->all();

array_walk_recursive($input, function (&$value) {

if (is_string($value)) {

$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');

}

});

$request->merge($input);

return $next($request);

}

敏感路由要保护好

给敏感路由加多层中间件保护:

Route::middleware(['auth', 'verified', '2fa', 'role:admin'])->group(function () {

// 敏感的管理后台路由

});

调试技巧

如果中间件不正常,试试这些方法:

启用查询日志

public function handle($request, $next)

{

DB::enableQueryLog();

$response = $next($request);

Log::debug('Queries executed:', DB::getQueryLog());

return $response;

}

记录执行日志

public function handle($request, $next)

{

Log::debug('Middleware executed: ' . static::class);

return $next($request);

}

用 Telescope 调试

Laravel Telescope 能帮你监控中间件执行、请求处理和性能指标。开发环境安装一下:

composer require laravel/telescope --dev

php artisan telescope:install

php artisan migrate

中间件 vs. 其他功能

什么时候用中间件,什么时候用其他特性?看这里:

vs. Form Requests

用中间件:多个路由的通用逻辑

用 Form Request:单个路由的验证逻辑

vs. Gates 和 Policies

用中间件:路由级别的权限检查

用 Gates/Policies:控制器里的资源权限检查

vs. Service 类

用中间件:HTTP 请求/响应相关的操作

用 Service 类:跟 HTTP 无关的业务逻辑

vs. Events 和 Listeners

用中间件:同步处理请求/响应

用 Events/Listeners:解耦的、可能异步的操作

性能优化

这些方法能提升中间件性能:

缓存耗时操作

public function handle($request, $next)

{

$userId = $request->user()->id;

$permissions = Cache::remember(

"user-permissions:{$userId}",

3600,

fn() => $this->permissionService->getUserPermissions($userId)

);

$request->merge(['permissions' => $permissions]);

return $next($request);

}

尽早返回

不符合条件就赶紧返回,别继续执行:

public function handle($request, $next)

{

if (! $request->user()) {

return redirect('/login');

}

if (! $request->user()->isActive()) {

return response('Account suspended', 403);

}

return $next($request);

}

按需加载

不要一开始就加载所有依赖,用到再加载:

public function handle($request, $next)

{

// 仅在需要时加载服务

if ($request->has('validate')) {

app(ValidationService::class)->validate($request);

}

return $next($request);

}

常见错误

写中间件时避免这些坑:

忘记 return

必须 return $next($request) 的结果:

// 错误

public function handle($request, $next)

{

if (! $request->user()) {

redirect('/login'); // 缺少 return!

}

$next($request); // 缺少 return!

}

// 正确

public function handle($request, $next)

{

if (! $request->user()) {

return redirect('/login');

}

return $next($request);

}

在 $next 之后改请求

调用 $next() 后再改请求已经没用了:

// 错误

public function handle($request, $next)

{

$response = $next($request);

$request->merge(['foo' => 'bar']); // 已经晚了

return $response;

}

// 正确

public function handle($request, $next)

{

$request->merge(['foo' => 'bar']);

return $next($request);

}

忘记注册

用之前记得在 bootstrap/app.php 里注册。

顺序错了

注意顺序,认证要放在权限检查之前:

// 错误的顺序

Route::middleware(['role:admin', 'auth'])

// 正确的顺序

Route::middleware(['auth', 'role:admin'])

从 Laravel 11 升级

如果你是从 Laravel 11 升级来的,中间件注册方式改了:

老写法(Laravel 11)

protected $middleware = [

\App\Http\Middleware\TrustProxies::class,

];

protected $middlewareGroups = [

'web' => [

\App\Http\Middleware\EncryptCookies::class,

],

];

protected $routeMiddleware = [

'auth' => \App\Http\Middleware\Authenticate::class,

];

新写法(Laravel 12)

->withMiddleware(function ($middleware) {

// 全局 Middleware

$middleware->append(\App\Http\Middleware\TrustProxies::class);

// Middleware 组

$middleware->appendToGroup('web', [

\App\Http\Middleware\EncryptCookies::class,

]);

// 路由 Middleware 别名

$middleware->alias([

'auth' => \App\Http\Middleware\Authenticate::class,

]);

})

总结

Laravel 中间件是个强大的特性,能以干净、可复用的方式处理通用逻辑。掌握好中间件的用法和最佳实践,就能写出更安全、更好维护、性能更好的 Laravel 应用。

重点回顾:

中间件是 HTTP 请求的过滤器

Laravel 12 把注册从 Kernel.php 移到了 bootstrap/app.php

有全局、路由和组三种中间件

中间件可以接收参数,提高灵活性

保持简单专注,记得写测试

适用于安全、日志、数据转换等通用场景

注重性能、安全和可维护性

随着使用的深入,你会发现更多中间件的用法,也会形成自己的实现模式。记住一点:中间件要保持专注、逻辑清晰、文档齐全。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:55:31

57、C Shell编程:控制流与循环

C Shell编程:控制流与循环 在C Shell编程中,有多种方式可以控制程序的执行流程,包括分支、退出和循环结构,下面将详细介绍。 1. 脚本中的条件判断与跳转 在一些脚本中,会要求用户输入学生的姓名,将其赋值给变量 name ,然后进行字符串比较,判断输入的姓名是 ajay …

作者头像 李华
网站建设 2026/4/12 19:44:56

80亿参数引爆多模态革命:Qwen3-VL-8B如何重塑行业智能化

80亿参数引爆多模态革命&#xff1a;Qwen3-VL-8B如何重塑行业智能化 【免费下载链接】Qwen3-VL-8B-Thinking-bnb-4bit 项目地址: https://ai.gitcode.com/hf_mirrors/unsloth/Qwen3-VL-8B-Thinking-bnb-4bit 导语 阿里通义千问团队推出的Qwen3-VL-8B-Thinking开源模型…

作者头像 李华
网站建设 2026/4/15 6:41:38

重大网络安全事件:2017年——WannaCry勒索病毒

一、事件概述&#xff1a;什么是WannaCry&#xff1f; WannaCry&#xff08;又称WannaCrypt、WCry&#xff09;是一种利用Windows操作系统漏洞进行传播的勒索软件。2017年5月12日起&#xff0c;这款病毒以惊人的速度席卷全球&#xff0c;感染了超过150个国家的数十万台计算机。…

作者头像 李华
网站建设 2026/4/15 4:07:32

基于vue的校园论坛管理系统的设计与实现_1xs8770k_springboot php python nodejs

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持java、ThinkPHP、Node.js、Spring B…

作者头像 李华