前后端分离项目接口权限检查方案
基于handleMethod写的一款分级式接口权限检查方案。
权限自动同步机制(启动更新,页面不提供增删改):
public class AuthorizationMappingGenerateExecutor implements EasyApplicationRunner {@Autowiredprivate AuthorizationMappingMapper authorizationMappingMapper;@Autowiredprivate RequestMappingHandlerMapping mapping;@Autowiredprivate SupportSecurityProperties securityProperties;// 启动时执行一次,随便用用作个排序。没安全问题int i = 0;@Overridepublic void doBusiness() {Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = mapping.getHandlerMethods();Collection<HandlerMethod> handlerMethods = handlerMethodMap.values();// 表中已存在的codeList<String> existsCodes = authorizationMappingMapper.selectProperties(Wrappers.lambdaQuery(), AuthorizationMapping::getCode);// 排重setSet<String> currentExistsCodes = new HashSet<>();// 配置的级别MappingLevel mappingLevel = securityProperties.getMappingLevel();// 添加的权限List<AuthorizationMapping> insertMappings = new ArrayList<>();// 需要更新的权限List<AuthorizationMapping> updateMappings = new ArrayList<>();for (HandlerMethod handlerMethod : handlerMethods) {Class<?> beanType = handlerMethod.getBeanType();String name = beanType.getName();// 只取本系统内的接口if (!name.contains("xxxx")) {continue;}AuthorizationMapping moduleMapping = buildModuleMapping(handlerMethod);int type = chooseType(existsCodes, currentExistsCodes, moduleMapping);if (type == 1) {insertMappings.add(moduleMapping);} else if (type == 2) {updateMappings.add(moduleMapping);}// 只开启了一级权限检查if (mappingLevel.is(MappingLevel.MODULE)) {continue;}AuthorizationMapping controllerMapping = buildControllerMapping(moduleMapping.getCode(), handlerMethod);int controllerType = chooseType(existsCodes, currentExistsCodes, controllerMapping);if (controllerType == 1) {insertMappings.add(controllerMapping);} else if (controllerType == 2) {updateMappings.add(controllerMapping);}// 只开启了二级权限检查if (mappingLevel.is(MappingLevel.CONTROLLER)) {continue;}// 方法级的权限检查AuthorizationMapping methodMapping = buildMethodMapping(controllerMapping.getCode(), handlerMethod);int methodType = chooseType(existsCodes, currentExistsCodes, methodMapping);if (methodType == 1) {insertMappings.add(methodMapping);} else if (methodType == 2) {updateMappings.add(methodMapping);}}// 批量插入authorizationMappingMapper.insertSplitBatch(insertMappings, 200);// 配置是否更新,一条条更新太慢,影响启动速度。if (securityProperties.getEnableMappingUpdate()) {updateMappings.forEach(authorizationMappingMapper::updateById);}// 清掉子级,不占表空间。开放的话重启构建即可。code不会变LambdaQueryWrapper<AuthorizationMapping> wrapper = Wrappers.lambdaQuery();if (mappingLevel.is(MappingLevel.MODULE)) {wrapper.in(AuthorizationMapping::getLevel, ZYListUtils.toList(MappingLevel.CONTROLLER.level(), MappingLevel.METHOD.level()));} else if (mappingLevel.is(MappingLevel.CONTROLLER)) {wrapper.eq(AuthorizationMapping::getLevel, MappingLevel.METHOD.level());}authorizationMappingMapper.delete(wrapper);}private int chooseType(List<String> existsCodes, Set<String> currentExistsCodes, AuthorizationMapping methodMapping) {String code = methodMapping.getCode();if (currentExistsCodes.contains(code)) {return 0;}// 去重currentExistsCodes.add(methodMapping.getCode());// 更新or插入if (!existsCodes.contains(code)) {return 1;} else {return 2;}}// 构建模块权限private AuthorizationMapping buildModuleMapping(HandlerMethod handlerMethod) {Class<?> beanType = handlerMethod.getBeanType();Package aPackage = beanType.getPackage();AuthorizationMapping packageMapping = buildBaseMapping(MappingLevel.MODULE);packageMapping.setMapping("");packageMapping.setParentCode(ZYTreeUtils.TREE_ROOT_ID);String moduleCode = PermissionMappingGenerator.generateModuleKey(handlerMethod);packageMapping.setCode(moduleCode);String packageName = aPackage.getName();packageMapping.setFileLocal(packageName);String packageChinaName = try2getPackageChinaName(handlerMethod);packageMapping.setName(null != packageChinaName ? packageChinaName : packageName);return packageMapping;}// 构建controller级权限private AuthorizationMapping buildControllerMapping(String parentCode, HandlerMethod handlerMethod) {AuthorizationMapping packageMapping = buildBaseMapping(MappingLevel.CONTROLLER);packageMapping.setMapping("");packageMapping.setParentCode(parentCode);String controllerCode = PermissionMappingGenerator.generateControllerKey(handlerMethod);packageMapping.setCode(controllerCode);Class<?> beanType = handlerMethod.getBeanType();packageMapping.setFileLocal(beanType.getName());Theme theme = beanType.getAnnotation(Theme.class);packageMapping.setName(null != theme ? theme.value() : beanType.getSimpleName());return packageMapping;}// 构建方法级权限private AuthorizationMapping buildMethodMapping(String parentCode, HandlerMethod handlerMethod) {String methodCode = PermissionMappingGenerator.generateMethodKey(handlerMethod);AuthorizationMapping packageMapping = buildBaseMapping(MappingLevel.METHOD);packageMapping.setCode(methodCode);packageMapping.setParentCode(parentCode);Class<?> beanType = handlerMethod.getBeanType();Method method = handlerMethod.getMethod();packageMapping.setFileLocal(beanType.getName() + "." + method.getName());packageMapping.setMapping(spellMapping(method, beanType));packageMapping.setName(getMappingName(handlerMethod));return packageMapping;}private AuthorizationMapping buildBaseMapping(MappingLevel level) {AuthorizationMapping packageMapping = new AuthorizationMapping();packageMapping.setCreateDate(System.currentTimeMillis());packageMapping.setIsMiniAuth(0);packageMapping.setSort(++i);packageMapping.setLevel(level.level());packageMapping.setRemarks(level.getMappingName());return packageMapping;}private String getMappingName(HandlerMethod handlerMethod) {SystemLog systemLog = handlerMethod.getMethodAnnotation(SystemLog.class);if (null != systemLog) {return systemLog.value();}return handlerMethod.getMethod().getName();}private String spellMapping(Method method, Class<?> beanType) {StringBuilder path = new StringBuilder();RequestMapping parentRequestMapping = beanType.getAnnotation(RequestMapping.class);if (null != parentRequestMapping) {String[] value = parentRequestMapping.value();if (value.length > 0) {path.append(value[0]);}}RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);GetMapping methodGetRequestMapping = method.getAnnotation(GetMapping.class);PostMapping methodPostRequestMapping = method.getAnnotation(PostMapping.class);if (null != methodRequestMapping) {String[] value = methodRequestMapping.value();if (value.length > 0) {path.append(value[0]);}} else if (null != methodGetRequestMapping) {String[] value = methodGetRequestMapping.value();if (value.length > 0) {path.append(value[0]);}} else if (null != methodPostRequestMapping) {String[] value = methodPostRequestMapping.value();if (value.length > 0) {path.append(value[0]);}}return path.toString();}private String try2getPackageChinaName(HandlerMethod handlerMethod) {Class<?> beanType = handlerMethod.getBeanType();Package aPackage = beanType.getPackage();Theme packageTheme = aPackage.getAnnotation(Theme.class);if (null != packageTheme) {return packageTheme.value();}return null;}
}
权限检查拦截器
public class AuthorizationMappingInterceptor implements EasyMvcInterceptor, InitializingBean {private final static Set<String> NO_NEED_CHECK_CODES = new HashSet<>();private final static Set<String> CHECK_CODES = new HashSet<>();@Autowiredprivate AuthorizationMappingMapper authorizationMappingMapper;@Autowiredprivate SupportSecurityProperties securityProperties;@Overridepublic boolean doBusiness(HandlerMethod handlerMethod, HttpServletRequest request, HttpServletResponse response) {// 开放接口if (ZYRequestUtils.isDirectApi()) {return true;}// 超管if (UserHelper.isSuperAdmin()) {return true;}// 需要跳过的权限if (isSkipMappingCheck(handlerMethod)) {return true;}// 查询需要的权限MappingLevel mappingLevel = securityProperties.getMappingLevel();String permissionCode = PermissionMappingGenerator.generateByLevel(handlerMethod, mappingLevel);// 缓存在存在,直接通过检查if (NO_NEED_CHECK_CODES.contains(permissionCode)) {return true;}// 该接口没有做权限限制,直接放行if (!CHECK_CODES.contains(permissionCode)) {NO_NEED_CHECK_CODES.add(permissionCode);return true;}// 用户权限信息LoginUser loginAreaUser = UserHelper.getLoginAreaUser();Set<String> mappingCodes = loginAreaUser.getMappingCodes();if (!hasPermission(permissionCode, mappingCodes)) {throw new PermissionException("您没有访问接口的权限");}return true;}private boolean hasPermission(String permissionCode, Set<String> mappingCodes) {return null != mappingCodes && mappingCodes.contains(permissionCode);}@Overridepublic void afterPropertiesSet() {// 根据权限级别查询需要检查的HandleMethod或controllerMappingLevel mappingLevel = securityProperties.getMappingLevel();LambdaQueryWrapper<AuthorizationMapping> wrapper = Wrappers.lambdaQuery();wrapper.eq(AuthorizationMapping::getLevel, mappingLevel.level());wrapper.eq(AuthorizationMapping::getIsMiniAuth, NO);wrapper.select(AuthorizationMapping::getCode);List<String> authorizationMappings = authorizationMappingMapper.selectProperties(wrapper, AuthorizationMapping::getCode);CHECK_CODES.addAll(authorizationMappings);}private boolean isSkipMappingCheck(HandlerMethod handlerMethod) {Class<?> beanType = handlerMethod.getBeanType();Package aPackage = beanType.getPackage();SkipMappingCheck packageSkip = aPackage.getAnnotation(SkipMappingCheck.class);if (null != packageSkip) {return true;}SkipMappingCheck classSkip = beanType.getAnnotation(SkipMappingCheck.class);if (null != classSkip) {return true;}Method method = handlerMethod.getMethod();SkipMappingCheck methodSkip = method.getAnnotation(SkipMappingCheck.class);return null != methodSkip;}
}
public enum MappingLevel implements Matcher {MODULE("模块"),CONTROLLER("控制类"),METHOD("接口方法");private String mappingName;public String level() {return this.name().toLowerCase();}@Overridepublic Object statusCode() {return level();}
}
code生成器
public class PermissionMappingGenerator {private final static Map<String, String> PACKAGE_KEY_CACHE = new HashMap<>();private final static Map<String, String> CONTROLLER_KEY_CACHE = new HashMap<>();private final static Map<String, String> METHOD_KEY_CACHE = new HashMap<>();public static String generateByLevel(HandlerMethod handlerMethod, MappingLevel mappingLevel) {switch (mappingLevel) {case METHOD:return generateMethodKey(handlerMethod);case CONTROLLER:return generateControllerKey(handlerMethod);case MODULE:return generateModuleKey(handlerMethod);default:throw new LocalException("不能确定的权限级别");}}public static String generateModuleKey(HandlerMethod handlerMethod) {Class<?> beanType = handlerMethod.getBeanType();Package aPackage = beanType.getPackage();String packageName = aPackage.getName();String cryptPackageCode = PACKAGE_KEY_CACHE.get(packageName);if (ZYStrUtils.isNull(cryptPackageCode)) {cryptPackageCode = ZYCryptUtils.encryptMD5(packageName).toLowerCase();PACKAGE_KEY_CACHE.put(packageName, cryptPackageCode);}return cryptPackageCode;}public static String generateControllerKey(HandlerMethod handlerMethod) {Class<?> beanType = handlerMethod.getBeanType();String className = beanType.getName();String cryptControllerCode = CONTROLLER_KEY_CACHE.get(className);if (ZYStrUtils.isNull(cryptControllerCode)) {cryptControllerCode = ZYCryptUtils.encryptMD5(className).toLowerCase();CONTROLLER_KEY_CACHE.put(className, cryptControllerCode);}return cryptControllerCode;}public static String generateMethodKey(HandlerMethod handlerMethod) {Class<?> beanType = handlerMethod.getBeanType();Method method = handlerMethod.getMethod();List<String> keySign = new ArrayList<>();keySign.add(beanType.getSimpleName());keySign.add(method.getName());Parameter[] parameters = method.getParameters();for (Parameter parameter : parameters) {keySign.add(parameter.getName());}String key = StrUtils.join(keySign, "#");String cryptKey = METHOD_KEY_CACHE.get(key);if (null == cryptKey) {cryptKey = CryptUtils.encryptMD5(key).toLowerCase();METHOD_KEY_CACHE.put(key, cryptKey);}return cryptKey;}
}
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipMappingCheck {
}
与传统的路径匹配接口权限检查相比,本方案采用HandleMethod信息与角角直接绑定。多了一个权限颗粒度划分。对减少接品权限数量及减少人工分配量有明显的优势。自动的权限生成工具省去了维护权限编号协删改查的人工操作。减去了大量人工。实际使用中,默认使用module级,维护量很少。较严情况下,使用controller级。严格安全等保要求的情况下。使用mothod级。方案选择很灵活。重启服务即可任意切换。可以使用SkipMappingCheck 注解对整个模块,整个controller进行跳过处理。
相关文章:
前后端分离项目接口权限检查方案
基于handleMethod写的一款分级式接口权限检查方案。 权限自动同步机制(启动更新,页面不提供增删改): public class AuthorizationMappingGenerateExecutor implements EasyApplicationRunner {Autowiredprivate AuthorizationMap…...
步入React正殿 - 事件处理
目录 扩展学习资料 React事件和DOM事件 和传统DOM事件处理异同 this关键字的处理 this关键字 在JSX中使用bind方法 在构造函数中使用bind方法 使用箭头函数【推荐】 向事件处理程序传递参数【不跨组件】 向父组件传递参数 /src/App.js /src/components/listItem.jsx…...
NLP(六十四)使用FastChat计算LLaMA-2模型的token长度
LLaMA-2模型部署 在文章NLP(五十九)使用FastChat部署百川大模型中,笔者介绍了FastChat框架,以及如何使用FastChat来部署百川模型。 本文将会部署LLaMA-2 70B模型,使得其兼容OpenAI的调用风格。部署的Dockerfile文件…...
个保新标 | 《信息安全技术 敏感个人信息处理安全要求》(征求意见稿)发布
8 月 9 日,全国信息安全标准化技术委员会公开发布关于国家标准《信息安全技术 敏感个人信息处理安全要求》(征求意见稿)(以下简称《标准》)的通知,面向社会广泛征求意见。 《标准》的制定背景是为支撑《个人…...
【uniapp 返回顶部】
返回顶部 参数说明示例官方链接 uni.pageScrollTo(OBJECT) 将页面滚动到目标位置。 参数说明 参数名类型必填说明scrollTopNumber否滚动到页面的目标位置(单位px)selectorString否选择器,App、H5、微信小程序2.7.3 、支付宝小程序1.20.0支持…...
无代码集成励销云CRM连接更多应用
场景描述: 基于励销云的开放API,实现无代码集成连接励销云与其它应用。通过Aboter可轻松搭建业务自动化流程,实现多个应用之间的数据连接。 接口能力: 用户模块业务模块拜访签到模块公海客户模块联系人模块合同模块客户模块任务…...
QT自带PDF库的使用
QT自带PDF库可以方便的打开PDF文件,并将文件解析为QImage,相比网上提供的开源库,QT自带PDF库使用更方便,也更加可靠,然而,QT自带PDF库的使用却不同于其他通用库的使用,具备一定的技巧。 1. 安装…...
SQL | 排序检索的数据
3-排序检索的数据 使用order by语句排序检索到的数据。 3.1-排序数据 使用SQL语句返回一个数据表的列。 select prod_id from products; --------------------- | prod_name | --------------------- | 8 inch teddy bear | | 12 inch teddy bear | | 18 inch teddy bear |…...
8. yaml文件管理
文章目录 yaml文件管理编写yaml配置文件获取配置模板方法一方法二方法三方法四 yaml文件管理 Kubernetes 支持 YAML 和 JSON 格式管理资源对象 JSON 格式:主要用于 api 接口之间消息的传递YAML 格式:用于配置和管理,YAML 是一种简洁的非标记性…...
Cobbler自定义yum源
再次了解下Cobbler的目录结构: 在/var/www/cobbler/ks_mirror目录下存放的是所有的镜像。 存放的是仓库镜像: 在/var/lib/cobbler/kickstarts目录下是存放的所有的kickstarts文件。 再有就是/etc/cobbler这个目录: [rootvm1 loaders]# cd /…...
《算法竞赛·快冲300题》每日一题:“特殊数字”
《算法竞赛快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。 所有题目放在自建的OJ New Online Judge。 用C/C、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。 文章目录 题目描述题解C代码Java代码Python代码 “ 特…...
在R中比较两个矩阵是否相等
目录 方法一:使用all.equal()比较两个R对象是否近似相等 方法二:使用identical比较两个R对象是否精确相等。 方法一:使用all.equal()比较两个R对象是否近似相等 使用函数:all.equal(x,y) 比较两个R对象x和y是否近似相等 > M1…...
商城-学习整理-基础-商品服务API-属性分组(七)
目录 一、创建系统菜单二、开发商品系统-平台属性-属性分组1、将三级分类功能抽取出来2、编写后端代码3、属性分组新增功能4、属性分组修改回显功能 三、商品系统-平台属性-规则参数1、列表展示页面2、新增规格参数页面 四、商品系统-平台属性-销售属性1、列表展示页面2、新增或…...
什么是响应式设计?列举几种实现响应式设计的方法。
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是响应式设计?⭐ 实现响应式设计的方法⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏…...
Java类和对象(一文读懂)
文章目录 类、对象是什么?创建类构造器 创建对象 类、对象是什么? 类:类是一个模板,它描述一类对象的行为和状态。类可以看成是创建 Java 对象的模板。 对象:对象是类的一个实例(对象不是找个女朋友&#x…...
用友移动管理系统 任意文件上传漏洞复现(HW0day)
0x01 产品简介 用友移动系统管理是用友公司推出的一款移动办公解决方案,旨在帮助企业实现移动办公、提高管理效率和员工工作灵活性。它提供了一系列功能和工具,方便用户在移动设备上管理和处理企业的系统和业务。 0x02 漏洞概述 用友移动管理系统 uploa…...
启动springboot,出现Unable to start embedded Tomcat
报错信息 org.apache.catalina.core.ContainerBase : A child container failed during startjava.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbedd…...
加密和安全
加密和安全 一.安全机制 安全攻击的几种典型方式: STRIDE Spoofing 假冒 Tampering 篡改 Repudiation 否认 Information Disclosure 信息泄漏 Denial of Service 拒绝服务 Elevation of Privilege 提升…...
Maven基础总结
前言 Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。 基本要求掌握 配置Maven环境直接查。 得会在IDEA创建Maven的java项目吧、会创建Maven的web项目吧、会创建多模块项目吧。 得会配置插件pligin、依赖dependency吧 一、Maven四大特性 1、…...
Java 编程实战:如何用 Java 编写一个简单而强大的 Tomcat
学习完了JavaWeb,为了深入了解tomcat,打算手撕tomcat搭建自己的tomcat,希望对来访小伙伴也有帮助 引言 Tomcat 是一个开源的 Web 服务器和 Servlet 容器,它可以提供动态 Web 内容的处理和交互功能。Tomcat 是用 Java 语言编写的&a…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...
Visual Studio Code 扩展
Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后,命令 changeCase.commands 可预览转换效果 EmmyLua…...
