前后端分离项目接口权限检查方案
基于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…...
Whoami开发者架构解析:深入理解模块化隐私保护系统设计
Whoami开发者架构解析:深入理解模块化隐私保护系统设计 【免费下载链接】whoami-project Whoami provides enhanced privacy, anonymity for Debian and Arch based linux distributions 项目地址: https://gitcode.com/gh_mirrors/wh/whoami-project Whoami…...
别再傻傻用Bicubic插值了!用PyTorch的PixelShuffle实现ESPCN超分,速度提升10倍
用PyTorch的PixelShuffle实现ESPCN超分:10倍速高清图像重构实战指南 当你在处理监控视频、医学影像或老照片修复时,是否曾被传统插值方法生成的模糊效果困扰?Bicubic插值虽然简单直接,但其边缘锯齿和细节丢失的问题始终无法回避。…...
【Docker 27网络策略终极指南】:27项生产级策略配置、隔离与审计实战(附策略合规检查清单)
第一章:Docker 27网络策略演进与核心架构解析Docker 27(代号“Nexus”)标志着容器网络模型的一次范式跃迁,其网络策略体系不再仅围绕桥接、主机与覆盖网络的静态划分,而是以零信任原则为基底,将策略执行点下…...
从“翻车”到“神图”:我的Stable Diffusion提示词避坑与调优笔记(附Lora使用心得)
从“翻车”到“神图”:我的Stable Diffusion提示词避坑与调优笔记 深夜三点,屏幕上的AI少女长着七根手指,背景里漂浮着半截手臂——这已经是我今晚第十七张“恐怖片剧照”了。作为从MidJourney转战Stable Diffusion的老玩家,我经历…...
iOS设备调试支持文件自动化管理解决方案:企业级开发效率提升架构
iOS设备调试支持文件自动化管理解决方案:企业级开发效率提升架构 【免费下载链接】iOSDeviceSupport All versions of iOS Device Support 项目地址: https://gitcode.com/gh_mirrors/ios/iOSDeviceSupport iOSDeviceSupport项目为解决iOS开发中Xcode与设备系…...
Charles手机App抓包完整配置指南
文档概述 本文档旨在提供一套完整、可操作的Charles配置流程,帮助开发者和测试人员在iOS设备上实现对手机App的HTTPS请求抓包,获取完整的请求URL(含参数)。 适用场景:App接口调试、网络请求分析、API逆向分析 目录 …...
技术迭代与未来趋势—晶体谐振器与振荡器发展与创新
晶体谐振器与振荡器自 20 世纪初发明以来,历经百年发展,已从最初的低频、低精度、大体积器件,迭代为高频、超高精度、微型化、低功耗的核心电子元件,支撑着通信、导航、工业控制、消费电子等产业的飞速发展。 一、传统石英晶振…...
2026年污水处理用聚丙烯酰胺:权威定义与横向数据解析
2026年,当我们深入探讨污水处理,聚丙烯酰胺作为絮凝剂的选择已不再是简单的品牌采购。笔者发现,行业焦点正从模糊的厂家宣传转向清晰的参数对标与性能评估。这种转变背后,是用户对处理效率、成本控制及长期稳定性的更高追求。因此…...
告别推送混乱:用Firebase Cloud Messaging (FCM) 统一管理Android/iOS/Web推送的完整实践
告别推送混乱:用Firebase Cloud Messaging (FCM) 统一管理Android/iOS/Web推送的完整实践 在移动互联网时代,推送通知已成为用户留存和活跃度提升的关键手段。然而,当业务扩展到多个平台时,开发者往往面临一个棘手问题࿱…...
【20年DevOps老兵亲授】Docker跨架构调试的3层抽象模型:底层指令集差异、中间层运行时适配、上层应用行为漂移
第一章:Docker跨架构调试的3层抽象模型总览Docker跨架构调试并非简单地运行不同CPU指令集的镜像,而是在运行时、构建时与平台描述三个正交维度上协同工作的系统性工程。这三层抽象分别对应**执行层(Runtime Abstraction)**、**构建…...
