Spring Boot AOP实现动态数据脱敏

依赖&配置


<!--  Spring Boot AOP起步依赖  -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
/*** @Author: 说淑人* @Date: 2025/1/18 23:03* @Description: 切面配置*/
@Configuration
// ---- 该注解用于开启AOP功能。
@EnableAspectJAutoProxy
public class AspectConfig {
}

数据脱敏


 注解&修饰

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @Author: 说淑人* @Date: 2023-11-24* @Description: 授权业务脱敏AO类*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OauthBizMask {// ---- 该注解用来修饰在控制器方法上以标注该方法的返回数据需要数据脱敏,其核心作用是// 为AOP提供切入点。// ---- 注意!根据切入方式的不同,该注解并不是必须的,下文在切入代码中提供了无需当前// 注解的切入方式。但我们并不推荐那么做,因为那会导致所有的接口都必须经历数据脱敏过程,// 即使我们并不想执行该操作。}
    /*** 获取宇宙** @param customerId 客户ID* @return 结果BO(客户业务宇宙VO回应)*/@OauthBizMask@ApiOperation("获取宇宙")@GetMapping(value = "get/universe")public ResultBox<CustomerBizUniverseResponse> getUniverse(@ApiParam(value = "客户ID", required = true) @RequestParam(value = "customerId") Long customerId) {return ResultBox.result(customerBizDispatcher.getUniverse(customerId));}/*** 查询宇宙集** @param customerBizQueryRequest 客户业务查询VO请求* @return 结果BO(查询BO(客户业务宇宙VO回应集))*/@OauthBizMask@ApiOperation("查询宇宙集")@GetMapping(value = "query/universes")public ResultBox<QueryBox<CustomerBizUniverseResponse>> queryUniverses(@Valid @ModelAttribute CustomerBizQueryRequest customerBizQueryRequest) {return ResultBox.result(customerBizDispatcher.queryUniverses(customerBizQueryRequest));}
import com.ssr.world.biz.manage.model.eo.oauth.OauthBizMaskRuleEnum;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @Author: 说淑人* @Date: 2023-11-24* @Description: 授权业务脱敏规则AO类*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface OauthBizMaskRule {// ---- 该注解只对字符串类型的字段有效!// ---- 该注解对嵌套超过5层的对象字段无效!/*** 权限 -- 在拥有指定权限的情况下可以避免数据脱敏,该功能可以视个人情况保留/删除。*/String authority() default "";/*** 规则 -- 具体数据脱敏规则*/OauthBizMaskRuleEnum rule();}
    /*** 账号*/@ApiModelProperty(value = "账号", required = true)// ---- 设置拥有“root/customer/nomask”权限的可以免数据脱敏,数据脱敏规则为账号。@OauthBizMaskRule(authority = "root/customer/nomask", rule = OauthBizMaskRuleEnum.ACCOUNT)private String account;/*** 手机号码*/@ApiModelProperty(value = "手机号码")@OauthBizMaskRule(authority = "root/customer/nomask", rule = OauthBizMaskRuleEnum.PHONE_NUMBER)private String phoneNumber;/*** 名称*/@ApiModelProperty(value = "名称")@OauthBizMaskRule(authority = "root/customer/nomask", rule = OauthBizMaskRuleEnum.NAME)private String name;

 枚举&工具

import com.ssr.world.tool.pedestal.util.string.StringUtil;import java.util.function.Function;/*** @Author: 说淑人* @Date: 2022/1/12 下午8:18* @Description: 授权业务脱敏规则EO类*/
public enum OauthBizMaskRuleEnum {/*** 授权业务脱敏规则枚举集*/ACCOUNT(s -> s.replaceAll("(\\S{5})\\S{10}(\\S*)", "$1**********$2")),PHONE_NUMBER(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),NAME(s -> s.charAt(0) + StringUtil.repeat('*', s.length() - 1)),;public final Function<String, String> masker;OauthBizMaskRuleEnum(Function<String, String> masker) {this.masker = masker;}}

 切面

    在对数据对象的字段进行反射遍历时,我们还需要考虑父类对象&嵌套对象的字段遍历。由于对象嵌套的层级可能非常深且还可能有相互嵌套的情况,因此在遍历&迭代时必须要限制层级以避免长遍历&死循环,以及还要尽可能避免不必要的遍历,例如原生/框架的类,从而尽可能的提升性能。关于这些问题在下文的代码中都有提及且处理,请仔细查看代码注释。
    下述代码对列表结构也做了处理,基本上可以直接拿来用。

import com.ssr.world.biz.manage.client.oauth.OauthBizStaffClient;
import com.ssr.world.biz.manage.model.ao.oauth.OauthBizMaskRule;
import com.ssr.world.biz.manage.model.eo.oauth.OauthBizUserType;
import com.ssr.world.biz.manage.model.eo.oauth.OauthBizUserTypeEnum;
import com.ssr.world.biz.manage.model.vo.response.oauth.OauthBizStaffAuthorityResponse;
import com.ssr.world.biz.manage.tool.util.oauth.OauthBizUtil;
import com.ssr.world.tool.pedestal.model.bo.result.ResultBox;
import com.ssr.world.tool.pedestal.util.string.StringUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Objects;/*** @Author: 说淑人* @Date: 2023-11-24* @Description: 授权业务脱敏AO类*/
@Aspect
@Component
public class OauthBizMaskAspect {@Autowiredprivate OauthBizStaffClient oauthBizStaffClient;//    /**
//     * 切入点
//     */
//    @Pointcut("execution(* com.ssr.world..controller..*(..))")
//    public void pointcut() {
//        // ---- 以工程路径下所有控制器方法为切入点。这种方式比较简便,因为无需额外注解进
//        // 行修饰。但对性能的损耗很大,因为所有的控制器方法都会被切入。
//    }/*** 切入点*/@Pointcut("@annotation(com.ssr.world.biz.manage.model.ao.oauth.OauthBizMask)")public void pointcut() {// ---- 以修饰了@OauthBizMask注解的方法为切入点。}/*** 环绕** @param proceedingJoinPoint 行动参与点* @return 值* @throws Throwable 可抛出*/@Around("pointcut()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {// ---- 获取方法的执行结果。Object object = proceedingJoinPoint.proceed();// ---- 判断是否需要对当前请求的返回数据进行脱敏操作,如果未携带令牌/用户为客户/用// 户为超级管理员,则直接返回而不执行的数据脱敏操作(该逻辑视个人情况保留/删除)。if (StringUtil.isBlank(OauthBizUtil.getAuthorization()) ||OauthBizUserTypeEnum.CUSTOMER.equals(OauthBizUtil.getUserType())|| OauthBizUserType.SHUO_SHU_REN.equals(OauthBizUtil.getAccount())) {return object;}// ---- 将控制器方法的返回值强制转化为ResultBox对象以获取内部的封装数据。(该逻辑// 视个人情况保留/删除)。ResultBox<?> resultBox = (ResultBox<?>) object;Object data = resultBox.getData();// ---- 迭代数据对象包括父类在内的所有字段,判断其是否标注了@OauthBizMask注解,是// 则对内部数据进行脱敏。if (Objects.nonNull(data)) {recursiveField(1, data.getClass(), data, // ---- 获取员工权限作为数据脱敏的执行依据。oauthBizStaffClient.getStaffAuthorityMapCache(OauthBizUtil.getAccount()));}return object;}/*** 迭代字段** @param tier         层级* @param clazz        类对象* @param data         数据* @param authorityMap 权限映射* @throws IllegalAccessException 非法访问异常*/private void recursiveField(int tier, Class<?> clazz, Object data, Map<String, OauthBizStaffAuthorityResponse> authorityMap) throws IllegalAccessException {// ---- 如果嵌套层级超过5级则直接返回。层级限制是为了避免深度嵌套导致的性能问题,// 以及相互嵌套导致的死循环问题。if (tier > 5) {return;}// ---- 判断数据对象是否是集(及子类)类型 ,是则迭代内部所有对象的所有字段。注意!// 迭代集中的对象不需要增加层级。if (data instanceof Collection) {for (Object collectionData : (Collection<?>) data) {if (Objects.nonNull(collectionData)) {recursiveField(tier, collectionData.getClass(), collectionData, authorityMap);}}return;}// ---- 如果数据对象不是集(及子类)类型,判断其是否是自开发的类型,否则直接返回。// 该判断可以帮助我们免去对原生/框架类的字段迭代,因为我们只能对自开发的类字段修// 饰@OauthBizMaskRule注解,从而有效提升性能。// ---- 当然,在极少数情况下,我们可能使用除"集类"以外的某些原生/框架类对象来承载自// 开发类对象。这种情况下当前逻辑会导致数据无法脱敏,因此后续可能需要和"集类"一样// 对这些类进行特殊处理。Package pack = clazz.getPackage();if (Objects.isNull(pack) || !pack.getName().startsWith("个人工程路径前缀,例如com.xxx.xxx")) {return;}// ---- 迭代当前class对象的所有直属字段,即非父类字段。Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {// ---- 判断当前字段值是否为null,是则直接略过。field.setAccessible(true);Object fieldData = field.get(data);if (Objects.isNull(fieldData)) {continue;}// ---- 判断当前字段是否是字符串类型,否则对该嵌套对象进行字段迭代,随后返回。if (!(fieldData instanceof String)) {recursiveField(tier + 1, fieldData.getClass(), fieldData, authorityMap);continue;}// ---- 判断字符串字段是否直接修饰了@OauthBizMaskRule注解,否则直接略过。OauthBizMaskRule oauthBizMaskRule = field.getDeclaredAnnotation(OauthBizMaskRule.class);if (Objects.isNull(oauthBizMaskRule)) {continue;}// ---- 如果字符串字段修饰了@OauthBizMaskRule注解,判断当前员工是否拥有指定权// 限且未曾过期,否则直接略过(该逻辑视个人情况保留/删除)。String authorityCode = oauthBizMaskRule.authority();OauthBizStaffAuthorityResponse authority;if (StringUtil.isNotBlank(authorityCode) &&Objects.nonNull(authority = authorityMap.get(authorityCode)) &&new Date().before(authority.getExpireDatetime())) {continue;}// ---- 进行数据脱敏操作。System.out.println("字段名:" + field.getName());System.out.println("字段值:" + fieldData);String value = (String) fieldData;field.set(data, oauthBizMaskRule.rule().masker.apply(value));}// ---- 获取父类,如果父类存在,继续迭代。注意!父类不属于嵌套。Class<?> parentClass = clazz.getSuperclass();if (Objects.nonNull(parentClass)) {recursiveField(tier, parentClass, data, authorityMap);}}}

 效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67828.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SparkSQL函数综合实践

文章目录 1. 实战概述2. 实战步骤2.1 创建项目2.2 添加依赖2.3 设置源目录2.4 创建日志属性文件2.5 创建hive配置文件2.6 创建数据分析对象2.6.1 导入相关类2.6.2 创建获取Spark会话方法2.6.3 创建表方法2.6.4 准备数据文件2.6.5 创建加载数据方法2.6.6 创建薪水排行榜方法2.6.…

Flutter中PlatformView在鸿蒙中的使用

Flutter中PlatformView在鸿蒙中的使用 概述在Flutter中的处理鸿蒙端创建内嵌的鸿蒙视图创建PlatformView创建PlatformViewFactory创建plugin&#xff0c;注册platformview注册插件 概述 集成平台视图&#xff08;后称为平台视图&#xff09;允许将原生视图嵌入到 Flutter 应用…

逆波兰表达式求值(力扣150)

这道题也是一道经典的栈应用题。为什么这样说呢&#xff1f;我们可以发现&#xff0c;当我们遍历到运算符号的时候&#xff0c;我们就需要操控这个运算符之前的两个相邻的数。这里相邻数不仅仅指最初数组里相邻的数&#xff0c;在进行了运算之后&#xff0c;得到的结果与后面的…

ElasticSearch DSL查询之排序和分页

一、排序功能 1. 默认排序 在 Elasticsearch 中&#xff0c;默认情况下&#xff0c;查询结果是根据 相关度 评分&#xff08;score&#xff09;进行排序的。我们之前已经了解过&#xff0c;相关度评分是通过 Elasticsearch 根据查询条件与文档内容的匹配程度自动计算得出的。…

《汽车维修技师》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答&#xff1a; 问&#xff1a;《汽车维修技师》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《汽车维修技师》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;北方联合出版传媒&#xff08;…

产品经理面试题总结2025【其一】

一、产品理解与定位 1、你如何理解产品经理这个角色&#xff1f; 作为一名互联网产品经理&#xff0c;我理解这个角色的核心在于成为产品愿景的制定者和执行的推动者。具体来说&#xff0c;产品经理是连接市场、用户和技术团队之间的桥梁&#xff0c;负责理解市场需求、用户痛…

数学基础 --线性代数之理解矩阵乘法

理解矩阵乘法的解析 矩阵乘法&#xff08;Matrix Multiplication&#xff09;是线性代数中的核心操作之一。在数学、几何和工程实际中&#xff0c;它不仅是一种代数运算规则&#xff0c;还承载着丰富的几何和映射意义。本文将从多个角度深入解析矩阵乘法&#xff0c;帮助读者理…

C#高级:用Csharp操作鼠标和键盘

一、winform 1.实时获取鼠标位置 public Form1() {InitializeComponent();InitialTime(); }private void InitialTime() {// 初始化 Timer 控件var timer new System.Windows.Forms.Timer();timer.Interval 100; // 设置为 100 毫秒&#xff0c;即每 0.1 秒更新一次timer.…

【中国电信-安全大脑产品介绍】

座右铭&#xff1a;人生的道路上无论如何选择总会有遗憾的&#xff01; 文章目录 前言一、安全大脑介绍二、中国电信-安全大脑产品分类1.防护版2.审计版 三、安全大脑-部署方案总结 前言 安全占据我们日常生活中首要地位&#xff0c;它时时刻刻提醒着我们出入平安。当然网络安…

数据库:MongoDB命令行帮助解释

MongoDB命令&#xff1a; mongodmongosmongoperrormongoexportmongofilesmongoimportmongorestoreMongostat MongoDB包中的核心组件包括: mongod 是 MongoDB 的核心服务器进程&#xff0c;负责数据存储和管理。mongos 是分片集群的路由进程&#xff0c;负责将请求路由到正确…

洛谷P8837

[传智杯 #3 决赛] 商店 - 洛谷 代码区&#xff1a; #include<stdio.h> #include<stdlib.h> int cmp(const void*a,const void *b){return *(int*)b-*(int*)a; } int main(){int n,m;scanf("%d%d",&n,&m);int w[n];int c[m];for(int i0;i<n;…

多线程杂谈:惊群现象、CAS、安全的单例

引言 本文是一篇杂谈&#xff0c;帮助大家了解多线程可能会出现的面试题。 目录 引言 惊群现象 结合条件变量 CAS原子操作&#xff08;cmp & swap&#xff09; 线程控制&#xff1a;两个线程交替打印奇偶数 智能指针线程安全 单例模式线程安全 最简单的单例&…

三分钟简单了解HTML的一些语句

1.图片建议建立一个文件夹如下图所示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"keywords"><title>魔神羽落</title><style>.testone{background-color: #ff53e…

HCIP笔记4--OSPF域内路由计算

1. 域内LSA 1.1 一类LSA 一类LSA: 路由器直连状态&#xff0c;Router LSA。 串口需要两端配置好IP,才会产生一类LSA; 以太网口只需要一端配置了IP就会直接产生一类LSA。 LSA通用头部 Type: Router 直连路由LS id: 12.1.1.1 路由器router idAdv rtr: 12.1.1.1 通告的路由器&…

k8s基础(7)—Kubernetes-Secret

Secret概述&#xff1a; Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。 这样的信息可能会被放在 Pod 规约中或者镜像中。 使用 Secret 意味着你不需要在应用程序代码中包含机密数据。 由于创建 Secret 可以独立于使用它们的 Pod&#xff0c; 因此在创建、查看和…

【leetcode100】验证二叉搜索树

1、题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1&…

谈谈MySQL中的索引和事务

目录 1. 索引 1.1 索引介绍 1.2 缺陷 1.3 使用 1.3.1 查看索引 1.3.2 创建索引 1.3.3 删除索引 2. 索引底层的数据结构 2.1 B树 3. 事务 3.1 为什么使用事务 3.2 事务的使用 3.3 事务的基本特性 1. 索引 1.1 索引介绍 索引相当于一本书的目录(index), 在一…

2024:CSDN上的收获与蜕变——我的技术成长之旅

2024&#xff1a;CSDN上的收获与蜕变——我的技术成长之旅 前言数据见证&#xff1a;2024年的创作足迹荣誉殿堂&#xff1a;各平台的创作证书与认可社区共建&#xff1a;行业贡献与互动交流展望未来&#xff1a;2025年的目标与计划结语 前言 博主简介&#xff1a;江湖有缘 在技…

博客之星2024年度-技术总结:技术探险家小板的一年的征程

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 技术探险家的新一年征程 2.0 数据库管理与优化&#xff1a;MySQL 的魔法森林 2.1 穿越基础概念的迷雾 2.2 实践应用&#xff1a;成为森林的主人 2.3 性能调优&…

【vim】vim怎样直接跳转到某行?

vim怎样直接跳转到某行&#xff1f; 一、使用行号跳转二、使用相对行号跳转三、使用标记跳转 在Vim中直接跳转到某行可以使用以下几种方法&#xff1a; 一、使用行号跳转 在命令模式下&#xff0c;输入冒号:&#xff0c;然后输入你想要跳转的行号&#xff0c;最后按回车键。例…