数据权限,一个注解搞定!

网站建设4年前发布
54 0 0

​上篇文章​​松哥和大家介绍了 Spring Security 中常见的八个权限注解,每一个权限注解都有它自己的使用场景,在这些权限注解中,有两个是后置的权限注解:@PostAuthorize 和 @PostFilter,这两个都是在目标方法执行完毕之后进行权限处理,具体的用法小伙伴们可以参考上篇文章,这里我就不再赘述了。,这些后置的权限处理注解有一个共同劣势,就是得先去数据库中查询到数据,然后再根据当前用户权限进行过滤,如果数据量比较小,这样做倒也没有啥大问题,但是如果数据量比较大,这样做很明显效率会特别低,如果能在查询的时候就把数据过滤好了,那么就会特别省事了。,巧了,松哥最近在做的 TienChin 项目使用了 RuoYi-Vue 脚手架,这个脚手架中自定义了一个注解 @DataScope 就是专门用来处理数据权限的,上次有小伙伴在微信上问这个注解的实现原理是什么样子的?好吧,今天就安排上。,这篇文章我先和大家分析一下 RuoYi-Vue 脚手架中 @DataScope 注解的实现原理,在 TienChin 项目视频中到时候还会有深入讲解。,首先我们先来捋一捋这里的权限实现的思路。,@DataScope 注解处理的内容叫做数据权限,就是说你这个用户登录后能够访问哪些数据。传统的做法就是根据当前认证用户的 id 或者角色或者权限等信息去查询,但是这种做法比较麻烦比较费事,每次查询都要写大量 SQL,而这些 SQL 中又有大量雷同的地方,所以我们希望能够将之进行统一处理,进而就引出了 @DataScope 注解。,在 RuoYi-Vue 脚手架中,将用户的数据权限分为了五类,分别如下:,在 TienChin 这个项目中,数据权限也基本上是按照这个脚手架的设计来的,我们只需要搞懂这里的实现思路,将来就可以随心所欲的去自定义数据权限注解了。,捋清楚了思路,再来看看表结构。,这里涉及到如下几张表:,这几个表中有一些细节我来和大家梳理下,一个一个来看:,好了,这些都分析完了,我们就来看看具体的实现。,先来看数据权限注解的定义:,这个注解中有两个属性,一个是 deptAlias 和 userAlias。由于数据权限实现的核心思路就是在要执行的 SQL 上动态追加查询条件,那么动态追加的 SQL 必须要考虑到原本 SQL 定义时的部门表别名和用户表别名。这两个属性就是用来干这事的。,所以小伙伴们可能也看出来,这个 @DataScope 跟我们以前的注解还不太一样,以前自定义的其他注解跟业务耦合度比较小,这个 @DataScope 跟业务的耦合度则比较高,必须要看一下你业务 SQL 中对于部门表和用户表取的别名是啥,然后配置到这个注解上。,因此,@DataScope 注解不算是一个特别灵活的注解,咱们就抱着学习的态度了解下他的实现方式就行了。,注解定义好了,接下来就是切面分析了。,我把 RuoYi-Vue 脚手架中,解析这个注解的切面代码列出来,咱们逐行进行分析。,首先一上来就定义了五种不同的数据权限类型,这五种类型咱们前面已经介绍过了,这里就不再赘述了。,接下来的 doBefore 方法是一个前置通知。由于 @DataScope 注解是加在 service 层的方法上,所以这里使用前置通知,为方法的执行补充 SQL 参数,具体思路是这样:加了数据权限注解的 service 层方法的参数必须是对象,并且这个对象必须继承自 BaseEntity,BaseEntity 中则有一个 Map 类型的 params 属性,我们如果需要为 service 层方法的执行补充一句 SQL,那么就把补充的内容放到这个 params 变量中,补充内容的 key 就是前面声明的 dataScope,value 则是一句 SQL。在 doBefore 方法中先执行 clearDataScope 去清除 params 变量中已有的内容,防止 SQL 注入(因为这个 params 的内容也可以从前端传来);然后执行 handleDataScope 方法进行数据权限的过滤。,在 handleDataScope 方法中,主要是查询到当前的用户,然后调用 dataScopeFilter 方法进行数据过滤,这个就是过滤的核心方法了。,由于一个用户可能有多个角色,所以在 dataScopeFilter 方法中要先遍历角色,不同的角色有不同的数据权限,这些不同的数据权限之间通过 OR 相连,最终生成的补充 SQL 的格式类似这样 AND(xxx OR xxx OR xxx) 这样,每一个 xxx 代表一个角色生成的过滤条件。,接下来就是根据各种不同的数据权限生成补充 SQL 了:如果数据权限为 1,则生成的 SQL 为空,即查询 SQL 不添加限制条件;如果数据权限为 2,表示自定义数据权限,此时根据用户的角色查询出用户的部门,生成查询限制的 SQL;如果数据权限为 3,表示用户的数据权限仅限于自己所在的部门,那么将用户所属的部门拎出来作为查询限制;如果数据权限为 4,表示用户的权限是自己的部门和他的子部门,那么就将用户所属的部门以及其子部门拎出来作为限制查询条件;如果数据权限为 5,表示用户的数据权限仅限于自己,即只能查看自己的数据,那么就用用户自身的 id 作为查询的限制条件。最后,再把生成的 SQL 稍微处理下,变成 AND(xxx OR xxx OR xxx) 格式,这个就比较简单了,就是字符串截取+字符串拼接。,好啦,大功告成,接下来我们通过几个具体的查询来看看这个切面的应用。,我们来看一下 @DataScope 注解三个具体的应用大家就明白了。,在 RuoYi-Vue 脚手架中,这个注解主要有三个使用场景:,假设我现在以 ry 这个用户登录,这个用户的角色是普通角色,普通角色的数据权限是 2,即自定义数据权限,我们就来看看这个用户是如何查询数据的。,我们分别来看。,首先查询部门的方法位于 org.javaboy.tienchin.system.service.impl.SysDeptServiceImpl#selectDeptList 位置,具体方法如下:,这个参数 SysDept 继承自 BaseEntity,而 BaseEntity 中有一个 params 属性,这个咱们前面也已经介绍过了,不再赘述。,我们来看下这个 selectDeptList 方法对应的 SQL:,大家可以看到,在 SQL 的最后面有一句 ${params.dataScope},就是把在 DataScopeAspect 切面中拼接的 SQL 追加进来。,所以这个 SQL 最终的形式类似下面这样:,可以看到,追加了最后面的 SQL 之后,就实现了数据过滤(这里是根据自定义数据权限进行过滤)。那么这里还涉及到一个细节,前面 SQL 在定义时,用的表别名是什么,我们在 @DataScope 中指定的别名就要是什么。,首先查询角色的方法位于 org.javaboy.tienchin.system.service.impl.SysRoleServiceImpl#selectRoleList 位置,具体方法如下:,这个参数 SysRole 继承自 BaseEntity,而 BaseEntity 中有一个 params 属性,这个咱们前面也已经介绍过了,不再赘述。,我们来看下这个 selectRoleList 方法对应的 SQL:,大家可以看到,在 SQL 的最后面有一句 ${params.dataScope},就是把在 DataScopeAspect 切面中拼接的 SQL 追加进来。,所以这个 SQL 最终的形式类似下面这样:,可以看到,追加了最后面的 SQL 之后,就实现了数据过滤(这里是根据自定义数据权限进行过滤)。,过滤的逻辑就是根据用户所属的部门 id 找到用户 id,然后根据用户 id 找到对应的角色 id,最后再把查询到的角色返回。,其实我觉得查询部门和查询用户进行数据过滤,这个都好理解,当前登录用户能够操作哪些部门,能够操作哪些用户,这些都容易理解,能操作哪些角色该如何理解呢?特别是上面这个查询 SQL 绕了一大圈,有的小伙伴可能会说,系统本来不就有一个 sys_role_dept 表吗?这个表就是关联角色信息和部门信息的,直接拿着用户的部门 id 来这张表中查询用户能操作的角色 id 不就行行了?此言差矣!这里我觉得大家应该这样来理解:用户所属的部门这是用户所属的部门,用户能操作的部门是能操作的部门,这两个之间没有必然联系。sys_user 表中的 dept_id 字段是表示这个用户所属的部门 id,而 sys_role_dept 表中是描述某一个角色能够操作哪些部门,这是不一样的,把这个捋清楚了,上面的 SQL 就好懂了。,最后再来看查询用户。,查询用户的方法在 org.javaboy.tienchin.system.service.impl.SysUserServiceImpl#selectUserList 位置,对应的 SQL 如下:,
,这个就比较容易了,根据部门查询用户或者就是查询当前用户。最终生成的 SQL 类似下面这样(这是自定义数据权限,即根据用户部门查找用户):,

© 版权声明

相关文章