源代码在GitHub - 629y/course: Spring Cloud + Vue前后端分离-在线课程
这一章我们不依赖第三方框架,我会从权限相关表的设计,到权限的配置,到权限的拦截,带大家一步一步的做出一个通用的权限设计方案。
权限拦截的对象:用户
权限拦截的点:菜单、路由、接口、按钮
控制用户对资源的访问
权限的操作:配置、读取、拦截
100用户 * 100资源
直接用用户和资源做关联来控制权限,适合小型的项目,简单,快速
三个核心概念:用户、角色、资源
100用户 * 2个角色 + 2个角色 * 100资源
经典的权限管理设计:用户和角色关联,角色和资源关联
功能点:
配置:
用户管理:用户表,用户管理界面,已完成
资源配置:资源表,资源配置界面
角色管理:角色表,角色管理界面
用户角色关联配置:用户角色关联表,复用角色管理界面
角色资源关联配置:角色资源关联表,复用角色管理界面
两张关联表的配置,可以单独设计界面,也可以直接做到角色管理界面里。
读取:
用户权限的读取:用户登录的时候,读取该用户的所有权限。
登录时,通过用户角色关联表,可以知道当前登录用户的角色,再通过角色资源关联表就可以查到当前用户所有的资源
拦截:
用户操作业务时,进行权限拦截
前端界面:菜单,路由,按钮,hidden disabled
后端接口:接口,gateway的过滤器
疑问:前端已经对菜单和按钮做拦截,用户不能操作了,为什么还要对接口做拦截?
重要提示:所有前端的设计都是不安全的。
例登录名被修改了,说明我们的接口没有做登录名不可修改的校验,因为我们认为给登录名加了disabled就可以了,这是一个常见的安全隐患。
权限初始化
系统上线时,要初始化这五张表的数据。初始有一个用户能登录,能管理角色,管理资源,分配权限。
前面的章节的讲解模式是从简单的功能入手,再不断的扩展和完善,这一章,我们换一种模式,就会先分析需求,再列出功能点,明确总体要做哪些功能,再开始开发。
在实际工作中,也是用这种模式,我们还会加入团队评审的环节,确保我们的功能不会做歪了。
中高级面试题:请简单的设计一个权限管理功能,对初级人员来说会有点难,不过学完这一章,就不是问题了。
1.通用权限管理:资源表的设计与基本代码生成
资源的名称一般是用页面上看得见的元素:菜单、按钮等。
all.sql
如果一个页面的所有操作统一控制权限,request可以填相关接口的前缀
生成代码
ResourceService.java报错原因:资源表的实体类是Resource和@Resource注解同名,导致冲突
admin.vue
router.js
测试
1.通用权限管理:资源树的保存
资源点是开发阶段就确定的,所以并不是上线后再一个一个配置的。
资源管理最简单的一种方案:上线前准备好sql,刷库。
将资源管理做成资源树进行管理。
填入数据:带有层级结构的json字符串
resource.json
用法:以后开发新功能的时候,就在该文件里加入新的资源,可以上线前将新的资源json通过控台保存进数据库。
resource.vue
ResourceDto.java
ResourceService.java
将节点一个一个的取出来,放入list中
ResourceController.java
测试
1.通用权限管理:资源树的显示
resource.vue
ResourceController.java
ResourceService.java
资源的保存是将树结构转成列表,重点是children属性;资源的显示是将列表转成树结构,重点是parent属性。
小知识:一边循环list,一边删除list中的对象,可以使用倒序循环。
list中有父节点的对象都会被remove掉,最终留下来的就是顶级的节点。
测试
1.通用权限管理:增加基本的角色管理功能
2.资源列表补全
all.sql
admin.vue
router.js
resource.json
[{
"id" : "01","name": "系统管理",
"children": [{
"id" : "0101","name": "用户管理","page": "/system/user",
"children": [
{"id": "010101","name": "保存","request": ["/system/admin/user/list","/system/admin/user/save"]},
{"id": "010102","name": "删除","request": ["/system/admin/user/delete"]},
{"id": "010103","name": "重置密码","request": ["/system/admin/user/save-password"]}
]
},
{
"id" : "0102","name": "资源管理","page": "/system/resource",
"children": [
{"id": "010201","name": "保存/显示","request": ["/system/admin/resource"]}
]
},{
"id" : "0103","name": "角色管理","page": "/system/role",
"children": [
{"id": "010301","name": "角色/权限管理","request": ["/system/admin/role"]}
]
}]
},
{
"id" : "02","name": "业务管理",
"children": [{
"id" : "0201","name": "分类管理","page": "business/category",
"children": [
{"id": "020101","name": "增删改查","request": ["/business/admin/category"]}
]
},
{
"id" : "0202","name": "课程管理","page": "business/course",
"children": [
{"id": "020201","name": "增删改查","request": ["/business/admin/course",
"/business/admin/category/all,/business/admin/teacher/list","/file/f/course","/business/chapter"]}
]
},{
"id" : "0203","name": "讲师管理","page": "business/teacher",
"children": [
{"id": "020301","name": "增删改查","request": ["/business/admin/teacher"]}
]
}, {
"id" : "0204","name": "大章管理","page": "business/chapter",
"children": [
{"id": "020401","name": "增删改查","request": ["/business/admin/course","/business/admin/chapter"]}
]
},
{
"id" : "0205","name": "小节管理","page": "business/section",
"children": [
{"id": "020501","name": "增删改查","request": ["/business/admin/section","/business/admin/chapter"]}
]
}
]
},
{
"id" : "03","name": "文件管理",
"children": [{
"id" : "0301","name": "文件管理","page": "file/file",
"children": [
{"id": "030101","name": "文件管理","request": ["/file/admin/file","/file/admin"]}
]
}]
}
]
测试
1.通用权限管理:增加角色资源关联功能表,生成持久层和服务端代码
all.sql
1.通用权限管理:点击[关联资源]按钮时,加载资源树
role.vue
测试
1.通用权限管理:点击资源树模态框【保存】按钮时,保存角色资源关联表
role.vue
RoleDto.java
RoleService.java
RoleController.java
测试
1.通用权限管理:打开资源树模态框时,加载角色资源关联数据,并自动勾选树节点
role.vue
批量操作的思路:先将原有的删除,再批量新增
RoleService.java
RoleController.java
测试
1.通用权限管理:增加角色用户关联表,生成持久层和服务端代码
all.sql
1.通用权限管理:点击【关联用户】按钮时,加载所有用户,弹出角色用户关联模态框
role.vue
测试
1.通用权限管理:点击用户模态框【保存】按钮时,保存角色用户关联表
role.vue
依赖vue的双向数据绑定特性,可以将复杂的页面操作变成简单的数据操作。很多前端框架都有双向数据绑定的特性,比如angular,微信小程序等。
RoleDto.java
RoleService.java
RoleController.java
测试
1.通用权限管理:打开用户模态框时,加载角色用户关联数据
role.vue
查关联表,得到的时userId,但是显示需要的是loginName。这里也可以通过写自定义mapper,把user表和role_user表关联得到loginName
RoleService.java
RoleController.java
测试
1.通用权限管理:登录时,读取当前登录用户所属的角色的所有资源
LoginUserDto.java
一个资源可以会用到多个接口,多个资源的接口就可能重复,所以这里用Set去重
MyUserMapper.xml
MyUserMapper.java
UserService.java
00000000
00000001
00000002
关于User的权限设置比较细,每个请求接口都可以单独控制。关于Resource的权限设置比较粗,所有接口用同一个request控制
1.通用权限管理:前端界面权限拦截,完成用户管理
tool.js
admin.vue
在html中要使用vue方法,这个方法得在methods中定义
user.vue
测试
目前只修改了用户管理的权限拦截,以用户管理为例
00000000
每次修改权限配置,需要重新登录后才生效
1.通用权限管理:前端界面权限拦截,完成所有界面控制
admin.vue
如果一个页面的所有按钮是统一控制的,那么只需要控制菜单就可以了,不需要给每个按钮加权限控制代码。
测试
1.通用权限管理:前端界面权限拦截,增加路由权限判断
没有资源管理,但是我们可以直接通过路由进入
resource.json
[{
"id" : "00","name": "欢迎","page": "welcome"
},{
"id" : "01","name": "系统管理",
"children": [{
"id" : "0101","name": "用户管理","page": "system/user",
"children": [
{"id": "010101","name": "保存","request": ["/system/admin/user/list","/system/admin/user/save"]},
{"id": "010102","name": "删除","request": ["/system/admin/user/delete"]},
{"id": "010103","name": "重置密码","request": ["/system/admin/user/save-password"]}
]
},
{
"id" : "0102","name": "资源管理","page": "system/resource",
"children": [
{"id": "010201","name": "保存/显示","request": ["/system/admin/resource"]}
]
},{
"id" : "0103","name": "角色管理","page": "system/role",
"children": [
{"id": "010301","name": "角色/权限管理","request": ["system/admin/role"]}
]
}]
},
{
"id" : "02","name": "业务管理",
"children": [{
"id" : "0201","name": "分类管理","page": "business/category",
"children": [
{"id": "020101","name": "增删改查","request": ["/business/admin/category"]}
]
},
{
"id" : "0202","name": "课程管理","page": "business/course",
"children": [
{"id": "020201","name": "增删改查","request": ["/business/admin/course",
"/business/admin/category/all,/business/admin/teacher/list","/file/f/course","/business/chapter"]}
]
},{
"id" : "0203","name": "讲师管理","page": "business/teacher",
"children": [
{"id": "020301","name": "增删改查","request": ["/business/admin/teacher"]}
]
}, {
"id" : "0204","name": "大章管理","page": "business/chapter",
"children": [
{"id": "020401","name": "增删改查","request": ["/business/admin/course","/business/admin/chapter"]}
]
},
{
"id" : "0205","name": "小节管理","page": "business/section",
"children": [
{"id": "020501","name": "增删改查","request": ["/business/admin/section","/business/admin/chapter"]}
]
}
]
},
{
"id" : "03","name": "文件管理",
"children": [{
"id" : "0301","name": "文件管理","page": "file/file",
"children": [
{"id": "030101","name": "文件管理","request": ["/file/admin/file","/file/admin"]}
]
}]
}
]
第一次加载admin.vue时,需要判断路由权限,比如从登录页跳到控台主页,或者刷新控台主页时,会执行mounted.
进入控台主页后,发生子路由跳转时,会触发watch。
测试
1.通用权限管理:增加后端接口权限拦截
在做登录功能时,我们也对接口做了登录校验,否则容易被绕开登录,直接调用后端接口。这里同样需要对接口做权限拦截
重新登录
可以通过搜索hidden,display:none等,来查看页面是否有隐藏元素
ctrl+f
LoginAdminGatewayFilter.java
gateway里没有饮用server模块,所以没用LoginUserDto类。这里转成JSONObject进行操作
比如资源的保存:
path=system/admin/resource/save,
而配置的
request=system/admin/resource,
那么path.contain(request)就是true
测试
可以在vue的拦截器中,针对401返回码做判断,如果是401,就跳到登录页面
将业务场景变成程序代码
需求分析:最终要做成什么样子,如何使用这个功能。
功能点拆分:要实现这个需求,都有哪些功能点。
代码编写
团队内部评审通过后,为每个功能点评优先级,并估工时。
每天早上开展例会,每个人花几分钟的时间回答3个问题:
1.昨天做了哪些内容;
2.有没有遇到什么问题;
3.今天准备做哪些内容。
有问题一定要反馈出来,可以是遇到新技术了,或是功能点比想象中复杂,工时估少了等等。