权限节点必须用rule_name(如admin.user.edit)作唯一标识,三表结构为auth_rule、auth_role、auth_role_rule;中间件需按需查缓存校验节点,权限变更须主动清理对应role_id缓存。

ThinkPHP 6 的 RBAC 权限节点表怎么设计才不翻车
直接说结论:权限节点必须用 rule_name(如 admin.user.edit)作唯一标识,不能用中文名或 ID 做判断依据。否则中间件根本没法做路由级拦截。
常见错误是把权限表设计成「角色-菜单」二元关系,漏掉「操作节点」这一层——结果只能控制到页面,拦不住 POST /admin/user/delete 这种接口请求。
推荐三张表结构:
-
auth_rule:存所有可被鉴权的节点,字段至少含id、rule_name(唯一,建议小写+点号分隔)、title(中文描述)、type(menu/action)、status -
auth_role:角色表,id+name -
auth_role_rule:中间表,role_id+rule_id,别用字符串拼接或 JSON 存权限
注意:rule_name 要和路由定义对齐。比如你用 Route::get('admin/user/list', 'UserController@list'),那对应节点就该是 admin.user.list,不是 user_list 或 用户列表。
立即学习“PHP免费学习笔记(深入)”;
中间件里怎么动态读权限并拦截请求
别在中间件里手动查数据库再 in_array 判断——TP6 自带的 think\facade\Auth 不支持 RBAC,得自己写逻辑;更别用 Session 预存全量权限数组,一改权限就得清空所有用户 Session。
实操建议走「请求时按需查 + 缓存兜底」:
- 在中间件
handle()里,先用request()->rule()->getName()拿到当前路由规则名(如admin/user/edit) - 把这个名映射成权限节点名:去掉前缀、统一格式,比如转成
admin.user.edit - 查缓存:
cache('auth_rule_map_' . $roleId),里面存的是该角色拥有的所有rule_name的Set结构(可用 RedisSMEMBERS) - 没命中缓存就查
auth_role_rule关联auth_rule,生成并写入缓存,过期时间设为 30 分钟
关键点:缓存 key 必须带 role_id,不能只缓存全量节点;否则 A 角色删了权限,B 角色缓存还没过期,就出现误放行。
为什么 Auth::check() 和 $this->auth->hasRole() 都拦不住接口请求
因为这两个方法只校验「是否登录」和「是否属于某角色」,根本不碰权限节点。TP6 的 think-auth 扩展默认只做角色级控制,不是 RBAC。
典型现象:Auth::check() 返回 true,但用户点「删除按钮」时后端接口仍被执行——说明中间件压根没介入该路由。
排查步骤:
- 确认中间件已注册到
app/middleware.php的全局或分组中间件中,且顺序在SessionInit之后 - 检查路由是否用了
allowCrossDomain()或withoutMiddleware()显式跳过中间件 - 打印
request()->rule()->getName(),看是不是返回空——某些闭包路由或资源路由可能没绑定规则名,得手动补->name('admin.user.destroy')
如果用的是多应用模式,还要确认中间件注册在正确的应用目录下,app/admin/middleware.php 和 app/middleware.php 是两套东西。
权限变更后缓存不更新,用户要等 30 分钟才生效?
不是等,是必须主动清理。RBAC 场景下,只要改了角色权限分配(增删 auth_role_rule 记录),就要立刻清掉对应角色的权限缓存。
最稳妥的做法是在权限管理后台的保存逻辑里加一行:
cache()->delete('auth_rule_map_' . $roleId);
如果用 Redis,也可以批量删:cache()->handler()->deleteMatching('auth_rule_map_*');,但生产环境慎用,可能误伤其他角色缓存。
另一个坑:前端菜单渲染也依赖权限节点,但很多人只清后端缓存,忘了让前端重新拉取菜单接口——导致界面上按钮消失了,但用户手动发请求还是能成功。菜单接口本身也得走同一套权限校验逻辑,不能另起一套。
真正麻烦的从来不是建表或写中间件,而是权限粒度变细之后,每个新接口都得手动配一条 rule_name 并授权——漏一条,就是安全缺口。


















