# 5.4 权限管理(RBAC) :::tip 概述 RBAC(基于角色的访问控制)链路:user →(user_role)→ role →(role_menu)→ menu(type=1 权限节点)。身份在 JWT 中间件加载(缓存 300s),权限判定在 Bind 包装层(HasPerm),前端按通配权限放开按钮。 ::: ## RBAC 授权链路图 ```text 请求 ──▶ JWT中间件 ──▶ LoadIdentity(userID) │ user ─────────┤ dept_id → DeptId(数据权限用) user_role ────┤ role.code → Roles[] │ role.data_scope → DataScope(多角色取最宽松) role_menu ────┤ menu(type=1, ┤ permission → Perms[] status=0, │ hide=0) ────┘ ▼ 缓存 easygoadmin:user:identity:{uid} JSON TTL 300s ▼ Bind 包装层 HasPerm("sys:user:page"): permission=="" → 通过 │ Roles 含 ROLE_SUPER_ADMIN → 通过 │ Perms 精确匹配 → 通过 │ 否则 403 数据范围经 ctx(WithDataScope/WithDeptID)随行,在 applyFilters 末环过滤 缓存失效:角色保存权限/改数据范围 / 用户分配角色/改部门 等主动 Del,过期兜底 ``` ## 身份加载(LoadIdentity,缓存 300s) ```text 1) user 行回查 dept_id → DeptId(单列查询) 2) user_role → roleIds → role.code 集合(Roles) 同一查询顺带读 role.data_scope:多角色取最宽松(数值 min, 1 最宽 4 最严;缺列值按"全部"参与比较)→ DataScope 3) role_menu → menuIds → menu 中 type=1 且 status=0 且 hide=0 的 permission 非空去重集合(Perms) 4) 超管/平台:DataScope 强制"全部"(过滤层另有 dataScopeBypass 双保险) ``` ## 权限判定(consts.HasPerm) ```text 判定顺序: 1. permission 标签为空 → 通过(仅登录即可,如 /api/index/*) 2. Roles 含 ROLE_SUPER_ADMIN → 通过(超管全放行) 3. Perms 精确匹配 → 通过 4. 否则 → code=403 信封 权限码规范:sys:{模块小驼峰}:{action} 前端识别超管通配权限 *:*:*(getUser 返回)放开全部按钮权限。 ``` ## 权限点声明与管理 | 环节 | 实现 | |------|------| | 声明 | 接口 gmeta `permission:"sys:user:page"` 标签 | | 存储 | goframe_menu 表 type=1 行(permission 列) | | 创建 | 生成器自动创建(7 固定 + status/import/export 按需)或菜单管理手工 | | 分配 | 角色管理勾选 → POST /api/role/menu/save 重写 role_menu + 清身份缓存 | | 判定 | Bind 包装层 HasPerm(每请求) | 角色管理扩展接口: ```text GET /api/role/menu/list/:roleId 查询角色已勾选菜单(sys:role:getPermission) POST /api/role/menu/save 重写 role_menu + 清理相关用户身份缓存 (sys:role:savePermission) ``` 菜单树:GetUserMenuTree 由 user→role→role_menu→menu 组装(index.GetMenus 消费);超管返回全量菜单。 ## 缓存失效链 ```text 身份缓存 easygoadmin:user:identity:{uid} 删除时机: * role.SavePermission → clearRoleIdentityCache(roleId) → 查该角色全部 user → 逐个 Del * role.Update(含 data_scope 变更)→ AfterUpdate → 同上 * user.AssignRoles(user.Update/Add 均经此,改部门亦然) → clearIdentityCache(userId) * 兜底:300s 自然过期 ``` ## 前端配合 + 权限指令(directives/permission.ts)按 getUser 返回的 permissions 控制按钮显隐 + 超管返回 `*:*:*`,指令识别通配直接放行 + 后端不做按钮级校验——按钮级是前端 UX 控制,接口级是后端强制(**新增接口必须声明 permission 或评审确认留空**) ## 安全注意 ```text * hide=1 的权限节点不进 Perms——"禁用权限"用 status,不能用 hide * 权限留空 = 登录即可,评审需明确该选择(新接口默认应声明权限) * 身份缓存 300s 窗口内直接改库不会即时生效(API 路径均主动清缓存) * 超管绕过全部权限校验——平台管理端点的收紧依赖节点设计 ```