首页 / 资讯中心 / 文章详情

SpringBoot通用后台管理系统:RBAC权限模型与JWT鉴权实战解析

SpringBoot通用后台管理系统:RBAC权限模型与JWT鉴权实战解析 ★ FEATURED ARTICLE
简介基于SpringBoot的通用后台管理系统是一份面向Java后端初中级开发者及毕业设计人群的实战代码包。项目融合了数据持久层操作、权限控制、模板渲染等常见后台功能模块适合作为管理系统快速开发脚手架或学习参考。压缩包共152个文件以129个Java源文件为主辅以XML配置、properties配置、SQL脚本、Gradle构建文件以及markdown和docx设计说明整体大小约623KB结构清晰便于按模块阅读。已有109人学习浏览代码量不大但覆盖了从实体映射、业务服务到工具类的完整分层如FileInfServiceImpl、UserServiceImpl、SmallPictUtil等类可在实际功能中直接对照。通过阅读构建脚本、数据库脚本和文件系统设计文档可以快速理解项目启动流程与设计思路适合用来巩固SpringBoot整合持久层框架与权限控制等知识并在此基础上扩展为企业级后台系统。1. springboot 通用后台管理系统开箱即用的 RBAC 权限底座接手外包项目最怕从零搭后台权限底座用户、角色、菜单三张表的关系能磨掉两天登录认证、接口鉴权、操作日志又是一串重复劳动。这套 springboot 通用后台管理系统就是直接开箱的权限底座——内置完整的 RBAC 权限模型、JWT 无状态登录、Shiro 接口鉴权和一套现成的管理界面。接外包或做内部系统的开发者能拿它快速交付刚学 Spring Boot 的也能把它当研究权限落地的完整骨架。它解决的是后台系统里最重复的那部分用户管理、角色分配、菜单与按钮权限、操作日志剩下业务模块用自带代码生成器补齐。下面从架构、核心代码到启动步骤完整拆一遍。2. 架构设计与技术选型模块划分、RBAC 表结构与代码地图2.1 技术栈组合为什么这个搭配能撑起通用后台先交代这套系统的技术构成。后端主体是 Spring Boot 2.xORM 用 MyBatis-Plus认证授权用 Apache Shiro 加 JWT 的组合缓存走 Redis数据库是 MySQL接口文档用 Knife4j 增强版 Swagger。前端是独立的管理模板工程通过 HTTP 调用后端接口菜单由接口动态下发渲染。这个组合在中小项目里的成熟度很高各组件分工明确MyBatis-Plus 接管单表的增删改查和分页查询业务表不用写重复的 XMLShiro 负责认证与授权的抽象JWT 把登录状态变成无状态 token天然适合前后端分离部署Redis 承接验证码、token 黑名单和热点数据。各层之间耦合度低替换其中任何一个组件都不会牵动业务代码。我自己的选择逻辑是企业内网项目并发不高用 Shiro 比 Spring Security 更省心配置直观、学习曲线平缓对刚上手的人友好。Spring Security 的过滤器链概念强大但抽象度高排查权限问题时容易把新手挡在门外。这套系统选 Shiro 更多是出于上手门槛的考虑。功能模块上系统管理通常覆盖用户管理、角色管理、菜单管理、操作日志、文件上传五块业务模块则预留一两个示例 CRUD等开发者用代码生成器去填充。2.2 数据库设计五张核心表组成的 RBAC 权限模型权限系统最核心的是一套标准 RBAC基于角色的访问控制模型。这套系统的权限相关表共五张用户表 sys_user、角色表 sys_role、菜单表 sys_menu以及两张关联表 sys_user_role、sys_role_menu。下面是核心建表语句。CREATE TABLE sys_user ( user_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, nickname VARCHAR(50) DEFAULT NULL COMMENT 显示昵称, status CHAR(1) DEFAULT 0 COMMENT 状态0正常 1停用, create_time DATETIME DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; CREATE TABLE sys_role ( role_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 角色ID, role_name VARCHAR(50) NOT NULL COMMENT 角色名称, role_key VARCHAR(50) NOT NULL COMMENT 角色权限标识如 admin, status CHAR(1) DEFAULT 0 COMMENT 状态0正常 1停用, PRIMARY KEY (role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT角色表; CREATE TABLE sys_menu ( menu_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 菜单ID, parent_id BIGINT DEFAULT 0 COMMENT 父菜单ID0表示根, menu_name VARCHAR(50) NOT NULL COMMENT 菜单名称, path VARCHAR(200) DEFAULT NULL COMMENT 前端路由地址, perms VARCHAR(100) DEFAULT NULL COMMENT 按钮权限标识如 system:user:add, menu_type CHAR(1) DEFAULT M COMMENT 类型M目录 C菜单 F按钮, sort INT DEFAULT 0 COMMENT 排序号, PRIMARY KEY (menu_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜单权限表;sys_user_role 与 sys_role_menu 是两张只含外键字段的关联表分别记录用户拥有哪些角色、角色拥有哪些菜单。查询时靠这两张表把用户、角色、菜单串起来。逻辑说明用户不直接绑定权限而是通过角色间接获得权限新增用户只需分配已有角色不必逐个勾选菜单。menu 表的 perms 字段是按钮级权限的关键前端按钮是否显示、后端接口是否放行最终都落到这个字符串标识上。menu_type 区分目录、菜单、按钮三层结构前端动态渲染菜单时靠它决定路由层级。参数说明status 字段统一用 0 表示正常、1 表示停用是后台系统约定俗成的状态位写法perms 命名习惯是「模块:子模块:动作」例如 system:user:add 对应新增用户按钮保持这个命名规范权限标识才能一眼看懂。表与表之间用逻辑外键而非物理外键删除角色后不会因为外键约束把关联表卡死这也是这类系统的常见取舍。2.3 目录结构与功能模块清单拿到源码先认准这七个包后端工程标准结构如下包名以 com.example.admin 为例src/main/java/com/example/admin ├── AdminApplication.java # 启动类 ├── common/ # 统一返回体 Result、全局异常、常量与分页对象 ├── config/ # Shiro、Redis、MyBatis-Plus、Swagger 配置类 ├── controller/ # 接口层按模块分包system、biz、auth ├── service/ # 业务逻辑层接口实现类 ├── mapper/ # MyBatis-Plus 的 Mapper 接口 ├── entity/ # 与数据库表对应的实体类 ├── shiro/ # JWT Filter、自定义 Realm、Token 封装 └── utils/ # JWT 工具、密码加密工具、文件工具 src/main/resources ├── application.yml # 数据源、Redis、端口等全部配置 ├── mapper/ # 自定义 SQL 的 XML 文件 └── sql/ # 建库建表脚本与初始化数据典型的四层分包controller 只做参数接收和结果返回service 放业务规则mapper 只跟数据库打交道entity 是纯数据结构。好处是定位代码快新同事接手不用翻完整工程。resources 下的 sql 目录是关键首次部署不用手动建表直接导入脚本默认管理员账号也在这里初始化。拿到源码第一天我建议先读 sql 目录和 application.yml确认数据结构和配置项再顺着 controller 往 service、mapper 逐层追比从业务代码倒推要快得多。前端工程的结构一般对应后端的菜单树路由表里每个组件路径和后端 menu 表的 path 字段一一对应改菜单不要只改数据库要前后端一起核对。3. 核心模块拆解登录、鉴权与日志的完整实现链路3.1 登录认证密码校验、JWT 签发与 Redis 缓存登录接口是理解认证逻辑的入口。前端提交用户名和密码后端做三步查库校验密码、签发 JWT、把 token 写入 Redis。核心代码如下。RestController RequestMapping(/api/auth) public class LoginController { Resource private SysUserService userService; Resource private JwtUtil jwtUtil; Resource private RedisTemplateString, String redisTemplate; PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 1. 校验用户名密码数据库存的是 BCrypt 密文不能明文比对 SysUser user userService.checkPassword(dto.getUsername(), dto.getPassword()); if (user null) { return Result.error(账号或密码错误); } // 2. 签发 JWT把用户 ID 和用户名写进 claims String token jwtUtil.createToken(user.getUserId(), user.getUsername()); // 3. token 写 Redis登出时能主动拉黑 redisTemplate.opsForValue().set(login:token: user.getUserId(), token, 2, TimeUnit.HOURS); return Result.ok(Collections.singletonMap(token, token)); } }逻辑说明第 1 步的 checkPassword 内部用 BCrypt 的 matches 方法比对明文与密文数据库只存密文第 2 步签发的 JWT 带过期时间默认两小时第 3 步把 token 写 Redis 有两个目的一是用户异地重新登录后旧 token 可以主动失效二是登出接口直接删除 key 完成拉黑。参数说明LoginDTO 只有 username 和 password 两个字段前端提交格式必须一致token 过期时间在 JwtUtil 里配置单位毫秒2 * 60 * 60 * 1000 是两小时。开发调试期可以把过期时间调到 24 小时省得频繁重新登录上线前再改回来。这里有一个常见误用把密码放进 JWT 的 claims 里完全没必要token 只要携带用户 ID 和用户名就够了密码信息暴露在 token 里反而是风险。3.2 接口鉴权Shiro 过滤器链与 JWT Filter 的执行顺序登录认证解决「你是谁」接口鉴权解决「你能干什么」。这套系统的实现是全局过滤器链上挂一个自定义 JWT Filter所有请求先过它Filter 从请求头取出 token解析出用户信息后交给 Shiro 完成登录状态绑定之后由 Shiro 的授权机制判断当前用户是否拥有某个权限标识。Configuration public class ShiroConfig { Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean bean new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager); MapString, String filterChain new LinkedHashMap(); // 登录、验证码、接口文档不需要认证直接放行 filterChain.put(/api/auth/login, anon); filterChain.put(/api/auth/captcha, anon); filterChain.put(/doc.html, anon); // 其余所有接口都走 jwt 过滤器 filterChain.put(/**, jwt); bean.setFilterChainDefinitionMap(filterChain); return bean; } }过滤链严格按照 Map 的插入顺序匹配先匹配到的规则生效所以放行接口必须写在 /** 之前否则会被通配符吃掉变成所有接口都要认证。jwt 过滤器解析成功后会调用 SecurityUtils.getSubject().login(token) 把用户身份绑定到当前线程后续的 RequiresPermissions(system:user:add) 注解才能拿到授权信息判断放行还是抛权限不足异常。逻辑说明这里有个前后端分离必须处理的差异——jwt 过滤器校验失败时要返回 JSON 格式的 401 响应不能跳转登录页面。如果返回的是 HTML前端会拿到一堆标签而不是可解析的错误对象接口调用方的错误提示就会变成「解析失败」这种玄学报错。另外自定义 Realm 里要重写 doGetAuthorizationInfo 方法从数据库查出当前用户的角色和权限标识集合Shiro 的注解判断就靠这份数据。3.3 操作日志与全局异常让每个请求都有迹可循后台系统还有个刚需是操作日志。实现方式是自定义注解 Log 加上 Spring AOP 切面在 controller 方法上标注 Log(新增用户)切面在方法执行后把操作人、操作内容、请求参数、耗时写入日志表。切面核心代码如下。Aspect Component public class LogAspect { Resource private SysLogMapper logMapper; AfterReturning(annotation(controllerLog)) public void afterReturning(JoinPoint point, Log controllerLog) { SysLog log new SysLog(); log.setTitle(controllerLog.value()); log.setMethod(point.getSignature().getName()); // 从 Shiro 会话里取当前登录用户 log.setOperName(SecurityUtils.getUsername()); log.setOperTime(new Date()); log.setRequestParams(Arrays.toString(point.getArgs())); logMapper.insert(log); } }逻辑说明AfterReturning 表示方法正常返回后才记录日志业务异常不会被记成「操作成功」。操作人取自 Shiro 会话前端不用传也传不可信。请求参数直接序列化进日志表排查线上问题很有用但要注意日志里绝不能出现密码字段写库前先做脱敏处理。与切面配合的是全局异常处理器 RestControllerAdvice统一把业务异常、权限不足异常、参数校验异常转成 Result 格式的 JSON 返回前端只需处理一种响应结构。service 层抛出自定义 BizException全局处理器接住message 直接展示给用户省掉每层重复写 try-catch 的样板代码。这套「注解 切面 全局异常」的组合几乎是所有通用后台的标配理解透了对以后写别的项目也有直接帮助。4. 本地跑起来从环境准备到前后端联调的完整步骤4.1 环境准备JDK、MySQL、Redis 缺一不可动手前先备齐依赖软件常见版本要求如下表软件建议版本用途JDK1.8 或 11运行 Spring BootMaven3.6 及以上依赖管理与构建MySQL5.7 或 8.0主数据库Redis5.x 及以上验证码与 token 缓存Node.js14 及以上前端工程构建可选判断标准很简单MySQL 和 Redis 是硬依赖不启动后端根本起不来Node.js 只在你要跑前端工程时才需要。如果只想先验证后端接口逻辑直接打开接口文档调试即可前端可以延后再搭。4.2 三步启动后端导入 SQL、改配置、跑应用第一步导入数据库脚本。在终端执行mysql -uroot -p sql/admin_db.sql脚本会建库建表并写入默认管理员账号。如果脚本里没有 create database 语句先手动创建库再指定库名导入。第二步修改 application.yml 的数据源与 Redis 配置。这一步的翻车率最高拿到的源码大概率带着别人的开发配置必须改成你本机的账号密码。spring: datasource: url: jdbc:mysql://localhost:3306/admin_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 改成你自己的密码 redis: host: localhost port: 6379 server: port: 8080url 里的 serverTimezoneAsia/Shanghai 不能省缺了它日期字段会差 8 小时避坑章节还会专门说。Redis 如果有密码在 redis 节点下加 password 字段没有就留空。端口如果被占用改 server.port 即可注意前端代理地址也要同步改。第三步在工程根目录执行mvn spring-boot:run看到 Tomcat started on port(s): 8080 的日志后端就算起来了。接着打开 http://localhost:8080/doc.html登录接口试一次能拿到 token 说明数据源、密码加密、JWT 签发这一整条链路都通了。4.3 联调验证清单登录、授权、登出全链路自测后端跑通只是第一步还要按清单过一遍确认权限链路没有隐患用默认管理员账号登录确认返回 token且 Redis 里出现 login:token:* 的 key。在接口文档里调用户列表接口带 token 能返回数据不带 token 返回 401。用一个没有授权的新账号调删除用户的接口确认返回 403 而不是 200。调登出接口旧 token 立即失效再调业务接口返回 401说明拉黑生效。新建一个测试角色只分配部分菜单用该角色登录确认前端只显示被授权的菜单。提示这套清单每次改动权限相关代码后都值得强制走一遍五分钟能兜住绝大多数安全层面的低级错误。前端工程要跑的话解压前端目录后按常规流程 npm install、npm run dev本地开发环境把接口地址代理到 http://localhost:8080 即可。前后端联调最常见的报错就是跨域这类系统后端一般已经加了跨域过滤器如果还是报跨域优先检查过滤器顺序和 allowedOrigins 配置。5. 避坑记录启动失败、401 与菜单空白的高频排查这一章的内容来自实际调试这类系统的踩坑记录每条按「现象 → 原因 → 解决」写遇到类似问题可以直接按顺序排查能省下不少对着报错发呆的时间。5.1 启动即失败Failed to configure a DataSource 报错现象执行 mvn spring-boot:run 后控制台抛出 Failed to configure a DataSource: url attribute is not specified应用直接退出。原因application.yml 的数据源配置没有生效。常见两种情况一是配置里 url 还指向别人的数据库地址和账号二是 IDE 没把 src/main/resources 标记为资源根目录配置文件没打进 classpathSpring Boot 启动时根本读不到。解决先确认 resources 目录在 IDE 里显示为 Resources 根目录再核对 yml 中 url、username、password 三项是否全改成本机环境。还有一个玄学点yml 不能混用 Tab 和空格缩进否则配置被静默忽略报错还指向不明排查起来很费时间。5.2 接口全部 401登录成功却进不了业务接口现象登录接口正常返回 token但拿着 token 调业务接口全部返回 401 未授权。原因前后端对 token 的传递位置不一致。前端把 token 放请求头 Authorization而后端 JWT 过滤器读的是自定义头 token或者在 Redis 里 token 已被新登录覆盖旧 token 校验不过。解决统一成 Authorization: Bearer 的规范写法后端过滤器先读 Authorization 头再兼容旧的自定义头。排查时先看 Redis 里 login:token:* 是否真实存在再比对过期时间两步能定位大部分问题。要注意的是 JWT 本身是无状态的服务端重启后签名密钥不变旧 token 依然有效如果希望重启即失效必须依赖 Redis 里的校验记录这也是这套设计把 token 同步写进 Redis 的根本原因。5.3 菜单树空白登录后侧边栏没有任何菜单现象登录成功进入首页但左侧菜单一直空白或转圈控制台要么没有请求要么接口返回空数组。原因数据没配全。全新导入的数据库里管理员账号可能没绑定角色或角色没绑定菜单接口返回的菜单列表是空数组。前端动态路由的 component 字段与真实组件路径不一致也会出现空白这种情况浏览器控制台会提示找不到组件模块。解决先用 SQL 验证关联数据是否完整SELECT m.* FROM sys_menu m JOIN sys_role_menu rm ON m.menu_id rm.menu_id JOIN sys_user_role ur ON rm.role_id ur.role_id WHERE ur.user_id 1 ORDER BY m.sort;查询为空就回数据库给管理员绑定角色并配菜单查询有数据但前端仍空白就对照前端路由目录修正 component 路径。另外注意菜单表里 sort 字段决定显示顺序全为 0 时排序不稳定不会导致空白但会让显示顺序很乱。5.4 登录接口 500Redis 连接失败导致系统异常现象后端正常启动但一点登录就报系统异常后台日志出现 RedisConnectionFailureException。原因验证码和 token 缓存都依赖 RedisRedis 没启动或端口配错登录链路直接断掉。常见于刚搭完环境忘了把 redis-server 拉起来。解决本地执行 redis-server 启动默认端口 6379再用 redis-cli ping 确认返回 PONG。如果前期只想调接口不想依赖 Redis可以在 config 里临时换成本地内存 Map 实现缓存但这只能用于本地调试部署环境必须 Redis否则 token 拉黑和验证码校验都会失效。5.5 时间差 8 小时列表里的创建时间对不上现象接口返回的 createTime 比数据库里实际时间晚 8 小时前端显示和日志对不上。原因MySQL 连接串没指定时区驱动按 UTC 处理JSON 序列化再按 GMT 时区输出叠加起来就差 8 小时。解决数据源 url 加 serverTimezoneAsia/Shanghai并在 application.yml 里补充配置spring: jackson: time-zone: GMT8 date-format: yyyy-MM-dd HH:mm:ss改完重启时间字段就正常了。这类问题没有明显报错属于「数据看起来不对但没人报错」的隐性坑我一般把时间字段检查固定加进验收清单每次新环境部署都看一眼。6. 二次开发实战代码生成器接入业务表与三个改造方向6.1 代码生成器十分钟接入一张业务表系统内置基于 MyBatis-Plus 的代码生成器配好数据源和表名自动生成实体、Mapper、Service、Controller 四层代码与前端页面。生成器配置的核心代码FastAutoGenerator.create(jdbc:mysql://localhost:3306/admin_db?serverTimezoneAsia/Shanghai, root, 你的密码) .globalConfig(builder - builder.author(dev).outputDir(src/main/java)) .packageConfig(builder - builder.parent(com.example.admin).moduleName(biz)) .strategyConfig(builder - builder.addInclude(biz_orders)) .execute();执行后把生成的 biz 包并入工程重启在菜单管理里给角色分配新菜单新模块即可使用。生成后检查 entity 里日期字段是否带 JsonFormat 注解没有就自己补上不然序列化格式跟全局配置对不上。6.2 三个值得动手的改造方向接入新表后先别碰前端直接用接口文档调新增接口做一遍增删改查确认权限注解生效后再挂菜单。改造优先级最高的三个方向一是 token 改造把 Redis 里存储的单值字符串换成包含用户信息的对象支持同账号多端登录互不挤掉线二是多租户隔离给业务表加 tenant_id 字段接入 MyBatis-Plus 自带的租户拦截器查询自动带租户条件三是认证框架替换把 Shiro 换成 Spring Security对接公司统一的认证中心。这三个方向都只动 config 层和工具类业务代码基本不动正是这套系统分层清晰带来的好处。当初我第一次拆这类项目的时候急着改业务代码结果权限接口全线 401把过滤器链路从头捋了一遍才发现是 token 头名称前后端不一致。从那以后我每次拿到新脚手架都先按第 4 章的验证清单走一遍登录、授权、登出确认链路通了再动业务代码。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站