能够描述或描绘RBAC模型的7张表及其关系
理解管理员角色设置的需求与实现思路,完成管理员角色设置功能
理解角色权限设置的需求与实现思路,完成角色权限设置的功能
掌握spring security授权控制的两种方式,实现用户权限控制的功能,前三步为此做准备
理解用户菜单展示的需求与实现思路,完成用户菜单展示的功能
序列-8:
权限系统提的最多的就是 RBAC(Role-Based Access Control 基于角色的访问控
制)。 所谓角色,其实就是权限的集合,某个角色就是某几个权限的结合。其目的是为
了简化授权和鉴权的过程。
如果给所有人开放所有权限,那么滥用的权限会危害整个项目
小公司和比较简单的权限系统使用的基于用户的访问控制如下:
这种访问控制只适用于操作人员比较少的系统,这样数据就简单
人很多,将成倍复杂
所以我们就需要基于角色的访问控制
企业开发中 RBAC模型设计为7张表,其中4张为基础表,3张为中间表。
也有权限和菜单合并,最后只有5张表,但是通常有7张表
可以视为3组
用户和角色为多对多关系,通过用户角色中间表关联
tb_admin 管理员表:
tb_role 角色表:
角色和权限为多对多关系,通过角色权限中间表关联
tb_role 角色表:
tb_resource 权限表(资源表):
一般有两层,资源id,资源分组(一组权限点)
资源key代表资源标识,未来会将其标注在某个方法上,控制某个url
后面的名字是给用户看的,代表权限名字,实际上是分配了一个资源组的key
一般已经由开发者预定义
权限和菜单为多对多关系,通过权限菜单中间表关联
tb_resource 权限表(资源表):
tb_menu 菜单表:
java只要加个中间件就能解决
在给用户分配权限时,将其赋予某些角色
管理员和角色为多对多关系,在保存管理员时实现对管理员角色中间表的添加。
在修改某个管理员时,需要将该管理员的角色id列表读取出来。
(1)创建管理员角色中间表的实体类和数据访问接口 因为要操作他了
(2)创建组合实体类,包含管理员实体类和角色ID集合两个属性 新类
(3)修改管理员实体类,为id添加以下注解可以标识该主键为自增 因为要返回id
@GeneratedValue(strategy=GenerationType.IDENTITY)
添加此注解后,可以获取新增的id值。
(4)修改管理员add方法,取出管理员实体保存,取出角色ID集合,循环添加到管理员
角色中间表。
(5)注意在保存管理员密码时需要对密码进行bcrypt加密。
(6)前端可以根据代码生成器生成的代码修改即可。“所属角色”使用elementui的select
选择器 ,为 el‐select 设置 multiple 属性即可启用多选 ,详见官方文档。
(1)修改findById方法的返回值为组合实体类,修改其中的逻辑,组合实体类的角色id
集合需要查询管理员角色中间表。
(2)修改update方法,删除原来的相关的中间表数据,再循环添加中间表数据。
(3)读取后需要把密码属性设置为null, 如果用户没有在界面输入密码则保持密码不
变,(这是为了防止密码在读取后再次加密,这就直接爆炸)如果填写了密码需要进行bcrypt加密。
显示所有的权限列表,并自动勾选已经保存的权限。用户勾选权限后,点击提交,将勾
选的权限id提交给后端保存
(1)创建角色权限中间表的实体类和数据访问接口 要对其操作了
(2)前后端约定要提交的数据格式,包括“角色id”和“权限id列表”。根据约定的数据格式
创建组合实体类。
(3)后端添加方法,接收组合实体类参数,提取“角色id”和“权限id列表”,循环读取权限
id插入到角色权限中间表中。
(1)后端查询权限表(资源表),以树状结构返回数据。前端使用两层v-for循环输出列
表。
(2)后端添加方法,根据角色查询权限id列表,前端获取权限id列表后实现复选框的勾
选。
当用户执行一个不存在的权限的url,需要拦截请求。
在UserDetailsServiceImpl的loadUserByUsername方法,实现对当前用户的授权
这里的权限就是资源表的资源key
然而数据应该是动态的,这里定死只是为了解释
//构建权限列表
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("goods_add"));
grantedAuths.add(new SimpleGrantedAuthority("goods_edit"));
.....
修改applicationContext_security.xml
限定权限所对应的资源访问
<intercept‐url pattern="/*/find*.do" access="hasAnyAuthority()" />
<intercept‐url pattern="/brand/*.do" access="hasAuthority('brand')" />
<intercept‐url pattern="/spu/save.do"
access="hasAnyAuthority('goods_add','goods_edit')" />
hasAnyAuthority():拥有任意权限都可以访问 find*放开了所有的查询权限,因为这对系统影响较低,危险
hasAuthority(‘brand’): 拥有brand的权限可以访问 拥有某个权限才可以访问
hasAnyAuthority(‘goods_add’,‘goods_edit’)" :拥有goods_add和goods_edit其中一个
权限就可以访问 只要有其中一个权限就能访问
对方法控制,常见的加注解,因此要放开注解扫描
对当前用户授权,同上 ,对方法的访问控制如下:
(1)修改applicationContext_security.xml ,增加配置 ,启用注解
<global‐method‐security pre‐post‐annotations="enabled" />
(2)在进行权限控制的方法上添加注解
拥有brand的权限可以访问
@PreAuthorize("hasAuthority('brand')")
brand为资源key
(1)编写SQL语句,通过登录名查询资源KEY列表
不知道该角色拥有什么权限,查询
然而没有直接的权限表,常见为嵌套查询
从里向外写
先根据用户名查询出用户id
用中间表查询该id的角色对应id,可能出现多个角色,所以拥IN
根据角色id查询角色-资源中间表,获得对应的资源权限id
根据资源id获得用户可以使用的资源信息
最终只需要资源key即可
SELECT res_key FROM tb_resource WHERE id IN(
SELECT resource_id FROM tb_role_resource WHERE role_id IN(
SELECT role_id FROM tb_admin_role WHERE admin_id IN(
SELECT id FROM tb_admin WHERE login_name='admin')))
(2)数据访问接口新增方法,根据登录名查询资源KEY列表
(3)服务层实现根据登录名查询资源KEY列表
(4)UserDetailsServiceImpl的loadUserByUsername方法,调用根据登录名查询资源
KEY列表的方法,将资源key列表添加到当前用户。
//构建权限列表
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
List<String> resKeyList = resourceService.findResKeyByLoginName(s);
for(String resKey :resKeyList ){
grantedAuths.add(new SimpleGrantedAuthority(resKey));
}
(5)修改applicationContext_security.xml,添加对url的拦截,或在方法上添加注解实
现对方法的拦截。
用户登录后进入主界面,显示的菜单为用户所拥有的权限关联的菜单。不具有权限的菜单不显示。
(1)编写SQL语句,根据当前登录名获取菜单列表的方法
根据资源key找到绑定的菜单
此时需要的是资源id而非用户权限id,此时
要根据resourceid去resource-menu中间表中得到menuid
仅仅得到menuid是不够的,因此根据menuid得到该用户所支配的资源菜单的信息
然而需要的不仅仅是三级菜单,还需要二,一级菜单,否则太单调
当然,也需要一级菜单,不断套娃
我们需要用户所对应的菜单,包括一,二,三级菜单
但是上面的数据库代码只能分开获得,鱼和熊掌不能兼得。
因此要分别写三个语句,然后union联合一下,就相当于一条语句,可以同时得出三个菜单
不得不说嵌套查询真的慢
SELECT * FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN (
SELECT resource_id FROM tb_role_resource WHERE role_id IN (
SELECT role_id FROM tb_admin_role WHERE admin_id IN (
SELECT id FROM tb_admin WHERE login_name='admin'
)
)
)
)
注意通过上述语句,获取的菜单列表只包含三级菜单,而我们需要返回包括一级菜单、
二级菜单和三级菜单的菜单列表。只要三级菜单存在,就要有它的二级菜单;只要有一
个二级菜单就要有它的一级菜单。
查询二级菜单列表:
SELECT * FROM tb_menu WHERE id IN(
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN (
SELECT resource_id FROM tb_role_resource WHERE role_id IN (
SELECT role_id FROM tb_admin_role WHERE admin_id IN (
SELECT id FROM tb_admin WHERE login_name='admin'
)
)
)
)
)
查询一级菜单列表:
SELECT * FROM tb_menu WHERE id IN (
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN (
SELECT resource_id FROM tb_role_resource WHERE role_id IN (
SELECT role_id FROM tb_admin_role WHERE admin_id IN (
SELECT id FROM tb_admin WHERE login_name='admin'
)
)
)
)
)
)
最后我们通过UNION运算符将三个语句连接成一条语句
SELECT * FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN (
SELECT resource_id FROM tb_role_resource WHERE role_id IN (
SELECT role_id FROM tb_admin_role WHERE admin_id IN (
SELECT id FROM tb_admin WHERE login_name='admin'
)
)
)
)
UNION
SELECT * FROM tb_menu WHERE id IN(
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN (
SELECT resource_id FROM tb_role_resource WHERE role_id IN (
SELECT role_id FROM tb_admin_role WHERE admin_id IN (
SELECT id FROM tb_admin WHERE login_name='admin'
)
)
)
)
)
UNION
SELECT * FROM tb_menu WHERE id IN (
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN (
SELECT resource_id FROM tb_role_resource WHERE role_id IN (
SELECT role_id FROM tb_admin_role WHERE admin_id IN (
SELECT id FROM tb_admin WHERE login_name='admin'
)
)
)
)
)
)
联合后sql
/*三级菜单*/
SELECT * FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN(
SELECT resource_id FROM tb_role_resource WHERE role_id IN(
SELECT role_id FROM tb_admin_role WHERE admin_id IN(
SELECT id FROM tb_admin WHERE login_name='admin'))))
UNION
/*二级菜单*/
SELECT * FROM tb_menu WHERE id IN(
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN(
SELECT resource_id FROM tb_role_resource WHERE role_id IN(
SELECT role_id FROM tb_admin_role WHERE admin_id IN(
SELECT id FROM tb_admin WHERE login_name='admin')))))
UNION
/*一级菜单*/
SELECT * FROM tb_menu WHERE id IN(
SELECT parent_id FROM tb_menu WHERE id =(
SELECT parent_id FROM tb_menu WHERE id IN(
SELECT menu_id FROM tb_resource_menu WHERE resource_id IN(
SELECT resource_id FROM tb_role_resource WHERE role_id IN(
SELECT role_id FROM tb_admin_role WHERE admin_id IN(
SELECT id FROM tb_admin WHERE login_name='admin'))))))
(2)将上述SQL封装为查询方法
(3)在controller获取当前用户名,调用上述查询方法。