SpringBoot开发企业后台-权限模型不用迷信RBAC可以去掉角色
SpringBoot开发企业后台-权限模型不用迷信RBAC可以去掉角色
工程判断:RBAC 用“用户—角色—权限”降低授权数量,是后台系统最常见的权限模型。但当组织较小、岗位变化频繁或角色只为某个人存在时,角色层也可能变成新的维护对象。
权限模型没有绝对标准,关键是授权关系是否可解释、变更是否可追踪、计算是否可缓存。为了套 RBAC 而制造大量临时角色,会让最终权限来源更难回答。
MetaLite 选择用户、组织与资源直接授权的轻量模型,减少角色中转,但保留资源与数据权限边界。本文先比较不同规模下的模型成本,再结合权限关系说明这一取舍适合什么系统。
一、经典 RBAC 解决的核心问题是什么
RBAC 的价值,不在于数据库里必须出现一张叫role的表,而在于避免直接为每个用户维护大量权限。
经典模型使用角色复用一组权限:
User ── UserRole ── Role ── RolePermission ── Permission当岗位跨越组织边界,或者同一组织内存在大量职责组合时,角色是一层非常有价值的间接关系。
例如,同一个研发部门里可能同时存在:
- 开发人员;
- 测试人员;
- 发布管理员;
- 只读审计人员。
这些职责与组织并不完全等价。若强行只按部门授权,就会让所有成员获得相同权限。
所以问题不应该是“RBAC 好不好”,而应该是:
业务中的权限集合,究竟更稳定地附着在角色上,还是组织上?
二、MetaLite 为什么直接使用用户—组织—资源
MetaLite 管理后台的功能权限由三类核心数据组成:
| 关系 | 当前实体 | 含义 |
|---|---|---|
| 用户—组织 | SysUserOrgEntity | 一个用户可属于多个组织 |
| 组织—资源 | SysFuncPermissionEntity | 一个组织可分配多个资源 |
| 资源 | SysResEntity | 描述菜单、页面、按钮和后端接口 |
关系可以简化成:
User ── UserOrg ── Org ── FuncPermission ── Resource设计重点是让组织同时承担两个职责:
- 表达人员归属;
- 承载一组功能权限。
当企业权限确实主要跟组织走时,它能减少一层重复映射。管理员不用先维护组织,再维护一套内容几乎相同的角色。
三、一个用户属于多个组织时,权限怎样计算
登录时,SysUserService.fillUserPermission先查询用户所属组织,只保留状态正常的关联:
List<String>orgIds=userOrgList.stream().filter(uo->StatusEnum.isNormal(uo.getStatus())).map(SysUserOrgEntity::getOrgId).toList();然后一次查询这些组织关联的资源,并对资源 ID 去重:
List<String>resIds=funcPermList.stream().map(SysFuncPermissionEntity::getResId).distinct().toList();因此,非管理员用户的最终功能权限是其所有正常组织权限的并集:
用户权限 = 组织 A 的资源 ∪ 组织 B 的资源 ∪ ……这条规则易于解释,也容易排查。看到某个权限时,可以沿着“用户属于哪些组织—这些组织分配了哪些资源”反查来源。
但它只有增加,没有显式拒绝语义。当前模型中不存在“组织 A 允许、组织 B 禁止,最后禁止生效”的优先级规则。
四、一份资源同时驱动菜单、按钮和接口
SysResEntity当前把资源分为四类:
resType | 资源类型 |
|---|---|
| 0 | 虚拟资源 |
| 1 | 菜单 |
| 2 | 页面 |
| 3 | 按钮 |
资源中既有前端字段,也有后端字段:
privateStringcomponentPath;privateStringresPath;其中componentPath可用于前端路由或按钮权限码,resPath表示需要控制的后端接口路径。
这让一次资源分配不只控制“菜单能不能看到”,还可以进入后端接口校验链路。
前端隐藏按钮只是体验控制,真正的安全边界仍必须在服务端。否则用户绕过页面直接发送请求,隐藏按钮没有任何保护作用。
五、后端如何判断当前接口是否需要权限
MetaLite 在API_RECEIVE入口切面中注册了ApiPermissionHandler。
它的判断顺序是:
- 没有登录用户 ID 时放行,由网关承担登录校验;
- 获取当前请求 URI;
- 判断该 URI 是否属于需要授权的资源;
- 从缓存读取当前用户的登录与资源信息;
- 管理员直接放行;
- 非管理员按资源中的
resPath做不区分大小写的精确匹配。
核心判断可以概括为:
booleanhasPermission=loginDto.getResList().stream().anyMatch(res->StringUtils.isNotBlank(res.getResPath())&&Strings.CI.equals(res.getResPath(),requestUri));这里有两个值得注意的边界。
第一,当前是 URI 精确匹配,不是 Ant Path 通配、正则或“HTTP 方法 + URI”的组合授权。
第二,只有被资源配置识别为需要授权的接口才进入这一步。因此,资源配置本身也是安全配置的一部分,不能只依赖前端是否展示入口。
六、为什么把权限放进登录缓存
如果每次请求都连接数据库,依次查询用户组织、组织资源和资源详情,权限校验会给所有业务接口增加多次查询。
MetaLite 在登录时计算orgIdList和resList,随后把登录信息写入 Redis。请求到来时,权限处理器读取缓存完成判断。
这是一种典型的读写取舍:
登录或授权变更时多做计算 ↓ 每次业务请求减少数据库访问缓存不能只写不刷新。当前源码在以下动作后会刷新在线非管理员用户的权限:
- 为组织重新分配资源;
- 组织状态发生变化;
- 用户加入或移出组织;
- 删除组织及其关联关系;
- 用户重新启用。
refreshUserPermissions会重新计算权限,然后更新 Redis 中的orgIdList和resList。
需要准确说明:这里只刷新当前仍有登录缓存的用户。离线用户无需更新,下一次登录时会重新计算。
当前源码还有一个值得继续收紧的边界:组织状态变化后虽然触发了刷新,但fillUserPermission过滤的是SysUserOrgEntity.status和资源状态,没有直接查询SysOrgEntity.status。如果停用组织时没有同步修改用户—组织关联状态,仅刷新缓存本身不能证明该组织的权限一定被排除。
更严谨的实现应在权限聚合查询中显式加入组织状态,或者在组织停用时以事务方式同步关联状态,并用集成测试验证在线用户权限立即收敛。文章不能把“调用了刷新方法”直接等同于“组织停用语义已经完整生效”。
七、省掉角色层,换来了什么,又失去了什么
组织直连资源的收益很直接:
- 表和配置关系更少;
- 权限来源更容易沿组织结构追踪;
- 人员调入、调出组织即可改变权限;
- 适合部门、门店、项目组主导授权的后台。
代价也同样明确:
- 同一组织内不同岗位难以只靠当前模型区分;
- 缺少跨组织复用的独立职责集合;
- 多组织权限按并集合并,没有显式拒绝和优先级;
- 权限模板、临时授权和职责分离需要额外设计。
如果业务开始频繁出现“同部门不同岗位”“跨部门同职责”,就说明独立角色层开始产生真实价值。那时可以演进为:
用户 → 组织 用户或组织 → 角色 → 资源角色应该由业务复杂度引入,而不是因为权限教程里通常有五张表就预先加入。
八、功能权限不等于数据权限
本文讨论的是“能否访问某个菜单、按钮或接口”,属于功能权限。
“能够看到哪些部门、哪些订单、哪些客户”属于数据权限,需要真正落到查询条件、租户边界或数据过滤器中。
MetaLite 当前虽然存在数据权限配置相关实体和管理代码,但在本次源码核验中,没有找到它已经自动注入所有业务查询条件的完整执行链路。因此不能把功能权限的组织模型直接宣传为已完成数据权限隔离。
这两个问题必须分开验收:
功能权限:这个接口能不能调用? 数据权限:调用后能够读写哪些数据?九、权限模型应该让排查路径更短
权限系统最难的往往不是第一次建表,而是半年后回答这些问题:
- 这个用户为什么有这个按钮?
- 调离部门后为什么还能调用接口?
- 修改授权后为什么没有立即生效?
- 前端没有菜单,后端为什么仍然放行?
MetaLite 选择用户—组织—资源直连,是因为在组织主导授权的系统里,这条路径更短、更符合管理员的实际操作。
它不是经典 RBAC 的万能替代品。真正可复用的设计原则是:先找到权限最稳定的业务载体,再决定是否需要角色这层间接关系。
十、用一个多组织用户还原权限并集
设用户 U 同时属于组织 A 和 B:A 拥有“查看用户”,B 拥有“重置密码”。登录聚合后,fillUserPermission应得到两个资源的去重并集。停用 U 与 B 的关系后重新计算,只应保留 A 的资源。
这组数据至少需要验证四个状态:关系正常、关系停用、组织停用、资源停用。当前源码对用户—组织关系状态有直接过滤,对组织状态的收敛还依赖管理链路触发刷新,因此不能只构造一次正常登录就宣布模型成立。
把权限来源输出为“资源来自哪个组织”的诊断信息,还能显著缩短线上排查时间;否则看到并集结果时,很难解释某个按钮究竟从哪条关系获得。
框架简介
MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。
源码基线
JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目backend-bom为准。
作者简介
15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。
持续更新
MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。
在线演示
演示地址: https://admin.metalite.top/
演示账号: guess
演示密码: admin@2026
