您好,我是湘王,这是我的慕课手记,欢迎您来,欢迎您再来~
把实体类及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所拥有的权限),依次类推,可以有三级、四级等多级分级授权。
总的来说,权限系统所用的技术并不复杂,也可以说非常简单。但是设计权限系统的过程是比较费时费力的,也可能比较烧脑。需要对业务需求有很深入的了解,这样才能设计出既能满足需要,又简单实用的权限架构。
其实大多数权限系统,都只到角色这一层,真正用到组的并不多,而且也没有二级权限、权限策略及用户数据范围。
如果项目涉及到了上面这几样,恭喜你,你已经把权限给看光了。
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~