前后端分离项目接口权限检查方案
基于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…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
