RuoYi-Vue 前后端分离版代码浅析-数据权限切面处理

1,766 阅读2分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

前言

本节介绍RuoYi-Vue的模块中是如何进行数据权限切面的,这里很重要,同时也很危险,因为动态加入sql,往往会带来sql注入的漏洞。因此如果你掌握不好,那最好的办法还是单独写对应的各权限页面而不是像Ruoyi这样进行权限动态添加。

权限字段

Ruoyi是在数据库的sys_role表中添加的data_scope字段,分别限定了数据范围为1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限

对应的注解

在对应的需要控制权限的接口添加,可以传入部门表别名和用户表别名,这样方便不同的sql中可以同步不同的表别名。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
    /**
     * 部门表的别名
     */
    public String deptAlias() default "";

    /**
     * 用户表的别名
     */
    public String userAlias() default "";
}

对应的权限控制sql逻辑

首先在处理权限过滤之前需要进行params.dataScope参数防止注入

<!-- 数据范围过滤 -->
${params.dataScope}

这些对应的数据权限的sql,都是用$修饰的,而不是用#修饰的,因此就会带来sql注入的问题。

之后按照用户的权限进行数据权限sql的拼接

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) + ")");
    }
}

对用户拥有的每个SysRole都进行检查,

全部数据权限

如果是全部数据权限直接break跳出循环,

自定义数据权限

我们可以看到这里是在sys_role_dept这个表里去检查对应的角色对应的部门Id,然后用in进行查询

本部门数据权限

这个比较简单,直接等于用户的部门id就可以了

本部门及子部门权限

WHERE dept_id = {} or find_in_set( {} , ancestors )中的dept_id = {}很好理解,直接是本部门的权限,那么后面的find_in_set( {} , ancestors )呢?这是什么意思? 这里需要来看一下对应的表格式

image.png 首先sys_dept表是有一个祖级列表ancestors这么一个字段的,它里面存放了该部门的所有父级Id,find_in_set会进行精确匹配,找到本部门的子级。顺便说一句,这个find_in_set不走索引。。。