道友,完整版前后端权限控制流程了解一下

1,331 阅读6分钟

介绍

如果你是纯前端,是否被前后端完整的权限管理流程所困扰,比如我本人就是,于是乎我参照若依全后端分离框架,用nestjs搞了一套服务端,最终还是验证了一句话:实践是检验真理正确与否的唯一标准。

如果你看完这篇能够理解,说明道友已经到达了编程界的筑基期,结丹指日可待啊!**本文讲的是整体逻辑贯通,并不是代码实现,**代码截图均是若依框架中的代码,话不多说,开始修炼。

我们常见的管理系统,比如若依、vben,权限逻辑都是大同小异,都是基于RBAC(Role-Based Access Control)模型来实现的,也就是基于角色的权限控制。

如下:

权限都挂到角色中,角色挂到用户中,这样用户就根据角色有了不同的权限。

当然,你可以说把权限直接挂载到用户上,这样不是更直接?

如果用户量有1W个,权限有100个,每个用户要挂不同的权限,试想一下需要多久完成这份工作,如果把不同的权限挂到角色上,用户只挂对应的角色,这样工作量是不是就较少了很多呢?

本文会按照若依框架的逻辑来说,权限是放到了菜单中,也就是 用户 -> 角色 -> 菜单

下面我将从后端的数据库表设计、接口访问权限,前端菜单动态生成、按钮显示控制方面去讲解

后端

有Java基础或者前端基础的都差不多能看明白,诸位道友大可放心修炼,无副作用。

表设计

首先我们要知道实现权限功能所需要的表都有哪些:

  1. 用户表
  2. 角色表
  3. 菜单表(权限表)
  4. 用户与角色 关联表(多对多)
  5. 角色与菜单 关联表(多对多)

数据模型如下图:

用户角色关联表里存的是user表id和role表id,同样的,角色权限表存的是role表id和menu表id。

给不懂数据库的道友简单解释下:关联表的作用顾名思义就是将两张表通过唯一标识关联起来,并且存储关联的数据,比如user_id = 2的用户有两个角色,role_id分别是11,12,那么在sys_user_role表中,存储的就有两条数据,如下如:

比如我想查找user_id为2的用户下的角色就可以直接去这张表里查找。

接口访问权限拦截

数据库表设计完后,然后是接口访问权限,什么意思呢?

比如你这个A角色本身没有访问add接口的权限,前端页面中也确实没有入口可以使用,但是token被人恶意使用了,别人不知道从哪知道的add接口地址,然后通过postman工具就恶意调取使用了,这时候后端一看token没问题,add接口就放行了,这可就坏了大事了,好好的系统被别人玩坏了,这可咋办啊?

这时候就要后端对于接口做权限处理了。

在若依框架中,在controller(简单说就是后端做接口路由的)中使用了一个装饰器来进行权限拦截,如下图:

这里 @PreAuthorize("@ss.hasPermi('system:user:list')") 的意思是通过@PreAuthorize注解在接口调用之前进行权限检查,@ss 表示名字为"ss"的spring bean的实现类,通过调用该类的方法来实现权限校验,简单来说就是通过注解进行了拦截,通过hasPermi方法进行检查,hasPermi是自己实现的,代码如下可以简单看下:

简单解释下这段代码,通过登陆的用户信息拿到该用户所拥有的权限,通过参数传过来的权限字符串与所拥有的权限做对比,有则通过,反之false。

用户信息、菜单接口

再简单说下getInfo和getRouters接口,因为这里有人也会产生疑惑。

当用户登陆后,首先就要调用getInfo和getRouters这两个接口来获取用户信息、角色、权限、菜单目录,只有获取了这些后才会执行后面的逻辑。

这里简单做下铺垫:

如下图:

框起来的就是权限唯一标识,菜单、按钮都有唯一权限标识,前后端都需要通过这个标识做权限控制。

通过若依框架返回数据中我们可以看到,在getInfo接口中返回了permissions数组,这个数组就是当前用户所拥有的所有权限

在getRouters接口中返回了当前角色所拥有的菜单数组(树形结构),如下图:

现在我要将 common 角色的权限改为只有用户管理下查看的权限,如下图:

那么我们登陆common角色的用户可以看到getInfo返回的就只有列表和查看了,如下图:

同时我们的菜单也只有用户管理这一个菜单

接下来说下后端接口实现逻辑,首先是getInfo接口,这个接口返回了三种数据:权限数组、角色数组、用户信息

代码流程示例如下:

具体的sql查询就不说了,无非就是根据单个条件查询,连表查询。

下面就是getRouters接口,这个接口返回的是当前用户所拥有的菜单列表,并不是返回所有的菜单然后前端根据权限手动循环的 ,这里对于入门不久的道友经常陷入这样的误区。

代码如下:

这里主要还是连表查询,通过userId查询用户所拥有的菜单,然后将查出来的菜单转为树形结构。

大体sql如下(简单看下就行):

前端

菜单

前端通过getRouters接口获取当前用户的菜单后,需要做两步:

  1. 需要将接口返回的路由转为真正的路由,因为接口返回的都是字符串,并不是真实的路径。
  2. 将处理好的路由合并到本地路由中。

如下图:

按钮

按钮权限:在若依中你会发现,如若该用户没有对应的按钮权限,实际上是不显示的,但是这里并不是通过if判断的,是通过vue自定义指令实现的,如下如:

其中的useUserStore().permissions就是我们上面说的getInfo接口返回的permissions数组。

解释下:在vue页面中给按钮增加自定义指令v-hasPermi="['system:user:add']",hasPermi文件中定义了自定义指令,mounted是vue本身提供的方法,通过传进来的权限标识字符串与getInfo接口中获取的permissions数组中的权限标识符做对比,如果有则不做处理,否则删除该元素。

结语

这样就是前后端完整的权限控制流程,实际的企业级项目也是这么用的,当然了方式并不只这一种,根据不同的业务需求,还有很多很多的方案,本文说的是市面上比较常见的一种方案。

好了,道友现在是否有一种豁然开朗,拨开迷雾见光明的感觉呢?如果有那么恭喜道友,结丹指日可待!