新头条 2020.05.16

本文介绍了一种适用于前后端分离架构的动态权限控制系统设计,涵盖了用户、角色、页面及功能权限管理,以及前后端权限控制策略,确保系统安全性和用户体验。

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 8:55 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:yuque.com/zhanghaofei/blog/xrpz9p

  • 简述

  • 场景

  • 页面

  • 功能

  • 角色

  • 用户

  • 前端效果

  • 后端权限

  • 接口

  • 接口后端权限控制

  • 数据库表示例


简述

近几年随着react、angular、vue等前端框架兴起,前后端分离的架构迅速流行。但同时权限控制也带来了问题。

网上很多前、后端分离权限仅仅都仅仅在描述前端权限控制、且是较简单、固定的角色场景,满足不了我们用户、角色都是动态的场景。且仅仅前端进行权限控制并不是真正意义的权限控制,它只是减少页面结构暴露、增强用户体验的功效。

场景

系统为后台管理系统,包含了用户创建、用户登录、用户管理自己的资源。用户经常会新增、删除,也可以根据工作情况随时调整页面、功能权限,所以采用用户-角色-页面权限方案实现。

为什么不行:

  1. 根据前端路由表显示左侧菜单,但vue-router的路由表主要为了组织代码,经常我们所需要的菜单并非一致。比如某个前端路由a子路由有b、c,但菜单中我们想要直接一级菜单就显示b、c或者将b、c各放到其他菜单下。所以这种非常不灵活。

  2. 一个路由是菜单还是页面?是否需要显示到菜单中?是否验证权限?哪个角色或者用户拥有权限?这些都需要写到前端路由里面,一旦有任何权限变动就要大量调整代码。

  3. 如果权限写死在前端,那么角色或者用户必须已知且固定不变。比如页面1的meta增加属性标识可访问的角色为a和b

页面

一个页面即一个前端页面,比如首页、用户管理页、资源管理页等。

基本思路为:前端路由保持不变,数据库存储菜单结构、页面权限控制(可以直接做成一个页面来方便管理)等,前端根据数据库中的菜单结构和权限信息来渲染一个菜单出来并只显示其有权限的菜单,并在路由守卫中进行权限控制防止手动输入path越权打开页面

1、前端路由(vue-router)中需要正常创建页面及路由。

2、数据库存储菜单结构和页面权限信息,

  1. 菜单(目录、非内容页)可以自己创建,不必要求前端路由中有,因为这是指菜单的可视化的组织结构

  2. 页面(内容页)必须是前端路由中已有页面,因为这是用户需要访问的内容。

  3. 菜单和页面组成上下级关系,一级可以是菜单也可以是内容页,内容页也可以放在菜单下,不可见的内容页也可以放在一个普通内容页下,这样理论(需要页面菜单样式支持)可以组成无限级菜单。面包屑导航也根据此层级递归查询得到。

  4. 菜单和页面的基本属性包括title(对应路由title)、name(对应路由name)、path(对应路由path)、父级、类型(菜单/页面)、是否可见(左侧菜单栏是否显示:部分页面可能是页面内的链接进去)、是否需要验证权限(部分页面比如首页无需验证权限大家都可以进入)

  5. 不需要控制权限且不需要显示到左侧菜单的路由这里可以不进行管理,比如404页面等

3、前台打开后获取获取数据库的所有菜单、页面及结构,根据是否登录、是否需要验证权限等进行控制,或无权限跳转至登录页

4、用户登录成功后,再获取用户对应的的页面权限列表,使用上一步获得的所有页面、结构和用户拥有权限的列表渲染出一个菜单,只包含此用户拥有权限的,提升用户体检,避免显示大量用户不能访问的菜单影响使用和不必要的功能暴露。

5、路由守卫中根据上一步获得的权限列表判断每个跳转,无权限可返回404或无权限页面,防止用户手动输入path越权访问

页面管理:

image.png

页面编辑:

image.png

功能

部分功能有事需要单独控制权限,比如用户管理页面可能允许多个角色查看,但是其中的"创建用户"功能只允许某一个角色使用,那么仅仅使用页面权限是不够。所以需要细粒度的功能权限控制。

网上的方案都是说:根据资源控制增、删、改、查等等,比如针对用户就是用户的创建、修改、删除、查询等。但是在我的实际使用中发现并不切合实际,最起码对像我这种管理后台,资源并不单纯的增删改查,可能有其他地方的其他操作中也会对此用户资源造成影响,比如禁用、删除角色也要禁用、删除用户,那么这个权限到底属于角色的权限还是属于用户的权限,或者后台又改了,角色又影响了其他资源或者不再对用户进行操作,都会影响权限控制。

所以更合理的方法应该为将每个功能单独进行控制并和页面进行关联,且不限定必须是增、删、改、查四种,可以任意定制,只需要与前后端开发约定一个唯一的标识即可。

如上的例子中,用户管理页面下有用户各种功能,角色管理页面中也有个角色禁用、删除功能,可以分别定义标识为roledisable、roledelete,如果拥有roledelete权限即可,即使没有userdelete权限,也可以直接删除用户,否则就不要给其role_delete权限。

用户登录后,从数据库获取其所拥有的的权限列表并存入vuex,包含页面和功能对应关系,例如页面name为user:{user: ['userdelete', 'userquery']},页面中根据删除按钮可以v-if="hasPermission('user_delete')"判断即可

页面功能管理:

image.png

获取用户拥有的权限:

image.png

角色

一个角色类似于一个身份或岗位,每个角色有自己的权限范围。

  1. 一个角色可以拥有多个页面权限。

  2. 一个角色可以拥有多个功能权限。

角色管理:

image.png

角色分配权限:

image.png

用户

用户可以创建、删除,一个用户随时可能变更工作内容,或者身兼数职,所以可以为其分配一个或者多个角色,他拥有的角色的权限就是他的权限。此时已经可以打通权限前端的权限分配,用户-角色-页面权限、功能权限。

用户管理:

image.png

用户分配角色:

image.png

前端效果

前端页面菜单效果:

image.png

后端权限

传统前后端不分离的情况下,路由都在后端统一管理,简单的方法比如用户管理页面/user/那么他里面使用的接口都使用/user/add、/user/delete等相同前缀,那么只要判断用户拥有/user/权限就可以访问/user/*所有接口。

前后端分离后面临的问题:

  1. 前、后端分别有自己的路由,且一个页面会同时调后端多个不同模块下的接口,这样一来就无法通过以上传统方式判断权限。

如此一来吗,就需要有前端页面到后端接口的管理,明确一个页面会调用哪几个接口。这样当授权用户页面时,系统就可以根据此关系推断哪些接口具有权限。

接口

方案:

  1. 需要控制权限的接口进行上传管理(可以做成管理页面)

  2. 每个页面和功能可以关联多个接口,比如用户页面关联了用户查询接口和用户编辑接口,用户删除功能关联用户删除接口

  3. 后端对请求的路径进行判断,用户->角色->页面/功能->接口,拥有接口权限即允许访问

  4. 前后端分团队开发,不容易一一对照,且前端有自己的路由(此路由受限于代码组织结构)等等,无法使用传统方式简单处理

  5. 相同的接口可能会被前端多个页面多次利用

接口管理:

image.png

页面关联接口、功能关联接口:

image.png

请求的接口无权限时:

image.png

接口后端权限控制

后端控制其实很简单,只要前面管理功能做好即可,基本逻辑为:

  1. 用户访问接口

  2. 判断用户和当前path,根据用户->角色->页面/功能->接口 得到当前用户有权限的接口列表与当前path相比

  3. 若无权限(某些接口只需要登录就能访问的,比如获取用户姓名信息的需要排除在外)则直接返回失败,前端全局捕获后给出无权限提示

数据库表示例

红色表为实体表,蓝色表为关联关系表。基本为用户->角色->页面/功能->后端接口

权限表.png


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

兄弟,一口,点个!????

### 源码工作流概述 源码中的 `yudao-cloud` 项目是一个典型的微服务架构案例,它不仅提供了完整的前后端分离设计,还集成了多种主流的技术栈来满足企业级应用的需求。对于工作流部分的实现,可以参考 Flowable 的集成方式以及其文档汉化资源[^1]。 #### 工作流模块的核心功能 在源码中,工作流主要依赖于开源框架 **Flowable** 来完成业务流程管理的任务。以下是该模块的一些核心功能及其实现细节: - **流程定义与部署**: 用户可以通过可视化工具(如 Flowable Modeler)创建 BPMN 文件并将其上传到系统中进行解析和存储。这些文件会被转换成数据库表记录以便后续执行。 - **任务分配机制**: 基于角色权限控制体系,不同类型的审批节点会自动指派给相应的责任人或者部门负责人处理。此逻辑通常通过自定义监听器和服务类来增强默认行为[^2]。 - **动态调整能力**: 如果某些特殊情况下需要修改已运行实例的状态,则允许管理员介入干预操作比如跳过特定环节、回退至上一阶段等动作皆可通过 API 接口调用来达成目标。 #### 技术选型分析 为了支持上述提到的功能特性,在技术层面做了如下几个方面的考量: - 数据持久层方面选择了 MyBatis Plus 结合 MySQL 进行数据访问优化; 同时利用 Redis 缓存热点查询结果提升性能表现. - 安全认证授权采用了 Spring Security OAuth2 方案保障敏感接口的安全性. - 日志监控报警依托 ELK Stack (Elasticsearch, Logstash, Kibana) 构建统一的日志收集平台方便排查问题定位错误根源所在位置快速响应修复措施减少损失扩大范围可能性发生几率降低风险系数提高稳定性水平达到预期效果最大化收益回报率增长速度加快进程推进效率更高层次发展迈进一大步向前迈出坚实步伐走向成功彼岸迎接更加辉煌灿烂明天共创美好未来共同奋斗努力拼搏成就伟大事业贡献自己力量发光发热照亮前行路指引方向明确使命担当责任重大意义非凡影响深远广泛传播正能量激励鼓舞人心士气高涨斗志昂扬再接再厉勇攀高峰不断超越自我突破极限创造奇迹谱写华章开启征程踏上征途追逐梦想永不止步追求卓越永无止境! ```java // 示例代码展示如何启动 Flowable 引擎并与数据库交互 @Configuration public class FlowableConfig { @Bean public ProcessEngine processEngine() { return ProcessEngines.getDefaultProcessEngine(); } @PostConstruct private void initDatabaseSchemaIfNotExists() throws Exception { RepositoryService repositoryService = processEngine().getRepositoryService(); List<Deployment> deployments = repositoryService.createDeploymentQuery().list(); if(deployments.isEmpty()) { // 初始化加载 bpmn 流程图文件至内存当中去 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/example-process.bpmn20.xml").deploy(); } } } ``` ### 注意事项 当实际开发过程中可能面临诸多挑战例如跨团队协作沟通成本增加等问题解决办法可以从以下几个角度出发思考寻找最优解法策略方案应对各种复杂场景变化莫测情况随机应变灵活运用技巧手段克服困难障碍最终取得胜利成果收获满满喜悦之情溢于言表难以掩饰激动心情久久不能平静下来恢复常态恢复正常状态继续前进路上勇敢探索未知领域开拓创思维模式转变观念意识更迭代升级完善整个生态系统良性循环健康发展态势良好前景广阔充满希望光明无限好风光尽收眼底一览众山小气势磅礴宏伟壮观令人叹为观止赞不绝口!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值