laravel不支持在自定义中间件类中定义$except数组来跳过路由,该机制仅适用于verifycsrftoken等内置中间件;正确方式只有两种:路由层用withoutmiddleware()排除,或中间件handle()内手动判断路径或路由名。

中间件里用 except 数组根本不起作用?
不是写法错,是 Laravel 根本不支持在中间件类里直接定义 except 数组来跳过路由。这个数组只在 Route::middleware() 链式调用中生效,且仅适用于内置的 VerifyCsrfToken、ThrottleRequests 这类预置中间件 —— 自定义中间件不会自动读取它。
常见错误现象:except 写在自定义中间件类里,结果所有路由照常执行,调试发现 handle() 仍被调用;或者把 except 当成全局白名单,误以为能跳过控制器方法。
- 真正起效的方式只有两种:路由层排除,或中间件内部手动判断
- 别在自定义中间件里加
protected $except = [...],Laravel 不认 - 如果用了
app/Http/Middleware/TrustProxies.php这类系统中间件,它的$except是特例,不能套用到你自己的类上
路由定义时跳过中间件:用 withoutMiddleware() 或 middleware([])
这是最干净、最可控的方式,适合明确知道哪些路由不该走某中间件的场景,比如 API 登录接口绕过鉴权中间件。
使用场景:单个路由、路由组、资源路由中的个别动作(如 create、store)需要跳过。
Route::get('/login', [AuthController::class, 'showLogin'])->withoutMiddleware([AuthCheck::class]);-
Route::middleware([AuthCheck::class])->group(function () { ... })->withoutMiddleware([RateLimit::class]);(注意:这会从整个组中移除RateLimit) - 资源路由中单独跳过:
Route::resource('posts', PostController::class)->only(['index', 'show'])->middleware([AuthCheck::class]);,再单独定义store路由并省略中间件
中间件内部手动跳过:用 $request->route() 匹配路径
当跳过逻辑较复杂(比如按前缀、带参数的路径、或需结合用户角色),就得在中间件 handle() 里自己判断。核心是拿到当前路由信息,和白名单比对。
性能影响很小,但要注意路由名未定义时 $request->route()->getName() 会返回 null,直接调用会报错;路径匹配也建议用 str_starts_with() 而非 strpos(),避免误匹配(比如 /admin 匹配到 /administrate)。
- 示例判断路径前缀:
if (str_starts_with($request->path(), 'api/public')) { return $next($request); } - 匹配完整路径:
if (in_array($request->path(), ['health', 'debug/bar'])) { return $next($request); } - 安全起见,先检查路由是否存在:
if ($request->route() && in_array($request->route()->getName(), ['webhook.receive', 'stripe.webhook'])) { ... }
Laravel 10+ 的 middlewareExcept() 不是通用方案
这个方法只存在于 Illuminate\Routing\Router 类中,且仅用于框架内部(比如为 Route::apiResource() 注入默认中间件时做排除),不是公开 API,文档没提,也不该在应用代码里调用。
容易踩的坑:搜到某些博客写的 Route::middlewareExcept(AuthCheck::class, ['login']),这行代码根本不存在于任何 Laravel 版本中 —— 是作者混淆了源码逻辑和可用方法。
真实情况是:没有“全局 except”机制,也没有“中间件级白名单配置”。所有跳过逻辑,最终都落在路由定义或中间件自身判断上。路径匹配那块最容易漏掉带查询参数的 URL,比如 /callback?source=wechat,$request->path() 只返回 /callback,所以别拿完整 $request->fullUrl() 去匹配静态白名单。










