Spring Boot 3 + Vue3 企业级 RBAC 权限框架设计与实战:菜单权限、按钮鉴权与数据范围如何贯通
🌐演示地址:http://ruoyioffice.com | 📦源码1·GitHub:ruoyi-office | 📦源码2·GitCode:ruoyi-office | 📦源码3·Gitee:ruoyi-office | 💬微信:17156169080(备注「RuoYi Office」)
「加个角色表关联菜单」只是 RBAC 的第一公里。企业里真正要命的是:前端按钮藏了,接口还能打;菜单有了,列表却看到全公司工资;Token 过了,租户却串了。本文以 RuoYi Office(Spring Boot 3 + Vue3)为样本,按「功能架构全景」把用户 → 角色 → 菜单/权限 → 数据范围四段链路讲成可落地的实战,而不是概念课。
▲ 分层全景:顶链 User→Role→Menu→DataScope;中层前端交互 / 鉴权引擎 / 数据权限;底层角色菜单用户关联表
引言:企业级 RBAC 要同时挡住三件事
| 层次 | 要挡住的问题 | 典型技术点 |
|---|---|---|
| 功能权限 | 能不能进菜单、点按钮、调接口 | 菜单permission、@PreAuthorize、v-access |
| 数据权限 | 点了之后能看哪些行 | DataScope、Dept 规则、MyBatis 改写 |
| 身份与租户 | 你是谁、属于哪个租户世界 | OAuth2/Token、租户上下文 |
三者缺一,都会在演示环境「看起来正常」、生产环境「出事」。
一、模型:User – Role – Menu(含按钮)
经典四表(名称以库表为准,业务上认这张关系图即可):
用户 ──< 用户-角色 >── 角色 ──< 角色-菜单 >── 菜单 │ └─ 菜单.type:目录 / 菜单 / 按钮 └─ 菜单.permission:权限标识字符串要点:
- 用户不直接绑菜单——改权限只改角色,避免人走茶凉改不完。
- 按钮也是菜单树节点(类型=按钮),权限标识挂在节点上,例如
system:user:create。 - 角色还可挂数据范围(全部 / 本部门 / 本部门及以下 / 自定义部门 / 仅本人)——这是「行级」能力,不是菜单树的一部分。
▲ 角色是权限分配的枢纽:停用角色、调整菜单、设置数据范围,都从这里收敛
二、权限标识:前后端必须是同一字符串
约定俗成:
{模块}:{资源}:{动作} system:user:query system:user:create bpm:task:update2.1 后端:接口上声明
@PreAuthorize("@ss.hasPermission('system:user:create')")@PostMapping("/create")publicCommonResult<Long>createUser(...){...}@ss指向安全框架服务,内部会调到权限域的hasAnyPermissions(userId, permissions...)。
2.2 权限判定核心逻辑(实战可读版)
// PermissionServiceImpl#hasAnyPermissions(逻辑摘要)List<RoleDO>roles=getEnableUserRoleListByUserIdFromCache(userId);for(Stringpermission:permissions){if(hasAnyPermission(roles,permission))returntrue;}returnroleService.hasAnySuperAdmin(...);// 超管放行hasAnyPermission采用严格模式:
- 用
permission反查菜单 ID 列表 - 若权限串在菜单里不存在 →直接无权限(防止瞎写标识被默许)
- 看这些菜单是否与当前用户角色有交集
缓存加速角色、菜单、菜单-角色关系;空角色缓存会回源 DB,避免错误租户上下文把「空集」污染 Redis 后长期无权限。
2.3 前端:按钮同源
<Button v-access:code="['system:user:create']">新增</Button>或:
const{hasAccessByCodes}=useAccess();hasAccessByCodes(['bpm:task:update']);登录后权限码集合来自后端(随用户角色菜单计算)。前端隐藏只是体验;后端注解才是安全边界。两端字符串不一致,是权限事故的头号来源。
▲ 给角色勾选菜单树(含按钮)时,实际在维护「角色 ↔ 权限标识」集合
三、请求链路:从 Token 到@PreAuthorize
浏览器携带 Token → 网关/过滤器解析登录用户 → Controller 方法 @PreAuthorize → SecurityFrameworkService.hasAnyPermissions → PermissionApi / PermissionService(角色∩菜单) → 通过则进业务;否则 403实战注意:
- 只做前端路由守卫不够——直接调 API 必须拦。
- 权限变更后要让缓存失效——改角色菜单后,用户需重新拉取权限或等缓存驱逐,否则「后台改了前台还是旧的」。
- 超管与租户管理员要有明确策略,避免所有人用超管账号日常办公。
四、数据范围 DataScope:挡住「行」
功能权限过了,只说明能进「用户列表」接口;还能看到哪些用户,由数据范围决定。
publicenumDataScopeEnum{ALL(1),// 全部DEPT_CUSTOM(2),// 指定部门DEPT_ONLY(3),// 本部门DEPT_AND_CHILD(4),// 本部门及以下SELF(5);// 仅本人}落地方式(框架层):
- 登录用户解析出部门数据权限 DTO(是否全部、部门 ID 集、是否仅本人)
DeptDataPermissionRule等规则在 MyBatis 层拼OR/IN条件- 业务 SQL不必每个手写
dept_id = ?,减少遗漏
▲ 角色上配置数据范围:同一「用户查询」权限,经理与职员看到的行集合不同
常见坑:
- 报表自定义 SQL 绕过规则 → 泄漏
- 联表别名未登记到规则 → 条件加错表
- 本该「仅本人」的单据用了「全部」角色联调 → 演示正常、上线事故
五、Vue3 侧:动态路由 + 按钮 + 指令
企业后台通常:
- 登录成功拉用户信息与权限码
- 按菜单生成侧边栏与动态路由(无权限的路由不注册或进 403)
- 页面内按钮用
v-access:code或表格操作列auth字段过滤
与 Spring Boot 3 的配合要点:
| 项目 | 建议 |
|---|---|
| 权限码来源 | 只信后端计算,不在前端写死角色名判断 |
| 路由 meta | 可冗余权限码便于调试,仍以接口鉴权为准 |
| 多页签 | 权限变更后关闭相关页签并刷新权限 |
| 与流程模块 | 流程菜单、办理按钮同样走 permission,不另造「流程管理员口令」 |
六、实战清单:新增一个「导出」按钮要改哪
以「系统用户导出」为例:
- 菜单管理:在用户菜单下新增按钮节点,
permission = system:user:export - 角色:给需要的角色勾选该按钮
- 后端:导出接口加
@PreAuthorize("@ss.hasPermission('system:user:export')") - 前端:导出按钮加
v-access:code="['system:user:export']" - 数据范围:导出查询走同一 Mapper,自动吃 DataScope(勿另写无规则 SQL)
- 验证:无权限角色 403;有权限但 SELF 范围只能导出自己相关数据
漏掉任何一步,都会出现「能点不能导出」或「不能点却能抓包导出」。
七、和多租户、OAuth2 的边界(实战视角)
- 租户:多数业务表带
tenant_id,权限数据也在租户内;跨租户访问需显式能力(如租户拜访权限),默认拒绝。 - 认证 vs 鉴权:Token 解决「你是谁」;RBAC 解决「你能做什么」。微服务下常网关认证、服务内鉴权。
- 缓存:角色菜单变更要设计驱逐;否则「刚授权仍 403 / 刚收回仍能进」。
更完整的三位一体长文可对照仓库内既有权限体系文章;本文聚焦Spring Boot 3 + Vue3 贯通实战与全景分层。
八、端到端体验建议
- 演示环境用非超管账号登录,确认侧边栏菜单已裁剪。
- 打开角色管理,去掉某按钮权限,刷新后按钮消失,抓包对应接口应 403。
- 将角色数据范围改为「仅本人」,列表行数应变少。
- 对比超管账号,理解「功能全开 + 数据全开」仅用于运维。
在线演示:http://ruoyioffice.com
源码仓库:GitHub | GitCode | Gitee
常见问题(FAQ)
为什么权限标识在菜单不存在就判无权限?
严格模式避免开发者随意写@PreAuthorize("xxx")却从未录入菜单,导致「以为有校验其实永远 false / 或被错误放行」。标识必须先进菜单树。
前端用角色 code 判断可以吗?
短期方便,长期难维护(角色合并、重命名)。优先权限码;角色码仅用于少数「整角」场景。
数据权限能否只用 MyBatis 插件、不要角色配置?
插件是执行器,范围数据仍来自角色/用户配置。没有配置源,插件不知道该滤哪些部门。
和字段权限、流程节点权限是什么关系?
RBAC 管「系统功能与行级数据」;流程里字段可写/只读、节点按钮开关是 BPM 上下文能力,二者互补,不要混成一个开关。
结语
Spring Boot 3 + Vue3 做企业 RBAC,成败不在「有没有角色表」,而在权限标识前后端同源、接口强制鉴权、数据范围在 ORM 层自动生效、缓存与租户不串味。RuoYi Office 把这套管线做成可运行的默认能力——你加业务模块时,按「菜单按钮 → 注解 → v-access → DataScope」清单走,比从零发明一套权限中心可靠得多。
你们现在卡在「按钮权限」还是「数据范围」?有没有出现过前端藏了接口没拦?欢迎评论区交流。
💡想要体验 RuoYi Office 的强大功能?
🌐在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
📦源码仓库:GitHub | GitCode | Gitee
💬技术咨询:添加微信17156169080,备注「RuoYi Office」
⭐如果觉得不错,请给个 Star 支持一下!