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 {

    @Autowired
    private 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);
        }
    }

}

 效果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UE虚幻引擎No Google Play Store Key:No OBB found报错如何处理?

问题描述&#xff1a; UE成功打包APK并安装过后&#xff0c;启动应用时提示&#xff1a; No Google Play Store KeyNo OBB found and no store key to try to download. Please setone up in Android Project SettingsUE配置默认在打包APK时会附加生成一个OBB文件&#xff0c;…

Github 2025-01-20 开源项目周报 Top15

根据Github Trendings的统计,本周(2025-01-20统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10Rust项目2TypeScript项目1C++项目1Jupyter Notebook项目1Go项目1Tabby: 自托管的AI编码助手 创建周期:310 天开发语言:Rust协议类…

给工作流穿上漂亮的衣服,创建用户交互界面并调用工作流,可定制的工作流,奶奶都能看明白的扣子智能体免费系列教程(10)

创建工作流看这个 搭建小红书梗图、歪理生成器工作流搭建 效果 欢迎来到滔滔讲AI。本教程为免费系列教程&#xff0c;感谢关注&#xff0c;以防找不到。 一、新建应用 方式一&#xff0c;点击左侧导航的加号 方式二、在工作空间面板选择右上角的创建按钮 选择创建应用 二、…

Python网络自动化运维---用户交互模块

文章目录 目录 文章目录 前言 实验环境准备 一.input函数 代码分段解析 二.getpass模块 前言 在前面的SSH模块章节中&#xff0c;我们都是将提供SSH服务的设备的账户/密码直接写入到python代码中&#xff0c;这样很容易导致账户/密码泄露&#xff0c;而使用Python中的用户交…

免费送源码:Java+springboot+MySQL 婴儿疫苗接种管理系统的设计与实现 计算机毕业设计原创定制

目 录 摘要 1 1 绪论 1 1.1概述 1 1.2课题意义 1 1.3研究思路及创新之处 1 1.4springboot框架介绍 1 1.5论文结构与章节安排 1 2 婴儿疫苗接种管理系统分析 3 2.1 可行性分析 3 2.1.1 技术可行性分析 3 2.1.2 经济可行性分析 3 2.1.3 法律可行性分析 3 2.2 系统功…

三相电变为家庭220V,市电火线和零线关系,为什么用三相电输送

参考&#xff1a; https://www.zhihu.com/question/30555841/answer/85723024 上面是电力系统的主要组成&#xff0c;发电站发电后升压传输&#xff0c;然后到各大城市再降压使用。 我们看到电塔上都是三根线&#xff0c;那么因为整个过程都是三相电。 为什么用三相电&#xff…

CIMRTS材质美化--放大采样、缩小采样

最新的CIMRTS v1.0.10中在要素管理中的材质美化增加「放大采样」和「缩小采样」参数&#xff0c;对于透明树叶可以达到较好效果。 在CesiumLab中&#xff0c;一棵树处理完成后&#xff0c;在EarthSDK中&#xff0c;就是呈现这样缩小就会有树叶丢失的情况。效果如下&#xff1a…

SQL-leetcode—1179. 重新格式化部门表

1179. 重新格式化部门表 表 Department&#xff1a; ---------------------- | Column Name | Type | ---------------------- | id | int | | revenue | int | | month | varchar | ---------------------- 在 SQL 中&#xff0c;(id, month) 是表的联合主键。 这个表格有关…

城市生命线安全保障:技术应用与策略创新

城市生命线工程是维系城市正常运行、满足群众生产生活需要的重要基础设施。随着城市化进程的加快&#xff0c;城市基础设施生命线安全运行的复杂性日益加剧&#xff0c;保障城市居民日常生活正常运行的水、电、气、热等各类地下管线以及桥梁、市政设施、轨道交通等城市基础设施…

vue2的$el.querySelector在vue3中怎么写

这个也属于直接操作 dom 了&#xff0c;不建议在项目中这样操作&#xff0c;不过我是在vue2升级vue3的时候遇到的&#xff0c;是以前同事写的代码&#xff0c;也没办法 先来看一下对比 在vue2中获取实例是直接通过 this.$refs.xxx 获取绑定属性 refxxx 的实例&#xff0c;并且…

Genetic Prompt Search via Exploiting Language Model Probabilities

题目 利用语言模型概率的遗传提示搜索 论文地址&#xff1a;https://www.ijcai.org/proceedings/2023/0588.pdf 项目地址&#xff1a;https://github.com/zjjhit/gap3 摘要 针对大规模预训练语言模型(PLMs)的即时调优已经显示出显著的潜力&#xff0c;尤其是在诸如fewshot学习…

如何引导LabVIEW项目相关方合理参与项目?

引导 LabVIEW 项目相关方合理参与项目&#xff0c;是保障项目顺利推进的关键所在。合理引导&#xff0c;既能显著提升项目执行效率&#xff0c;又能让各方清晰知晓自身在项目中的角色与责任。以下为具体策略与建议&#xff1a; ​ 一、明确项目目标与需求 清晰沟通项目目标&a…

Maven的下载安装配置

maven的下载安装配置 maven是什么 Maven 是一个用于 Java 平台的 自动化构建工具&#xff0c;由 Apache 组织提供。它不仅可以用作包管理&#xff0c;还支持项目的开发、打包、测试及部署等一系列行为 Maven的核心功能 项目构建生命周期管理&#xff1a;Maven定义了项目构建…

光谱相机在智能冰箱的应用原理与优势

食品新鲜度检测 详细可点击查看汇能感知团队实验报告&#xff1a;高光谱成像技术检测食物新鲜度 检测原理&#xff1a;不同新鲜程度的食品&#xff0c;其化学成分和结构会有所不同&#xff0c;在光谱下的反射、吸收等特性也存在差异。例如新鲜肉类和蔬菜中的水分、蛋白质、叶…

MySQL可直接使用的查询表的列信息

文章目录 背景实现方案模板SQL如何查询列如何转大写如何获取字符位置如何拼接字段 SQL适用场景 背景 最近产品找来&#xff0c;想让帮忙出下表的信息&#xff0c;字段驼峰展示&#xff0c;每张表信息show create table全部展示&#xff0c;再逐个粘贴&#xff0c;有点太耗费时…

设计模式的艺术-享元模式

结构性模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解享元模式 当一个软件系统在运行时产生的对象数量太多&#xff0c;将导致运行代价过高&#xff0c;带来系统性能下降等问题。 在享元模式中&#xff0c;存储这些共享实例对象的地方称为享元池&…

汇编与逆向(二)-汇编基础

一、汇编入门 &#xff08;一&#xff09;x86体系的CPU的工作模式 有两种基本的工作模式&#xff1a;实模式和保护模式。 实模式&#xff1a;也称为实地址模式&#xff0c;该模式最早被DOS&#xff0c;win9x所支持。可访问1M内存&#xff0c;可直接访问硬件&#xff0c;如对…

Excel 技巧17 - 如何计算倒计时,并添加该倒计时的数据条(★)

本文讲如何计算倒计时&#xff0c;并添加该倒计时的数据条。 1&#xff0c;如何计算倒计时 这里也要用公式 D3 - TODAY() 显示为下面这个样子的 然后右键该单元格&#xff0c;选 设置单元格格式 然后点 常规 这样就能显示出还书倒计时的日数了。 下拉适用到其他单元格。 2&a…

1Panel开源面板项目GitHub Star数量突破25,000!

截至2025年1月22日8:00&#xff0c;飞致云旗下开源项目——1Panel开源Linux服务器运维管理面板GitHub Star数超过25,000个&#xff01; 继Halo和JumpServer之后&#xff0c;1Panel成为飞致云旗下第三个GitHub Star数量超过25,000个的开源项目&#xff0c;也是飞致云旗下最快达…

高效安全文件传输新选择!群晖NAS如何实现无公网IP下的SFTP远程连接

文章目录 前言1. 开启群晖SFTP连接2. 群晖安装Cpolar工具3. 创建SFTP公网地址4. 群晖SFTP远程连接5. 固定SFTP公网地址6. SFTP固定地址连接 前言 随着远程办公和数据共享成为新常态&#xff0c;如何高效且安全地管理和传输文件成为了许多人的痛点。如果你正在寻找一个解决方案…