进退无门网

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

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

笔者最近在努力的深入数据分析开源项目若依框架,今天看到了若依对数据权限进行控制的分析分析部分,自定义注解+AOP+动态SQL的若依注入,看的权限我是眼花缭乱,然后我又认真的解A接循进附复盘了一遍整个的实现过程,不由得感叹一句,动态若依YYDS~~

简单猜测

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JiPrHVq-1649742911724)(D:\soft\Typora\image\image-20220412121559708.png)]

对应实体类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uJq5nvTm-1649742911726)(D:\soft\Typora\image\image-20220412121959106.png)]

深入分析

一个用户肯定是 有一种角色的,也肯定是解A接循进附隶属于一个部门的。

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

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mb8Z0tn3-1649742911728)(D:\soft\Typora\image\image-20220412131444860.png)]

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

public class BaseEntity implements Serializable{     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 Mapparams;    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 MapgetParams()    {         if (params == null)        {             params = new HashMap<>();        }        return params;    }    public void setParams(Mapparams)    {         this.params = params;    }}

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

高阶玩法

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

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

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

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

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

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

/** * 数据过滤处理 *  * @author ruoyi */@Aspect@Componentpublic 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这个表的含义了吧,笔者当时也是不明白这个到底是个什么意思。其实这张表就是存放那些 自定义数据权限的角色和它对应的部门数据权限。

未经允许不得转载:进退无门网 » 深入分析若依数据权限@datascope (注解+AOP+动态sql拼接) 【循序渐进,附分析过程】