首页 > 数据库 >深入解析RuoYi框架中的DataScopeAspect:不同权限类型的SQL语句生成与作用

深入解析RuoYi框架中的DataScopeAspect:不同权限类型的SQL语句生成与作用

时间:2024-12-22 19:27:42浏览次数:6  
标签:DataScopeAspect RuoYi dept user SQL SCOPE 权限 DATA id

目录

AOP简介

面向切面编程(AOP)的概念

AOP在RuoYi框架中的应用

DataScopeAspect类的作用

类的功能

切点选择

权限检查

​编辑

在前端如何给不同的用户设置不同的权限

实际代码示例

控制层

服务层

Mapper层

DataScopeAspect类

1.全部权限

2.自定义权限

3. 本部门及以下权限

4.仅本人权限

 总结


AOP简介

面向切面编程(AOP)的概念

面向切面编程(Aspect-Oriented Programming, AOP)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,以提高代码的模块化和可维护性。横切关注点是指那些影响多个模块但又不属于任何单一模块的核心功能,如日志记录、事务管理、权限控制等。

在传统的面向对象编程(OOP)中,这些横切关注点通常会散布在各个业务方法中,导致代码重复和难以维护。AOP通过引入“切面”(Aspect)、“切点”(Pointcut)、“通知”(Advice)等概念,允许开发者将这些横切关注点集中处理,从而使得业务逻辑更加清晰和专注。

切面(Aspect):包含横切关注点的模块化组件。

切点(Pointcut):定义了在哪些连接点(Join Point)上应用通知的规则。

通知(Advice):在特定的连接点执行的动作,如前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。

连接点(Join Point):程序执行过程中的某个点,如方法调用、异常抛出等。

织入(Weaving):将切面应用到目标对象的过程,可以在编译时、类加载时或运行时进行。

AOP在RuoYi框架中的应用

在RuoYi框架中,AOP被广泛用于实现各种横切关注点,如权限控制、日志记录、性能监控等。特别是DataScopeAspect类,它利用AOP机制来动态调整SQL查询语句,确保用户只能访问其权限范围内的数据。这种设计不仅提高了系统的安全性和可控性,还简化了权限管理和数据隔离的实现。

DataScopeAspect类的作用

类的功能

DataScopeAspect类的主要功能是根据用户的权限级别动态调整SQL查询语句,以实现精确的数据范围控制。具体来说,它会在SQL查询中添加额外的条件,确保查询结果仅限于用户有权限查看的数据。这有助于保护敏感信息,防止越权访问。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)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

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

    /**
     * 数据范围过滤
     *
     * @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) + ")");
            }
        }
    }

    /**
     * 拼接权限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, "");
        }
    }
}

切点选择

DataScopeAspect通过Spring AOP的切点机制介入到业务逻辑中。它使用@before注解来定义一个切点,该切点会在带有@DataScope注解的方法执行之前触发。具体实现如下:

权限检查

DataScopeAspect通过SecurityUtils.getLoginUser()方法获取当前登录的用户信息,并根据用户的权限级别进行相应的处理。以下是具体的步骤:

如果用户是超级管理员,则不过滤数据,因为超级管理员拥有全部权限。否则,调用dataScopeFilter方法进行数据范围过滤。dataScopeFilter方法遍历用户的每个角色,根据角色的权限类型生成相应的SQL片段。

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

在前端如何给不同的用户设置不同的权限

为不同的角色分配不同的权限

通过赋予不同用户对应的角色来实现不同的用户拥有其对应的权限

实际代码示例

控制层

服务层

Mapper层

params通过parameterTypr中的SysUser继承的对象类中获取

DataScopeAspect类

DataScopeAspect类的作用在前面已经说明了,接下里通过不同权限的所对应的sql代码进行解读

1.全部权限

if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                break;
            }

 当用户拥有全部权限时,DataScopeAspect不会对SQL语句进行任何修改。

例如:

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'

2.自定义权限

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()));
            }

 DataScopeAspect会读取自定义权限配置,并将相应的条件(如特定部门或项目)添加到SQL查询中。例如,如果用户的某个角色设置了特定的部门ID,则会在SQL语句中添加AND u.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = {role_id})这样的条件。 

例如:

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'
  AND u.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = 1)

3. 本部门及以下权限

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()));
            }

DataScopeAspect会在SQL语句中添加条件,使得查询结果仅限于用户所在部门及其下属部门的数据。具体来说,它会添加一个递归条件,如AND u.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = {user.dept_id} OR find_in_set({user.dept_id}, ancestors)),以确保查询结果包括用户所在部门及其所有子部门的数据。

例如:

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'
  AND (u.dept_id = 1001 OR u.dept_id IN (SELECT dept_id FROM sys_dept WHERE find_in_set(1001, ancestors)))

4.仅本人权限

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

DataScopeAspect会在SQL语句中添加严格的过滤条件,使查询结果只包含与当前用户直接相关的记录。例如,它会添加AND u.user_id = {current_user_id}这样的条件,确保查询结果只返回当前用户自己的数据。

例如:

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'
  AND u.user_id = 10001

 总结

DataScopeAspect类中dataScopeFilter方法生成的SQL片段会被存储在baseEntity.getParams()中。 在Mapper层的SQL映射文件中,使用${params.dataScope}占位符来插入这个SQL片段。从而实现不同权限下使用相应的sql语句来进行权限管理。

标签:DataScopeAspect,RuoYi,dept,user,SQL,SCOPE,权限,DATA,id
From: https://blog.csdn.net/dggdg1249/article/details/144648665

相关文章

  • 渗透测试-前后端加密分析之AES加密下的SQL注入
    本文是高级前端加解密与验签实战的第9篇文章,也是最后一篇文章。本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过前后端加密进行SQL注入。登录输入账号密码,抓包查看数据包,看上去就是一个普通的aes加密:这里热加载代码不算太难,常规的加解密函数就可以了:encryp......
  • 同步mysql数据到ElasticSearch
    同步mysql数据到ElasticSearch|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|AccessPermission||-------------|-------------|-------------|----------......
  • 同步mysql数据到ElasticSearch的最佳实践
    同步mysql数据到ElasticSearch的最佳实践|Id|Title|DateAdded|SourceUrl|PostType|Body|BlogId|Description|DateUpdated|IsMarkdown|EntryName|CreatedTime|IsActive|AutoDesc|AccessPermission||-------------|-------------|-------------|......
  • sql注入总结
    sql注入一.什么是注入所谓SQL注入,就是通过把SQL命令插入到WEB表单提交或输入域名或页面请求的查询字符串,最终到达欺骗服务器执行恶意的SQL命令,从而进一步得到相应的数据信息。通过构造一条精巧的语句,来查询到想要得到的信息。二.常规注入步骤1.判断注入点类型提交and1=1和a......
  • MySQL——DQL查询(最重要,最常用) 多表设计
    数据库开发-MySQL在上次学习的内容中,我们讲解了:使用DDL语句来操作数据库以及表结构(数据库设计)使用DML语句来完成数据库中数据的增、删、改操作(数据库操作)我们今天还是继续学习数据库操作方面的内容:查询(DQL语句)。查询操作我们分为两部分学习:DQL语句-单表操作DQL语句-多表......
  • MySQL 数据库优化:分区、分表与索引创建
    MySQL数据库优化:分区、分表与索引创建目录概述MySQL分区(Partitioning)2.1什么是分区?2.2使用场景2.3分区类型2.4分区维护2.5示例:创建分区表MySQL分表(Sharding)3.1什么是分表?3.2使用场景3.3分片键选择3.4示例:手动分表3.5分表的挑战MySQL索引创建4.1什么是......
  • MySQL 主备部署与主库读写分离
    MySQL主备部署与主库读写分离目录概述环境准备主备同步配置主服务器(Master)配置备服务器(Slave)配置主库读写分离常见问题与解决方法总结概述MySQL是一个广泛使用的开源关系型数据库管理系统。为了提高系统的可用性和数据的安全性,通常会采用主备(Master-Slave)架构来部......
  • 【MySQL】--- 数据类型
     Welcometo9ilk'sCodeWorld    (๑•́₃•̀๑) 个人主页:    9ilk(๑•́₃•̀๑) 文章专栏:  MySQL  ......
  • 【MySql】事务管理(下)
    ......
  • 强化学习SQL算法(soft q leanring)中的squash_correction是否存疑?
    SQL算法的官方实现地址:https://openi.pcl.ac.cn/devilmaycry812839668/softlearning提两个问题:SQL算法的原始论文中在计算Qlossfunction的时候建议使用重要性采样,而实际代码中却使用的是均匀采样,同时也没有采样重要性采样的方法进行修正,而原始论文中在这一步的推导公式......