详细分析Java中的脱敏注解(附Demo)

目录

  • 前言
  • 1. 基本知识
  • 2. 核心逻辑
  • 3. Demo
  • 4. 模版

前言

对于隐私信息,需要做特殊处理,比如身份证或者手机号等

对于Java的相关知识推荐阅读:java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)

1. 基本知识

脱敏(Desensitization)指在保持数据结构不变的前提下,对敏感数据进行处理,使其不再具备直接识别个人身份或敏感信息的能力,从而保护用户隐私

常见的脱敏方法包括:

  • 替换(Masking):将敏感数据中的一部分或全部字符替换为特定字符,如将姓名中的一部分字符替换为星号
  • 截断(Truncation):截取敏感数据的一部分,只保留部分信息,如只保留电话号码的前几位
  • 加密(Encryption):使用算法将敏感数据转换为密文,只有经过解密才能还原为原始数据
  • 哈希(Hashing):将敏感数据通过哈希算法转换为固定长度的哈希值,不可逆转

序列化器是指在将对象转换为字节流或其他格式时,负责对对象进行序列化的组件。在脱敏处理中,序列化器可以通过自定义的逻辑对敏感数据进行处理,使其在序列化过程中不泄露隐私信息
主要将其自定义注解继承自 Jackson 库中的 JsonSerializer 类,在序列化过程中做一定的处理

2. 核心逻辑

在定义的字段中加入自定义注解类

类似如下:

@Data
public static class DesensitizeDemo {
    @ChineseNameDesensitize
    private String nickname;
}

对应注解的核心内容如下:

// 脱敏注解
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = ChineseNameDesensitization.Serializer.class)
@interface ChineseNameDesensitize {
    int prefixKeep() default 1; // 前缀保留的字符数,默认为1
    int suffixKeep() default 2; // 后缀保留的字符数,默认为2
    String replacer() default "*"; // 替换字符,默认为 "*"
}

其中涉及的改造方法也可通过某个类进行重写

// 脱敏方法
private String desensitize(String value) {
    // 实现自定义的脱敏逻辑,根据注解参数进行处理
    String prefix = value.substring(0, Math.min(prefixKeep, value.length()));
    String suffix = value.substring(Math.max(0, value.length() - suffixKeep));
    String maskedPart = StringUtils.repeat(replacer, value.length() - prefixKeep - suffixKeep);
    return prefix + maskedPart + suffix;
}

后续测试的时候直接调用即可实现脱敏数据

3. Demo

直接在Demo文件中执行,先看一个Demo的执行方式

// 导入注解相关的类
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;

// 导入 lombok 提供的注解
import lombok.Data;

// 导入 Apache Commons Lang 库中的 StringUtils 类
import org.apache.commons.lang3.StringUtils;

// 导入 IOException 异常类
import java.io.IOException;

// 导入元注解相关的类
import java.lang.annotation.*;

// 脱敏注解
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = ChineseNameDesensitization.Serializer.class)
@interface ChineseNameDesensitize {
    int prefixKeep() default 1; // 前缀保留的字符数,默认为1
    int suffixKeep() default 2; // 后缀保留的字符数,默认为2
    String replacer() default "*"; // 替换字符,默认为 "*"
}

// 脱敏处理器
@Data
class ChineseNameDesensitization {
    public static class Serializer extends JsonSerializer<String> {
        private int prefixKeep; // 前缀保留的字符数
        private int suffixKeep; // 后缀保留的字符数
        private String replacer; // 替换字符

        // 默认构造函数
        public Serializer() {
            this.prefixKeep = 1;
            this.suffixKeep = 2;
            this.replacer = "*";
        }

        // 带参构造函数
        public Serializer(int prefixKeep, int suffixKeep, String replacer) {
            this.prefixKeep = prefixKeep;
            this.suffixKeep = suffixKeep;
            this.replacer = replacer;
        }

        // 序列化方法
        @Override
        public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            System.out.println("Value before desensitization: " + value);
            System.out.println("Prefix keep: " + prefixKeep);
            System.out.println("Suffix keep: " + suffixKeep);
            System.out.println("Replacer: " + replacer);

            if (StringUtils.isNotBlank(value)) {
                String desensitizedValue = desensitize(value); // 调用脱敏方法
                gen.writeString(desensitizedValue);
            } else {
                gen.writeString(value);
            }
        }

        // 脱敏方法
        private String desensitize(String value) {
            // 实现自定义的脱敏逻辑,根据注解参数进行处理
            String prefix = value.substring(0, Math.min(prefixKeep, value.length()));
            String suffix = value.substring(Math.max(0, value.length() - suffixKeep));
            String maskedPart = StringUtils.repeat(replacer, value.length() - prefixKeep - suffixKeep);
            return prefix + maskedPart + suffix;
        }
    }
}

// 直接执行的 Demo 类
public class test {
    public static void main(String[] args) throws IOException {
        // 创建 ObjectMapper 对象
        ObjectMapper objectMapper = new ObjectMapper();
        // 创建 SimpleModule 对象
        SimpleModule module = new SimpleModule();

        // 准备参数
        int prefixKeep = 1;
        int suffixKeep = 2;
        String replacer = "*";

        // 创建带参数的 ChineseNameDesensitization.Serializer 对象
        ChineseNameDesensitization.Serializer serializer = new ChineseNameDesensitization.Serializer(prefixKeep, suffixKeep, replacer);

        // 注册脱敏处理器并应用于注解中定义的字段
        module.addSerializer(String.class, serializer);
        objectMapper.registerModule(module);

        // 准备参数
        DesensitizeDemo demo = new DesensitizeDemo();
        demo.setNickname("码农研究僧");

        // 将对象序列化为 JSON 字符串并输出
        String json = objectMapper.writeValueAsString(demo);
        System.out.println("Serialized JSON:");
        System.out.println(json);
    }

    // 用于执行的 POJO 类
    @Data
    public static class DesensitizeDemo {
        @ChineseNameDesensitize(prefixKeep = 1, suffixKeep = 2, replacer = "*")
        private String nickname;
    }
}

执行结果如下:

在这里插入图片描述

4. 模版

以下只是展示的模版,执行操作请看第二章

@ExtendWith(MockitoExtension.class)
public class DesensitizeTest {

    @Test
    public void test() {
        // 准备参数
        DesensitizeDemo desensitizeDemo = new DesensitizeDemo();
        desensitizeDemo.setNickname("张三");
        desensitizeDemo.setBankCard("6228480402564890018");
        desensitizeDemo.setCarLicense("京A88888");
        desensitizeDemo.setFixedPhone("010-12345678");
        desensitizeDemo.setIdCard("110101199003077172");
        desensitizeDemo.setPassword("password123");
        desensitizeDemo.setPhoneNumber("13812345678");
        desensitizeDemo.setSlider1("ABCDEFG");
        desensitizeDemo.setSlider2("ABCDEFG");
        desensitizeDemo.setSlider3("ABCDEFG");
        desensitizeDemo.setEmail("test@example.com");
        desensitizeDemo.setRegex("这是一条测试数据");
        desensitizeDemo.setAddress("北京市朝阳区XX路XX号");
        desensitizeDemo.setOrigin("初始数据");

        // 调用
        DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
        // 断言
        assertNotNull(d);
        assertEquals("张*", d.getNickname());
        assertEquals("622848********0018", d.getBankCard());
        assertEquals("京A8***8", d.getCarLicense());
        assertEquals("010-*****5678", d.getFixedPhone());
        assertEquals("110101********7172", d.getIdCard());
        assertEquals("***********", d.getPassword());
        assertEquals("138****5678", d.getPhoneNumber());
        assertEquals("#######", d.getSlider1());
        assertEquals("ABC*EFG", d.getSlider2());
        assertEquals("*******", d.getSlider3());
        assertEquals("t***@example.com", d.getEmail());
        assertEquals("这是一条****据", d.getRegex());
        assertEquals("北京市朝阳区XX路XX号*", d.getAddress());
        assertEquals("初始数据", d.getOrigin());
    }

    @Data
    public static class DesensitizeDemo {

        @ChineseNameDesensitize
        private String nickname;
        @BankCardDesensitize
        private String bankCard;
        @CarLicenseDesensitize
        private String carLicense;
        @FixedPhoneDesensitize
        private String fixedPhone;
        @IdCardDesensitize
        private String idCard;
        @PasswordDesensitize
        private String password;
        @MobileDesensitize
        private String phoneNumber;
        @SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = "#")
        private String slider1;
        @SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
        private String slider2;
        @SliderDesensitize(prefixKeep = 10)
        private String slider3;
        @EmailDesensitize
        private String email;
        @RegexDesensitize(regex = "这是一条测试数据", replacer = "*")
        private String regex;
        @Address
        private String address;
        private String origin;

    }

}

对应实行各个注解进行脱敏

假设还是刚刚的中文脱敏

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

    /**
     * 前缀保留长度
     */
    int prefixKeep() default 1;

    /**
     * 后缀保留长度
     */
    int suffixKeep() default 0;

    /**
     * 替换规则,中文名;比如:吗喽研究僧脱敏之后为码****
     */
    String replacer() default "*";

}

对应的注解如下:

public class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler<ChineseNameDesensitize> {

    @Override
    Integer getPrefixKeep(ChineseNameDesensitize annotation) {
        return annotation.prefixKeep();
    }

    @Override
    Integer getSuffixKeep(ChineseNameDesensitize annotation) {
        return annotation.suffixKeep();
    }

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

}

其中改写的函数如下:

public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
        implements DesensitizationHandler<T> {

    @Override
    public String desensitize(String origin, T annotation) {
        int prefixKeep = getPrefixKeep(annotation);
        int suffixKeep = getSuffixKeep(annotation);
        String replacer = getReplacer(annotation);
        int length = origin.length();

        // 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
        if (prefixKeep >= length || suffixKeep >= length) {
            return buildReplacerByLength(replacer, length);
        }

        // 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
        if ((prefixKeep + suffixKeep) >= length) {
            return buildReplacerByLength(replacer, length);
        }

        // 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
        int interval = length - prefixKeep - suffixKeep;
        return origin.substring(0, prefixKeep) +
                buildReplacerByLength(replacer, interval) +
                origin.substring(prefixKeep + interval);
    }

    /**
     * 根据长度循环构建替换符
     *
     * @param replacer 替换符
     * @param length   长度
     * @return 构建后的替换符
     */
    private String buildReplacerByLength(String replacer, int length) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(replacer);
        }
        return builder.toString();
    }

    /**
     * 前缀保留长度
     *
     * @param annotation 注解信息
     * @return 前缀保留长度
     */
    abstract Integer getPrefixKeep(T annotation);

    /**
     * 后缀保留长度
     *
     * @param annotation 注解信息
     * @return 后缀保留长度
     */
    abstract Integer getSuffixKeep(T annotation);

    /**
     * 替换符
     *
     * @param annotation 注解信息
     * @return 替换符
     */
    abstract String getReplacer(T annotation);

}

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

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

相关文章

06 - 步骤 add constants

简介 Add Constants 步骤是用于在数据流中添加常量字段的步骤。它允许用户在数据流中插入一个或多个常量字段&#xff0c;并为这些字段指定固定的数值、字符串或其他类型的常量值。 使用 场景 我需要在数据清后&#xff0c;这个JSON 字符串有一个固定的行流数据。 1、拖拽…

基于Springboot的社区医疗服务系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区医疗服务系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

6.python网络编程

文章目录 1.生产者消费者-生成器版2.生产者消费者--异步版本3.客户端/服务端-多线程版4.IO多路复用TCPServer模型4.1Select4.2Epoll 5.异步IO多路复用TCPServer模型 1.生产者消费者-生成器版 import time# 消费者 def consumer():cnt yieldwhile True:if cnt < 0:# 暂停、…

LLM优化:开源星火13B显卡及内存占用优化

1. 背景 本qiang~这两天接了一个任务&#xff0c;部署几个开源的模型&#xff0c;并且将本地经过全量微调的模型与开源模型做一个效果对比。 部署的开源模型包括&#xff1a;星火13B&#xff0c;Baichuan2-13B, ChatGLM6B等 其他两个模型基于transformers架构封装&#xff0…

使用Git把写好的项目放到github上

把之前的文章差缺补漏了一下&#xff0c;发现少一个TUserController文件&#xff0c;然后加上了。 以及发现前后端交互时的跨域问题需要处理。 在Controller文件里加入注释 CrossOrigin(origins "*")即可。 不然数据在Vue里显示不出来。 ** 壹 首先先把前端项目 de…

ElasticSearch教程入门到精通——第四部分(基于ELK技术栈elasticsearch 7.x新特性)

ElasticSearch教程入门到精通——第四部分&#xff08;基于ELK技术栈elasticsearch 7.x新特性&#xff09; 1. Elasticsearch进阶1.1 核心概念1.1.1 索引Index1.1.1.1 索引创建原则1.1.1.2 Inverted Index 1.1.2 类型Type1.1.3 文档Document1.1.4 字段Field1.1.5 映射Mapping1.…

AI项目二十一:视频动态手势识别

若该文为原创文章&#xff0c;转载请注明原文出处。 一、简介 人工智能的发展日新月异&#xff0c;也深刻的影响到人机交互领域的发展。手势动作作为一种自然、快捷的交互方式&#xff0c;在智能驾驶、虚拟现实等领域有着广泛的应用。手势识别的任务是&#xff0c;当操作者做出…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习六

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

设备能源数据采集新篇章

在当今这个信息化、智能化的时代&#xff0c;设备能源数据的采集已经成为企业高效运营、绿色发展的重要基石。而今天&#xff0c;我们要向大家介绍的就是一款颠覆传统、引领未来的设备能源数据采集神器——HiWoo Box网关&#xff01; 一、HiWoo Box网关&#xff1a;一站式解决…

virtualbox kafka nat + host-only集群 + windows 外网 多网卡

virtualbox kafka nat + host-only集群 + windows 映射访问 kafka集群搭建背景kafka集群搭建 背景 使用virtualbox搭建kafka集群,涉及到不同网络策略的取舍 首先 桥接 网络虽说 啥都可以,但是涉及到过多ip的时候,而且还不能保证使用的ip不被占用,所以个人选择kafka虚拟机…

2024.5.5 机器学习周报

引言 Abstract 文献阅读 1、题目 SuperGlue: Learning Feature Matching with Graph Neural Networks 2、引言 本文介绍了SuperGlue&#xff0c;这是一种神经网络&#xff0c;它通过联合寻找对应关系并拒绝不匹配的点来匹配两组局部特征。通过求解一个可微的最优运输问题…

【Mac】Photoshop 2024 for mac最新安装教程

软件介绍 Photoshop 2024是Adobe公司推出的一款图像处理软件&#xff0c;它支持Windows和Mac OS系统。Adobe Photoshop是业界领先的图像编辑和处理软件之一&#xff0c;广泛用于设计、摄影、数字绘画等领域。 Photoshop 2024的功能包括&#xff1a; 1.图像编辑&#xff1a;提…

如何提高商务认知与情商口才(3篇)

如何提高商务认知与情商口才&#xff08;3篇&#xff09; **篇&#xff1a;提高商务认知 商务认知的提升是一个系统工程&#xff0c;需要我们不断地积累知识、理解市场和关注行业动态。以下是一些具体的方法&#xff1a; 持续学习&#xff1a;通过阅读商业书籍、参加行业研讨…

相机知识的补充

一&#xff1a;镜头 1.1MP的概念 相机中MP的意思是指百万像素。MP是mega pixel的缩写。mega意为一百万&#xff0c;mega pixel 指意为100万像素。“像素”是相机感光器件上的感光最小单位。就像是光学相机的感光胶片的银粒一样&#xff0c;记忆在数码相机的“胶片”&#xff…

【深耕 Python】Data Science with Python 数据科学(17)Scikit-learn机器学习(二)

写在前面 关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 往期数据科学博文一览&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;2&…

论文精读-基于FPGA的卷积神经网络和视觉Transformer通用加速器

论文精读-基于FPGA的卷积神经网络和视觉Transformer通用加速器 优势&#xff1a; 1.针对CNN和Transformer提出了通用的计算映射&#xff08;共用计算单元&#xff0c;通过不同的映射指令&#xff0c;指导数据通路和并行计算&#xff09; 2.非线性与归一化加速单元&#xff0…

路由器的构成

一、路由器简介 路由器是互联网中的关键设备&#xff1a; 连接不同的网络路由器是多个输入端口和多个输出端口的专用计算机&#xff0c;其任务是转发分组&#xff08;转发给下一跳路由器&#xff09;下一跳路由器也按照这种方法处理分组&#xff0c;直到该分组到达终点为止 …

线程的概念

文章目录 1、什么是线程2、进程和线程的区别3、多线程的概述4、在Java中实现多线程的方法1.继承Thread类2.实现Runnable接口3.使用匿名内部类来继承Thread类&#xff0c;实现run方法4.使用匿名内部类来实现Runnable接口&#xff0c;实现run方法5.使用 lambda表达式 1、什么是线…

STM32 DMA直接存储器存取

单片机学习&#xff01; 目录 文章目录 前言 一、DMA简介 1.1 DMA是什么 1.2 DMA作用 1.3 DMA通道 1.4 软硬件触发 1.5 芯片资源 二、存储器映像 2.1 存储器 2.2 STM32存储器 三、DMA框图 3.1 内核与存储器 3.2 寄存器 3.3 DMA数据转运 3.4 DMA总线作用 3.5 DMA请求 3.6 DMA结构…

linux的常见命令

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Linux ⛺️稳中求进&#xff0c;晒太阳 Linux中检查进程是否存在&#xff1a; ps -ef | grep [进程名或进程ID] pgrep -f [进程名|进程ID] pidof [进程名] Linux中检查某个端口是否被…