Laravel中间件执行顺序由注册位置决定,构成嵌套闭包链:越早注册越靠近外层,越早拦截请求、越晚处理响应;全局中间件包裹整个应用,路由中间件需显式绑定,数组顺序即执行顺序,且必须调用$next($request)才能继续链式执行。

中间件的执行顺序由 $middleware、$middlewareGroups 和路由定义共同决定
Laravel 不是按“全局 > 路由组 > 单个路由”线性叠加执行,而是把所有中间件构造成一个嵌套闭包链(类似俄罗斯套娃)。每次 next($request) 调用,实际是进入下一层中间件。所以顺序取决于你往链里“塞”的位置——越早注册的中间件,越靠近外层,也就越早拦截请求、越晚看到响应。
关键点:
-
$middleware(在app/Http/Kernel.php)里的中间件会包裹整个应用,包括 Artisan 命令和非 HTTP 请求(如队列监听器触发的 HTTP 请求),但不包含 CLI 命令本身 -
$middlewareGroups中的web或api是命名分组,必须显式通过->middleware('web')或Route::middleware('api')应用,不会自动生效 - 路由上直接调用
->middleware(XXX)时,该中间件会被插入到对应分组之后、控制器执行之前 - 多个中间件同时绑定时,数组顺序即执行顺序:
->middleware([First::class, Second::class])表示First先执行
handle() 方法中忘记调用 $next($request) 就会中断请求链
这是最常踩的坑:中间件不是“过滤器”,而是“拦截器 + 转发器”。如果你在 handle() 里做了权限判断后直接 return response()->json(...),那后续所有中间件和控制器都不会执行——这没错,但容易误以为“只是跳过”,其实整个链已终止。
正确做法是明确区分“放行”和“拦截”:
public function handle($request, Closure $next)
{
if (! $request->user() || ! $request->user()->hasRole('admin')) {
return response()->json(['error' => 'Unauthorized'], 403);
}
// ✅ 必须调用 $next 才能继续往下走
return $next($request);
}
漏掉 return $next($request) 的后果是:控制器永远收不到请求,且无报错,只有空白响应或超时。
全局中间件和路由中间件对 session、csrf 等依赖有隐式顺序要求
比如 StartSession 必须在 VerifyCsrfToken 之前运行,否则 CSRF token 读不到 session;而 EncryptCookies 又必须在 StartSession 之前,否则无法解密 session cookie。这些依赖关系不是靠文档记住的,而是看 $middleware 数组的书写顺序。
Laravel 默认顺序已经调好,但一旦你自定义中间件并加到 $middleware 开头或中间,就可能破坏这个链条。例如:
- 把自定义日志中间件放在
EncryptCookies之前 → 读不到解密后的 cookie - 把权限中间件放在
StartSession之前 →$request->user()为 null - 在
api组里错误加入StartSession→ API 请求无状态,session 写入无效且可能引发异常
中间件参数传递靠字符串语法,不能传对象或复杂结构
路由绑定中间件时支持传参,但仅限简单值,语法是 middleware('role:admin,editor'),对应 handle($request, $next, ...$roles) 中的 $roles = ['admin', 'editor']。
注意限制:
- 参数只能是字符串,用英文逗号分隔,不能带空格(
'role:admin, editor'会导致$roles[1]是" editor") - 无法传数组字面量、布尔值或变量引用,比如
middleware('throttle:60,1,true')中最后一个true仍是字符串 - 若需动态逻辑(如根据 URL 参数决定角色),应在中间件内部解析
$request,而不是试图塞进参数
参数本质是 Laravel 在解析路由时做的字符串切割,没有类型推断,也不经过 DI 容器。
中间件真正的复杂点不在写法,而在它横跨请求生命周期的两个方向:进入时可修改$request,退出时可修改 $response。很多人只关注“拦不拦”,却忘了返回的 $response 会被上游中间件再次处理——比如你在底层加了 X-Processed-By: MyMiddleware,但上层某个中间件又调用了 $response->withHeaders([]),这个 header 就丢了。











