首页 > 数据库 >深入分析若依数据权限@datascope (注解+AOP+动态sql拼接) 【循序渐进,附分析过程】

深入分析若依数据权限@datascope (注解+AOP+动态sql拼接) 【循序渐进,附分析过程】

时间:2024-01-22 15:13:29浏览次数:26  
标签:String sql datascope dept params AOP id 权限 public

除了我们平时都知道的 路由权限(即对接口的访问权限外),在日常生产开发中,我们还应该有对数据的访问权限。

在若依这个框架中,通过角色中的数据范围这个属性来对数据权限进行控制。

对应实体类:

深入分析
一个用户肯定是 有一种角色的,也肯定是隶属于一个部门的。

这里咱们就以用户在查询用户时,即 selectUserList时所做的数据权限为例进行分析。

若依在进行数据权限的访问时,持久层(Mapper层)中对数据进行处理,根据用户角色的权限对数据进行过滤。

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
   select u.user_id, u.dept_id, u.login_name, u.user_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.salt, 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="loginName != null and loginName != ''">
      AND u.login_name like concat('%', #{loginName}, '%')
   </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> 

我们可以看到倒数第二行${params.dataScope}就是对数据范围进行过滤

其中,params指的是parameterType="SysUser"传来的参数 SysUse的一个属性,然后这个属性的dataScope属性。

思考:既然params.dataScope通过占位符嵌入在这里,那么他肯定是一个sql语句。我们返回到sysUser实体类中,发现sysUser中并没有params 这个属性。

这里我们可以看到SysUser继承了BaseEntity,果然我们在BaseEntity这个类中发现了 params 这个属性

{
    private static final long serialVersionUID = 1L;

    /** 搜索值 */
    private String searchValue;

    /** 创建者 */
    private String createBy;

    /** 创建时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    /** 更新者 */
    private String updateBy;

    /** 更新时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    /** 备注 */
    private String remark;

    /** 请求参数 */
    private Map<String, Object> params;

    public String getSearchValue()
    {
        return searchValue;
    }

    public void setSearchValue(String searchValue)
    {
        this.searchValue = searchValue;
    }

    public String getCreateBy()
    {
        return createBy;
    }

    public void setCreateBy(String createBy)
    {
        this.createBy = createBy;
    }

    public Date getCreateTime()
    {
        return createTime;
    }

    public void setCreateTime(Date createTime)
    {
        this.createTime = createTime;
    }

    public String getUpdateBy()
    {
        return updateBy;
    }

    public void setUpdateBy(String updateBy)
    {
        this.updateBy = updateBy;
    }

    public Date getUpdateTime()
    {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime)
    {
        this.updateTime = updateTime;
    }

    public String getRemark()
    {
        return remark;
    }

    public void setRemark(String remark)
    {
        this.remark = remark;
    }

    public Map<String, Object> getParams()
    {
        if (params == null)
        {
            params = new HashMap<>();
        }
        return params;
    }

    public void setParams(Map<String, Object> params)
    {
        this.params = params;
    }
}

到这里 我们已经知道有 params这个属性的存在,并且能够肯定 params.dataScope 这是一个sql语句。 所以我们下一步就是要确定这个sql语句是什么时候注入的,怎么注入的?

高阶玩法
我们能够发现若依自定义了一个注解 @DataScope

 * 数据权限过滤注解
 * 
 * @author ruoyi
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
    /**
     * 部门表的别名
     */
    public String deptAlias() default "";

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

我们继续探究这个注解是怎么执行的

我们可以发现在 SysUserServiceImpl Service业务层实现类中发现 @DataScope 的使用

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

我们现在来观察DataScopeAspect 这个切面类,具体实现步骤我都在旁边进行注解了

 * 数据过滤处理
 * 
 * @author ruoyi
 */
@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";



    @Before("@annotation(controllerDataScope)")    // 这个controllerDataScope是一个形参
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
    {
        // 获取当前的用户
        SysUser currentUser = ShiroUtils.getSysUser();
        if (currentUser != null)
        {
            // 如果是超级管理员,则不过滤数据
            if (!currentUser.isAdmin())
            {
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤
     * 
     * @param joinPoint 切点
     * @param user 用户
     * @param deptAlias 部门别名
     * @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) + ")");
                //将完成好的sql语句放在实体类 params的 dataScope属性中 这个属性是一个Map
            }
        }
    }

    /**
     * 拼接权限sql前先清空params.dataScope参数防止注入
     */
    private void clearDataScope(final JoinPoint joinPoint)
    {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
        {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, "");
        }
    }
}

!!!!完结撒花!!!

如果你对这个过程看懂了的话,相信你也能够数据库中 sys_role_dept这个表的含义了吧,笔者当时也是不明白这个到底是个什么意思。其实这张表就是存放那些 自定义数据权限的角色和它对应的部门数据权限。

标签:String,sql,datascope,dept,params,AOP,id,权限,public
From: https://www.cnblogs.com/kisshappyboy/p/17980084

相关文章

  • windows dos 脚本(mysql)自动备份
    @echooff::以下1~8参数请按自己的情况修改;其他的不需要修改::=======================视各环境情况修改=================::1,备份目标主机setdbhost=127.0.0.1::2,数据库端口setdbport=3306::3,本地MYSQL的bin路径setmysqlbin="C:\ProgramFiles\MySQL\MySQLServe......
  • MySQL回表查询与索引覆盖
    前言  InnoDB引擎中,B+树索引可以分为聚簇索引和辅助索引两大类。在介绍“回表”和“索引覆盖”之前,我们先来了解一下这两个概念。聚簇索引  聚簇索引也叫聚集索引,它并不是一种单独的索引类型,在聚簇索引的叶子页中,保存了整张表的行数据信息,所以也将聚簇索引的叶子节点......
  • SQL—排序专用窗口函数
    下面介绍三种用于进行排序的专用窗口函数:1、RANK()   在计算排序时,若存在相同位次,会跳过之后的位次。   例如,有3条排在第1位时,排序为:1,1,1,4······2、DENSE_RANK()   这就是题目中所用到的函数,在计算排序时,若存在相同位次,不会跳过之后的位次。   例如,有3......
  • Mysql UDF提权实验
    通过实训平台学习一下UDF提权实验环境 实验原理udf='userdefinedfunction',即‘用户自定义函数’。是通过添加新函数,对MYSQL的功能进行扩充,性质就象使用本地MYSQL函数如abs()或concat()。udf在mysql5.1以后的版本中,存在于‘mysql/lib/plugin’目录下,5.1之前位于“C:\Windo......
  • win10上安装PostgreSQL的 vector扩展
    win10上安装PostgreSQL的vector扩展错误:extension"vector"isnotavailable\nDETAIL:Couldnotopenextensioncontrolfile"D:/ProgramFiles/PostgreSQL/16/share/extension/vector.control":Nosuchfileordirectory.\nHINT:Theextensionmus......
  • MySQL-8.3.0 innovation 创新版本YUM安装配置
     MySQL-8.3.0innovation版本已发布了,想抢先体验一下最新的功能,可以用以下的方式快速在虚拟机上安装一下哈 服务器环境:[root@node213~]#cat/etc/redhat-releaseCentOSLinuxrelease7.9.2009(Core)[root@node213~]#uname-aLinuxnode2133.10.0-1160.el7.x86_64#1......
  • ubuntu22.04 mysql服务每天自动shutdown问题
    1.问题描述MYSQL每天自动关闭,查看/var/log/mysql/error.log.1.gz,内容如下:2019-06-12T06:33:13.582973+08:000[Note]Shuttingdownplugin‘CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS’2019-06-12T06:33:13.583022+08:000[Note]Shuttingdownplugin‘CONNECTION_CON......
  • Spark SQL五大关联策略
    1、五种连接策略选择连接策略的核心原则是尽量避免shuffle和sort的操作,因为这些操作性能开销很大,比较吃资源且耗时,所以首选的连接策略是不需要shuffle和sort的hash连接策略。◦BroadcastHashJoin(BHJ):广播散列连接◦ShuffleHashJoin(SHJ):洗牌散列连接◦ShuffleSortMergeJoi......
  • sqlglot python sql 解析以及翻译工具
    sqlglot实际上是由和sqlmesh一个团队开发的就用python的sql解析以及翻译工具,功能还是比较强大的支持20多种sql方言的格式化以及翻译,sqlmesh就使用了此工具包,当然相比其他一些工具,性能也是很不错的参考资料https://github.com/tobymao/sqlglothttps://sqlglot.com/sqlglot.html......
  • Mysql日志审计自动备份日志(按天)
    echooffecho------------------开始备份日志文件------------------setymd=%Date:~0,4%%Date:~5,2%%Date:~8,2%setbackup-dir=C:\tools\mysql-server-auditsetpwd=%你自己的数据库密码%echo备份目录:%backup-dir%echo--------------------------------------------------if......