自定义RBAC(5)

2023-02-16,

您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~

把实体类及Service类都准备好了之后,就可以开始继续写业务代码了。Spring Security的强大之一就在于它的拦截器。那么这里也可以参照它的方式,实现自己的拦截器。

/**
* 权限注解
*
* @author 湘王
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
// 组
String group() default ""; // 角色
String role() default ""; // 权限
String permission() default "";
}

拦截器需要完成两个任务:

1、查找用户拥有的资源,其实分解一下,也就是要完成下面的工作

1.1、找到某个用户所属的所有组(不需要去找这些组的父组)

1.2、找到某个用户拥有的所有角色(同时要逐个找到所有这些角色的父角色)

1.3、找到某个用户拥有的所有权限

2、将权限与资源做比对,确认是否对该资源有访问权限

开始定义拦截处理器:

/**
* 拦截处理器
*
* @author 湘王
*/
@Aspect
@Component
public class InterceptorHandler {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService; /**
* 拦截controller包下面的所有类中,有@RequestMapping注解的方法
*/
@Pointcut("execution(* com.xiangwang.controller..*.*(..)) " +
"&& @annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void controllerMethodPointcut() {
} /**
* 拦截器具体实现
*/
@Around("controllerMethodPointcut()")
public Object Interceptor(final ProceedingJoinPoint pjp) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
Map<String, String> argsMap = new HashMap<String, String>();
Enumeration<String> em = request.getParameterNames();
String methodName = pjp.getSignature().getName(); // 加入参数
while (em.hasMoreElements()) {
String paramName = em.nextElement();
String value = request.getParameter(paramName);
argsMap.put(paramName, value);
}
String username = argsMap.get("username");
String platform = argsMap.get("platform");
String timestamp = argsMap.get("timestamp");
String signature = argsMap.get("signature");
// 验证参数
if (null == platform || null == timestamp || null == signature) {
return "params required";
} // 后端生成签名:platform = "xiangwang", timestamp = "159123456789", signature = a8354bc1b54a39528e81c549ec373c14
String sign = DigestUtils.md5DigestAsHex((platform + timestamp).getBytes());
if (!signature.equalsIgnoreCase(sign)) {
return "signature error";
} // 获取切面标记
Signature signatureObject = pjp.getSignature();
if (!(signatureObject instanceof MethodSignature)) {
throw new IllegalArgumentException("this annotation can be applied on method only");
} // 获取用户信息
SysUser user = userService.queryByUsername(username);
if (null == user) {
return "user is not exist";
} /**
* 获得用户拥有的全部角色
*/
// 用户所属的角色
Set<SysRole> userRoleSet = new HashSet<>();
// 用户所拥有的全部角色
Set<SysRole> userAllRoleSet = new HashSet<>();
// 查询这些角色的全部父角色
StringBuilder roleIds = new StringBuilder();
Set<String> userRoleNameSet = new HashSet<>();
// 用户-组-角色
List<SysRole> ugr = roleService.queryUGRByUserId(user.getId());
if (null != ugr && ugr.size() > 0) {
ugr.stream().forEach(r -> userRoleSet.add(r));
}
// 用户-角色
List<SysRole> ur = roleService.queryURByUserId(user.getId());
if (null != ur && ur.size() > 0) {
ur.stream().forEach(r -> userRoleSet.add(r));
}
// 合并全部角色
for (SysRole role : userRoleSet) {
List<SysRole> list = roleService.queryParentsById(role.getParentids());
if (null != list && list.size() > 0) {
list.stream().forEach(r -> {
userAllRoleSet.add(r);
userAllRoleSet.add(role);
});
}
}
// 查询这些角色的全部权限
userAllRoleSet.stream().forEach(r -> {
roleIds.append(r.getId() + "," + r.getParentids());
userRoleNameSet.add(r.getName());
});
List<SysPermission> rolePermissions = permissionService.queryByMultiRoleIds(roleIds.toString()); /**
* 获得用户拥有的全部权限
*/
Set<String> userPermissionSet = new HashSet<>();
// 用户-组-角色-权限
List<SysPermission> ugrp = permissionService.queryUGRPByUserId(user.getId());
if (null != ugrp && ugrp.size() > 0) {
ugrp.stream().forEach(r -> userPermissionSet.add(r.getPath()));
}
// 用户-角色-权限
List<SysPermission> urp = permissionService.queryURPByUserId(user.getId());
if (null != urp && urp.size() > 0) {
urp.stream().forEach(r -> userPermissionSet.add(r.getPath()));
}
// 用户-权限
List<SysPermission> up = permissionService.queryUPByUserId(user.getId());
if (null != up && up.size() > 0) {
up.stream().forEach(r -> userPermissionSet.add(r.getPath()));
}
// 合并之前的结果
if (null != rolePermissions && rolePermissions.size() > 0) {
rolePermissions.stream().forEach(r -> userPermissionSet.add(r.getPath()));
} // 权限判断
Object target = pjp.getTarget();
Class<?> clazz = target.getClass();
MethodSignature methodSignature = (MethodSignature) signatureObject;
try {
Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
// 获取注解类
PreAuthorize preAuthorize = method.getDeclaredAnnotation(PreAuthorize.class);
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
if (null != preAuthorize) {
// 只有当用户具备此角色且访问路径与权限中的path相等时才能认为具有该资源的操作权限
if (userRoleNameSet.contains(preAuthorize.role()) && userPermissionSet.contains(requestMapping.value()[0])) {
System.out.println(username + " ==> " + clazz.getSimpleName() + " - " + method.getName() + " - " +
preAuthorize.role() + " - " + requestMapping.value()[0]);
// 继续往下执行
return pjp.proceed();
} else {
return "permission denied";
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
} return "faliure";
}
}

最后来定义controller:

/**
* 用户Controller
*
* @author 湘王
*/
@RestController
public class UserController {
/**
* 接口访问:
* 用例一:小林(uid=7)->组(无)->角色(产品,rid=5),结果:无权限
* 用例二:小黄(uid=9)->组(无)->角色(客服,rid=4),结果:正常访问
* 用例三:张总(uid=3)->组(gid=10001)->角色(客服、产品、运营,rid=4,5,6),结果:正常访问
*
*/
@PreAuthorize(role = "客服")
@RequestMapping(value = "/api/v1.0.0/user/details", method = RequestMethod.GET)
public String details(String username) {
return username + " 有查看用户详情的权限";
} /**
* 接口访问:
* 用例四:蔡总(uid=4)->组(gid=10002)->角色(会计、出纳、库管、配送,rid=7,8,9,10),结果:无权限
*
*/
@PreAuthorize(role = "产品")
@RequestMapping(value = "/api/v1.0.0/system/setting/password", method = RequestMethod.GET)
public String password(String username) {
return username + " 有修改密码的权限";
}
}

可以通过SQL来完成测试,看看被测试用户权限的正确性

用例一:小林(uid=7)->组(无)->角色(产品,rid=5),结果:无权限

用例二:小黄(uid=9)->组(无)->角色(客服,rid=4),结果:正常访问

用例三:张总(uid=3)->组(gid=10001)->角色(客服、产品、运营,rid=4,5,6),结果:正常访问

用例四:蔡总(uid=4)->组(gid=10002)->角色(会计、出纳、库管、配送,rid=7,8,9,10),结果:无权限

查找用户资源需要注意:

1、查找权限,需要找到某个权限的所有子权限

2、查找角色,需要找到某个角色的所有父角色

4、查找组,可能需要找到父组,也可能不需要

5、想想这都是为什么?

5.1、因为权限是集合关系,角色是组合关系,组是既不是集合关系也不是聚合关系

5.2、父权限是某一类权限的代表,通过它可以找到相关的子权限

5.3、子角色是某个或某些角色的聚合/组合,这些角色组成了新角色

5.4、相对于父组,子组其实更多的是一种权限上的隔离

5.5、在设计权限系统时,需要了解这些隐含的语义逻辑和语境

最后,再来看看遗留的一个小尾巴。

之前在定义SysUser时有一个scope字段(0:全部,1:部门及以下,2:仅个人),它用来限制用户浏览数据的范围。例如,在用户通过拦截器对权限的检查以后,当需要浏览诸如会员、商品、订单时,可以依据这个条件来过滤。

具体做法就是在商品表中加入branchid和userid,然后依据scope来过滤。

1、当scope=1时,过滤条件增加branchid及以下机构(parentids LIKE '%?,%')

2、当scope=2时,过滤条件增加userid(WHERE userid=?)

RBAC2和RBAC1的不同在于增加了一个sys_config表,用来实现权限相关的策略。这个表主要在为用户分配组、角色、权限时起作用,而不是在拦截器中验证时。例如,服务端有一个SystemController,其中有create/assign/update/remove + Group/Role/Permission之类的方法,执行这些方法时,就会从sys_config表中读取策略

例如,如果某两个角色A和B互斥,那么当给用户赋了A时,就不能再赋予用户B角色了。

而所谓二级权限,就是用户A可以把自己拥有的权限再赋予另一个用户,即权限的转授。实现二级权限也不复杂,只需要在SysPermission中增加对一个“分配权限”就行了。如果用户A有这个“分配权限”的权限,就能把自己所拥有的权限分配给另外一个用户(但仅仅限于用户A所拥有的权限),依次类推,可以有三级、四级等多级分级授权。

总的来说,权限系统所用的技术并不复杂,也可以说非常简单。但是设计权限系统的过程是比较费时费力的,也可能比较烧脑。需要对业务需求有很深入的了解,这样才能设计出既能满足需要,又简单实用的权限架构。

其实大多数权限系统,都只到角色这一层,真正用到组的并不多,而且也没有二级权限、权限策略及用户数据范围。

如果项目涉及到了上面这几样,恭喜你,你已经把权限给看光了。


感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

自定义RBAC(5)的相关教程结束。

《自定义RBAC(5).doc》

下载本文的Word格式文档,以方便收藏与打印。