中间件注册位置错误导致请求未进入:全局$middleware会拦截所有请求(含健康检查、静态资源),权限校验应放$middlewareGroups['api']或$routeMiddleware并显式绑定路由;handle()必须return $next($request)或明确响应,否则请求挂起。

中间件注册位置错了,请求根本进不来
你写了 CheckPermission 类,也运行了 php artisan make:middleware CheckPermission,但日志里完全没看到任何调试输出——说明请求压根没走到这个中间件。最常见原因是注册位置不对:
-
$middleware(全局):所有 HTTP 请求都经过,包括/health、静态资源、甚至队列触发的 HTTP 请求;不适合做权限拦截,容易误杀 -
$middlewareGroups['api']:只对显式标记为->middleware('api')的路由生效;适合放auth:sanctum、throttle这类 API 专用中间件 -
$routeMiddleware(路由级):必须先在这里命名,比如'check.permission' => \App\Http\Middleware\CheckPermission::class,再在路由中调用->middleware('check.permission');适合按需启用的权限校验
查错最快方式:在 handle() 开头加一句 Log::debug('check.permission hit');,然后看日志有没有输出。没输出,八成是注册位置或路由绑定漏了。
handle() 里漏了 return $next($request),请求就挂了
这是最隐蔽也最常踩的坑。浏览器转圈、cURL 超时、返回空响应——不是报错,就是没响应,十有八九是这里断链了。
中间件不是“过滤器”,而是“拦截器 + 转发器”;$next($request) 是把控制权交给下一个中间件或控制器的唯一入口。即使你要提前终止,也必须显式 return 一个响应:
- ✅ 正确:
return response()->json(['error' => 'Forbidden'], 403); - ❌ 错误:
$next($request);(没return) - ❌ 错误:
redirect()->to('/login');(少return) - ❌ 错误:
die('no access');(绕过 Laravel 异常处理和响应格式化)
权限判断逻辑写在 handle() 里,但用户未登录就调用 $request->user()
在 auth:sanctum 或 auth:api 中间件之后才能安全调用 $request->user()。如果直接在自定义中间件里写:
if (!$request->user()->hasRole('admin')) { ... }
而该中间件又没放在认证中间件之后,就会触发 Call to a member function hasRole() on null 错误。
解决办法:
- 确保中间件注册顺序:把
auth:sanctum放在你的权限中间件之前(在$middlewareGroups['api']数组中靠前) - 或改用防御性写法:
if (!$request->user() || !$request->user()->hasRole('admin')) - 或使用 Spatie 的
@can/@roleBlade 指令做视图层兜底,但不要依赖它替代中间件拦截
中间件参数传递失败,role:ADMIN,TEACHER 不生效
像 role:ADMIN,TEACHER 这种带参数的中间件写法,要求中间件类的 handle() 方法签名必须匹配:
public function handle($request, Closure $next, $role)
如果实际传入多个角色(如 ADMIN,TEACHER),Laravel 会把逗号分隔的字符串整体当做一个参数传进来,不会自动拆成数组。所以你需要手动解析:
$roles = explode(',', $role);
常见错误:
- 没声明第三个参数
$role,导致Too few arguments报错 - 用了
func_get_args()却没跳过前两个固定参数,误把$request和$next当成角色名 - 角色名大小写不一致(数据库存的是
admin,但中间件传的是ADMIN)
复杂点在于:中间件参数不能传数组或对象,只能是字符串;角色校验逻辑若涉及多表关联(比如通过团队继承权限),必须在 handle() 内部完成,不能依赖外部缓存或延迟加载——否则容易在高并发下出现状态不一致。


















