核心问题是权限校验未执行或用户状态未注入:必须调用Auth::setUser($uid)使Auth::check()生效;权限名须与路由规则名(如admin/user/list)完全一致;中间件需正确绑定路由分组并显式return;API场景应从JWT或Redis读权限而非依赖Session。

ThinkPHP前后端分离下权限控制不生效,核心问题往往不是权限逻辑写错了,而是请求链路中某个环节“没接上”——比如权限校验根本没跑、用户状态没传过去、或权限名对不上。排查要从请求入口开始,一层层往下压。
检查登录态是否真正注入到 Auth 缓存
前后端分离时,登录成功后必须主动调用 Auth::setUser($uid),否则后续所有 Auth::check() 都会返回 false。这不是可选步骤,是强制前提。
- 确认登录接口里,在验证密码通过、生成 token 后,立刻执行
Auth::setUser($user->id) - 别只设 session 或写 token 就完事;TP 的 Auth 类默认从
session('think_auth')读缓存,这个 session 必须由setUser()写入 - 若用 JWT,
setUser()仍需调用(它会把权限数据预加载进内存或 Redis),不能跳过
确认权限名和路由定义完全一致
前后端分离常导致权限名误写成前端路径(如 /api/user/list)或拼接字符串(如 'user:' . $id),但 ThinkPHP 的 Auth::check() 只认路由规则名,且大小写、斜杠、前缀一个都不能错。
- 在中间件里加一行:
dump($request->rule()->getName());,看输出值是不是和auth_rule.name字段一模一样 - 例如路由定义为
Route::get('admin/user/list', 'Admin/UserController@list');,权限名只能是admin/user/list,不是admin.user.list、user_list或/admin/user/list - API 路由建议统一用命名方式,比如
->name('api.user.list'),然后权限名就固定用这个 name
验证中间件是否真正生效并正确返回
前后端分离常用 API 中间件做全局拦截,但中间件注册位置、绑定方式、返回逻辑出错都会导致“看似挂了,实则没跑”。
立即学习“PHP免费学习笔记(深入)”;
- 确保中间件已注册到对应路由分组,例如:
Route::group('api', function () { ... })->middleware('rbac'); - 中间件函数里每个分支都必须显式 return:
return $next($request);(放行)或return json(['code'=>403, 'msg'=>'无权限']);(拦截) - 漏掉
return $next($request);会导致函数末尾隐式返回 null,框架当成中断请求处理,表现就是接口直接空白或 500
区分 Session 和无状态鉴权场景
前后端分离通常不用 Session,但 Auth::check() 默认依赖 Session 缓存。如果登录态存在 JWT 或 Redis 中,就不能照搬后台模板那一套。
- API 场景下,不要在中间件里直接调
Auth::check('xxx')—— 它底层仍查 session,而 session 是空的 - 正确做法:从 JWT payload 里取
permissions数组,或根据用户 ID 查 Redis 缓存的权限集合(如SMEMBERS role_permissions_123),再用in_array()判断 - 按钮显隐只是前端体验,后端接口仍需做字段级校验和数据过滤,不能只靠中间件放行就认为安全



















