若依中是如何实现数据权限控制【五月05】

举报
KevinQ 发表于 2022/05/21 11:25:32 2022/05/21
【摘要】 问题起源前一段给公司的后台管理系统做权限管理,领导提出三步走:首先做页面的权限控制,即用户不应该看到他无权操作的内容;这一部分我们使用后台动态生成路由,或者由前端Vue-Router做权限控制,可以做到页面的权限控制;页面按钮的权限控制可以通过Vue的指令,来动态控制其是否显示;第二步是做接口的权限控制,即用户无权访问的接口,就访问不到;这一步,我们借助于开源框架Shiro来实现,通过Sh...

问题起源

前一段给公司的后台管理系统做权限管理,领导提出三步走:

  1. 首先做页面的权限控制,即用户不应该看到他无权操作的内容;

这一部分我们使用后台动态生成路由,或者由前端Vue-Router做权限控制,可以做到页面的权限控制;页面按钮的权限控制可以通过Vue的指令,来动态控制其是否显示;

  1. 第二步是做接口的权限控制,即用户无权访问的接口,就访问不到;

这一步,我们借助于开源框架Shiro来实现,通过Shiro可以做到接口的权限控制;样例如下:

@RequiresPermissions("user:list")
public void list() {
    // ...
}
  1. 第三步是做数据的权限控制,即用户无权访问的数据,就访问不到;

数据的权限控制较为复杂,到底是根据用户的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";
}

根据注释,我们猜测,其实现了按照以下粒度控制数据访问权限:

  1. 全部数据权限
  2. 自定义数据权限
  3. 部门数据权限
  4. 部门及以下数据权限
  5. 仅本人数据权限

执行代码分析

查看DataScopeAspect类的源码,我们可以看到其中的关键方法是:dataScopeFilter,调用逻辑是:

doBefore -> handleDataScope -> dataScopeFilter

我们打个断点看看:

image.png

成功拦截!

进一步,我们逐步执行,终于发现其对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') &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>

注意看最后的一句:

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

拼接后,完整sql语句为:

    原sql
    AND (权限控制语句)

如此,便实现了数据权限控制。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200