Spring Boot 可扩展脱敏框架设计全解析 | 注解+策略模式+模板方法模式实战

news2025/4/13 4:25:34

一、需求场景:为什么需要脱敏框架?

在数据安全合规要求下,敏感信息处理成为系统必备能力。典型场景:

  • 用户隐私保护(手机号、身份证、邮箱等)
  • 日志敏感信息过滤
  • 接口返回数据自动脱敏

传统方案痛点:

  • 硬编码脱敏逻辑,维护成本高
  • 不同字段需重复编写相似代码
  • 无法动态调整脱敏规则

二、框架设计全景图

脱敏注解
Jackson序列化器
脱敏处理器
正则处理器
滑动窗口处理器
自定义处理器

三、核心实现三步走

1. 注解体系设计(声明式配置)

顶级脱敏注解

@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
public @interface DesensitizeBy {

    /**
     * 脱敏处理器
     */
    @SuppressWarnings("rawtypes")
    Class<? extends DesensitizationHandler> handler();

}

邮箱脱敏注解

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = EmailDesensitizationHandler.class)
public @interface EmailDesensitize {

    /**
     * 匹配的正则表达式
     */
    String regex() default "(^.)[^@]*(@.*$)";

    /**
     * 替换规则,邮箱;
     *
     * 比如:example@gmail.com 脱敏之后为 e****@gmail.com
     */
    String replacer() default "$1****$2";

    /**
     * 是否禁用脱敏
     *
     * 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
     */
    String disable() default "";

}

2. 处理机制实现(策略模式+模板方法模式)

脱敏处理器

public interface DesensitizationHandler<T extends Annotation> {

    /**
     * 脱敏
     *
     * @param origin     原始字符串
     * @param annotation 注解信息
     * @return 脱敏后的字符串
     */
    String desensitize(String origin, T annotation);

    /**
     * 是否禁用脱敏的 Spring EL 表达式
     *
     * 如果返回 true 则跳过脱敏
     *
     * @param annotation 注解信息
     * @return 是否禁用脱敏的 Spring EL 表达式
     */
    default String getDisable(T annotation) {
        // 约定:默认就是 enable() 属性。如果不符合,子类重写
        try {
            return (String) ReflectUtil.invoke(annotation, "disable");
        } catch (Exception ex) {
            return "";
        }
    }

}

正则表达式脱敏处理器抽象类(模板方法模式+策略模式)

public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
        implements DesensitizationHandler<T> {
//==========================================不可变部分即公共方法即模板方法模式的实现===========================


    @Override
    public String desensitize(String origin, T annotation) {
        // 1. 判断是否禁用脱敏
        Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
        if (Boolean.TRUE.equals(disable)) {
            return origin;
        }

        // 2. 执行脱敏
        String regex = getRegex(annotation);
        String replacer = getReplacer(annotation);
        return origin.replaceAll(regex, replacer);
    }


	//==========================================可变部分抽象由具体子类实现===========================

    /**
     * 获取注解上的 regex 参数
     *
     * @param annotation 注解信息
     * @return 正则表达式
     */
    abstract String getRegex(T annotation);

    /**
     * 获取注解上的 replacer 参数
     *
     * @param annotation 注解信息
     * @return 待替换的字符串
     */
    abstract String getReplacer(T annotation);

}

EmailDesensitize的脱敏处理器

//==========================================邮箱脱敏策略===========================

public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler<EmailDesensitize> {

    @Override
    String getRegex(EmailDesensitize annotation) {
        return annotation.regex();
    }

    @Override
    String getReplacer(EmailDesensitize annotation) {
        return annotation.replacer();
    }

}

3. Jackson序列化集成

脱敏序列化器

public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {

    @Getter
    @Setter
    private DesensitizationHandler desensitizationHandler;

    protected StringDesensitizeSerializer() {
        super(String.class);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
        DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
        if (annotation == null) {
            return this;
        }
        // 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
        StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
        serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
        return serializer;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
        if (StrUtil.isBlank(value)) {
            gen.writeNull();
            return;
        }
        // 获取序列化字段
        Field field = getField(gen);

        // 自定义处理器
        DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);
        if (ArrayUtil.isEmpty(annotations)) {
            gen.writeString(value);
            return;
        }
        for (Annotation annotation : field.getAnnotations()) {
            if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
                value = this.desensitizationHandler.desensitize(value, annotation);
                gen.writeString(value);
                return;
            }
        }
        gen.writeString(value);
    }

    /**
     * 获取字段
     *
     * @param generator JsonGenerator
     * @return 字段
     */
    private Field getField(JsonGenerator generator) {
        String currentName = generator.getOutputContext().getCurrentName();
        Object currentValue = generator.getCurrentValue();
        Class<?> currentValueClass = currentValue.getClass();
        return ReflectUtil.getField(currentValueClass, currentName);
    }

}

四、使用示例:三步完成脱敏

1. 添加注解

public class UserVO {
    @EmailDesensitize(replacer = "$1***$2") // 自定义替换规则
    private String email;
    
    @PhoneDesensitize // 使用默认手机号脱敏规则
    private String phone;
}

2. 返回结果对比

// 原始数据
{
  "email": "test@example.com",
  "phone": "13812345678"
}

// 脱敏后
{
  "email": "t***@example.com",
  "phone": "138****5678"
}

3. 动态关闭脱敏(SpEL支持)

@EmailDesensitize(disable = "${app.desensitize.disable:false}")
private String email;

五、设计亮点分析

1. 开闭原则实践

100% 0% 扩展成本对比 新增脱敏类型 修改核心逻辑
  • 新增类型:只需添加注解+处理器
  • 修改规则:调整注解参数即可

2. 性能优化点

  • 单例模式:处理器全局单例复用
  • 缓存机制:注解解析结果缓存
  • 懒加载:按需初始化处理器

3. 灵活配置能力

// 多规则叠加示例
@EmailDesensitize(regex = "(?<=.{2}).", replacer = "*")
@ConditionalOnProperty("security.enabled")
private String email;

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

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

相关文章

STM32F103_LL库+寄存器学习笔记13 - 梳理外设CAN与如何发送CAN报文(串行发送)

导言 CAN总线因其高速稳定的数据传输与卓越抗干扰性能&#xff0c;在汽车、机器人及工业自动化中被广泛应用。它采用分布式网络结构&#xff0c;实现多节点间实时通信&#xff0c;确保各控制模块精准协同。在汽车领域&#xff0c;CAN总线连接发动机、制动、车身系统&#xff0c…

Linux系统调用编程

文章目录 一、进程和线程二、Linux的虚拟内存管理和stm32的真实物理内存**Linux虚拟内存管理**STM32物理内存映射2. 主要区别 三、Linux系统调用函数 fork()、wait()、exec()1. fork()&#xff1a;创建子进程2. wait()&#xff1a;等待子进程状态改变3. exec()&#xff1a;替换…

游戏引擎学习第203天

回顾当前情况 在这里我将直播完成整个游戏的制作。我们现在面临一些技术上的困难&#xff0c;确实如此。我的笔记本电脑的电源接口坏了&#xff0c;所以我不得不准备了这台备用笔记本&#xff0c;希望它能够正常工作。我所以希望一切都还好&#xff0c;尽管我不完全确定是否一…

深度学习数据集划分比例多少合适

在机器学习和深度学习中&#xff0c;测试集的划分比例需要根据数据量、任务类型和领域需求灵活调整。 1. 常规划分比例 通用场景 训练集 : 验证集 : 测试集 60% : 20% : 20% 适用于大多数中等规模数据集&#xff08;如数万到数十万样本&#xff09;&#xff0c;平衡了训练数…

CExercise_1_5 水仙花数

题目&#xff1a; 经典循环案例&#xff1a;请求出所有的水仙花数&#xff0c;并统计总共有几个。 所谓的水仙花数是指一个三位数&#xff0c;其各位数字的立方和等于该数本身。 举例&#xff1a;153就是一个水仙花数&#xff0c;153 1 * 1 * 1 5 * 5 * 5 3 * 3 * 3 1 125…

哈密尔顿路径(Hamiltonian Path)及相关算法题目

哈密尔顿路径要求访问图中每个顶点恰好一次&#xff0c;通常用于解决旅行商问题&#xff08;TSP&#xff09;或状态压缩DP问题。 哈密尔顿路径&#xff08;Hamiltonian Path&#xff09;是指在一个图中经过每个顶点恰好一次的路径。如果这条路径的起点和终点相同&#xff08;即…

MINIQMT学习课程Day10

开始获取股票数据课程的学习&#xff1a; 获取qmt账号的持仓情况后&#xff0c;我们进入下一步&#xff0c;如何获得当前账号的委托状况 还是之前的步骤&#xff0c;打开qmt&#xff0c;选择独立交易&#xff0c; 之后使用pycharm&#xff0c;编写py文件 导入包&#xff1a…

JAVA实战开源项目:智慧图书管理系统(Vue+SpringBoot) 附源码

本文项目编号 T 152 &#xff0c;文末自助获取源码 \color{red}{T152&#xff0c;文末自助获取源码} T152&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

Linux 系统管理综合实训 —— 基于 NAT 模式的多 IP 配置、Nginx 服务部署及存储管理

1. 虚拟机网络配置&#xff1a;NAT模式与多IP地址设置 将你的虚拟机的网卡模式设置为nat模式&#xff0c;给虚拟机网卡配置三个主机位分别为100、200、168的ip地址 设置静态IP [rootlocalhost ~]# nmcli c modify ens160 ipv4.method manual ipv4.addresses 192.168.2.100/2…

如何在windows 环境、且没有显卡的情况下用python跑通从ModelScope下载的大模型的调用

文章目录 背景介绍源代码&#xff1a;安装调试过程1.设置第三方镜像源2.预先安装&#xff1a;3.在python中创建代码&#xff1a;4.最终修改程序,将device_map从“cuda”改成“auto”&#xff0c;大模型调用1.5B&#xff08;1___5B)的5.最终跑出结果解释&#xff1a;示例&#x…

黑马点评redis改 part 1

本篇将主要阐述短信登录的相关知识&#xff0c;感谢黑马程序员开源&#xff0c;感谢提供初始源文件&#xff08;给到的是实战第7集开始的代码&#xff09;【Redis实战篇】黑马点评学习笔记&#xff08;16万字超详细、Redis实战项目学习必看、欢迎点赞⭐收藏&#xff09;-CSDN博…

【Ragflow】11. 文件解析流程分析/批量解析实现

概述 本文继续对ragflow文档解析部分进行分析&#xff0c;并通过脚本的方式实现对文件的批量上传解析。 文件解析流程 文件解析的请求处理流程大致如下&#xff1a; 1.前端上传文件&#xff0c;通过v1/document/run接口&#xff0c;发起文件解析请求 2.后端api\apps\docum…

第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)

✨前言&#xff1a;传参和状态管理&#xff0c;看似简单其实门道不少 在 Web 开发中&#xff0c;前端和后端最核心的交流方式就是“传参”&#xff0c;而“传参”除了涉及如何写代码获取参数&#xff0c;还藏着很多开发者容易忽略的细节&#xff1a; 为什么 URL 带了中文&…

Everything 安装教程与使用教程(附安装包)

文章目录 前言一、Everything 介绍二、Everything 安装教程1.Everything 安装包下载2.选择安装文件3.选择安装语言4.接受许可协议5.选择安装位置6.配置安装选项7.完成安装 三、Everything 使用教程1.启动软件2.简单关键词搜索3.按类型搜索 前言 在日常使用电脑时&#xff0c;随…

SQL语句(三)—— DQL

目录 基本语法 一、基础查询 1、查询多个字段 2、字段设置别名 3、去除重复记录 4、示例代码 二、条件查询 1、语法 2、条件列表常用的运算符 3、示例代码 三、分组查询 &#xff08;一&#xff09;聚合函数 1、介绍 2、常见的聚合函数 3、语法 4、示例代码 &…

Opencv计算机视觉编程攻略-第九节 描述和匹配兴趣点

一般而言&#xff0c;如果一个物体在一幅图像中被检测到关键点&#xff0c;那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一&#xff0c;它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。 1.局部模板匹配 凭单个像素就…

汇编学习之《push , pop指令》

学习本章前线了解ESP, EBP 指令 汇编学习之《指针寄存器&大小端学习》-CSDN博客 栈的特点&#xff1a; 好比一个垂直容器&#xff0c;可以陆续放入物体&#xff0c;但是先放的物体通常会被后面放的物体压着&#xff0c;只有等上面后放的物品拿出来后&#xff0c;才能…

Python循环控制语句

1. 循环类型概述 Python提供两种主要的循环结构&#xff1a; while循环 - 在条件为真时重复执行for循环 - 遍历序列中的元素 2. while循环 基本语法 while 条件表达式:循环体代码示例 count 0 while count < 5:print(f"这是第{count1}次循环")count 13. f…