RBAC权限管理
RBAC权限管理
Role-Based Access Control:基于角色的权限控制。通过角色关联用户,角色关联权限的方式间接赋予用户权限。在后端开发场景下,权限即表示对某path的可访问性or能访问到什么地步。具体到实现上,一是验证这个请求能否访问这个path,二是验证这个请求的参数是否符合他的权限。
在招新平台中,具体的实现思路是:
包含三个基本单位:路径、用户、权限组 (角色) 。用户属于某个权限组,某个权限组拥有某些路径的访问权限。
- 初始化:获取整个项目的所有路径以方便后续分配权限。
- 配置权限:在配置页面,配置每个用户组能访问的路径,同时配置某个用户属于哪个权限组。
- 拦截请求:向springboot中添加interceptor,拦截请求。
- 检验权限:符合条件正常放行,不符合条件返回403 forbidden。
初始化
分为路径初始化和权限初始化两个步骤。路径初始化是指提取整个项目所有路径,并保存到MySQL数据库。
路径初始化
Springboot会把所有URL和Method(具体而言是RequestMapping注解的信息封装的RequestMappingInfo类)与JAVA类的对应关系保存到RequestMappingHandlerMapping对象中,通过这对象获取所有接口并保存到数据库中。
@PostConstruct
private void initPaths(){
log.info("path初始化开始");
initPermissionGroups();
// 生成版本号
Long versionId = SnowflakeUtil.getId();
// 新增预先设置的path
initPrePaths(versionId);
// 将管理员加入dev组中
initAdmin();
// 获取所有path
Map<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> m : requestMappingInfoHandlerMethodMap.entrySet()) {
RequestMappingInfo info = m.getKey();
HandlerMethod method = m.getValue();
RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
if (methodsCondition.getMethods().isEmpty()){
continue;
}
Paths entity = getPathByRequestMappingInfo(versionId, m);
entity = pathsService.insertOrUpdate(entity);
// 判断是否有默认组
// 省略默认组相关逻辑,这里提供了一个注解,可以写在控制器类上,可以直接注明这个类默认是什么权限组的,方便配置。
}
// 查询所有旧path
List<Paths> oldPaths = pathsService.queryAllOldPath(versionId);
for (Paths oldPath:
oldPaths) {
pathsService.deleteByIdAndHandleGroupAndBlackList(oldPath.getId());
}
log.info("path初始化结束");
}
权限初始化
这里实际上是一个优化措施,只初始化免登录与基础权限组(实质上是把相关内容从数据库搬到内存中),保存在PermissionMappingInfo类的变量里面,后续在检查这两个权限时,直接拿出这个Bean,获取变量然后检验就可以,速度可以非常快(因为不用访问数据库)。
这里的具体逻辑写在:
//对应Config类
@PostConstruct//在SpringBean全部构建完成后要做的操作(系统启动时的初始化操作)
public void init() throws InterruptedException {
log.info("权限初始化开始");
permissionMappingInfo.init(requestMappingHandlerMapping);
log.info("权限初始化完成");
}
//PermissionMappingInfo类
public void init(RequestMappingHandlerMapping requestMappingHandlerMapping) throws InterruptedException {
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
methodsPermissionGroupsNoNeedLogin = new ConcurrentHashMap<>();
methodsPermissionGroupsOther = new ConcurrentHashMap<>();
methodsPermissionGroupsIsBase = new ConcurrentHashMap<>();
updateMappingInfoExecutor = new ThreadPoolExecutor(8,
8,
100,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
preInit();//函数内容是调用setAllMappingPermissionGroupsInfo()
updateMappingInfoExecutor.execute(this::task);
}
//定时任务,每十秒刷新这两个组的内容
private void task() {
try {
while (true) {
setAllMappingPermissionGroupsInfo();//作用是读取数据库,把内容读取到内存中
clearOldData();
Thread.sleep(10000);
}
}catch (Exception e) {
log.error("ERROR: ", e);
}
}
配置权限
这个比较简单,主要是写个接口更新数据库的值就好,简单的增删改查。具体内容就不写了,没什么意义。
拦截请求与检验权限
配置MvcConfigurer,添加拦截器LoginInterceptor()。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;// 把handler强转为HandlerMethod
Method key = method.getMethod();
// 是否在免登录组里
if (checkNeedLogin(key)) {
return true;
}
Users user = checkLogin(request);
// TODO: 黑名单
if (checkIsBase(key)) {
return true;
}
// 查出该用户加入的非基础权限组 基础权限组 和 免登录组映射关系不在usersofGroup表中
UsersOfGroup usersOfGroupParams = new UsersOfGroup();
usersOfGroupParams.setUserId(user.getId());
usersOfGroupParams.setIsEffective(1);
List<UsersOfGroup> usersOfGroupRet = usersOfGroupService.queryAll(usersOfGroupParams);
ConcurrentHashMap<Long, PermissionMappingInfo.PermissionMappingInfoEntry> map = permissionMappingInfo.getMethodsPermissionGroupsOther().get(key);
for (UsersOfGroup ug:
usersOfGroupRet) {
// 判断该用户加入组是否有效
if (ug.getIsEffective() != 1 || !checkDate(ug.getStartAt(), ug.getEndAt())) {
// 一个有效即可
continue;
}
// 是否是dev组成员
if (Objects.equals(ug.getGroupId(), PermissionGroupConstant.DEV_GROUP.getId())) {
return true;
}
if (map == null || map.isEmpty()) {
// 该方法没有加入到其他权限组
continue;
}
PermissionMappingInfo.PermissionMappingInfoEntry entry = map.get(ug.getGroupId());
if (entry == null) {
continue;
}
if (checkSingleGroup(entry.getPermissionGroups(), entry.getPathsOfGroup())) {
//简单检测date和isEffective后
return true;
}
}
throw new ForbiddenException();
}
throw new ForbiddenException();
}
这样就好了,对每个请求都检测。
License:
CC BY 4.0