跳转到内容

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。

维度ShiroSpring Security
学习曲线低,API 简洁高,配置复杂
JWT 集成需自定义 Filter有官方 Spring Security OAuth2
细粒度权限支持注解 + 代码级支持方法级 + 表达式
与 Spring Boot 集成需手动配置天然集成

选择 Shiro 的原因:JeecgBoot 早期版本就使用 Shiro,迁移成本高。且 Shiro 的 API 更简洁,对低代码平台的动态权限配置(运行时添加角色、修改权限)更友好。

动态路由 vs 静态路由

JeecgBoot 选择:动态路由(后端返回菜单配置,前端动态注册)。

方案优势代价
动态路由权限变更无需重新部署前端前端路由逻辑复杂,首屏需要等待菜单加载
静态路由简单直接,首屏快权限变更需要重新部署前端

在企业级平台中,权限由管理员动态配置,动态路由是必须的能力。


参考来源