Ruo-Yi前后端分离的数据过滤

Ruo-Yi前后端分离的数据过滤

若依官网的介绍:http://doc.ruoyi.vip/ruoyi/document/htsc.html#%E6%95%B0%E6%8D%AE%E6%9D%83%E9%99%90

1、数据过滤的实现

​ 其实是在mybatis 的 sql 上添加了${params.dataScope}这个东西,实现了数据过滤。

    <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
		select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
		left join sys_dept d on u.dept_id = d.dept_id
		where u.del_flag = '0'
		<if test="userId != null and userId != 0">
			AND u.user_id = #{userId}
		</if>
		<if test="userName != null and userName != ''">
			AND u.user_name like concat('%', #{userName}, '%')
		</if>
		<if test="status != null and status != ''">
			AND u.status = #{status}
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
		</if>
		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
		</if>
		<if test="deptId != null and deptId != 0">
			AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>

为什么加这个就可以实现呢?我们根据/system/user/list这个请求路径来分析?

@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
    // 设置 分页 参数
    startPage();
    // 根据分页参数查询数据 表:sys_user 和 sys_dept
    List<SysUser> list = userService.selectUserList(user);
    // 封装前台需要的数据
    return getDataTable(list);
}

然后我们进入selectUserList方法

@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
    return userMapper.selectUserList(user);
}

​ 在这里我们发现了关键,就是==@DataScope==注解,其实若依框架的数据过滤就是通过这个注解实现的。这个是一个AOP知识点的利用,我们需要找到标注了@Aspect的注解,我们找到了类DataScopeAspect。之后我们要进入 selectUserList()方法之前,我们先要进入DataScopeAspect类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCKvTkYQ-1666165318913)(数据权限.assets/image-20221019151535820.png)]

方法的调用流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izkviK1k-1666165318913)(数据权限.assets/image-20221019152129069.png)]

进入切面类 DataScopeAspect ,首先进入

@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
    clearDataScope(point); // 拼接权限sql前先清空params.dataScope参数防止注入
    handleDataScope(point, controllerDataScope);
}

然后:

protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
    // 获取当前的用户
    LoginUser loginUser = SecurityUtils.getLoginUser(); // 获取当前登陆的用户
    if (StringUtils.isNotNull(loginUser))
    { // 如果 loginUser 不为空的话
        SysUser currentUser = loginUser.getUser();
        // 如果是超级管理员,则不过滤数据
        if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
        {// 下面就要过滤数据了
            String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());

            // 这里其实就是 想给 每一个实体类都会继承的 BaseEntity 里面的 Map<String, Object> params; 属性添加一些过滤条件
            // 之后再 mapper 生成sql的时候就可以用来 过滤数据了
            dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                            controllerDataScope.userAlias(), permission);
        }
    }
}

然后:

public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
    StringBuilder sqlString = new StringBuilder();
    List<String> conditions = new ArrayList<String>(); // 存储 数据权限 1,2,3,4,5
    // 获取当前用户 的全部角色信息
    for (SysRole role : user.getRoles())
    {
        String dataScope = role.getDataScope(); // 获取 当前角色的数据权限
        // String DATA_SCOPE_CUSTOM = "2"; => 自定数据权限
        // 如果当前的 数据权限 不是 自定数据权限  并且 conditions 里面不包含当前的 数据权限
        if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
        {
            continue;
        }
        // 如果 ①permission 不为空 并且 ② 角色的权限信息不为空 并且 ③角色的权限不包含传递过来的角色权限
        if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
            && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
        {
            continue;
        }


        //===================================== 从这里之后,sqlString 前面被赋值后,后面要是再次赋值会覆盖前面的 ====================================


        // String DATA_SCOPE_ALL = "1"; 全部数据权限
        // 如果当前的 数据权限是 全部数据权限
        if (DATA_SCOPE_ALL.equals(dataScope))
        {
            // 如果遍历到 一个 角色的数据权限为1的时候,就是  全部数据权限 就不需要过滤了,直接返回
            sqlString = new StringBuilder();
            break;
        }
        // String DATA_SCOPE_CUSTOM = "2"; =>  自定数据权限
        // 如果当前的 数据权限是 自定数据权限
        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()));
        }
        // String DATA_SCOPE_DEPT = "3"; => 部门数据权限
        // 如果当前的 数据权限是 部门数据权限
        else if (DATA_SCOPE_DEPT.equals(dataScope))
        {
            sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
        }
        // String DATA_SCOPE_DEPT_AND_CHILD = "4"; => 部门及以下数据权限
        // 如果当前的 数据权限是 部门及以下数据权限
        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()));
        }
        // String DATA_SCOPE_SELF = "5"; => 仅本人数据权限
        // 如果当前的 数据权限是 仅本人数据权限
        else if (DATA_SCOPE_SELF.equals(dataScope))
        {
            if (StringUtils.isNotBlank(userAlias))
            {
                sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
            }
            else
            {
                // 数据权限为仅本人且没有userAlias别名不查询任何数据
                sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
            }
        }
        conditions.add(dataScope);
    }
    
    //====================上面是为了 设置 需要拼接的sql,下面是为将 sql 设置到实体类里面 =======================

    // 如果 sqlString不为 空的话
    if (StringUtils.isNotBlank(sqlString.toString()))
    {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
        {
            BaseEntity baseEntity = (BaseEntity) params;
            // DATA_SCOPE = "dataScope";
            // 因为每一个实体类都继承了 BaseEntity 类,所以,每一个类也就有里面的每一个属性
            // 这里进行的操作就是给,实体类的 Map<String, Object> params; 字段 put("dataScope","xxxx")
            baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
        }
    }
}

然后就进入:

@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
    return userMapper.selectUserList(user);
}

​ 执行sql去了,这个时候 user 对象因为经过 DataScopeAspect类的一大堆骚操作,因为每一个实体类都继承了 BaseEntity 类,所以,每一个类也就有里面的每一个属性,经过上面的方法 给 params属性增加了key :"dataScope"值:“xxxx”

​ 然后再 mybatis 的sql里面就可以取到${params.dataScope},这个东西取到的就是sqlString字符串

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
	....
    <!-- 数据范围过滤 -->
    ${params.dataScope}
</select>

总结:

1、如果我们再若依项目里面想用数据过滤其实就只需要再 service 层加上一个 @DataScope注解即可

2、若依框架能实现数据过滤的主要原因就是利用了AOP切面,再进入 service 方法之前先进入切面类里面的方法,执行完毕后再进入 service 层的方法。

2、数据过滤测试每一种情况

下面我们根据系统管理下的用户管理页面作为分析,查看每一种数据权限。

调用的接口:

http://localhost/dev-api/system/user/list?pageNum=1&pageSize=10

测试的数据

INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2022-10-19 14:22:42', 'admin', '2022-09-30 22:02:23', '', '2022-10-19 14:22:42', '管理员');
INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2022-09-30 22:02:23', 'admin', '2022-09-30 22:02:23', 'admin', '2022-10-17 11:51:08', '测试员');
INSERT INTO `sys_user` VALUES (100, NULL, '111', '1', '00', '', '', '0', '', '$2a$10$t0cnTqGNcOZlx99Hj3FIk.C3.4tFvtperOpp8nmhlhOl/vax.DjZq', '0', '0', '', NULL, 'admin', '2022-10-13 14:09:55', 'admin', '2022-10-17 11:51:10', NULL);
INSERT INTO `sys_user` VALUES (101, 101, 'zs', 'zs', '00', '', '', '0', '', '$2a$10$MI9C2H3sw6orfGUcGOQoZushCERPPYmF0RCJFapI8ShBrU27bH8s6', '0', '0', '127.0.0.1', '2022-10-19 14:16:16', 'admin', '2022-10-13 17:48:39', 'admin', '2022-10-19 14:16:16', NULL);
INSERT INTO `sys_user` VALUES (102, 101, 'sz', '深圳总公司', '00', '', '', '0', '', '$2a$10$Z1qGn6CKHZ9UgMpCILsKoO3Bgsc.oOOswqhChqrFpgAklFrDMzy6C', '0', '0', '', NULL, 'admin', '2022-10-19 11:45:10', '', NULL, NULL);
2.1、① 仅自己权限

现象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lW0sSfsf-1666165318914)(数据权限.assets/image-20221019141523757.png)]

登陆zs用户查询,zs用户的数据权限只有仅自己。查询结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6inGdNY-1666165318915)(数据权限.assets/image-20221019141623670.png)]

分析

相当于查询用户id只为当前登陆用户

if (StringUtils.isNotBlank(userAlias))
{
    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
    // 数据权限为仅本人且没有userAlias别名不查询任何数据
    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}

查询的SQL:

SELECT
	u.user_id,
	u.dept_id,
	u.nick_name,
	u.user_name,
	u.email,
	u.avatar,
	u.phonenumber,
	u.sex,
	u.STATUS,
	u.del_flag,
	u.login_ip,
	u.login_date,
	u.create_by,
	u.create_time,
	u.remark,
	d.dept_name,
	d.leader 
FROM
	sys_user u
	LEFT JOIN sys_dept d ON u.dept_id = d.dept_id 
WHERE
	u.del_flag = '0' 
	AND ( u.user_id = 101 ) 
	LIMIT 10;
2.2、② 本部门

现象:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5NGi5gy-1666165318915)(数据权限.assets/image-20221019115431318.png)]

登陆zs用户查询,zs用户的数据权限只有本部门。查询结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeKDrMeV-1666165318915)(数据权限.assets/image-20221019115645550.png)]

分析

根据当前用户的部门信息过滤数据

sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
SELECT
	u.user_id,
	u.dept_id,
	u.nick_name,
	u.user_name,
	u.email,
	u.avatar,
	u.phonenumber,
	u.sex,
	u.STATUS,
	u.del_flag,
	u.login_ip,
	u.login_date,
	u.create_by,
	u.create_time,
	u.remark,
	d.dept_name,
	d.leader 
FROM
	sys_user u
	LEFT JOIN sys_dept d ON u.dept_id = d.dept_id 
WHERE
	u.del_flag = '0' 
	AND ( d.dept_id = 101 ) 
	LIMIT 10;

其实是给原本的SQL拼接了:

AND ( d.dept_id = 101 ) 
2.3、③ 本部门及以下数据权限

现象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTsuJbb7-1666165318916)(数据权限.assets/image-20221019133902852.png)]

登陆zs用户查询,zs用户的数据权限只有本部门及以下数据权限。查询结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jPIPw1m-1666165318917)(数据权限.assets/image-20221019134006401.png)]

分析

根据当前用户本部门及以下数据权限信息过滤数据

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()));
SELECT
	u.user_id,
	u.dept_id,
	u.nick_name,
	u.user_name,
	u.email,
	u.avatar,
	u.phonenumber,
	u.sex,
	u.STATUS,
	u.del_flag,
	u.login_ip,
	u.login_date,
	u.create_by,
	u.create_time,
	u.remark,
	d.dept_name,
	d.leader 
FROM
	sys_user u
	LEFT JOIN sys_dept d ON u.dept_id = d.dept_id 
WHERE
	u.del_flag = '0' 
	AND (d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = 101 OR find_in_set(101, ancestors) )) 
	LIMIT 10;

其实是给原本的 SQL 拼接了:

AND (d.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = 101 OR find_in_set(101, ancestors) )) 
2.4、④ 全部数据权限

现象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8QVuH9Aa-1666165318917)(数据权限.assets/image-20221019134937313.png)]

登陆zs用户查询,zs用户的数据权限只有全部数据权限。查询结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N4elxONG-1666165318917)(数据权限.assets/image-20221019135141590.png)]

全部都可以查询出来

分析

相当于给原本的 sql 后面什么 都不拼接

sqlString = new StringBuilder();
break;

查询数据的sql语句:

SELECT
	u.user_id,
	u.dept_id,
	u.nick_name,
	u.user_name,
	u.email,
	u.avatar,
	u.phonenumber,
	u.sex,
	u.STATUS,
	u.del_flag,
	u.login_ip,
	u.login_date,
	u.create_by,
	u.create_time,
	u.remark,
	d.dept_name,
	d.leader 
FROM
	sys_user u
	LEFT JOIN sys_dept d ON u.dept_id = d.dept_id 
WHERE
	u.del_flag = '0' 
	LIMIT 10;

相当于什么数据没有过滤。

2.5、⑤ 自定数据权限

现象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fynpBReo-1666165318918)(数据权限.assets/image-20221019135603747.png)]

设置自定义数据权限只能看到研发部门。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlwqfsTf-1666165318919)(数据权限.assets/image-20221019135737731.png)]

# 题外话:后面会详细讲解
# 1
在按下确定的这一瞬间其实发起了请求`http://localhost/dev-api/system/role/dataScope`
这个请求往数据库表`sys_role_dept`插入数据,这个表就是 自定义数据权限才会用到的表
# 2 
这里 还存在一个 父子联动的问题,按照我这里显示的问题,这里我们选择父子联动
- 如果选择父子联动: 用户属于若依科技、深圳总公司、研发部门,这三个任意一个都可以查询出来
- 如果不选择父子联动:只能查询用户属于 深圳总公司下研发部门的用户

登陆zs用户查询,zs用户的数据权限只有自定数据权限。查询结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2pkBPBXw-1666165318919)(数据权限.assets/image-20221019140139543.png)]

分析

根据当前用户本部门及以下数据权限信息过滤数据

相当于根据角色idssys_role_dept表里面找到部门id,然后根据部门id进行过滤数据

sqlString.append(StringUtils.format(
		" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
    role.getRoleId()));
SELECT
	u.user_id,
	u.dept_id,
	u.nick_name,
	u.user_name,
	u.email,
	u.avatar,
	u.phonenumber,
	u.sex,
	u.STATUS,
	u.del_flag,
	u.login_ip,
	u.login_date,
	u.create_by,
	u.create_time,
	u.remark,
	d.dept_name,
	d.leader 
FROM
	sys_user u
	LEFT JOIN sys_dept d ON u.dept_id = d.dept_id 
WHERE
	u.del_flag = '0' 
	AND ( d.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = 104 ) ) 
	LIMIT 10;

3、父子联动问题

​ 前面我们不是有说到一个 父子联动的按钮吗,我们现在就来详细来看看,这里为了更加能说明问题,我在数据库有添加了一个用户。她是属于长沙分公司的研发部门员工。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nyDjCvmx-1666165318920)(数据权限.assets/image-20221019142533118.png)]

INSERT INTO `sys_user` VALUES (103, 205, 'csyf', '长沙-研发1', '00', '', '', '0', '', '$2a$10$VdGvUO.ZHGgAFCGBXMNBA.sjOtnYoODeqYYkrJhuqC4etrh/m.sXK', '0', '0', '', NULL, 'admin', '2022-10-19 14:08:46', '', NULL, NULL);
3.1、① 不勾选父子联动

​ 我们先来看第①种情况,父子不联动,我们只选择 深圳分公司下面的 研发部门

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvoW7QBv-1666165318920)(数据权限.assets/image-20221019142929643.png)]

点击确定后,我们发现请求了地址:http://localhost/dev-api/system/role/dataScope

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVSQ5D9x-1666165318920)(数据权限.assets/image-20221019143226204.png)]

保存到数据库表sys_role_dept里面的也只有一条数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EN6TXbUN-1666165318920)(数据权限.assets/image-20221019143322052.png)]

admin用户的页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwg3LB7e-1666165318921)(数据权限.assets/image-20221019143624004.png)]

​ 我们现在再使用zs用户登陆系统。根据我们上面的分析,过滤的时候过滤的时候是查询当前角色下的部门id,根据这些部门id进行过滤的。所以 我们也就只能看到 深圳分公司下面研发部门的用户,即只能看到zs用户。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LcCgrhdj-1666165318921)(数据权限.assets/image-20221019143558083.png)]

3.2、② 勾选父子联动

​ 下面我们再看一下勾选父子联动,选择深圳分公司下面的研发部门会发生什么。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wa3WFvVt-1666165318921)(数据权限.assets/image-20221019143810568.png)]

点击确定后,我们发现请求了地址:http://localhost/dev-api/system/role/dataScope

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uM9wUVqh-1666165318921)(数据权限.assets/image-20221019143939974.png)]

再看看数据库:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Bpdgjc5-1666165318921)(数据权限.assets/image-20221019144000199.png)]

admin用户的页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0NiRrFLP-1666165318922)(数据权限.assets/image-20221019144026824.png)]

​ 我们现在再使用zs用户登陆系统。根据我们上面的分析,过滤的时候过滤的时候是查询当前角色下的部门id,根据这些部门id进行过滤的。所以 我们也就只能看到 深圳分公司下面研发部门的用户,即我们可以看到 admin、zs、sz这三个用户。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7PGn2Ga-1666165318922)(数据权限.assets/image-20221019144206933.png)]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值