1. 前言
本篇文章是本人基于个人项目的总结,希望对正在做个人项目的同学有所帮助。
项目中,权限验证和安全性是非常重要的,可以说这是一个项目开始时就必须要考虑的基础核心功能。一般权限的实现方式是RBAC模型,RBAC(Role-Based Access Control:基于角色的访问控制),它由三个基础组成部分即用户、角色、权限。
- 用户:每个用户都有唯一标识并被授予不同的角色。
- 角色:不同的角色拥有不同的权限。
- 权限:访问权限,即能看到什么,能访问什么。
(这里画一个简单的草图)
总结就是什么样的用户能做什么样的事情。当然这个模型是一般的模型,还有更复杂的,约束性更强的模型,如:RBAC0、RBAC1、RBAC2。
RBAC0
用户和角色之间可以是多对多的关系RBAC1
角色间的继承关系,即角色上有了上下级的区别RBAC2
基于RBAC0模型的基础上,进行了角色的访问控制,有了互斥角色、级别等概念。
实际企业中做权限控制是不局限于用户权限,它们还可能包含数据权限、接口权限、按钮权限、甚至每个层级的权限(如每个公司拥有不同的权限)。对于一个单体项目而言,如果没有相对庞大的用户群体,很多复杂的业务,那么使用RBAC0模型的权限是完全够用的,每个用户有着不同的角色,每个角色都有着自己的权限集。
那么我们该如何在自己的项目中去实现这种权限控制呢?
2. 权限究竟由前端控制还是后端?
在回答这个问题前,我要先说一下我个人是如何做权限控制的。首先前端会拿到一份路由表,这个路由表表示当前用户能够访问的路由有哪些(页面),然后还有一个权限集合,即这个用户拥有哪些权限,会将这些信息告知前端。前端拿到这份路由表会用addRoutes方式动态挂载,那么就能实现不同用户看到不同的页面。当然这只能控制页面,控制不了哪些人能访问哪些接口,进行哪些操作,哪些数据,所以再怎么做权限控制都是不安全的,后端必须要有对应的权限验证方法。
为什么这么说?举个例子:如一个公告页面中含有编辑和保存的功能,对于用户A拥有编辑和保存的权限,对于用户B仅拥有编辑的权限,对于前端来说用户A和B都可以进入公告界面,那么这里的权限就必须要由后端来验证是否可以访问对应的业务。前端和后端划分的权限是不一样的。
前端来控制页面权限,后端需要验证每个涉及相关请求的操作(如修改,删除等等)是否有操作的权限,即前端负责页面展示的控制,而后端负责是否可以访问业务的控制。前端发送的请求中必须在请求头中包含一个身份标识用于后端进行身份校验,约定请求头名称为token,值为该用户的标识,后端根据该值获取用户信息进权限校验。
一个相对安全的系统必须要前后端进行协调,各自做好各自的事情,重重控制最大程度确保系统的权限安全,防止非法操作与访问。
3. 设计
接下来讲讲如何去设计。
路由表是用于前端的页面权限控制,它和vue-router的格式一样,这个是由后端处理好返回给前端的,格式和前端路由配置一样(具体属性还请看官方文档):
export const constantRoutes = [
{
path: '/test',
component: Layout,
redirect: '/test/index',
children: [{
"path": "/index",
"meta": { "title": "test", "icon": "test" }
}]
}
]
我们知道前端的路由是对应一个页面,所以也可以把路由的这些属性看作是页面的所具备的一些属性,而后端就需要把这些属性存储下来,根据不同的用户所具备的角色给出对应的路由表,前端根据这个路由表来控制侧边栏的渲染。
路由表就是由目录权限和菜单权限动态组成,而我们也想实现按钮的控制,那么我们的权限类型也要增加按钮权限。
所以最终我们的权限划分为目录权限、菜单权限、按钮权限,其中目录权限和菜单权限组成路由表。
角色和权限是多对多关系,用户和角色也是多对多关系,所以要有中间表来存储它们的对应关系。
权限使用标识字符串的形式标识,可以{模块}:{功能}:{业务}来表示,比如公告修改system:notice:update
标识。
4. 实现
后端的权限和角色验证我选择的是Sa-Token(结合注解和拦截器实现),自己实现也很简单使用自定义注解+请求切面处编程,或者全局拦截器interceptor都能实现,使用框架更加方便减少代码量。
前端提交登录信息后,首先要核实登录信息,然后生成用户访问凭证也就是token,查询出用户有哪些权限标识,将用户信息、token和权限标识返回给前端,前端拿到这些信息后先将这些信息存储(本地缓存或者cookie),然后取拉取路由表,根据路由表去渲染侧边栏。
4. 代码实现
代码很多,这里就不单独贴出来了,我这里告知如何去看,大家可以去项目中阅读。
数据库设计都在resource/db目录下。
后端:
- com.guet.ARC.service.SysMenuService 这个menu表示权限,查询和构建路由表的逻辑在此。
- com.guet.ARC.controller.SysMenuController 这里时对外提供的接口。
- 权限验证由注解表示@SaCheckPermission,都在controller层控制。
- com.guet.ARC.config.SaTokenConfig Sa-Token的配置,包含拦截器配置和跨域配置。
- com.guet.ARC.component.StpInterfaceComponent 这里是存储用户的角色和权限集合,这样每次获取时不用重新查询数据库。
前端:
- permission.js 路由拦截鉴权
- store/modules/permission.js 路由动态渲染逻辑
- utils/request.js 请求鉴权逻辑
完全可以将这部分代码移植给自己用。
开源项目:
项目有演示版本可查看
登录账号:202300001,密码:123456