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

把实体类及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)的更多相关文章

  1. django自定义rbac权限组件(二级菜单)

    一.目录结构 二.表结构设计 model.py from django.db import models # Create your models here. class Menu(models.Mo ...

  2. 在微服务系统开发部署中使用Azure RBAC自定义角色

    Azure的官方文档介绍了如何创建用于Azure基于角色的访问控制的自定义角色(RBAC Role). 我们也可以根据同样的原理把RBAC细粒度资源管理运用于微服务产品的开发部署中.(https:// ...

  3. 22-1 rbac权限设计

    一 表结构设计 from django.db import models # Create your models here. from django.db import models # Creat ...

  4. Rancher 2.1平台搭建及使用

    一.概述 1.1.什么是Rancher Rancher是一套容器管理平台,它可以帮助组织在生产环境中轻松快捷的部署和管理容器. Rancher可以轻松地管理各种环境的Kubernetes,满足IT需求 ...

  5. Admin后台权限管理、三大认证

    目录 APIView的请求生命周期 三大认证规则 权限六表 自定义User表 详细配置演示 models.py setting.py admin.py 使用过程: 控制填写信息的字段 控制添加权限 控 ...

  6. 视图家族 & 路由组件

    目录 视图家族 & 路由组件 视图集与路由组件 基于 GenericAPIView 的十大接口 基于 generics 包下工具视图类的六大基础接口 视图集 路由组件:必须配合视图集使用 自定 ...

  7. Rancher概述

    概述 What’s Rancher? Rancher是一套容器管理平台,它可以帮助组织在生产环境中轻松快捷的部署和管理容器. Rancher可以轻松地管理各种环境的Kubernetes,满足IT需求并 ...

  8. k8认证机制

    参考下面博文 http://www.mamicode.com/info-detail-2270627.html 需要补充: k8s的的认证机制场景使用 客户端证书认证         采用双向证书进行 ...

  9. ranche2.0-CN

    遵循以下两步,快速运行rancher2.0 Step1:准备一台linux主机 准备一台64位Linux主机(推荐centos7.5+),至少4GB内存.安装Kubernetes支持的Docker-c ...

  10. [Django REST framework - RBAC-基于角色的访问控制、base64编码 、xadmin的使用]

    [Django REST framework - RBAC-基于角色的访问控制.base64编码 .xadmin的使用] RBAC-基于角色的访问控制 RBAC 是基于角色的访问控制(Role-Bas ...

随机推荐

  1. 基于python的RSA解密算法

    摘要 网上有很多关于RSA的解密脚本,欧拉函数.欧几里得函数什么的,对于一个大专生的我来说,一窍不通,至此经历了三天三夜,我翻阅了RSA的加密原理,以及其底层算法,专研出了一套我自己的解密算法,尚有不 ...

  2. 如何生成均匀随机数 C++

    #include <iostream> #include <fstream> #include <cstdlib> #include <ctime> u ...

  3. H5与APP的交互框架(WebViewJavascriptBridge)

    基本原理是: 把 OC 的方法注册到桥梁中,让 JS 去调用. 把 JS 的方法注册在桥梁中,让 OC 去调用.(注册自己,调用它人.) WebViewJavaScriptBridge 使用的基本步骤 ...

  4. 【YOLOv5】LabVIEW+YOLOv5快速实现实时物体识别(Object Detection)含源码

    前言 前面我们给大家介绍了基于LabVIEW+YOLOv3/YOLOv4的物体识别(对象检测),今天接着上次的内容再来看看YOLOv5.本次主要是和大家分享使用LabVIEW快速实现yolov5的物体 ...

  5. Xray

    Xray基础操作 代理设置 运行xray.exe xray.exe genca 运行后会生成ca.crt和cr.key 浏览器导入证书 设置代理7777端口 第一次启动 xray 之后,当前目录会生成 ...

  6. golang单元测试一(简单函数测试)

    0.1.索引 https://blog.waterflow.link/articles/1663688140724 1.简介 单元测试是测试代码.组件和模块的单元函数.单元测试的目的是清除代码中的错误 ...

  7. Android掌控WiFi不完全指南

    前言 如果想要对针对WiFi的攻击进行监测,就需要定期获取WiFi的运行状态,例如WiFi的SSID,WiFi强度,是否开放,加密方式等信息,在Android中通过WiFiManager来实现 WiF ...

  8. 9_Vue事件修饰符

    概述 首先需要理解下什么是事件修饰符 常用事件修饰符 案例1_阻止默认行为发生 我这里有一个a标签 这个标签呢我会给它配置一个点击事件 点击事件输出一句话,那么效果是这样的 代码 <body&g ...

  9. Codeforces Round #826 (Div. 3) A-E

    比赛链接 A 题解 知识点:模拟. 时间复杂度 \(O(n)\) 空间复杂度 \(O(n)\) 代码 #include <bits/stdc++.h> #define ll long lo ...

  10. Python基础之函数:6、异常相关和生成器对象、yield用法、生成器表达式

    目录 一.异常常见类型 1.类型错误 2.缩进错误 3.索引错误 4.语法错误 5.属性错误 6.key键错误 二.异常处理语法结构 1.基本语法结构 2.查看错误类型 3.针对不同类型所作措施 4. ...