当前位置: 首页 > news >正文

JAVA—— 杜绝越权访问!一套方案搞定数据权限隔离 + 多租户,直接集成项目


1. 背景与问题

1.1 数据权限场景

在企业级应用中,除了功能权限(能否访问某个接口),还需要控制数据权限(能看到哪些数据)。

典型场景:

场景 1:物业公司管理系统 ┌──────────────────────────────────────┐ │ 集团管理员:能看到所有物业公司数据 │ │ 物业公司管理员:只能看到本公司数据 │ │ 社区物业:只能看到本社区数据 │ │ 普通员工:只能看到自己负责的数据 │ └──────────────────────────────────────┘ 场景 2:销售管理系统 ┌──────────────────────────────────────┐ │ 销售总监:能看到全国所有订单 │ │ 大区经理:只能看到本大区订单 │ │ 销售经理:只能看到本团队订单 │ │ 销售代表:只能看到自己创建的订单 │ └──────────────────────────────────────┘ 场景 3:社区管理系统 ┌──────────────────────────────────────┐ │ 系统管理员:所有社区数据 │ │ 物业经理:管辖的所有社区数据 │ │ 客服主管:本社区所有业务数据 │ │ 客服人员:自己处理的工单数据 │ └──────────────────────────────────────┘

1.2 传统方案的缺陷

方案 1:硬编码过滤条件
// ❌ 糟糕的设计publicList<OrderEntity>listOrders(LonguserId,Stringrole){if("admin".equals(role)){returnorderMapper.selectList(null);// 全部数据}elseif("manager".equals(role)){returnorderMapper.selectByDept(getDeptId());// 本部门数据}else{returnorderMapper.selectByUserId(userId);// 本人数据}}

问题:

  • 每个查询方法都要写一遍权限判断
  • 新增数据权限类型需要修改所有查询
  • 容易遗漏导致数据泄露
方案 2:AOP 切面拦截
// ⚠️ 仍然不够好@Aspect@ComponentpublicclassDataPermissionAspect{@Around("execution(* com.example.service.*.*(..))")publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{// 获取当前用户角色Stringrole=getCurrentUserRole();// 根据角色添加过滤条件if("manager".equals(role)){// 怎么动态添加 SQL 条件?}returnpjp.proceed();}}

问题:

  • 难以动态修改 SQL 参数
  • 不支持复杂的数据权限规则
  • 性能开销大

1.3 我们的目标

设计一个完善的数据权限体系:

  • 无侵入性:业务代码无需感知数据权限
  • 动态可配置:后台界面化配置数据权限规则
  • 自动注入:SQL 执行时自动添加过滤条件
  • 灵活扩展:支持自定义数据权限规则
  • 高性能:权限缓存,避免重复计算

2. 数据权限设计思路

2.1 数据权限范围定义

我们定义了 5 种标准数据权限范围:

/** * 数据权限范围枚举 */publicenumDataScopeType{/** * 全部数据权限 * 适用角色:超级管理员、系统管理员 */ALL("all","全部数据"),/** * 本部门数据权限 * 适用角色:部门经理 */DEPT("dept","本部门数据"),/** * 本部门及子部门数据权限 * 适用角色:大区经理(包含下级部门) */DEPT_AND_CHILD("dept_and_child","本部门及子部门数据"),/** * 仅本人数据权限 * 适用角色:普通员工 */SELF("self","仅本人数据"),/** * 按社区过滤数据权限 * 适用角色:社区物业人员 */COMMUNITY("community","按社区数据");privatefinalStringcode;privatefinalStringdesc;}

2.2 数据权限模型

┌──────────────────────────────────────────────────────────┐ │ 用户 (User) │ │ - 所属部门:dept_id │ │ - 所属社区:community_id │ └──────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────┐ │ 角色数据权限 (Role Data Scope) │ │ - 角色 A:全部数据权限 │ │ - 角色 B:本部门数据权限 │ │ - 角色 C:本社区数据权限 │ │ - 角色 D:仅本人数据权限 │ └──────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────┐ │ SQL 自动过滤 │ │ SELECT * FROM table │ │ WHERE │ │ (dept_id = ? OR │ │ community_id = ? OR │ │ create_by = ?) │ └──────────────────────────────────────────────────────────┘

2.3 数据权限注解设计

2.3.1 @DataScope 注解
packagenet.lab1024.sa.base.annotation;importjava.lang.annotation.*;/** * 数据权限注解 * * @author example */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceDataScope{/** * 部门表别名(默认 dept) */StringdeptAlias()default"dept";/** * 用户表别名(默认 user) */StringuserAlias()default"user";/** * 社区表别名(默认 community) */StringcommunityAlias()default"community";}
2.3.2 使用示例
// Mapper 接口@DataScope(deptAlias="d",userAlias="u")List<UserEntity>selectUserList(UserEntityuser);// XML<select id="selectUserList"resultMap="UserMap">SELECTu.*,d.dept_nameFROMsys_user uLEFTJOINsys_dept dONu.dept_id=d.dept_id ${params.dataScope}--自动注入数据权限SQL</select>

2.4 数据权限 SQL 生成器

packagenet.lab1024.sa.base.handler;importlombok.extern.slf4j.Slf4j;importnet.lab1024.sa.base.annotation.DataScope;importnet.lab1024.sa.base.context.UserContext;importorg.springframework.stereotype.Component;importjava.util.List;importjava.util.stream.Collectors;/** * 数据权限 SQL 生成器 * * @author example */@Slf4j@ComponentpublicclassDataScopeHandler{/** * 获取数据权限 SQL * * @param dataScope 数据权限注解 * @return 数据权限 SQL 片段 */publicStringgetDataScopeSQL(DataScopedataScope){// 1. 获取当前登录用户LonguserId=UserContext.getUserId();LongdeptId=UserContext.getDeptId();LongcommunityId=UserContext.getCommunityId();StringroleKey=UserContext.getRoleKey();// 2. 超级管理员拥有全部数据权限if("super_admin".equals(roleKey)){log.debug("超级管理员,不需要数据权限过滤");return"";}// 3. 获取用户角色数据权限范围List<String>dataScopes=getUserDataScopes(userId);// 4. 构建数据权限 SQLStringBuildersqlBuilder=newStringBuilder();for(StringdataScopeType:dataScopes){if(sqlBuilder.length()>0){sqlBuilder.append(" OR ");}switch(dataScopeType){case"all":// 全部数据权限sqlBuilder.append(" 1=1 ");break;case"dept":// 本部门数据sqlBuilder.append("(").append(dataScope.deptAlias()).append(".dept_id = ").append(deptId).append(")");break;case"dept_and_child":// 本部门及子部门List<Long>deptIds=getChildDeptIds(deptId);StringdeptIdStr=deptIds.stream().map(String::valueOf).collect(Collectors.joining(","));sqlBuilder.append("(").append(dataScope.deptAlias()).append(".dept_id IN (").append(deptIdStr).append(")").append(")");break;case"self":// 仅本人数据sqlBuilder.append(
http://www.cnnetsun.cn/news/1677366.html

相关文章:

  • minSdk 从 26 升到 31 ,打包体积变大3倍 ,packaging dex 了解一下
  • PhpWebStudy:轻量级多环境开发工具箱的安全与效率革新
  • OpenMS深度解析:质谱数据分析的终极解决方案与架构创新
  • Windows驱动深度管理与系统优化:DriverStore Explorer全方位实战指南
  • android studio 解决git用户名和用户邮箱不一致的问题
  • AI创意视频进入量产,AdEff 帮你选出好创意
  • 如何5分钟快速部署Akagi雀魂AI助手:专业实战配置完整指南
  • 上海AI公司招聘百万年薪工程师
  • memtest_vulkan显存检测工具全攻略:从问题诊断到专家优化
  • Kandinsky-5.0-I2V-Lite-5s效果对比:不同光源提示词对视频光影质感的影响
  • 用户智能体交互协议AG-UI(上)
  • 换个框架聊Kuikly:探秘轻量高性能动态化方案
  • 郭庆华 生物量产品 数据介绍及下载
  • 【文献速递】山西大学 ACS Nano :限域焦耳加热,让单原子催化剂实现工业化量产
  • Claude Code 源码设计分析
  • 基于深度学习的设备监控技术:从被动报警到主动预警的革新
  • 做对这三步,拥有一个聪明的智能问数与分析Agent
  • 2026数据治理解决方案(PPT文件)
  • 实时数仓与维表时态Join实战
  • 04 月 03 日 AI 每日参考:中外厂商密集发新模型,智元发布周将启
  • 2026届毕业生推荐的五大AI科研神器实测分析
  • Blender场景教程:秘密实验室
  • 如何彻底解决Mac滚动方向混乱:Scroll Reverser完整配置指南
  • 3.30作业
  • 剑指offer-14、链表中倒数第k个结点
  • 2026 Java后端面试“三剑客”:集合、JUC、Redis 高频考点解析
  • 【初中地生会考练习系统】一个类似驾考宝典的初中地理生物会考在线练习系统,支持题库管理、错题本、顺序练习、模拟考试等功能
  • 软件开发常见骗局有哪些?
  • 什么是数据标准管理?怎么进行数据标准管理?
  • 09_语义网之行业应用与企业实践