参考官方文档:权限管理
https://el-admin.vip/guide/hdsc.html
本系统权限控制采用 RBAC
思想。简单地说,一个用户拥有若干角色,每一个角色拥有若干个菜单,菜单中存在菜单权限与按钮权限, 这样,就构造成“用户-角色-菜单” 的授权模型。在这种模型中,用户与角色、角色与菜单之间构成了多对多的关系,如下图
本系统安全框架使用的是 Spring Security + Jwt Token, 访问后端接口需在请求头中携带token进行访问,请求头格式如下:
# Authorization: Bearer 登录时返回的token
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1ODk2NzY0OSwiaWF0IjoxNTU4OTQ2MDQ5f
用户登录 -> 后端验证登录返回 token -> 前端带上token请求后端数据 -> 后端返回数据, 数据交互流程如下:
Spring Security
提供了Spring EL
表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限,常用的 EL
如下
表达式 | 描述 |
---|---|
hasRole([role]) | 当前用户是否拥有指定角色。 |
hasAnyRole([role1,role2]) | 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。 |
下面的接口表示用户拥有admin
、menu:edit
权限中的任意一个就能能访问update
方法, 如果方法不加@preAuthorize
注解,意味着所有用户都需要带上有效的 token
后能访问 update
方法
@Log(description = "修改菜单")
@PutMapping(value = "/menus")
@PreAuthorize("hasAnyRole('admin','menu:edit')")
public ResponseEntity update(@Validated @RequestBody Menu resources){
// 略
}
由于每个接口都需要给超级管理员放行,而使用 hasAnyRole('admin','user:list')
每次都需要重复的添加 admin 权限,因此在新版本 (2.3) 中加入了自定义权限验证方式,在验证的时候默认给拥有admin权限的用户放行。
源码:
// eladmin-common -> me.zhengjie.config.ElPermissionConfig
@Service(value = "el")
public class ElPermissionConfig {
public Boolean check(String ...permissions){
// 获取当前用户的所有权限
List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// 判断当前用户的所有权限是否包含接口上定义的权限
return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);
}
}
使用方式:
@PreAuthorize("@el.check('user:list','user:add')")
在我们使用的时候,有些接口是不需要验证权限的,这个时候就需要我们给接口放行,使用方式如下
1、使用注解方式
只需要在Controller的方法上加入该注解即可
@AnonymousAccess
2、修改配置文件方式
eladmin-system -> modules -> security -> config -> SecurityConfig
TIP
使用
permitAll()
方法所有人都能访问,包括带上token
访问使用
anonymous()
所有人都能访问,但是带上token
访问后会报错
// 关键代码,部分略
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 支付宝回调
.antMatchers("/api/aliPay/return").anonymous()
// 所有请求都需要认证
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
用户表
用户id | 用户名 | 性别 |
---|---|---|
1 | 张三 | 男 |
2 | 李四 | 女 |
3 | 王五 | 男 |
用户-角色表
id | 用户id | 角色id |
---|---|---|
1 | 1 | {1,2,3} |
2 | 2 | {2} |
3 | 3 | {3} |
角色表
角色id | 角色名称 | 角色表角色level | 角色描述 |
---|---|---|---|
1 | admin | 1 | 管理员 |
2 | staff | 2 | 职工 |
3 | visitor | 3 | 游客 |
权限表
权限id | 权限内容 |
---|---|
1 | admin |
2 | user:del |
3 | user:add |
4 | user:edit |
角色-授权表
id | 角色id | 权限id |
---|---|---|
1 | 1 | {1,2,3} |
2 | 2 | {2,4} |
3 | 3 | {3} |
这个是标准的RBAC的权限设置思路,接口的安全由权限表直接控制,之后将权限赋予对应的角色,这样用户有什么角色就有什么接口权限。
代码实现(伪)
@Controller
@RequestMapping("/user")
public UserController{
@PostMapping()
@PreAuthorize("hasAnyRole('admin')")
public void createUser(){
// 拥有admin权限的角色才能使用
}
@PutMapping()
@PreAuthorize("hasAnyRole('admin','user:edit')")
public void update(){
// 拥有 admin 或者 user:edit 权限的角色才能使用
}
@DeleteMapping()
@PreAuthorize("hasAnyRole('user:del')")
public void dataController(){
// 拥有 user:del 权限的角色才能使用
}
}
通过上述图,如果用户发出接口请求,会从根据当前用户的角色寻找此角色的权限接口有哪些,最后自动判断后通过接口
。
所以不要再写根据当前用户角色判断业务逻辑的方式了。
1、权限赋予:
权限赋予是把当前用户的权限拉出来,然后分配的客服可以小于等于当前用户的权限。
2、权限加载:
正常的加载权限,当用户登录后,并且第一次使用权限判断的时候, Shiro 会去加载权限。
3、权限判断:
走正常用户权限判断,但是数据操作需要判断是不是当前归属的用户的数据,其实这个是属于业务层,就算你不是客服,也是需要判断。
4、禁用|启用:
禁用启用,也是正常的用户流程,添加到禁用列表里,如果被禁用,就无法操作任何内容。
所谓的权限控制说白了,就是一个字符串,在前端 用户登录后会查到当前用户的所有权限标识符,进行crud操作的时候会去匹配,匹配到这个字符串,就是有权限,能操作,反之则不能。 在后端 访问接口的时候通过token 查询到当前用户的所有权限去匹配接口上的权限标识,进行判断。
接下来详细看议案源码中是怎么实现的吧
前面已经介绍过,在登录后访问接口时会携带token进行识别
随便打开一个接口,
可以看到标红的注解@PreAuthorize("@el.check(‘user:add’)")
,这是什么意思呢?在IDEA环境下,按住ctrl键点击@el.check,可以看到源代码如下:
/**
* @author Zheng Jie
*/
@Service(value = "el")
public class ElPermissionConfig {
public Boolean check(String ...permissions){
// 获取当前用户的所有权限
List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// 判断当前用户的所有权限是否包含接口上定义的权限
return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);
}
}
这里的意思就是,用户的权限包含 admin 或 接口上的权限标识符 就返回true
用户登录成功后,会查询到当前用户的所有信息(包括权限信息),同时这些信息也会存在token中,在前端操作中,会通过这些信息进行判断,访问后端接口则通过token
这就是 RBAC
的权限设计思想,并没有想象中那么复杂