若依系统实现了以下数据权限:
- 全部数据权限
- 自定义数据权限
- 部门数据权限
- 部门及以下数据权限
- 仅本人数据权限
问题起源
前一段给公司的后台管理系统做权限管理,领导提出三步走:
- 首先做页面的权限控制,即用户不应该看到他无权操作的内容;
这一部分我们使用后台动态生成路由,或者由前端Vue-Router做权限控制,可以做到页面的权限控制;页面按钮的权限控制可以通过Vue的指令,来动态控制其是否显示;
- 第二步是做接口的权限控制,即用户无权访问的接口,就访问不到;
这一步,我们借助于开源框架Shiro来实现,通过Shiro可以做到接口的权限控制;样例如下:
1 2 3 4
| @RequiresPermissions("user:list") public void list() { }
|
- 第三步是做数据的权限控制,即用户无权访问的数据,就访问不到;
数据的权限控制较为复杂,到底是根据用户的ID来做复杂的限制呢,还是根据用户的角色做限制呢?
在学习的开源框架若依中,它又是怎么做的呢?我们来学习研究一下。
若依中的数据权限控制
在若依的部分接口中,可以看到如下注解:
1 2 3 4 5 6
| @DataScope(deptAlias = "d", userAlias = "u") public List<SysUser> selectUserList(SysUser user) { return userMapper.selectUserList(user); }
|
该注解DataScope是若依中自己定义的一个注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.ruoyi.common.annotation;
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataScope {
public String deptAlias() default "";
public String userAlias() default ""; }
|
注解中的两个参数分别表示部门表的别名与用户表的别名。
在前几天学习注解的过程中,我们知道,注解的定义并不能解释注解的执行逻辑,而更重要的是注解的解释执行器。
在项目中搜索一下关键字符:DataScope。在模块ruoyi-framework
中,找到了类DataScopeAspect。
先来看看该类中的几个常量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @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"; }
|
根据注释,我们猜测,其实现了按照以下粒度控制数据访问权限:
- 全部数据权限
- 自定义数据权限
- 部门数据权限
- 部门及以下数据权限
- 仅本人数据权限
执行代码分析
查看DataScopeAspect类的源码,我们可以看到其中的关键方法是:dataScopeFilter,调用逻辑是:
doBefore -> handleDataScope -> dataScopeFilter
我们打个断点看看:
成功拦截!
进一步,我们逐步执行,终于发现其对SQL的修改行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) { StringBuilder sqlString = new StringBuilder();
for (SysRole role : user.getRoles()) { String dataScope = role.getDataScope(); if (DATA_SCOPE_ALL.equals(dataScope)) { sqlString = new StringBuilder(); 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(" OR 1=0 "); } } }
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) + ")"); } } }
|
已我们登陆的账号ry为例:
其角色权限为2,即“自定义数据权限”:
执行过程中,首先:
1
| sqlString = " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) "
|
然后多个角色权限叠加OR,得到:
最后修改参数JoinPoint中的params.dataScope,其值为:
而实际执行的SQL中有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <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.password, 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') >= 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') <= 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>
|
注意看最后的一句:
1 2 3
| 原mysql ${params.dataScope}
|
拼接后,完整sql语句为:
如此,便实现了数据权限控制。