Laravel 12中使用中间件保护/api/admin/* API路由时持续返回403 Forbidden的问题求助
各位好,我目前在做一个基于Laravel 12 + Vue SPA的项目,用Sanctum做身份认证。现在想通过中间件保护/api/admin/*的路由,但无论怎么调整配置,访问这些路由都会返回403 | FORBIDDEN,想请大家帮忙排查问题。
先说说我目前的实现:
已做的配置
- Admin中间件:用来校验用户是否有
admin_access权限
// app/Http/Middleware/Admin.php public function handle(Request $request, Closure $next): Response { abort_if(Gate::denies('admin_access'), 403); return $next($request); }
- AuthGates中间件:动态根据用户角色和权限定义Gate规则
// app/Http/Middleware/AuthGates.php public function handle(Request $request, Closure $next): mixed { $user = auth()->user(); if ($user) { $roles = Role::with('permissions')->get(); $permissionsArray = []; foreach ($roles as $role) { foreach ($role->permissions as $permission) { $permissionsArray[$permission->title][] = $role->id; } } foreach ($permissionsArray as $title => $roles) { Gate::define($title, function (User $user) use ($roles) { return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0; }); } } return $next($request); }
- 中间件注册:在
bootstrap/app.php里配置了中间件
->withMiddleware(function (Middleware $middleware) { $middleware->appendToGroup('api', [ AuthGates::class, ]); $middleware->append(Admin::class); // 这里把Admin设为了全局中间件 $middleware->statefulApi(); })
- 路由配置:用
auth:sanctum和Admin中间件保护admin路由
Route::middleware('auth:sanctum')->group(function () { // ...其他路由 Route::prefix('admin')->group(function () { Route::prefix('users')->group(function () { Route::get('/', [AdminUserController, 'index']); // ...其他用户路由 }); })->middleware(Admin::class); });
问题现象
不管当前登录用户有没有admin_access权限,访问/api/admin/users这类路由都会直接返回403,完全没达到预期的权限校验效果。
排查方向与解决方案
经过反复测试,我梳理出几个可能的问题点和对应的解决办法:
1. 中间件执行顺序错误(最可能的根因)
我把Admin::class注册成了全局中间件($middleware->append(Admin::class)),而全局中间件的执行顺序是在路由组中间件之前的。也就是说,当请求到达时:
- 全局的
Admin中间件先执行,这时候AuthGates中间件还没运行(它属于api组中间件,在路由阶段才会执行) - 此时
admin_access这个Gate还没被定义,Gate::denies('admin_access')会默认返回true,直接触发403
解决办法:
删掉全局中间件注册里的$middleware->append(Admin::class);,因为我们已经在路由组里显式指定了Admin中间件。这样Admin中间件只会在路由组阶段执行,而此时AuthGates已经完成了Gate的定义,权限校验就能正常工作了。
2. 验证Gate定义的准确性
如果调整顺序后还是有问题,可以在Admin中间件里临时添加调试代码,确认Gate的状态:
public function handle(Request $request, Closure $next): Response { // 调试用:打印当前用户和Gate校验结果 dump(auth()->user()?->roles->pluck('id')); dump(Gate::has('admin_access')); // 看admin_access是否被正确定义 dump(Gate::allows('admin_access')); // 看当前用户是否有权限 abort_if(Gate::denies('admin_access'), 403); return $next($request); }
通过这些输出可以快速定位是Gate没定义,还是用户权限不匹配。
3. 检查权限名称的大小写和一致性
确保数据库里permissions表的title字段值是完全小写的admin_access,和Gate::denies('admin_access')里的字符串完全一致,大小写敏感会导致Gate匹配失败。
4. 确认用户已正确认证
虽然路由用了auth:sanctum,但要确保请求头里带了正确的Authorization: Bearer {token},或者是状态域请求(因为开启了statefulApi()),保证auth()->user()能拿到当前登录用户。如果用户未认证,AuthGates不会定义任何Gate,同样会触发403。
总结
最核心的问题大概率是中间件执行顺序导致的,全局Admin中间件先于AuthGates运行,导致Gate未定义就触发了权限校验。调整中间件注册方式后应该就能解决问题。
内容来源于stack exchange

