目录
一、简介
二、接口权限控制的实现
1、数据库表
2、后端代码实现
1. 添加AOP依赖
2. 编写自定义注解
3. 编写切面类
4. 使用自定义注解限制用户访问
三、数据权限控制的实现
1、后端代码实现
1. 编写自定义注解
2. 编写切面类
3. 使用自定义注解限制参数
一、简介
RBAC (Role-based access control )全称为基于角色的访问控制模型,在该模型中,通过让权限与角色关联来实现授权,给用户分配一系列的角色来让注册用户得到这些角色对应的权限,使系统权限分配更加方便。
用户权限控制分下面3类:
访问权限: 哪些页面可以访问、哪些页面元素可见等等
操作权限: 如页面按钮是否可点击、是否可以增删改查等等
接口与数据权限: 接口是否可以调用、接口具体字段范围等等
前面2类可以在前端页面来实现,后面的接口和数据权限可以在后端进行实现。
二、接口权限控制的实现
1、数据库表
我们在数据库中设计5种类型的表。
?如某系统角色权限设计如下:
- t_auth_role中的code(表示等级)有Lv0、Lv1、Lv2,等级越高,权限和功能越多。
- ?在数据库中写入用户Tom的权限数据,让Tom在t_user_role中关联角色表中Lv0的等级。
- 角色后面不同的功能也可以关联不同的等级来实现权限管理。
2、后端代码实现
我们通过Spring AOP的前置通知功能在Controller层检测某用户访问某URL的时候,携带的token来解析出的UserId来查询数据库是否符合等级要求。
1. 添加AOP依赖
<!--AOP,跟springboot版本一致-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.3</version>
</dependency>
2. 编写自定义注解
?在domain实体类层下创建annotation\ApiLimitedRole注解类
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) //在运行阶段
@Target({ElementType.METHOD}) //标注放在方法上
@Documented
@Component //注入
public @interface ApiLimitedRole {
// 将来用于标志限制URL访问的禁止权限
String[] limitedRoleCodeList() default {};
}
3. 编写切面类
在Api(也就是Controller)类层下创建aspect\ApiLimitedRoleAspect类。
通过@apiLimitedRole注解传入的禁止访问的等级code,该code跟user拥有的权限等级code取交集,如果交集值大于0,说明该用户的权限等级在被禁止的名单中,则被禁止访问该URL。
@Aspect
@Order(1)
@Component
public class ApiLimitedRoleAspect {
@Resource
private UserSupport userSupport; //用来通过request查询token中含的userId
@Resource
private UserRoleService userRoleService; //用来查询userId拥有的权限code
// 切点(也就是使用@apiLimitedRole注解的位置)
@Pointcut("@annotation(com.yygs.bilibili.domain.annotation.ApiLimitedRole)")
public void check() {
}
// AOP前置通知
// 并通过注解传入禁止访问的等级code
@Before("check() && @annotation(apiLimitedRole)")
public void doBefore(JoinPoint joinPoint, ApiLimitedRole apiLimitedRole) {
Long userId = userSupport.getCurrentUserId();
// 查询数据库获得的用户权限
List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(userId);
// 通过注解传入权限不足的等级
String[] limitedRoleCodeList = apiLimitedRole.limitedRoleCodeList();
Set<String> limitedRoleCodeSet = Arrays.stream(limitedRoleCodeList).collect(Collectors.toSet());
// 用户有的权限code
Set<String> roleCodeSet = userRoleList.stream().map(UserRole :: getRoleCode).collect(Collectors.toSet());
// 取交集
roleCodeSet.retainAll(limitedRoleCodeSet);
if (roleCodeSet.size() > 0) {
throw new ConditionException("权限不足!");
}
}
}
4. 使用自定义注解限制用户访问
?注解中的值AuthRoleConstant.Role_LV0是一个常量,对应数据库中的Lv0。因为在开始的时候我们给Tom添加的权限等级为Lv0,那么如果Tom访问/user-moments的URL的时候会抛出“权限不足!”的异常通知。
@ApiLimitedRole(limitedRoleCodeList = {AuthRoleConstant.ROLE_LV0})
@PostMapping("/user-moments")
public JsonResponse<String> addUserMoments(@RequestBody UserMoment userMoment) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
Long userId = userSupport.getCurrentUserId();
userMoment.setUserId(userId);
userMomentsService.addUserMoments(userMoment);
return JsonResponse.success();
}
这样通过注解和赋值code就可以限制URL的访问等级就变得优雅很多,也比较灵活。那么在后面添加功能和角色之后,如果角色变得十分多,还可以通过添加权限组的方法解决,只要用户在权限组中,或者权限在角色组中就可以访问,以此简化代码。
三、数据权限控制的实现
1、后端代码实现
1. 编写自定义注解
在domain实体类下创建annotation\DataLimited注解类
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Component
public @interface DataLimited {
}
2. 编写切面类
?在Api(也就是Controller)层下创建asperct\DataLimitedAspect类。
如果角色的等级是Lv0,且传入的参数不是“0”,则参数异常(也就是限制等级Lv0传某数据的参数只能为0),以此进行数据权限的管理。
@Aspect
@Order(1)
@Component
public class DataLimitedAspect {
@Resource
private UserSupport userSupport;
@Resource
private UserRoleService userRoleService;
@Pointcut("@annotation(com.yygs.bilibili.domain.annotation.DataLimited)")
public void check() {
}
@Before("check()")
public void doBefore(JoinPoint joinPoint) {
// 通过request获取token携带的userId
Long userId = userSupport.getCurrentUserId();
// 查询数据库获得用户的全部权限
List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(userId);
// 转换成Set,去重
Set<String> roleCodeSet = userRoleList.stream().map(UserRole :: getRoleCode).collect(Collectors.toSet());
// 从参数获得数据
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
// 如果有userMoment这个参数
if (arg instanceof UserMoment) {
UserMoment userMoment = (UserMoment) arg;
String type = userMoment.getType();
// 如果角色是Lv0,且传入的参数不是“0”,则参数异常(也就是Lv0只能传参数0)
if (roleCodeSet.contains(AuthRoleConstant.ROLE_LV0) && !"0".equals(type)) {
throw new ConditionException("参数异常");
}
}
}
}
}
3. 使用自定义注解限制参数
@ApiLimitedRole(limitedRoleCodeList = {AuthRoleConstant.ROLE_LV0})
@DataLimited //限制数据(参数的传入)
@PostMapping("/user-moments")
public JsonResponse<String> addUserMoments(@RequestBody UserMoment userMoment) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
Long userId = userSupport.getCurrentUserId();
userMoment.setUserId(userId);
userMomentsService.addUserMoments(userMoment);
return JsonResponse.success();
}
除了通过注解进行权限的控制还可已通过过滤器、拦截器进行URl的拦截。但是通过AOP的注解标注方法对用户进行权限控制,是比较优雅的,AOP的粒度更小,也是比较灵活的。
|