权限管理

ThinkAdmin 的后台权限由 RbacAccess 路由中间件、AdminServiceNodeService、菜单服务和后台用户授权数据共同完成。实现上不是完整的“角色表”模型,而是“权限组 + 功能节点 + 用户授权”的简化 RBAC:

  • 控制器注释由 NodeService::getMethods() 扫描成节点。
  • 权限组保存在 system_auth,权限组与节点关系保存在 system_auth_node
  • 后台用户的 system_user.authorize 保存已分配的权限组 ID。
  • 用户登录后,AdminService::apply(true) 将可访问节点写入 Session 的 user.nodes
  • 菜单显示和模板按钮显示再通过 AdminService::check() 或全局函数 auth() 判断。

基础模型

功能节点

功能节点来自应用和插件控制器,格式通常为:

应用/控制器/方法

示例:

admin/user/index
admin/user/add
admin/user/remove
plugin-wemall/shop.goods/index

节点扫描规则来自 think\admin\service\NodeService

  • 扫描 app/ 下所有 PHP 文件中符合 应用/controller/*.php 结构的控制器,以及已安装插件目录下所有 controller/*.php 控制器。
  • 跳过 config/app.phprbac_ignore 配置的应用。
  • 跳过 think\admin\Controller 基类中的公共方法。
  • 控制器类名会转为小写节点,驼峰会转下划线;方法名只转小写,不会拆分驼峰。
  • 多级控制器在节点中使用点号,例如 shop/goods/Cate 对应 shop.goods.cate

权限组

权限组是后台“系统权限管理”里的授权分组,对应 system_auth。给权限组勾选节点后,关联数据写入 system_auth_node

只有标注了 @auth true 的方法节点会进入权限树供勾选;控制器父节点会作为树形展示节点补齐。

用户授权

后台用户对应 system_user。用户表中的 authorize 字段保存权限组 ID 列表。

用户登录成功后,后台登录控制器会调用:

\think\admin\service\AdminService::apply(true);

该方法会读取用户的权限组,筛选启用状态的 system_auth,再从 system_auth_node 读取节点列表写入 Session。

超级管理员

超级管理员不是固定用户 ID,而是账号名等于 config/app.php 中的 super_user 配置。默认值为 admin

return [
    'super_user' => 'admin',
];

AdminService::isSuper() 判断当前登录用户名是否等于该配置。超级管理员会直接通过默认权限检查,不需要分配权限组。

注解规则

节点注解写在控制器类或公共方法的 PHPDoc 中。当前实现只识别以下形式:

注解作用
@auth true该方法需要授权,进入权限树并由 system_auth_node 控制访问
@menu true该节点进入菜单表单的可选节点提示
@login true该方法仅要求后台登录,不要求权限组授权

注意:

  • 只识别 true,不要写成 @auth 1@auth yes 或只有 @auth
  • @auth true 表示权限控制;未登录访问时也会因为无授权节点而跳转登录。
  • @login true 只做登录检查,不会进入权限树。
  • @menu true 只提供菜单节点候选,不会自动创建菜单。
  • 节点标题来自 PHPDoc 的第一行说明;如果第一行就是注解,则使用类名或方法名作为默认标题。

常见写法:

/**
 * 系统用户管理
 * @auth true
 * @menu true
 */
public function index()
{
    SystemUser::mQuery()->layTable();
}

/**
 * 添加系统用户
 * @auth true
 */
public function add()
{
    SystemUser::mForm('form');
}

/**
 * 修改当前用户资料
 * @login true
 */
public function info()
{
    // 仅要求后台登录
}

权限检查流程

后台路由会注册 think\admin\support\middleware\RbacAccess。请求进入后台控制器时,大致流程如下:

请求进入路由中间件
  |
  |-- 当前应用在 rbac_ignore 中:直接放行
  |
  |-- AdminService::check() 通过:继续执行控制器
  |
  |-- 已登录但无权限:返回 code=0,提示“禁用访问!”
  |
  |-- 未登录或会话失效:返回登录地址

AdminService::check() 的默认逻辑:

  1. 使用 NodeService::fullNode() 将传入节点补全成完整节点。
  2. 读取 NodeService::getMethods() 的节点扫描结果。
  3. 读取 Session 中的 user.nodes
  4. 若存在 AdminService::registerCheckCallable() 注册的回调,会依次执行所有回调;任一回调返回 false 即拒绝,全部不返回 false 时直接通过,不再进入后续默认检查。
  5. 若存在全局函数 admin_check_filter(),使用该函数结果作为检查结果,不再进入后续默认检查。
  6. 超级管理员直接通过。
  7. 目标节点未标注 @auth true 时不做权限组校验;若标注了 @login true,则要求已登录。
  8. 目标节点标注 @auth true 时,必须存在于当前用户的 user.nodes

因此,auth() 判断只适合用于已扫描且已标注 @auth true 的节点。未知节点或未标注授权的节点不会被当作受控节点。

auth() 节点写法

全局函数 auth() 等同于:

\think\admin\service\AdminService::check($node);

它支持相对节点补全:

写法含义
auth(null)auth('')检查当前请求节点
auth('remove')检查当前控制器的 remove 方法
auth('user/remove')检查当前应用下的 user/remove
auth('admin/user/remove')检查完整节点
auth('shop.goods.cate/index')检查当前应用下多级控制器节点

模板中常用于控制按钮显示:

<!--{if auth("add")}-->
<button data-modal="{:url('add')}" class="layui-btn layui-btn-sm">添加</button>
<!--{/if}-->

<!--{if auth("remove")}-->
<button data-action="{:url('remove')}" data-value="id#{{d.id}}" class="layui-btn layui-btn-sm layui-btn-danger">删除</button>
<!--{/if}-->

这类按钮级控制是模板显隐控制,真正的服务端访问仍由目标方法的 @auth true 和 RBAC 中间件拦截。

权限配置

配置应用名称

权限树展示应用名称时会读取 config/app.phpapp_names,未配置时显示应用代码或插件名称。

return [
    'app_names' => [
        'admin' => '系统管理',
        'plugin-wemall' => '微商城管理',
    ],
];

忽略应用

rbac_ignore 中的应用不会被 RBAC 中间件检查,也不会参与节点扫描。

return [
    'rbac_ignore' => ['index', 'api'],
];

只应把确实不走后台会话权限的应用加入该配置。

登录地址

未登录或会话失效时,中间件会返回登录地址。默认使用 admin/login/index,也可以通过 rbac_login 调整:

return [
    'rbac_login' => 'admin/login/index',
];

若配置值以 /http://https:// 开头,会按原始地址返回;否则会通过 sysuri() 生成系统地址。

后台操作步骤

  1. 在控制器方法中补充 @auth true
  2. 打开“系统权限管理”,新增或编辑权限组。
  3. 在权限组中勾选需要授权的功能节点。
  4. 打开“系统用户管理”,给用户分配权限组。
  5. 用户重新登录,或重新调用 AdminService::apply(true) 刷新 Session 授权。

权限配置截图

菜单权限

菜单数据来自 system_menu,后台展示时由 MenuService::getTree() 过滤。

菜单过滤规则:

  • 只读取 status = 1 的菜单。
  • 会先执行可选的全局 admin_menu_filter()
  • 有子菜单的节点会保留为分组,最终 URL 会置为 #
  • 叶子菜单的 url 为空或 # 时会被移除。
  • 如果菜单配置了 node,会先检查 AdminService::check($menu['node'])
  • 内部 URL 会先用 / 分割并取前三段作为节点再次检查,再通过 admuri() 生成后台 Hash URL。
  • 外部 URL 不生成后台地址,只在带 params 时拼接查询参数。

菜单表单中的“节点”候选来自 MenuService::getList(),只列出标注了 @menu true 的节点。

/**
 * 系统菜单管理
 * @auth true
 * @menu true
 */
public function index()
{
    SystemMenu::mQuery()->layTable();
}

菜单层级最多选择到三级以内的父级,带实际 URL 的菜单不能再作为父菜单。

菜单管理截图

用户权限

后台创建或编辑用户时,除超级管理员账号外,系统要求给用户选择至少一个权限组。保存时权限组 ID 会写入 system_user.authorize

登录流程会做以下处理:

  • 校验账号、状态、密码和验证码。
  • 将用户基础信息写入 Session 的 user
  • 更新登录次数、登录时间和登录 IP。
  • 调用 AdminService::apply(true) 刷新当前用户节点。

禁用的权限组不会生效。删除权限组时,对应的 system_auth_node 授权关系会同步删除。

用户管理截图

缓存与刷新

控制器节点扫描结果会缓存在运行缓存中,缓存键为 SystemAuthNode,同一次请求中还会通过 sysvar('think.admin.methods') 复用。

可以使用以下方式刷新节点缓存:

\think\admin\service\AdminService::clear();

在调试模式下,打开权限表单或菜单表单时,后台会自动清理节点缓存并重新扫描。

用户授权节点保存在 Session 中,修改用户权限后通常需要用户重新登录;也可以在当前会话中调用:

\think\admin\service\AdminService::apply(true);

扩展点

默认权限检查可以通过两种方式扩展:

use think\admin\service\AdminService;

AdminService::registerCheckCallable(function (string $current, array $methods, array $userNodes) {
    // 返回 false 表示拒绝;不返回 false 时会短路通过,不再执行默认检查
});

或定义全局函数:

function admin_check_filter(string $current, array $methods, array $userNodes): bool
{
    return in_array($current, $userNodes, true);
}

菜单数据也可以通过全局函数处理:

function admin_menu_filter(array $menus): array
{
    return $menus;
}

节点扫描结果可以通过全局函数处理:

function admin_node_filter(array $nodes): array
{
    return $nodes;
}

常见问题

节点没有出现在权限配置中

优先检查以下几点:

  • 方法必须是控制器中的 public 方法。
  • 方法注释必须写 @auth true
  • 当前应用不能在 rbac_ignore 中。
  • 控制器类可以被正常自动加载。
  • 清理节点缓存后重新打开权限配置。

菜单节点没有出现在候选列表中

菜单候选列表只读取 @menu true 节点。若只写了 @auth true,节点可以进入权限树,但不会出现在菜单候选列表。

已经分配权限但仍然无权访问

可能原因:

  • 用户未重新登录,Session 中的 user.nodes 仍是旧数据。
  • 权限组状态被禁用。
  • 控制器节点缓存未刷新,节点名与实际访问节点不一致。
  • 菜单 URL 指向的前三段节点没有授权。
  • 目标方法不是实际被访问的方法,例如前端请求到了其他 API 节点。

auth('remove') 为什么没有拦住按钮

auth('remove') 会补全为当前控制器的 remove 节点。它依赖目标方法被扫描,并且目标方法标注 @auth true。如果节点不存在或未标注 @auth true,默认检查不会把它当作受控权限。

@auth true@login true 如何选择

后台业务操作通常使用 @auth true。只要登录即可访问、无需分配权限组的接口才使用 @login true,例如后台首页个人资料、已登录接口等。

最近更新:
Contributors: 邹景立, Anyon