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

news2024/12/23 15:28:20

目录

  • 前言
  • 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);

}

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

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

相关文章

【4/26-4/30】 Arxiv安全类文章速览

4/26 标题: Merchants of Vulnerabilities: How Bug Bounty Programs Benefit Software Vendors 作者: Esther Gal-Or, Muhammad Zia Hydari, Rahul Telang摘要: 软件漏洞允许恶意黑客利用&#xff0c;威胁系统和数据安全。本文研究了激励道德黑客发现并负责任地向软件供应商披…

[PS小技能学习]抠图和切图

详情见视频教程&#xff1a;PS小技巧--抠图与切图 今天我们来学习如何使用PS对表情包合辑进行抠图和裁剪保存 1、首先&#xff0c;将图片导入&#xff0c;双击图层新建一个图层 2、然后点击工具栏的魔棒工具&#xff0c;再点击顶部菜单栏的添加到选区 3、点击图片的空白区域即…

spring boot学习第十八篇:使用clickhouse

1、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://…

目标检测算法YOLOv3简介

YOLOv3由Joseph Redmon等人于2018年提出&#xff0c;论文名为&#xff1a;《YOLOv3: An Incremental Improvement》&#xff0c;论文见&#xff1a;https://arxiv.org/pdf/1804.02767.pdf &#xff0c;项目网页&#xff1a;https://pjreddie.com/darknet/yolo/ 。YOLOv3是对YOL…

我独自升级崛起怎么注册?新手一看就会的注册方法

《我独自升级&#xff1a;ARISE》是网石旗下一款以网漫《我独自升级》IP开发的ARPG&#xff0c;游戏中&#xff0c;玩家会成为漫画主角程肖宇进行战斗&#xff0c;并通过升级使用更多技能和武器&#xff0c;打造出属于自己的战斗风格。同时&#xff0c;游戏中还将体现原作的核心…

2.1 Java全栈开发前端+后端(全栈工程师进阶之路)-前端框架VUE3-基础-初识Vue

Vue概述 早期前后端分离模式 早期的前后端分离开发模式是这样的&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge">&l…

blender(布兰德)下载安装-windows系统安装

1.简单介绍 这些介绍都是一些百科或官网提供的内容&#xff0c;直接搜索对应的信息后即可看到。Blender&#xff08;布兰德&#xff09;是一款永久开源免费的3D创建套件。支持整个3D创作流程&#xff1a;建模、雕刻、骨骼装配、动画、模拟、实时渲染、合成和运动跟踪&#xff…

基于Spring Boot的校园闲置物品交易网站设计与实现

基于Spring Boot的校园闲置物品交易网站设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看…

Java线上CPU内存冲高问题排查步骤

01 引言 作为一名从事Java开发快一年的程序员&#xff0c;在线上经常碰到某个模块的Pod发出CPU与内存告警的问题&#xff0c;而这些问题会导致系统响应缓慢甚至是服务不可用。一般情况下可以通过重启或者调高Pod的资源量或者增加Pod数量暂时解决问题&#xff0c;但这是治标不治…

【C++】拷贝复制:拷贝构造函数的使用

欢迎来到CILMY23的博客 本篇主题为&#xff1a;拷贝复制&#xff1a;拷贝构造函数的使用 博客主页&#xff1a;CILMY23-CSDN博客 个人专栏&#xff1a;Python | C | C语言 | 数据结构与算法 感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 写在前头…

RCE学习(一)

一.知识点 RCE&#xff1a;远程命令/代码执行漏洞&#xff0c;简称为RCE漏洞&#xff0c;可以直接向服务器后台远程注入操作系统的命令或者代码&#xff0c;从而拿到服务器后台的权限。RCE分为远程执行命令&#xff08;执行ping命令&#xff09;和远程代码执行eval 简单来说就…

【数据结构】时间复杂度和空间复杂度解析

数据结构前言&#xff1a; 1. 什么是数据结构 打个比方来说不同的数据就相当于不同的书籍&#xff0c;我们经常在图书馆可以看到不同类别的书籍会被整理放在书架上方便查看存放&#xff0c;数据结构就是一种计算机存储管理数据的方式。 2. 什么是算法 算法就是一系列的计算…

蓝桥杯练习系统(算法训练)ALGO-953 混合积

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 众所周知&#xff0c;人人都在学习线性代数&#xff0c;既然都学过&#xff0c;那么解决本题应该很方便。   宇宙大战中&…

上位机图像处理和嵌入式模块部署(树莓派4b和pyqt5界面开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在大部分linux程序开发中&#xff0c;一般是没有界面的。不过不排除有些场合&#xff0c;是需要用界面进行数据交互、调参使用的。这种情况下一般就…

JavaScript+B/S版云LIS系统源码ASP.NET CORE 3.1 MVC云LIS系统如何实现样本追踪的预警功能?医院云LIS检验系统源码

JavaScriptB/S版云LIS系统源码ASP.NET CORE 3.1 MVC云LIS系统如何实现样本追踪的预警功能&#xff1f;医院云LIS检验系统源码 实验室信息管理系统&#xff08;Trasen Laboratory Information Management System&#xff09;是一套专业的医疗实验室信息管理软件&#xff0c;包含…

远程链接linux

远程连接 ssh 远程登录操作&#xff0c;ssh会对用用户进行身份信息的验证&#xff0c;会对两台主机之间发通信数据进行加密 安装 ssh 远程登录的服务端 yum install -y openssh-server启动 ssh 服务 systemctl start ssh.service 关闭 ssh 服务 systemctl stop ssh.service …

伦敦金的交易时间段都适合投资吗?

是所有的交易时间段都适合投资。首先&#xff0c;让我们了解伦敦金的交易时间。伦敦金市场的交易时间分为两个主要时段&#xff1a;亚洲盘和欧美盘。亚洲盘通常在北京时间早晨6点至下午5点半左右&#xff0c;而欧美盘则从北京时间晚上8点半开始&#xff0c;一直到次日早晨4点半…

tomcat篇-windows 运行tomcat的startup.bat时,终端打印的中文显示为乱码

当运行Tomcat的startup.bat时&#xff0c;如果终端中中文显示为乱码&#xff0c;这通常是因为Tomcat使用的日志输出编码与Windows命令行默认的编码不匹配。针对这一问题&#xff0c;你可以尝试以下步骤来解决&#xff1a; 1、执行startup.bat&#xff0c;在输出的窗口右击&…

HCIP第二节

OSPF&#xff1a;开放式最短路径协议&#xff08;属于IGP-内部网关路由协议&#xff09; 一。OSPF的数据包类型 3层报头 协议号89 1.Hello&#xff1a;周期收发&#xff0c;用于邻居发现&#xff0c;关系建立&#xff0c;周期保活-10s/30s&#xff08;路由之间相互认识&#…

如何搭建本地的 NPM 私有仓库 Nexus

NPM 本地私有仓库&#xff0c;是在本地搭建NPM私有仓库&#xff0c;对公司级别的组件库进行管理。在日常开发中&#xff0c;经常会遇到抽象公共组件的场景&#xff0c;在项目内部进行公用。新的项目开始时&#xff0c;也会拷贝一份创建一个新的项目&#xff0c;这样做不易于管理…