权限节点命名须与路由名完全一致,auth::check() 直接匹配 rule_name 字段,大小写或斜杠差异均导致失败;菜单生成需先过滤用户有效权限再递归建树;菜单渲染与鉴权解耦,超级管理员逻辑应下沉至 auth::check() 末端且不依赖数据库字段。

权限节点命名必须和路由名完全一致
ThinkPHP 6 的 Auth::check() 不会做任何格式转换,它直接拿当前请求的路由名($request->rule()->getName())去匹配 auth_rule.rule_name 字段。哪怕只差一个斜杠或大小写,校验就失败。
常见错误现象:菜单能显示、按钮也点了,但中间件始终拦截,Auth::check('admin.user.list') 返回 false。
-
Route::get('admin/user/list', 'admin.UserController@list')→ 对应的rule_name必须是admin.user.list,不是admin/user/list、adminUserList或user_list - 所有控制器方法必须显式绑定路由,不能依赖隐式路由(如
Route::rule()或资源路由未指定 name) - 调试时在中间件里加一行:
dump($request->rule()->getName());,确认输出值和数据库里存的rule_name一模一样
递归生成菜单树前先过滤用户有效权限
直接从 permissions 表全量递归生成菜单,会导致未授权用户看到灰掉但可点击的菜单项——这不是前端遮盖问题,而是后端数据没筛干净。
正确做法是:先查出当前用户所有已启用的 rule_name 列表(经角色中转),再用这些标识去关联菜单表,最后递归建树。
- 菜单表(如
jrbac_menu)需有rule_name字段,与auth_rule.rule_name对应,而非仅靠url匹配 - 查询语句要带权限过滤:
SELECT * FROM jrbac_menu WHERE rule_name IN (:rules) ORDER BY m_order,:rules是用户实际拥有的权限数组 - 递归函数
buildTree($menus, $parentId = 0)只处理已过滤后的数据集,避免遍历无意义节点
中间件里别直接调 Auth::check() 做菜单渲染
Auth::check() 是为接口鉴权设计的,返回布尔值;而菜单渲染需要结构化数据(含 children、icon、title 等)。两者混用会导致重复查库、缓存失效、层级错乱。
真实场景中,菜单树和权限校验应解耦:前者一次性生成并缓存,后者按需触发。
- 登录成功后,调用
Auth::setUser($uid)初始化权限缓存,同时额外执行一次菜单树构建并存入cache('menu_tree_'.$uid) - 菜单接口(如
/api/menu)直接返回缓存结果,不走Auth::check() - 中间件里只用
Auth::check()拦截非法请求,不参与菜单拼装逻辑 - 若菜单需动态响应权限变更,用事件监听器清空对应缓存,而非每次请求都重算
超级管理员权限绕过不能只靠 is_super 字段
单纯在用户表加个 is_super 字段,然后在中间件里写 if ($user->is_super) return true;,会埋下严重隐患:一旦该字段被 SQL 注入或管理后台误操作修改,整个系统权限体系就崩了。
更稳妥的做法是把超级管理员逻辑下沉到权限校验链路最末端,且不依赖数据库字段。
- 在
Auth类的check()方法里,判断$userId === 1(或配置的固定 super_id)时直接返回true,绕过所有规则查询 - 避免在
auth_rule表里给超级管理员插一堆*权限节点,这种设计会让日志审计失效、无法追溯具体操作来源 - 敏感操作(如删除用户、修改密码策略)仍建议单独加二次确认或短信验证,不因 super 权限自动豁免
权限树递归本身不难,难的是每层数据来源是否可信、缓存键是否带用户上下文、超级权限是否可审计——这些细节漏掉一个,上线后就只能靠日志一行行翻。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











