java 后端数据权限解读

一、代码

package com.dkd.framework.aspectj;import com.dkd.common.annotation.DataScope;
import com.dkd.common.core.domain.BaseEntity;
import com.dkd.common.core.domain.entity.SysRole;
import com.dkd.common.core.domain.entity.SysUser;
import com.dkd.common.core.domain.model.LoginUser;
import com.dkd.common.core.text.Convert;
import com.dkd.common.utils.SecurityUtils;
import com.dkd.common.utils.StringUtils;
import com.dkd.framework.security.context.PermissionContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** 数据过滤处理** @author ruoyi*/
@Aspect
@Component
public class DataScopeAspect
{/*** 全部数据权限*/public static final String DATA_SCOPE_ALL = "1";/*** 自定数据权限*/public static final String DATA_SCOPE_CUSTOM = "2";/*** 部门数据权限*/public static final String DATA_SCOPE_DEPT = "3";/*** 部门及以下数据权限*/public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";/*** 仅本人数据权限*/public static final String DATA_SCOPE_SELF = "5";/*** 数据权限过滤关键字*/public static final String DATA_SCOPE = "dataScope";@Before("@annotation(controllerDataScope)")public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable{// 清理数据范围(权限)过滤条件(params.dataScope)防止sql注入clearDataScope(point);// 设置数据范围(权限)过滤条件handleDataScope(point, controllerDataScope);}protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope){// 获取当前的登录用户LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNotNull(loginUser)){// 获取用户基本信息SysUser currentUser = loginUser.getUser();// 如果是超级管理员,则不过滤数据if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()){// 获取目标方法的权限字符串  例如:用户列表(system:user:list)String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());// 设置数据范围(权限)过滤条件,根据当前用户、部门别名、用户别名和权限标识对切点对象进行过滤处理dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),controllerDataScope.userAlias(), permission);}}}/*** 数据范围过滤** @param joinPoint 切点* @param user 用户* @param deptAlias 部门别名* @param userAlias 用户别名* @param permission 权限字符*/public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission){// 构建SQL字符串,用于拼接数据范围条件StringBuilder sqlString = new StringBuilder();// 用于存储已经添加的数据范围类型,避免重复添加List<String> conditions = new ArrayList<String>();// 遍历用户的所有角色for (SysRole role : user.getRoles()){// 获取当前角色的数据范围条件类型 1~5String dataScope = role.getDataScope();// 如果数据范围类型是自定义类型且已添加,则跳过本次循环if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)){continue;}// 如果当前角色权限列表不包含目标方法的权限字符串(system:user:list),则跳过本次循环if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))){continue;}// 如果角色的数据范围类型是全部数据,则清空SQL字符串并添加数据范围类型,结束循环if (DATA_SCOPE_ALL.equals(dataScope)){sqlString = new StringBuilder();conditions.add(dataScope);break;}// 如果角色的数据范围类型是自定义数据else if (DATA_SCOPE_CUSTOM.equals(dataScope)){// 拼接SQL条件,限制部门ID在角色所关联的部门范围内sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,role.getRoleId()));}// 如果角色的数据范围类型是本部门数据else if (DATA_SCOPE_DEPT.equals(dataScope)){// 拼接SQL条件,限制部门ID等于用户所在部门IDsqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));}// 如果角色的数据范围类型是本部门及子部门数据else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)){// 拼接SQL条件,限制部门ID等于用户所在部门ID或在用户所在部门的子孙部门中sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",deptAlias, user.getDeptId(), user.getDeptId()));}// 如果角色的数据范围类型是仅本人数据else if (DATA_SCOPE_SELF.equals(dataScope)){// 如果用户表别名不为空,拼接SQL条件限制用户ID等于当前用户IDif (StringUtils.isNotBlank(userAlias)){sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));}else{ // 否则,拼接SQL条件限制部门ID为0,即不查询任何数据sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));}}// 添加当前角色的数据范围类型条件conditions.add(dataScope);}// 如果数据范围类型集合(即多角色情况下,所有角色都不包含传递过来目标方法的权限字符),则添加一个条件使SQL查询不返回任何数据if (StringUtils.isEmpty(conditions)){sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));}// 如果SQL字符串不为空,则将构造好的数据范围条件添加到方法参数对象中,用于后续的SQL查询if (StringUtils.isNotBlank(sqlString.toString())){// 获取切点方法的第一个参数Object params = joinPoint.getArgs()[0];// 检查参数是否非空且为BaseEntity类型if (StringUtils.isNotNull(params) && params instanceof BaseEntity){// 将参数转换为BaseEntity类型BaseEntity baseEntity = (BaseEntity) params;// 向BaseEntity的params属性中添加数据范围条件baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");}}}/*** 拼接权限sql前先清空params.dataScope参数防止注入*/private void clearDataScope(final JoinPoint joinPoint){// 获取切点方法的第一个参数Object params = joinPoint.getArgs()[0];// 检查参数不为null且是否为BaseEntity类型if (StringUtils.isNotNull(params) && params instanceof BaseEntity){// 将参数转为BaseEntity类型BaseEntity baseEntity = (BaseEntity) params;// 将数据权限过滤条件设置为空baseEntity.getParams().put(DATA_SCOPE, "");}}
}

二、代码使用

(一)、核心功能

1.数据权限控制

根据用户的权限和角色,动态生成 SQL 查询条件,确保用户只能看到自己有权访问的数据。

2.AOP 实现

使用 AspectJ 实现 AOP,通过 @Before 注解在方法执行前进行数据权限过滤。

3.多种数据范围类型

支持多种数据范围类型,包括全部数据、自定义数据、部门数据、部门及子部门数据、仅本人数据等

(二)、基本配置

@Aspect
@Component
public class DataScopeAspect {// 数据权限常量定义public static final String DATA_SCOPE_ALL = "1";public static final String DATA_SCOPE_CUSTOM = "2";public static final String DATA_SCOPE_DEPT = "3";public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";public static final String DATA_SCOPE_SELF = "5";public static final String DATA_SCOPE = "dataScope";

(三)、@Before 注解

@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {// 清理数据范围(权限)过滤条件(params.dataScope)防止sql注入clearDataScope(point);// 设置数据范围(权限)过滤条件handleDataScope(point, controllerDataScope);
}

这个方法在带有 @DataScope 注解的方法执行前被调用,主要负责清理和设置数据范围过滤条件。

(四)、handleDataScope 方法

protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {// 获取当前的登录用户LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNotNull(loginUser)) {// 获取用户基本信息SysUser currentUser = loginUser.getUser();// 如果是超级管理员,则不过滤数据if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {// 获取目标方法的权限字符串  例如:用户列表(system:user:list)String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());// 设置数据范围(权限)过滤条件dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),controllerDataScope.userAlias(), permission);}}
}

该方法首先获取当前登录用户的信息,然后检查是否为超级管理员。如果不是超级管理员,则根据用户的角色和权限生成 SQL 查询条件。

(五)、dataScopeFilter 方法

public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) {// 构建SQL字符串,用于拼接数据范围条件StringBuilder sqlString = new StringBuilder();List<String> conditions = new ArrayList<>();for (SysRole role : user.getRoles()) {String dataScope = role.getDataScope();if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) {continue;}if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) {continue;}if (DATA_SCOPE_ALL.equals(dataScope)) {sqlString = new StringBuilder();conditions.add(dataScope);break;} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,role.getRoleId()));} else if (DATA_SCOPE_DEPT.equals(dataScope)) {sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",deptAlias, user.getDeptId(), user.getDeptId()));} else if (DATA_SCOPE_SELF.equals(dataScope)) {if (StringUtils.isNotBlank(userAlias)) {sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));} else {sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));}}conditions.add(dataScope);}if (StringUtils.isEmpty(conditions)) {sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));}if (StringUtils.isNotBlank(sqlString.toString())) {Object params = joinPoint.getArgs()[0];if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {BaseEntity baseEntity = (BaseEntity) params;baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");}}
}

该方法根据用户的不同角色和权限生成 SQL 查询条件,并将其添加到方法参数对象中。具体逻辑如下:
遍历用户的所有角色,根据每个角色的数据范围类型生成 SQL 条件。
最终生成的 SQL 条件会被添加到 BaseEntity 的 params 属性中,用于后续的 SQL 查询。

(六)、clearDataScope 方法

private void clearDataScope(final JoinPoint joinPoint) {Object params = joinPoint.getArgs()[0];if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {BaseEntity baseEntity = (BaseEntity) params;baseEntity.getParams().put(DATA_SCOPE, "");}
}

该方法用于清除 BaseEntity 中的数据范围条件,防止 SQL 注入攻击。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54739.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源PHP导航网源码/精美简约网址导航收录网站/QQ技术导航程序

源码简介&#xff1a; 一款给力的开源PHP导航网源码&#xff0c;它不仅外观精美简约&#xff0c;还是个网址导航收录网站/QQ技术导航程序哦&#xff01; 在信息爆炸的时代&#xff0c;找网页就像大海捞针一样难。但是有了像PHP 导航网这样的神器&#xff0c;一切都变得简单了…

One API本地开发环境搭建

One API本地开发环境搭建 简介 摘要 &#xff1a; 本文介绍如何在本地搭建 One API 开发环境&#xff0c;包括安装 Go 语言和 GoLand IDE&#xff0c;以及如何新建项目和配置数据库信息。通过简明的步骤说明&#xff0c;帮助开发者快速完成基本的开发环境配置&#xff0c;方…

STM32与51单片机的区别:是否应该直接学习STM32?

STM32与51单片机的区别&#xff1a;是否应该直接学习STM32&#xff1f; 在单片机的世界里&#xff0c;STM32和51单片机都是非常重要的角色。对于初学者来说&#xff0c;是否可以直接跳过51单片机&#xff0c;直接学习STM32&#xff0c;这个问题一直存在争议。让我们深入探讨这…

Vue:默认插槽

目录 一.性质 1.内容分发 2.无名称标识 3.作用域 4.使用方式 二.使用 1.父组件 2.子组件 三.代码 1.父组件代码 2.子组件代码 四.效果 一.性质 1.内容分发 默认插槽允许组件的使用者定义一些内容&#xff0c;这些内容会被插入到组件模板中的特定位置。这有助于实…

TomCat乱码问题

TomCat控制台乱码问题 乱码问题解决&#xff1a; 响应乱码问题 向客户端响应数据&#xff1a; package Servlet;import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servl…

【HTTP】方法(method)以及 GET 和 POST 的区别

文章目录 方法&#xff08;method&#xff09;登录上传GET 和 POST 有什么区别&#xff08;面试&#xff09;区别不准确的说法 方法&#xff08;method&#xff09; 首行中的第一部分。首行是由方法、URL 和版本号组成 方法描述了这次请求想干什么&#xff0c;最主要的是&…

Unity3D入门(一) : 第一个Unity3D项目,实现矩形自动旋转,并导出到Android运行

1. Unity3D介绍 Unity3D是虚拟现实行业中&#xff0c;使用率较高的一款软件。 它有着强大的功能&#xff0c;是让玩家轻松创建三维视频游戏、建筑可视化、实时三维动画等互动内容的多平台、综合型 虚拟现实开发工具。是一个全面整合的专业引擎。 2. Unity安装 官网 : Unity…

苹果macOS 15.0 Sequoia正式版发布:iPhone应用镜像玩、手机消息电脑知

9月17日苹果向 Mac 电脑用户推送了 macOS 15 更新&#xff08;内部版本号&#xff1a;24A335&#xff09;&#xff0c;除了引入数个 iOS 18 的新功能外&#xff0c;macOS 15 Sequoia 还带来了全新的 Continuity 功能 ——iPhone 镜像。 iPhone 镜像功能可以让用户直接在 Mac 上…

Ubuntu 安装和使用 Fcitx 中文输入法;截图软件flameshot

一、Ubuntu 安装和使用 Fcitx 中文输入法 在 Ubuntu 上安装和使用 Fcitx 输入法框架是一个常见的选择&#xff0c;特别是对于需要中文输入的用户。以下是详细的步骤来安装和配置 Fcitx 输入法&#xff1a; 1. 安装 Fcitx 和相关输入法 首先&#xff0c;更新你的包列表并安装…

单词搜索问题(涉及递归等)

目录 一题目&#xff1a; 二思路解释&#xff1a; 三解答代码&#xff1a; 一题目&#xff1a; newcode题目链接&#xff1a; 单词搜索_牛客题霸_牛客网 二思路解释&#xff1a; 思路&#xff1a;个人理解是找到word中的第一个元素&#xff0c;然后去递归的上下左右查找&am…

跳跃列表(Skip List)详解

什么是跳跃列表&#xff1f; 跳跃列表是一种概率性的数据结构&#xff0c;旨在提高链表的搜索、插入和删除效率。它通过在普通链表的基础上增加多个层次&#xff0c;以实现更快的访问速度。跳跃列表的设计灵感来源于跳跃图&#xff08;Skip Graph&#xff09;和多层索引的概念…

手把手搞定VMware 的CentOS硬盘扩容

1.背景 用VMware虚拟机创建Centos系统时&#xff0c;选了40GB硬盘&#xff0c;用着用着发现硬盘不够用了。于是&#xff0c;我为了给硬盘扩容&#xff0c;实操了下centos的硬盘扩容。本文是记录下整个操作过程&#xff0c;方便后面查询和使用。 2.操作 2.1 VMware操作 2.2 Ce…

基于51单片机的两路电压检测(ADC0808)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过ADC0808获取两路电压&#xff0c;通过LCD1602显示 二、硬件资源 基于KEIL5编写C代码&#xff0c;PROTEUS8.15进行仿真&#xff0c;全部资源在页尾&#xff0c;提供…

mysql优化之sql语句优化、以及mysql一些高频面试题

文章目录 一、索引1、什么是索引2、添加索引的原则3、索引的优缺点4、索引分类5、mysql存储过程&#xff08;方法&#xff09; 二、MySQL的逻辑架构1、逻辑架构2、MyISAM 和 InnoDB的区别 三、mysql的索引数据结构1、B Tree2、B Tree 四、缓冲池 Buffer Pool1、预读机制2、预读…

Qt中多语言的操作(以QtCreator为例)

1、首先&#xff0c;我们在代码中与文本相关的且需要支持多语言的地方&#xff0c;用tr来包含多语言key&#xff08;多语言key是我们自己定义的&#xff09;&#xff0c;如下 //举例 QPushButton* btnnew QPushButton(this); btn->move(20,20); btn->resize(100,50); //…

Selenium with Python学习笔记整理(网课+网站持续更新)

本篇是根据学习网站和网课结合自己做的学习笔记&#xff0c;后续会一边学习一边补齐和整理笔记 学习网站&#xff1a; selenium 实战二_PO代码重构 Selenium自动化测试python篇 看云 https://selenium-python.readthedocs.io/getting-started.html#simple-usage WEB UI自…

erlang学习:Linux命令学习4

顺序控制语句学习 if&#xff0c;else对文件操作 判断一个文件夹是否存在&#xff0c;如果存在则进行删除&#xff0c;如果不存在则创建该文件夹&#xff0c;并复制一份该脚本后&#xff0c;删除该脚本 if [ -d "/erlangtest/testdir"]; then echo "删除文件夹…

【路径规划】绘制算术和几何布朗运动- 绘制布朗桥、2D 和 3D 布朗运动- 绘制一些随机路径

摘要 本文演示了如何生成和绘制布朗运动、几何布朗运动和布朗桥的随机路径。这些随机路径广泛应用于金融、物理和工程领域&#xff0c;用于模拟随机过程。实验结果包括了多条随机路径的示例&#xff0c;展示了不同类型的布朗运动的特征。 理论 1. 布朗运动 (Brownian Motion…

构建高效房屋租赁系统:Spring Boot应用

1 绪论 1.1 研究背景 中国的科技的不断进步&#xff0c;计算机发展也慢慢的越来越成熟&#xff0c;人们对计算机也是越来越更加的依赖&#xff0c;科研、教育慢慢用于计算机进行管理。从第一台计算机的产生&#xff0c;到现在计算机已经发展到我们无法想象。给我们的生活改变很…

如何在NXP源码基础上适配ELF 1开发板的UART功能

UART即通用异步收发器&#xff0c;是一种支持全双工串行通信协议的接口。在i.MX6ULL处理器平台上&#xff0c;该处理器原生支持多达8路的UART接口&#xff0c;提供了丰富的串行通信能力。 针对ELF 1开发板&#xff0c;实际引出了4路UART接口供开发者使用&#xff0c;具体包括U…