权限管理

RBAC模型

1,控制用户对系统资源(URI)的操作。前端的权限控制:对页面或页面元素的权限控制。后端的权限控制:对接口及数据的权限控制。

访问权限:哪些页面可以访问、哪些页面元素可见等等;操作权限:如页面按钮是否可点击、是否可以增删改查等等;接口与数据权限:接口是否可以调用、接口具体字段范围等等。

2,RBAC权限控制模型(Role-Based Access Control):基于角色的权限控制。RBAC模型的层级由低到高为:RBAC0、RBAC1、RBAC2、RBAC3。RBAC权限模型核心授权逻辑如下:某用户是什么角色;某角色具有什么权限;通过角色的权限推导用户的权限。

一个用户有一个或多个角色;一个角色包含多个用户;一个角色有多种权限;一个权限属于多个角色。用户:注册用户;角色:Lv0~Lv2会员;权限:视频投稿、发布动态、各种弹幕功能等等的权限;资源:页面、页面元素;操作:按钮点击、页面跳转、数据增删改查等等。

数据表设计

1,四张核心表,三张关联表。后续权限变更只需要更改连接表,关键信息表不变,方便拓展。

image-20220412202938412

2,核心表:用户表存储用户(idphone);角色表存储可选的用户等级(lv0,lv1,lv2);页面元素操作表存储可对前端页面上某个元素进行的操作(比如可点击的上传视频按钮);页面访问表存储页面(比如购买邀请码的页面)。为加速查询为经常匹配字段建立索引。

3,连接表:用户-角色关联表t_user_role存储用户与角色的关联关系(指明用户所处等级);角色-元素操作关联表t_auth_role_element_operation存储角色与页面元素操作间关联(指明不同等级与不同可操作元素间关系,比如lv0不能点击视频上传按钮,lv1可以点击);角色-页面关联表t_auth_role_menu存储角色与可访问页面的关系(指明角色与页面间的可访问关系,比如lv0不能访问邀请码购买页面,lv1可以正常访问)。由于关联表关联两个表的主键,需要添加外键约束。同时为加速查询为经常匹配字段建立索引。

级联查询

1,为方便之后根据关联表查询到与之关联表的内容,使用级联查询, 免得需要右表内容时再去数据库查询,加快响应速度,同时查询出对象体积变大,增大了存储需求,如果被包含对象体积较大,在大量查询时会带来较大压力。

级联查询:先确定所连接的表,再确定所要查询的字段,确定连接条件以及连接方式

2,ibatis下级联查询实现

由于涉及到两张表,所以存在两个数据bean,常见做法是将右表bean放入左表bean,最后返回左表bean。如果右表字段较少,直接将右表字段直接加入左表bean。

如果右表结构复杂,需要设定resultMap将返回的多个平铺字段构建为右表bean,再嵌入左表bean返回。

权限service结构

UserAuthService中通过UserRoleService实现获得用户角色(等级)信息userRoleList;通过AuthRoleService查询用户对应的userRoleList所拥有的页面元素操作权限RoleEleOps与后台数据操作权限RoleMeau。在获取到用户角色信息(等级信息)、页面元素操作权限信息、菜单操作权限信息后,前端就可以对按钮是否可点击做出限制,后端就可以根据预设权限规则对用户操作进行鉴权、拦截。

image-20220413003618570

权限验证

1,在进行后台操作前需要验证权限,无操作权限直接抛出异常并返回;验证成功才继续后续处理。上述流程对多个controller通用,可以使用面向切面编程,将通用功能抽取,避免重复代码,使被影响代码更加专注于业务逻辑的处理。

2,实现功能:lv1及以上用户页面上发表动态按钮才是可点击的,并且lv2用户才可发表直播信息动态。对于按钮可点击限制,可能存在用户绕过前端无法点击按钮的限制直接访问接口的情形,定义注解ApiLimitedRole用于拦截lv0用户直接访问发送动态接口的情形。定义注解DataLimitedAspect用于拦截lv1用户发送直播动态的情形。

3,定义切面ApiLimitedRoleAspect,拦截有ApiLimitedRole注解的方法,在进行业务的执行之前根据传入注解的限制等级集合,验证当前用户是否在黑名单中,如果在将报异常,否则正常进行后续流程。

定义切面DataLimitedAspect,拦截有DataLimited注解的方法,在进行业务的执行之前判断拦截当前用户等级低于lv2且发送的动态类型为直播动态的情形,否则正常进行后续流程。

在需要权限验证的地方添加注解

对于等级为0的用户发表动态时将提示权限不足:

image-20220422175035660

对于等级为1的用户发表直播动态时时也将提示权限不足:

image-20220422174931723

针对后续功能不断丰富,权限越来越多的情形,可以使用权限组的概念,同一个权限组中的不同用户拥有一系列相同的权限,简化多个权限挨个判断的情形。

AOP

1,常用功能分离形成可重用组件(日志),减少系统的重复代码,降低模块间的耦合度,集中管理维护方便,声明式(切面构成、将功能应用到要影响的组件中,无需修改受影响的类)编程较少模板代码,解耦,有利于未来的可拓展性和可维护性;核心代码更关注业务逻辑(高内聚、简单),无感知;

2,静态代理(在编译阶段就可生成 AOP 代理类,AspectJ),动态代理(spring AOP:运行时借助于JDK动态代理、CGLIB等在内存中“临时”生成AOP动态代理类,final修饰的类不能被代理,同样static和final修饰的方法也不会代理,因为static和final方法是不能被覆盖的)

640

3,通知(什么+时机):定义切面功能与出发时机;(bofore,after,around等)。连接点:程序中能够插入切面的点(方法调用、抛出异常),通过切入点添加新功能。切点(何处):表达式匹配要织入的连接点。切面(是什么+何时+何处)=通知+切点。织入:把切面应用到目标对象并创建代理对象的过程,运行期:创建动态代理、springAOP。

springAOP

1,jdk动态代理:拦截接口方法,创建接口的代理实例,只支持方法连接点只能拦截方法;代理类包裹目标类,拦截方法调用,执行切面逻辑,转发给真正的bean。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

定义一个InvocationHandler实例,它负责实现接口的方法调用->通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:使用的ClassLoader,通常就是接口类的ClassLoader;需要实现的接口数组,至少需要传入一个接口进去;用来处理接口方法调用的InvocationHandler 实例->将返回的Object强制转型为接口。

2,cglib代理:目标对象不是接口,字节码生成代理类继承目标类,并覆盖其中特定方法并添加增强代码,所以final修饰的类不能被代理,同样static和final修饰的方法也不会代理。CGLib创建的代理对象性能比JDK动态代理创建的代理对象高很多,但花费的时间多,spectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理,cglib适合单例的对象代理创建,jdk动态代理合多例的对象代理创建。

SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。如果需要默认使用 JDK 动态代理可以通过配置项 spring.aop.proxy-target-class=false来进行修改。

3,切点:切点的定义会匹配通知所要织入的一个或多个连接点,描述要连接的连接点进行匹配,这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。spring AOP只能方法拦截(execution)。多条件与或非复杂逻辑,被影响的类无感知。

image-20220311175300036

image-20220311174849429

4,通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

5,Spring AOP 属于运行时增强,动态代理,而 AspectJ 是编译时增强,静态代理。 Spring AOP 基于代理 (Proxying),而 AspectJ 基于字节码操作 (Bytecode Manipulation)。AspectJ 最完整的 AOP 框架了,可以实现方法、等段等拦截,spring AOP功能受限,但是 Spring AOP 相对来说更简单,如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。