Laravel的中间件(Middleware)是如何实现请求过滤的? (全局与路由中间件)

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

laravel的中间件(middleware)是如何实现请求过滤的? (全局与路由中间件)

中间件的执行顺序由 $middleware$middlewareGroups路由定义共同决定

Laravel 不是按“全局 > 路由组 > 单个路由”线性叠加执行,而是把所有中间件构造成一个嵌套闭包链(类似俄罗斯套娃)。每次 next($request) 调用,实际是进入下一层中间件。所以顺序取决于你往链里“塞”的位置——越早注册的中间件,越靠近外层,也就越早拦截请求、越晚看到响应。

关键点:

  • $middleware(在 app/Http/Kernel.php)里的中间件会包裹整个应用,包括 Artisan 命令和非 HTTP 请求(如队列监听器触发的 HTTP 请求),但不包含 CLI 命令本身
  • $middlewareGroups 中的 webapi 是命名分组,必须显式通过 ->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) 的后果是:控制器永远收不到请求,且无报错,只有空白响应或超时。

全局中间件和路由中间件对 sessioncsrf 等依赖有隐式顺序要求

比如 StartSession 必须在 VerifyCsrfToken 之前运行,否则 CSRF token 读不到 session;而 EncryptCookies 又必须在 StartSession 之前,否则无法解密 session cookie。这些依赖关系不是靠文档记住的,而是看 $middleware 数组的书写顺序。

Prisms AI

Prisms AI

无代码构建AI应用的平台

下载

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 就丢了。

https://www.php.cn/faq/1972031.html

发表回复

Your email address will not be published. Required fields are marked *