news 2026/11/5 6:15:53

Spring Boot+Vue权限管理系统:RBAC模型与接口级鉴权实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot+Vue权限管理系统:RBAC模型与接口级鉴权实战

简介:这份资源是一套基于 SpringBoot、MyBatis 与 Vue.js 构建的权限管理系统完整工程,面向具备一定 Java Web 基础、希望深入理解前后端分离权限控制实现的学习者与开发者。系统围绕用户管理、角色管理、角色资源与权限分配、日志记录及回收站恢复等模块展开,清晰呈现了从登录凭据、角色绑定到菜单资源授权的完整链路,适合用于课程设计、毕业项目参考或权限框架学习。压缩包共 312 个文件,约 292.22MB,以 187 个 xml 映射与配置、38 个 java 源码、34 个 class 编译文件为主,辅以 jar 依赖、yml 与 properties 配置、sql 建表脚本及 cdm、pdm 数据模型文件,工程结构与数据库设计资料相对齐全。目前已有 1463 人学习下载,读者可据此梳理 SpringBoot 整合 MyBatis 的持久层写法、Vue 前端交互组织方式,以及角色权限与日志、回收站等业务的具体落地思路。

1. 权限系统不是“加个登录框”:从一次越权事故说起

某高校实验室的内部管理系统上线两周,一个普通成员在浏览器地址栏把/user/list改成/user/delete?id=3,直接把另一个成员的账号删了。事后排查发现,后端接口只校验了“是否登录”,没校验“这个角色能不能调这个接口”,前端菜单藏起来了,但接口裸奔。这类事故在课程设计和毕设里极其常见,因为大多数权限系统只做了登录和菜单渲染,把真正的权限校验留成了黑匣子。

这套基于 Spring Boot + MyBatis + Vue.js 的权限管理系统,要解决的就是这件事:把“用户—角色—权限”三层关系落到数据库、后端拦截器和前端路由三处,做到接口级鉴权、菜单级渲染、按钮级控制。适合正在做毕设或课程设计、需要一套能讲清 RBAC 模型又能跑起来的学生,也适合要交付内部管理后台雏形的从业者。下面按需求、设计、实现、避坑、验收的顺序拆开讲,关键处给可抄的代码和表结构。

2. 需求与选型:为什么是 RBAC 而不是硬编码角色

2.1 先把权限需求拆成三类问题

权限系统的需求看着杂,归拢起来就三类。第一类是“谁能进来”,即认证,账号密码登录、会话保持、退出失效。第二类是“进来能看什么”,即菜单与页面可见性,不同角色登录后左侧菜单不一样。第三类是“能操作什么”,即接口与按钮级鉴权,这是最容易翻车的地方,也是这套系统区别于“登录 Demo”的核心。

把这三类问题对应到数据模型上,就是经典 RBAC(基于角色的访问控制):用户不直接持有权限,而是通过角色间接持有。一个用户可以挂多个角色,一个角色可以挂多个权限,权限颗粒度落到“菜单”和“操作”两种类型。这样新增一个岗位时,只需要建角色、勾权限,不用改代码。通常的做法是建五张核心表:用户表、角色表、权限(菜单)表、用户角色关联表、角色权限关联表。下面这张表是我一般会先画出来的字段规划,字段名直接对应后面 MyBatis 的映射。

表名关键字段说明
sys_userid, username, password, salt, status密码加盐存储,status 控制启用禁用
sys_roleid, role_name, role_key, statusrole_key 如 admin、editor,供代码判断
sys_menuid, parent_id, menu_name, path, perms, menu_typemenu_type 区分目录/菜单/按钮,perms 存权限标识
sys_user_roleuser_id, role_id多对多关联
sys_role_menurole_id, menu_id多对多关联

menu_type 这个字段值得多说一句。很多同学把菜单和按钮混在一张表里却不加类型字段,结果前端渲染菜单时把按钮也渲染出来了。用M(目录)、C(菜单)、F(按钮)三值区分,前端只取 M 和 C 建路由,后端鉴权只认 F 和 C 上的 perms 标识,职责就清楚了。

2.2 技术选型:三件套各管一段,别让它们互相越界

Spring Boot 负责后端容器、依赖注入和拦截器编排,MyBatis 负责把上面五张表的关联查询写清楚,Vue.js 负责前端路由和菜单渲染。选型上没有玄学,关键是划清边界:权限判断的“真相”只在后端,前端做的菜单隐藏和按钮置灰只是体验优化,不能当安全边界。

后端用 Spring Security 还是自己写拦截器,是课程设计里常见的纠结。我的建议是:如果毕设答辩要讲清鉴权链路,自己基于 HandlerInterceptor + 自定义注解实现一套轻量方案更好讲,代码量可控,逻辑透明;如果追求工程规范,用 Spring Security 的@PreAuthorize配合UserDetailsService更标准。这套方案采用前者,因为学生更容易在答辩时把“请求进来—解析 token—查权限—比对注解—放行或拒绝”这条链路讲明白。前端用 Vue Router 的addRoutes动态挂载路由,配合一个全局前置守卫做登录态判断。

依赖版本不必追新,Spring Boot 2.7.x、MyBatis 3.5.x、Vue 3 或 Vue 2 均可,重点是接口契约稳定。下面给出后端核心依赖的配置片段,版本号按你本地仓库实际可用的填,不要照抄一个不存在的版本。

<!-- pom.xml 关键依赖,版本按本地仓库实际可用填写 --> <dependencies> <!-- Web 容器与 MVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis 与 MySQL 驱动 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- JWT 用于无状态会话,也可换 session --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> </dependencies>

这段依赖里,mybatis-spring-boot-starter负责把 Mapper 接口自动注册成 Bean,jjwt-api负责签发和解析 token。如果你的课设要求用 session 保持登录态,把 JWT 换成 Spring Session 即可,拦截器里取用户的方式从解析 token 改成读 session 属性,其余鉴权逻辑不变。选 JWT 的理由是前后端分离下跨域调试方便,且答辩时“无状态”是个能讲清的点。

3. 总体架构与数据模型:五张表撑起三层鉴权

3.1 分层结构:Controller 只做参数校验,鉴权交给拦截器

后端按 Controller → Service → Mapper 三层组织,但鉴权不放在 Controller 里逐个人工判断,而是抽到拦截器统一处理。请求进入后,拦截器先解析 token 拿到 userId,再查该用户拥有的权限标识集合,最后和当前请求方法上的自定义注解@RequiresPerm("system:user:add")比对,命中则放行,否则返回 403。这样新增接口时只要加注解,不用改拦截器,符合开闭原则。

前端结构对应三个部分:登录页、布局容器(含侧边菜单)、业务页面。登录成功后,前端拿 token 存本地,同时请求/getInfo接口拿到用户信息和权限标识列表,用权限列表过滤本地路由表,再addRoutes动态挂载。菜单不是写死的,而是后端根据角色查sys_menu返回树形结构,前端递归渲染。这条链路的关键是:前端路由表和后端菜单表要能对上,靠的是path字段一致。

3.2 权限查询 SQL:一次关联查出用户的全部权限标识

鉴权时最频繁的操作是“根据 userId 查权限标识集合”,这条 SQL 写得好不好直接决定系统性能。用一条三表关联即可,不需要在 Java 里循环查角色再查权限。

-- 根据用户 id 查询其拥有的全部权限标识(去重) SELECT DISTINCT m.perms FROM sys_menu m JOIN sys_role_menu rm ON m.id = rm.menu_id JOIN sys_user_role ur ON rm.role_id = ur.role_id WHERE ur.user_id = #{userId} AND m.perms IS NOT NULL AND m.perms <> '' AND m.status = 1;

这条 SQL 的过滤条件有三个要点。perms IS NOT NULL AND perms <> ''是为了排除目录节点,目录只用于渲染菜单树,不参与接口鉴权。m.status = 1保证被禁用的菜单权限不生效,避免管理员禁用某功能后旧 token 还能调。DISTINCT是因为一个用户可能有多个角色,权限会重复。对应的 MyBatis Mapper 方法返回List<String>,Service 层把它塞进一个Set缓存到当前请求上下文,避免同一次请求里多次查库。

菜单树查询则用另一条 SQL 查全部启用的 M、C 类型节点,在 Service 层用递归或两次遍历组装成树。这里有个血泪经验:不要在 SQL 里用 MySQL 的递归 CTE 拼树,虽然能写,但可读性差,答辩时不好讲;在 Java 里组装树反而逻辑清晰,还能顺手做排序。

3.3 前端动态路由:权限标识过滤本地路由表

前端维护一份完整的本地路由表,每条路由的meta里写上perms标识,登录后拿后端返回的权限集合去过滤。下面这段是路由守卫的核心逻辑,Vue Router 3 和 4 的写法略有差异,这里按 3 的addRoutes写,4 换成addRoute循环即可。

// router/guard.js 全局前置守卫核心逻辑 router.beforeEach(async (to, from, next) => { const token = localStorage.getItem('token') if (!token) { // 未登录一律回登录页,白名单页面除外 return to.path === '/login' ? next() : next('/login') } if (store.state.user.perms.length === 0) { // 首次进入,拉取用户信息和权限 const info = await store.dispatch('user/getInfo') // 用权限标识过滤本地路由表,生成可访问路由 const accessible = filterRoutes(asyncRoutes, info.perms) accessible.forEach(r => router.addRoute(r)) // 动态路由挂载后需重新进入一次,否则白屏 return next({ ...to, replace: true }) } next() })

这段代码里有两个容易忽略的点。filterRoutes要递归处理子路由,不能只过滤第一层,否则二级菜单会漏。next({ ...to, replace: true })这行是必须的,因为addRoute之后当前导航已经匹配过了,不重新触发一次会白屏,这是新手最常踩的坑。权限标识的比对用includes即可,后端返回的是扁平数组,前端路由meta.perms是单个字符串,注意类型一致。

4. 核心模块实现:登录、鉴权、菜单三处落地

4.1 登录与 token 签发:密码加盐与过期策略

登录接口接收用户名密码,先按用户名查用户,再用盐值对明文密码做一次哈希比对。盐值存在用户表里,每个用户独立,避免彩虹表。比对通过后签发 JWT,payload 里只放 userId 和过期时间,不放角色和权限,因为权限会变,token 里带权限会导致改权限后旧 token 仍然生效。

// 登录核心逻辑,省略参数校验与异常包装 public String login(String username, String rawPassword) { SysUser user = userMapper.selectByUsername(username); if (user == null || user.getStatus() != 1) { throw new BizException("账号不存在或已禁用"); } // 用用户独立盐值做哈希,与库中密文比对 String encoded = DigestUtils.md5DigestAsHex( (rawPassword + user.getSalt()).getBytes(StandardCharsets.UTF_8)); if (!encoded.equals(user.getPassword())) { throw new BizException("密码错误"); } // 只放 userId 与过期时间,权限实时查库 return JwtUtil.createToken(user.getId(), 2 * 60 * 60 * 1000L); }

这里用 MD5 加盐是为了课设演示简单,实际交付建议换 BCrypt,Spring Security 的BCryptPasswordEncoder自带盐值管理,不用自己维护 salt 字段。token 有效期设两小时,配合前端在 401 时跳登录页。注意不要在 token 里塞权限列表,这是很多人图省事会犯的错,改一次角色就得让所有人重新登录,体验很差。

4.2 接口鉴权拦截器:注解 + 反射比对权限标识

拦截器在preHandle里做三件事:放行白名单、解析 token 拿 userId、查权限集合并与注解比对。注解定义在方法或类上,类上的注解作为默认值,方法上的覆盖类上的。

// 权限注解与拦截器核心片段 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresPerm { String value(); // 如 "system:user:add" } public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) { if (!(handler instanceof HandlerMethod)) return true; HandlerMethod hm = (HandlerMethod) handler; RequiresPerm anno = hm.getMethodAnnotation(RequiresPerm.class); if (anno == null) anno = hm.getBeanType().getAnnotation(RequiresPerm.class); if (anno == null) return true; // 未标注的接口默认放行,登录接口走白名单 Long userId = JwtUtil.parseUserId(req.getHeader("Authorization")); Set<String> perms = permissionService.getPermsByUserId(userId); if (!perms.contains(anno.value())) { resp.setStatus(403); return false; } return true; }

这段代码的关键设计是“未标注默认放行”,配合白名单使用。但要注意,默认放行意味着任何忘记加注解的接口都是裸奔的,所以我在项目里会加一条约定:所有/api/**下的接口必须显式标注@RequiresPerm或在白名单里登记,代码评审时专门查这个。getPermsByUserId内部做了请求级缓存,同一次请求多次调用只查一次库,实现上用一个ThreadLocal<Set<String>>存,请求结束时在afterCompletion里清理,防止线程池复用导致权限串号。

4.3 菜单树与按钮级控制:前端指令与后端返回对齐

菜单树由后端返回,前端递归渲染成侧边栏。按钮级控制用一个自定义指令v-hasPerm,在元素挂载时判断当前用户权限集合是否包含指定标识,不包含则移除该元素。

// 按钮级权限指令,Vue 3 写法 app.directive('hasPerm', { mounted(el, binding) { const perms = store.state.user.perms // binding.value 支持字符串或数组,数组表示满足其一即可 const need = Array.isArray(binding.value) ? binding.value : [binding.value] const ok = need.some(p => perms.includes(p)) if (!ok) el.parentNode && el.parentNode.removeChild(el) } })

指令里用removeChild而不是display:none,是为了防止用户通过开发者工具改样式把按钮“变”出来。当然这只是体验层,真正的防线还是 4.2 的接口鉴权。菜单渲染时,后端返回的树节点里带path和component字段,前端用component动态import对应页面,path与本地路由表对齐。这里要对齐好命名规范,比如后端存system/user/index,前端路由表里也写同样的相对路径,否则动态挂载会找不到组件。

5. 避坑与排查:五个最容易翻车的点

5.1 现象:改了角色权限,用户不重新登录就不生效

原因:权限集合被缓存在 token 或前端本地存储里,改库后旧数据还在。解决:token 只放 userId,权限每次请求实时查库并做请求级缓存;前端在关键操作前重新拉一次/getInfo,或在后端返回 403 时提示刷新。我一般会在角色权限变更接口里主动清掉相关用户的缓存 key。

5.2 现象:动态路由挂载后刷新页面白屏

原因:addRoute是运行时挂载,刷新后路由表重建,守卫里没重新挂载就next()了。解决:守卫里判断权限集合为空时先拉权限、挂路由,再next({ ...to, replace: true })重新进入。另外把 404 通配路由放到动态路由挂载之后再添加,否则会先匹配到 404。

5.3 现象:接口返回 403 但前端没提示,用户以为卡死

原因:拦截器直接setStatus(403)返回空 body,前端 axios 拦截器没统一处理。解决:后端返回统一 JSON 结构{code:403, msg:"无权限"},前端响应拦截器里判断 code 跳提示。注意 401 和 403 要分开处理,401 跳登录,403 只提示不跳转。

5.4 现象:多角色用户权限取交集而不是并集

原因:查权限时按角色逐个查再取交集,或 SQL 里漏了DISTINCT导致逻辑混乱。解决:一条关联 SQL 查出全部权限标识去重,天然是并集。RBAC 的语义就是“拥有任一角色的任一权限即可”,不要自作主张做交集。

5.5 现象:删除角色后关联表残留,用户登录报错

原因:只删了sys_role主表,没清sys_user_role和sys_role_menu。解决:删除角色放在一个事务里,先删两张关联表再删主表;或者建表时加外键并设ON DELETE CASCADE。课设里我倾向手写事务,因为答辩时能讲清“为什么要保证一致性”。

6. 验收与答辩:用一张检查表把系统讲透

验收这套系统,我一般不看页面多花哨,而是拿一张检查表逐项过。下面这张表可以直接拿去当答辩提纲,每一条都对应前面章节的实现点。

检查项操作步骤合格标准
认证错误密码登录、禁用账号登录均被拒绝且提示明确
接口鉴权用低权限账号 token 直接请求高权限接口返回 403,不返回业务数据
菜单渲染不同角色登录对比侧边栏菜单项与角色权限一致
按钮控制低权限账号查看列表页新增/删除按钮不渲染
权限变更后台改角色权限后重新登录菜单与接口权限同步生效
数据一致性删除角色后查关联表无残留记录

答辩时最容易被问的三个问题,提前准备好答案。第一个是“前端隐藏了按钮,用户直接调接口怎么办”,答案指向 4.2 的拦截器,强调安全边界在后端。第二个是“权限数据变了 token 里的权限不就过期了”,答案指向 token 只存 userId 的设计。第三个是“多角色权限冲突怎么处理”,答案指向并集语义和去重 SQL。把这三条讲顺,基本能覆盖 RBAC 的核心考点。

还有一个实操技巧:答辩演示前,准备两个账号,一个管理员一个普通成员,现场切换演示越权被拦截的过程,比讲十页 PPT 都直观。演示时故意用普通成员的 token 去调删除接口,让评委看到 403,这个动作本身就是对“接口级鉴权”最有力的证明。

从那以后我每次做权限相关的项目,都会在开发阶段就用低权限账号把每个接口手动请求一遍,而不是等测试阶段才发现裸奔的接口。这个习惯帮我挡掉过好几次上线前的越权隐患。希望这套结构和检查表能帮到你,照着搭一遍,毕设和交付都能拿得出手。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 6:15:32

CentOS7下Nacos 2.2.3单机版部署与MySQL持久化配置

1. 项目概述在微服务架构盛行的当下&#xff0c;服务发现与配置管理已成为系统设计的核心需求。Nacos作为阿里巴巴开源的动态服务发现、配置和服务管理平台&#xff0c;凭借其轻量级、高可用和易扩展的特性&#xff0c;在众多微服务解决方案中脱颖而出。本文将基于CentOS7操作系…

作者头像 李华
网站建设 2026/11/5 6:14:08

过滤器模式解析:从电商筛选到日志处理的实践

1. 过滤器模式基础认知第一次接触过滤器模式是在重构一个电商平台的商品筛选模块时。当时系统里有几十种商品属性&#xff0c;从价格区间、品牌到用户评价星级&#xff0c;各种筛选条件交织在一起&#xff0c;代码里到处都是if-else嵌套。直到发现过滤器模式这个利器&#xff0…

作者头像 李华
网站建设 2026/11/5 6:13:03

区块链与智能冷链优化中非海鲜贸易

1. 项目背景与行业痛点中非海鲜贸易长期以来面临着一个有趣的矛盾&#xff1a;一方面非洲西海岸盛产优质龙虾等海产品&#xff0c;另一方面中国作为全球最大的海鲜消费市场存在巨大需求。然而传统的跨境海鲜贸易链路存在几个核心痛点&#xff1a;冷链物流成本高昂&#xff08;占…

作者头像 李华
网站建设 2026/11/5 6:12:28

无人机分布式监控系统:架构设计与算法实现

1. 项目背景与核心价值无人机搭载相机网络的分布式监控系统正在成为安防、灾害监测和智慧城市领域的热门解决方案。相比传统固定摄像头网络&#xff0c;这种系统具有三大不可替代的优势&#xff1a;首先是机动性&#xff0c;无人机可以快速部署到任何需要监控的区域&#xff1b…

作者头像 李华
网站建设 2026/11/5 6:12:04

NHibernate联合主键实现与优化指南

1. NHibernate中的联合主键概念解析在数据库设计中&#xff0c;我们经常会遇到需要多个字段组合才能唯一标识一条记录的场景。这就是联合主键&#xff08;composite-id&#xff09;的典型应用场景。NHibernate作为.NET平台下成熟的ORM框架&#xff0c;提供了完善的联合主键支持…

作者头像 李华
网站建设 2026/11/5 6:11:43

东北大学高数上期末核心突破:可导性判定与极值拐点辨析

简介&#xff1a;本资源为东北大学《高等数学&#xff08;上&#xff09;》历年期末考试真题汇编&#xff0c;面向该校及同类理工科高校本科生&#xff0c;专用于考前系统复习、考点查漏与解题能力强化。试卷覆盖2005—2007年三套完整试题&#xff0c;题型规范、难度梯度合理&a…

作者头像 李华