RBAC权限管理模型,附java前后端实现代码、SQL

张开发
2026/4/9 5:09:56 15 分钟阅读

分享文章

RBAC权限管理模型,附java前后端实现代码、SQL
RBAC权限管理代码地址https://gitee.com/tengyangxun/rbac-permission-management 有兴趣的帮忙点点star一、常见权限模型1、RBAC模型基于角色的访问控制。这是目前企业级应用最主流的模型。它的核心逻辑是用户 ----角色 ---- 权限。核心逻辑用户本身没有权限用户是什么角色有什么权限。打个比方公司里的“财务章”不是锁在某个人兜里而是谁当了“财务主管”谁就能盖这个章。优点管理极简Alice 离职了新来的 Eve 只要接替“财务总监”的角色权限立刻自动继承。职责明确方便进行“职责分离”例如会计不能兼任出纳。缺点不够灵活。如果想让 Alice 只能在周五看文档RBAC 很难实现除非你专门建个“周五财务总监”的角色。2、ACL模型访问控制列表核心逻辑权限是挂在“资源”身上的。每个文件都带个小本本记着谁能干什么。打个比方一间教室门口贴张纸上面写着“张三可以进李四不能进”。优点直观对单个文件的控制极其精准。缺点维护灾难如果公司有 1 万个文件1 千个员工当一个员工岗位变动时你得去修改 1 万个文件上的“小本本”。3、ABAC模型基于属性的访问控制这是目前最先进、最复杂的模型通常用于金融、国防等对安全性要求极高的系统。核心逻辑通过逻辑表达式动态计算。权限取决于用户属性职级、部门环境属性时间、IP、设备资源属性文档密级。打个比方大楼保安不看你的职位他有一套规则“如果是本校学生且体温正常且带了证件且在 23:00 前准许进入。”优点极致灵活可以实现“只有在公司 WiFi 下才能查看工资条”这种复杂逻辑。权限最小化根据当前上下文动态授信安全性最高。缺点性能开销每次访问都要跑一堆逻辑判断。配置复杂策略写错了容易产生逻辑漏洞。4、总结对比在实际开发中往往是组合使用RBAC 为主确定 Alice 是“财务”这是基础。ACL 为辅针对某份特别机密的“年度审计报告”单独指定只有 Alice 能看。ABAC 增强在 Security 拦截器里加逻辑——即便 Alice 是财务如果她从外网 IP访问则禁止下载附件。特性维度ACL(访问控制列表)RBAC(基于角色的访问控制)ABAC(基于属性的访问控制)核心思想在资源上直接绑定用户/组的权限列表通过“角色”作为中间层连接用户和权限通过评估主体、资源、环境的属性组合来动态决策管理粒度资源级可精细到单个文件角色级权限以角色为单位打包属性级可达到极其精细的条件控制灵活性低每增删用户都需修改资源列表中适合大部分管理系统极高细粒度控制二、RBAC代码实现代码链接前端代码、后端代码1、项目结构这是一个完整的前后端分离项目模拟javaWeb企业级的权限管理机制。RBAC/ ├── backend/ # 后端 Spring Boot 项目 │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/lb/rbac/ │ │ │ │ ├── RbacApplication.java # 启动类 │ │ │ │ ├── config/ │ │ │ │ │ └── SecurityConfig.java # Spring Security 配置 │ │ │ │ ├── controller/ │ │ │ │ │ ├── SysLoginController.java # 登录、获取用户信息、获取路由 │ │ │ │ │ └── LaborInfoController.java # 劳动力信息管理演示权限控制 │ │ │ │ ├── entity/ │ │ │ │ │ ├── SysUser.java # 用户实体 │ │ │ │ │ ├── SysRole.java # 角色实体 │ │ │ │ │ └── SysMenu.java # 菜单实体 │ │ │ │ ├── mapper/ │ │ │ │ │ ├── SysUserMapper.java # 用户 Mapper查询角色和权限 │ │ │ │ │ └── SysMenuMapper.java # 菜单 Mapper查询用户菜单 │ │ │ │ ├── service/ │ │ │ │ │ ├── UserDetailsServiceImpl.java # 用户详情服务加载用户、角色、权限 │ │ │ │ │ ├── PermissionService.java # 自定义权限校验服务ss.hasPermi │ │ │ │ │ └── SysMenuService.java # 菜单服务构建动态路由树 │ │ │ │ ├── security/ │ │ │ │ │ ├── LoginUser.java # 登录用户信息包含角色和权限 │ │ │ │ │ └── JwtAuthenticationFilter.java # JWT 认证过滤器 │ │ │ │ ├── util/ │ │ │ │ │ └── JwtUtil.java # JWT 工具类 │ │ │ │ ├── dto/ │ │ │ │ │ ├── LoginRequest.java # 登录请求 DTO │ │ │ │ │ └── RouterVO.java # 路由 VO │ │ │ │ └── common/ │ │ │ │ └── Result.java # 统一响应结果 │ │ │ └── resources/ │ │ │ ├── application.yml # 配置文件 │ │ │ └── schema.sql # H2 数据库初始化脚本 │ │ └── pom.xml # Maven 配置 │ ├── frontend/ # 前端 Vue 3 项目 │ ├── src/ │ │ ├── api/ │ │ │ ├── login.js # 登录相关 API │ │ │ └── labor.js # 劳动力信息 API │ │ ├── store/ │ │ │ └── permission.js # 权限 Store存储 token、roles、permissions │ │ ├── router/ │ │ │ ├── index.js # 路由配置 │ │ │ └── permission.js # 路由守卫动态路由加载 │ │ ├── directives/ │ │ │ └── permission.js # v-hasPermi 指令 │ │ ├── utils/ │ │ │ ├── request.js # Axios 封装 │ │ │ └── component.js # 动态组件加载工具Vite 环境 │ │ ├── views/ │ │ │ ├── login/ │ │ │ │ └── index.vue # 登录页面 │ │ │ └── labor/ │ │ │ └── info/ │ │ │ └── index.vue # 劳动力信息页面演示 v-hasPermi │ │ ├── layout/ │ │ │ ├── index.vue # 布局组件 │ │ │ └── components/ │ │ │ └── SidebarItem.vue # 侧边栏菜单项 │ │ ├── App.vue # 根组件 │ │ └── main.js # 入口文件 │ ├── index.html │ ├── vite.config.js # Vite 配置 │ └── package.json │ └── sql/ └── schema.sql # 数据库脚本5 张表 初始数据2、技术栈与环境后端- Spring Boot 3.2.0 - Spring Security 6 - MyBatis Plus 3.5.5 - JWT (jjwt 0.12.3) - H2 Database (内存数据库) - Lombok - java 17前端- Vue 3 (Composition API) - Vite 5 - Pinia (状态管理) - Vue Router 4 - Element Plus (UI 组件库) - Axios数据库本文使用H2内存数据库只需要将后端启动数据库自动启动可访问控制台http://localhost:8080/h2-console进行数据管理3、运行项目1 启动后端cdbackend mvn cleaninstallmvn spring-boot:run后端启动后访问应用地址http://localhost:8080H2 控制台http://localhost:8080/h2-consoleJDBC URL:jdbc:h2:mem:rbac用户名:sa密码: (留空)2 启动前端cdfrontendnpminstallnpmrun dev前端启动后访问http://localhost:51733 测试流程1. 使用 labor_user / 123456 登录 2. 进入劳动力管理 - 劳动力信息页面 3. 观察只有查询按钮可见其他按钮被 v-hasPermi 指令隐藏 4. 点击查询按钮成功调用后端接口 5. 退出登录使用 admin / 123456 登录 6. 观察所有按钮都可见4测试账号账号密码角色权限说明admin123456超级管理员所有权限可以看到所有菜单和按钮labor_user123456劳动力查询角色labor:info:query重点测试账号只能看到查询按钮test_user123456普通角色无操作权限可以看到菜单但看不到任何按钮4、数据库设计核心表关系图解这 5 张表的关系可以用“蝴蝶型”结构来描述sys_user (用户表)存储用户信息。sys_role (角色表)存职位/角色。sys_menu (菜单/权限表)存权限你能干什么包括看页面和点按钮。sys_user_role用户角色关联表绑定用户的角色。sys_role_menu角色权限关联表绑定角色的权限。蝴蝶模型属性图如下5、权限流转1业务场景模拟当你以labor_user账号登录时后端程序会执行以下“三步走”查询查角色在sys_user_role里发现labor_user对应的role_id是2。查权限码在sys_role_menu里找到角色 2 关联的所有menu_id再去sys_menu表里把对应的perms字段取出来。结果得到labor:info:list和labor:info:query。鉴权前端发现你有labor:info:query于是显示“查询”按钮。后端当请求发送到/labor/info接口时Spring Security 检查你身上是否有labor:info:query这个字符串。匹配成功放行。2权限字符串权限字符串labor:info:query用于区分用户权限数据库中也存的是这样的格式这种命名方式遵循[模块]:[资源]:[操作]的逻辑每一段都有明确的意义第一段模块定义作用域。如system代表系统底层labor代表业务层。这方便在代码里进行大类过滤。第二段资源定义操作的对象。如user用户、role角色、info劳动力信息。第三段操作定义具体的动词。如list列表查询、query详细查询、add新增、remove删除。流转过程数据库 (sys_menu.perms) ↓ 后端 SQL 查询 (SysUserMapper.selectPermsByUserId) ↓ UserDetailsService 加载用户权限 ↓ LoginUser 对象包含 permissions 集合 ↓ /getInfo 接口返回给前端 ↓ 前端 Pinia Store 存储 ↓ v-hasPermi 指令判断按钮显示 ↓ 后端 PreAuthorize 二次校验3后端核心模块1. **PermissionService.java** - 自定义权限校验服务 - 实现 hasPermi() 方法用于 PreAuthorize(ss.hasPermi(...)) 注解 - 从 Spring Security 上下文中获取当前用户的权限集合 - 判断用户是否拥有所需权限 2. **UserDetailsServiceImpl.java** - 用户详情服务 - 实现 Spring Security 的 UserDetailsService 接口 - 登录时关联查询用户的角色和权限 - 将权限字符串注入到 LoginUser 对象中 3. **JwtAuthenticationFilter.java** - JWT 认证过滤器 - 从请求头中提取 Token - 验证 Token 并将用户信息注入到 Security 上下文 4. **SysMenuService.java** - 菜单服务 - 根据用户 ID 构建动态路由树 - 将菜单数据转换为前端路由格式后端AOP切面拦截,在Contorller中//***/** * 查询劳动力信息列表 * 使用 PreAuthorize 注解保护接口只有拥有 labor:info:query 权限的用户才能访问 * ss.hasPermi(labor:info:query) 会调用 PermissionService.hasPermi() 方法进行权限验证 * SpEL表达式去IoC容器寻找ID为ss的Bean通常是自定义的PermissionService * * return 劳动力信息列表 */PreAuthorize(ss.hasPermi(labor:info:query))GetMapping(/list)publicResultListMapString,Objectlist(){...略...}PreAuthorize自定义权限校验服务/** * 自定义权限校验服务 * 用于 PreAuthorize 注解中的权限验证 * 例如PreAuthorize(ss.hasPermi(labor:info:query)) * * author tom */Service(ss)publicclassPermissionService{/** * 验证用户是否具备某个权限 * 这是权限控制的核心方法用于后端接口鉴权 * * param permission 权限字符串如labor:info:query * return true 表示有权限false 表示无权限 */publicbooleanhasPermi(Stringpermission){// 1. 从 Spring Security 上下文中获取当前登录用户AuthenticationauthenticationSecurityContextHolder.getContext().getAuthentication();if(authenticationnull){returnfalse;}// 2. 获取 LoginUser 对象包含用户的权限集合Objectprincipalauthentication.getPrincipal();if(!(principalinstanceofLoginUser)){returnfalse;}LoginUserloginUser(LoginUser)principal;SetStringpermissionsloginUser.getPermissions();// 3. 判断用户的权限集合中是否包含所需权限// 如果用户拥有 *:*:* 权限表示超级管理员拥有所有权限if(CollectionUtils.isEmpty(permissions)){returnfalse;}returnpermissions.contains(*:*:*)||permissions.contains(permission);}/** * 验证用户是否具备任意一个权限 * * param permissions 权限字符串数组 * return true 表示有任意一个权限false 表示都没有 */publicbooleanhasAnyPermi(String...permissions){if(permissionsnull||permissions.length0){returnfalse;}for(Stringpermission:permissions){if(hasPermi(permission)){returntrue;}}returnfalse;}/** * 验证用户是否具备某个角色 * * param role 角色字符串如admin, labor_query * return true 表示有角色false 表示无角色 */publicbooleanhasRole(Stringrole){AuthenticationauthenticationSecurityContextHolder.getContext().getAuthentication();if(authenticationnull){returnfalse;}Objectprincipalauthentication.getPrincipal();if(!(principalinstanceofLoginUser)){returnfalse;}LoginUserloginUser(LoginUser)principal;SetStringrolesloginUser.getRoles();if(CollectionUtils.isEmpty(roles)){returnfalse;}returnroles.contains(admin)||roles.contains(role);}}4前端核心1. **permission.js (Store)** - 权限 Store - 存储 token、roles、permissions - 提供 hasPermission() 方法用于权限判断 2. **permission.js (Directive)** - v-hasPermi 指令 - 从 Store 中获取用户权限 - 判断是否显示按钮没有权限则从 DOM 中移除 3. **permission.js (Router)** - 路由守卫 - 登录后调用 /getInfo 获取用户信息和权限 - 调用 /getRouters 获取动态路由树 - 使用 router.addRoute 动态添加路由 4. **component.js** - 动态组件加载工具 - 使用 import.meta.glob 预加载所有组件 - 将后端返回的组件路径字符串转换为实际的 Vue 组件Vue页面中代码el-button v-hasPermi[labor:info:add]新增劳动力/el-button三、完整登录和权限鉴定实战1. 完整业务流转图2. 详细步骤拆解第一阶段认证Authentication—— 证明“你是谁”用户登录请求前端用户在界面输入labor_user/123456点击登录。后端验证账号密码。由于密码在数据库中是$2a$10$...的 BCrypt 加密格式后端会调用BCrypt.checkpw进行比对。生成令牌Token验证通过后后端生成一个 JWTJson Web Token。返回结果后端将 Token 返回给前端。此时前端会将 Token 存入环境变量如你在 Postman 配置文件中看到的token_labor项。第二阶段鉴权初始化Authorization Init—— 明确“你能干什么”由于 JWT 通常只包含用户基本 ID不建议放大量权限信息因此前端会立即发起第二次请求获取用户信息GetInfo。获取用户信息与权限列表查角色后端接收到请求根据当前用户 IDID: 2在sys_user_role表中查到对应的role_id为2。查权限码根据role_id: 2在sys_role_menu中找到关联的menu_id(2, 201, 2011)最终从sys_menu表提取出perms字段。结果后端返回一个对象给前端包含用户昵称劳动力查询员、所属角色标识labor_query、权限列表[labor:info:list, labor:info:query]。第三阶段权限应用Permission Application—— 实际控制前端 UI 控制前端收到权限列表后存入全局状态Vuex/Pinia。渲染页面时v-hasPermi[labor:info:add]指令启动。判断因为权限列表中不包含add字符串该按钮会被 DOM 移除。你只能看到“劳动力查询”按钮。后端安全拦截当你点击查询按钮发送请求到/labor/info时请求头携带token_labor。切面拦截后端MethodSecurityInterceptor拦截方法上的PreAuthorize(ss.hasPermi(labor:info:query))。放行SecurityContext中已缓存了该用户的权限集匹配成功执行数据库查询。3. 技术要点总结阶段关键组件核心数据作用登录JwtAuthenticationFiltertoken_labor建立会话确立身份获取信息GetInfo接口perms字符串数组为前后端鉴权提供依据UI 渲染v-hasPermi指令labor:info:query控制界面元素可见性接口保护PreAuthorizelabor:info:list最终的安全护城河四、补充1、接口测试文档地址在.\RBAC\backend\apifox目录下可以直接将json文件导入postman或者aipfox等测试工具

更多文章