权限校验必须在控制器执行前完成,中间件因其位于路由匹配后、控制器调用前的“黄金窗口”,天然适配该逻辑;若将权限判断写入控制器则重复冗余且易遗漏,而permissionmiddleware依赖authenticate中间件提供的$user实例,故其必须置于auth之后。

权限拦截逻辑天然适合中间件的执行位置
权限校验必须发生在控制器执行前,且不能侵入业务代码——中间件正好卡在路由匹配之后、控制器调用之前这个“黄金窗口”。Kernel 把请求按顺序推过中间件栈,Authenticate 必须先跑通,PermissionMiddleware 才能拿到有效的 $user 实例;如果把权限判断写进控制器,等于每处都要重复 if (!$user->can('delete-post')),既难维护又容易漏。
PermissionMiddleware 依赖用户实例,而它只在认证中间件后才可用
看源码就知道:PermissionMiddleware 的 handle() 方法直接调用 $user->canAny($permissions),但 $request->user() 是空的,除非前面已经有 Authenticate 或类似中间件把它塞进 request。常见错误就是把 permission:delete-post 放在 auth 前面,结果抛出 Call to a member function canAny() on null。
- 检查
app/Http/Kernel.php中$middlewareGroups['web']的顺序,确保\App\Http\Middleware\Authenticate::class在自定义权限中间件之前 - 不要在全局
$middleware数组里注册权限中间件——它会作用于所有请求(包括登录页),导致未登录时直接报错 - API 路由用
api组,记得对应组里也配了auth:sanctum或auth:api
中间件参数让权限规则可配置,避免硬编码
路由层传参是 Laravel 权限中间件能复用的关键:middleware('permission:edit-post') 和 middleware('permission:publish-post,delete-post') 共用同一个 PermissionMiddleware 类,只是 $permissions 参数不同。不用为每个权限写新中间件,也不用在控制器里拼字符串。
- 参数以字符串形式传入,
handle()方法第三个参数开始接收,比如function handle($request, Closure $next, ...$permissions) - 多个权限默认是“或”关系(
canAny),要“且”关系得自己改逻辑或换用canAll - 注意参数里别带空格:
permission:edit post会被拆成['edit', 'post'],应写成permission:edit-post
性能上,中间件比模型层拦截更早止损
如果把权限判断放在 Eloquent 模型的 boot() 或 Observer 里,请求已经进了数据库查询甚至事务,才发现没权限——白花了 IO 和 CPU。中间件在框架最外层就拦截,连 DB 连接都省了。但要注意:Laravel Permission 的 canAny() 默认走的是缓存 + 关系查询,高频权限检查建议配合 spatie/laravel-permission 的缓存开关或预加载优化。
真正容易被忽略的是「权限缓存失效时机」:手动调用 $user->givePermissionTo() 后,can() 不会自动刷新,得清缓存或等 TTL 到期。这不是中间件的问题,但常被误认为是中间件没生效。











