JeecgBoot RBAC 权限体系与数据权限设计
JeecgBoot RBAC 权限体系与数据权限设计
学习目标
通过本章学习,你将能够:
- 理解 JeecgBoot 的 RBAC 角色权限模型
- 掌握按钮级权限与数据权限的实现方案
- 了解 Shiro + JWT 的认证架构
- 掌握多租户隔离与 SSRF 防护的安全设计
项目实践
RBAC 模型架构
权限层级
JeecgBoot 的权限分为四个层级:
| 层级 | 粒度 | 实现方式 |
|---|---|---|
| 菜单权限 | 页面级 | 动态路由,未授权菜单不返回给前端 |
| 按钮权限 | 操作级 | @RequiresPermissions 注解 + 前端按钮 v-has 指令 |
| 数据权限 | 行级 | SQL 拦截器,根据角色过滤数据范围 |
| 字段权限 | 列级 | 表单配置,隐藏或只读特定字段 |
Shiro + JWT 认证
JWT 结构:
{ "header": {"alg": "HS256", "typ": "JWT"}, "payload": { "id": "用户ID", "username": "admin", "exp": 1717200000 }, "signature": "HMACSHA256(base64(header) + '.' + base64(payload), secret)"}按钮级权限
后端控制:
@RestController@RequestMapping("/sys/user")public class SysUserController {
@RequiresPermissions("sys:user:add") @PostMapping("/add") public Result<?> add(@RequestBody SysUser user) { return sysUserService.save(user); }
@RequiresPermissions("sys:user:edit") @PutMapping("/edit") public Result<?> edit(@RequestBody SysUser user) { return sysUserService.updateById(user); }
@RequiresPermissions("sys:user:delete") @DeleteMapping("/delete") public Result<?> delete(@RequestParam String id) { return sysUserService.removeById(id); }}前端控制:
<template> <a-button v-has="'sys:user:add'" @click="handleAdd">新增</a-button> <a-button v-has="'sys:user:edit'" @click="handleEdit">编辑</a-button> <a-button v-has="'sys:user:delete'" @click="handleDelete">删除</a-button></template>v-has 指令根据用户权限列表控制按钮是否显示,未授权用户看不到操作按钮。
数据权限(行级过滤)
数据权限通过 SQL 拦截器实现:
// 伪代码:数据权限拦截器public class DataPermissionInterceptor implements InnerInterceptor {
@Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, ...) { // 1. 获取当前用户的数据权限规则 DataPermissionRule rule = dataPermissionService.getRule(currentUser);
if (rule != null) { // 2. 构建 WHERE 条件 String permissionSql = buildPermissionWhere(rule); // 例:AND create_by = '当前用户' // 或:AND dept_id IN (1, 2, 3)
// 3. 追加到原始 SQL originalSql += " WHERE " + permissionSql; } }}数据权限规则:
| 规则类型 | SQL 条件 | 说明 |
|---|---|---|
| 全部数据 | 无额外条件 | 管理员角色 |
| 本部门数据 | dept_id = 当前用户部门 | 部门经理 |
| 本部门及以下 | dept_id IN (当前部门 + 子部门) | 多级部门管理 |
| 仅本人数据 | create_by = 当前用户 | 普通用户 |
| 自定义规则 | 用户配置的 SQL 条件 | 灵活定制 |
Shiro 排除路径配置
某些路径不需要 Shiro 认证(如登录接口、静态资源):
jeecg: shiro: excludeUrls: - /sys/login - /sys/randomCode/** - /druid/** - /actuator/** - /jeecg-boot/aigc/**SSRF 安全过滤
在文件上传和多模态聊天中,JeecgBoot 实现了 SSRF 防护:
// 文件路径遍历防护SsrfFileTypeFilter.checkPathTraversal(filePath);
// 规范化后确保文件在允许目录内File uploadDir = new File(uploadpath).getCanonicalFile();File targetFile = new File(filePath).getCanonicalFile();if (!targetFile.toPath().startsWith(uploadDir.toPath())) { throw new JeecgBootException("非法文件路径,禁止访问上传目录之外的目录: " + filePath);}文件扩展名白名单:
CHAT_FILE_EXT_WHITELIST = { "txt", "pdf", "docx", "doc", "pptx", "ppt", "xlsx", "xls", "md"};仅允许上传这些类型的文件,防止恶意脚本上传。
问题与规避
| 陷阱 | 表现 | 对策 |
|---|---|---|
| JWT Token 泄露 | 攻击者使用截取的 Token 冒充用户 | Token 设置较短过期时间,配合 Redis 黑名单 |
| 数据权限规则冲突 | 用户拥有多个角色,规则互相冲突 | 取权限并集(最大权限原则) |
| 前端按钮隐藏≠后端保护 | 隐藏按钮但接口仍可调用 | 后端 @RequiresPermissions 是最终防线,前端隐藏只是 UX 优化 |
| Shiro 排除路径配置错误 | 敏感接口被排除导致免认证访问 | 定期审计 excludeUrls 配置,禁止通配符过度排除 |
设计取舍
Shiro vs Spring Security
JeecgBoot 选择:Apache Shiro 2.0.4。
| 维度 | Shiro | Spring Security |
|---|---|---|
| 学习曲线 | 低,API 简洁 | 高,配置复杂 |
| JWT 集成 | 需自定义 Filter | 有官方 Spring Security OAuth2 |
| 细粒度权限 | 支持注解 + 代码级 | 支持方法级 + 表达式 |
| 与 Spring Boot 集成 | 需手动配置 | 天然集成 |
选择 Shiro 的原因:JeecgBoot 早期版本就使用 Shiro,迁移成本高。且 Shiro 的 API 更简洁,对低代码平台的动态权限配置(运行时添加角色、修改权限)更友好。
动态路由 vs 静态路由
JeecgBoot 选择:动态路由(后端返回菜单配置,前端动态注册)。
| 方案 | 优势 | 代价 |
|---|---|---|
| 动态路由 | 权限变更无需重新部署前端 | 前端路由逻辑复杂,首屏需要等待菜单加载 |
| 静态路由 | 简单直接,首屏快 | 权限变更需要重新部署前端 |
在企业级平台中,权限由管理员动态配置,动态路由是必须的能力。
参考来源
- Apache Shiro 官方文档
- JeecgBoot 源码 v3.9.2