若依中是如何实现数据权限控制【五月05】
问题起源
前一段给公司的后台管理系统做权限管理,领导提出三步走:
- 首先做页面的权限控制,即用户不应该看到他无权操作的内容;
这一部分我们使用后台动态生成路由,或者由前端Vue-Router做权限控制,可以做到页面的权限控制;页面按钮的权限控制可以通过Vue的指令,来动态控制其是否显示;
- 第二步是做接口的权限控制,即用户无权访问的接口,就访问不到;
这一步,我们借助于开源框架Shiro来实现,通过Shiro可以做到接口的权限控制;样例如下:
@RequiresPermissions("user:list")
public void list() {
// ...
}
- 第三步是做数据的权限控制,即用户无权访问的数据,就访问不到;
数据的权限控制较为复杂,到底是根据用户的ID来做复杂的限制呢,还是根据用户的角色做限制呢?
在学习的开源框架若依中,它又是怎么做的呢?我们来学习研究一下。
若依中的数据权限控制
在若依的部分接口中,可以看到如下注解:
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
该注解DataScope是若依中自己定义的一个注解:
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;
/**
* 数据权限过滤注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
}
注解中的两个参数分别表示部门表的别名与用户表的别名。
在前几天学习注解的过程中,我们知道,注解的定义并不能解释注解的执行逻辑,而更重要的是注解的解释执行器。
在项目中搜索一下关键字符:DataScope。在模块ruoyi-framework
中,找到了类DataScopeAspect。
先来看看该类中的几个常量:
@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的修改行为:
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param userAlias 别名
*/
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
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
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,即“自定义数据权限”:
执行过程中,首先:
sqlString = " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) "
然后多个角色权限叠加OR,得到:
" OR ..."
最后修改参数JoinPoint中的params.dataScope,其值为:
" AND (权限控制语句)"
而实际执行的SQL中有:
<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>
注意看最后的一句:
原mysql
<!-- 数据范围过滤 -->
${params.dataScope}
拼接后,完整sql语句为:
原sql
AND (权限控制语句)
如此,便实现了数据权限控制。
- 点赞
- 收藏
- 关注作者
评论(0)