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

目录

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

前言

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

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

1. 基本知识

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

常见的脱敏方法包括:

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

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

2. 核心逻辑

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

类似如下:

@Data
public static class DesensitizeDemo {@ChineseNameDesensitizeprivate String nickname;
}

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

// 脱敏注解
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = ChineseNameDesensitization.Serializer.class)
@interface ChineseNameDesensitize {int prefixKeep() default 1; // 前缀保留的字符数,默认为1int suffixKeep() default 2; // 后缀保留的字符数,默认为2String 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; // 前缀保留的字符数,默认为1int suffixKeep() default 2; // 后缀保留的字符数,默认为2String 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;}// 序列化方法@Overridepublic 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 类@Datapublic static class DesensitizeDemo {@ChineseNameDesensitize(prefixKeep = 1, suffixKeep = 2, replacer = "*")private String nickname;}
}

执行结果如下:

在这里插入图片描述

4. 模版

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

@ExtendWith(MockitoExtension.class)
public class DesensitizeTest {@Testpublic 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());}@Datapublic static class DesensitizeDemo {@ChineseNameDesensitizeprivate String nickname;@BankCardDesensitizeprivate String bankCard;@CarLicenseDesensitizeprivate String carLicense;@FixedPhoneDesensitizeprivate String fixedPhone;@IdCardDesensitizeprivate String idCard;@PasswordDesensitizeprivate String password;@MobileDesensitizeprivate 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;@EmailDesensitizeprivate String email;@RegexDesensitize(regex = "这是一条测试数据", replacer = "*")private String regex;@Addressprivate 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> {@OverrideInteger getPrefixKeep(ChineseNameDesensitize annotation) {return annotation.prefixKeep();}@OverrideInteger getSuffixKeep(ChineseNameDesensitize annotation) {return annotation.suffixKeep();}@OverrideString getReplacer(ChineseNameDesensitize annotation) {return annotation.replacer();}}

其中改写的函数如下:

public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>implements DesensitizationHandler<T> {@Overridepublic 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.mzph.cn/web/5977.shtml

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

相关文章

软件定义汽车落地的五大关键要素

1、架构升级 1.1 软件架构&#xff1a;分层解耦、服务化、API 接口标准化 随着企业向软件定义汽车开发方法的转变&#xff0c;软件架构也需要同步进行升级&#xff0c;引入面向服务的架构&#xff08;Service-Oriented Architecture&#xff0c;简称 SOA&#xff09;方法论。…

LeetCode刷题之买卖股票的最佳时机

文章目录 1. 买卖股票的最佳时机1.1 描述1.2 分析1.3 解答 2.买卖股票的最佳时机II2.1 描述2.2 分析2.3 解答2.4 拓展2.5 拓展二 1. 买卖股票的最佳时机 题121 买卖股票的最佳时机 1.1 描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 …

【Android学习】按钮监听代码

1. 简介 Button组件是Android中常用的组件&#xff0c;Button常需要和View.OnClickListener配合使用。这里记录下Button配置监听的过程。 2. 代码分析 2.1 Layout的XML代码 <Buttonandroid:id"id/btn"android:layout_width"match_parent"android:lay…

ThreeJS:响应式画布与全屏控制

响应式画布 响应式画布&#xff1a;在用户缩放浏览器窗口时&#xff0c;为便于动态更新画布尺寸与宽高比例&#xff0c;需要通过监听resize事件&#xff0c;来实现响应式画布。 window.onresize function () {//TODO:重置渲染器宽高比renderer.setSize(window.innerWidth, wi…

图文、视频处理等自媒体工具

文章目录 文本转文本图片canva同类竞品文本生成PDF,PDF再导出为图片贴入笔记类应用(如小米笔记App)中然后选择以图片形式分享UU在线工具的文字生成长图醒图App其他竞品文本转配音视频剪映将文本生成一段朗读该文本的配音视频(需要自行切割多段内容并分配时间轴)腾讯智影将…

为人处事电影解说,全新升级瀚海跑道一分钟一条视频,全平台可推广,轻轻松松日入1000

自古以来&#xff0c;我国流行的一种现象是&#xff0c;大多数人都会与领导或上司打交道。由于某些话题不宜公开讨论&#xff0c;因此出现了许多含蓄的表达方式。随着年龄的增长&#xff0c;人们的态度也发生了变化&#xff0c;从最初的轻视到现在的重视。 下 载 地 址&#…

VG做mirror引起的块偏移

事件起因 Oracle10.2环境 Aix操作系统使用aix的lvm技术。制作vg的mirror。以此来替换掉老的存储。 做mirror前&#xff0c;数据库已完全关闭 故障现象 在启动数据库时&#xff0c;发现IO错误。该系统的spfile&#xff0c;ctl&#xff0c;dbf均是用lv做的裸设备。其中dbf是使…

WebGL是啥

WebGL&#xff08;全写为Web Graphics Library&#xff09;是一种3D绘图协议&#xff0c;这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起&#xff0c;通过增加OpenGL ES 2.0的一个JavaScript绑定&#xff0c;WebGL可以为HTML5 Canvas提供硬件3D加速渲染。这样&…

【重学C语言】十二、指针高级-函数指针

【重学C语言】十二、指针高级-函数指针 函数指针小案例回调函数如何看懂复杂的指针右左法则案例走起1.int (\*p[5])(int\*)2. int (\*fun)(int \*p,int (\*pf)(int \*))3. int (\*(\*fun)[5])(int \*p)4. int (\*(\*fun)(int \*p))[5]5. int(\*(\*fun())())()函数指针 函数指针…

cmake的使用方法: 编译生成库文件

一. 简介 前面文章学习了针对单个 .c文件&#xff0c;cmake 工具是如何编译的&#xff1f; 针对包含多个 .c文件&#xff0c;cmake工具又是如何编译的&#xff1f;文章如下&#xff1a; cmake的使用方法: 单个源文件的编译-CSDN博客 cmake的使用方法: 多个源文件的编译-CS…

Java入门-final关键字

final关键字 修饰基本类型 变量为只读&#xff0c;不能修改变量的内容。 final int SIZE 3;修饰引用类型 引用的对象不能改变&#xff0c;但是对象的内容可以修改。 final Car c new Car( );c.setColor("红色");修饰类的属性 类的属性不能被修改。 第一种方式&…

Linux 进程间通信之命名管道

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 命名管道 创建一个命名管道 …

Pandas库的基本使用

什么是Pandas? 一个开源的Python类库&#xff1a;用于数据分析、数据处理、数据可视化 高性能容易使用的数据结构容易使用的数据分析工具 很方便和其它类库一起使用&#xff1a; numpy&#xff1a;用于数学计算scikit-learn&#xff1a;用于机器学习 怎样下载安装Pandas …

Flask后端之建立模型类间的外键联系

1、模型类代码&#xff1a; 定义了三个模型类&#xff1a;User、Goods 和 Sign&#xff1a; class User(db.Model):__tablename__ userid db.Column(db.Integer, primary_keyTrue)name db.Column(db.String(100), nullableFalse)email db.Column(db.String(100), uniqueT…

商城数据库(36 37 38 39 40)

36 CREATE TABLE sxh_log_operates (operateId int(11) NOT NULL AUTO_INCREMENT COMMENT 自增ID,staffId int(11) NOT NULL DEFAULT 0 COMMENT 职员ID,operateTime datetime NOT NULL COMMENT 操作时间,menuId int(11) NOT NULL COMMENT 所属菜单ID,operateDesc varchar(255)…

UI自动化与接口自动化比较

UI自动化与接口自动化优比较&#xff1a; 1、执行效率 接口自动化执行效率比UI自动化执行效率更高(调用接口比打开页面要快很多) 2、稳定性 UI自动化容易受设备卡顿&#xff0c;系统弹框等因素影响而导致脚本执行失败、接口自动化不存在此问题&#xff0c;因此接口自动化测试…

ES常用查询方式

Elasticsearch&#xff08;ES&#xff09;作为功能强大的检索引擎&#xff0c;提供了多种查询方式&#xff0c;在不同的场景下需要选择合适的查询方式以取得最佳查询效果。 ES常用查询方式 方式说明Match Query&#xff08;匹配查询&#xff09;根据字段的内容进行全文匹配查…

Leetcode—976. 三角形的最大周长【简单】(ranges::sort函数)

2024每日刷题&#xff08;122&#xff09; Leetcode—976. 三角形的最大周长 实现代码 class Solution { public:int largestPerimeter(vector<int>& nums) {ranges::sort(nums);for(int i nums.size() - 1; i > 1; i--) {if(nums[i - 1] nums[i - 2] > nu…

洛谷 P1377 [TJOI2011]:树的序 ← 笛卡尔树

【题目来源】https://www.luogu.com.cn/problem/P1377【题目描述】 众所周知&#xff0c;二叉查找树的形态和键值的插入顺序密切相关。准确的讲&#xff1a; 1.空树中加入一个键值 k&#xff0c;则变为只有一个结点的二叉查找树&#xff0c;此结点的键值即为 k。 2.在非空树中插…

『大模型笔记』AI 智能体(Agent)在推理(Reasoning)、规划(Planning)与工具调度(Tool Calling)方面的研究:综合调查!

AI 智能体(Agent)在推理(Reasoning)、规划(Planning)与工具调度(Tool Calling)方面的研究:综合调查! 文章目录 o. 摘要一. Introduction1.1. Taxonomy(分类学)二. 关键考虑因素以实现有效的智能体2.1. 概述2.2. 推理和规划的重要性2.3. 有效工具调用的重要性三. 单智能体架…