详细分析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;方法论。…

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

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

为人处事电影解说,全新升级瀚海跑道一分钟一条视频,全平台可推广,轻轻松松日入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是使…

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; 目录 前言 命名管道 创建一个命名管道 …

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.在非空树中插…

智能物联网与Web3:连接未来数字生活的桥梁

随着科技的不断进步&#xff0c;智能物联网&#xff08;IoT&#xff09;和Web3技术正成为数字化时代的关键驱动力。智能物联网将各种物理设备连接到互联网&#xff0c;使其能够感知环境、收集数据并与其他设备通信&#xff0c;而Web3技术则以去中心化、安全性和透明性为核心&am…

Linux开发板 FTP 服务器移植与搭建

VSFTPD&#xff08;Very Secure FTP Daemon&#xff09;是一个安全、稳定且快速的FTP服务器软件&#xff0c;广泛用于Unix和Linux操作系统。它以其轻量级、高效和易于配置而受到赞誉。VSFTPD不仅支持标准的FTP命令和操作&#xff0c;还提供了额外的安全特性&#xff0c;如匿名F…

Python日志记录库之logbook使用详解

概要 在软件开发和运维中,日志记录是一项至关重要的任务。Python 的 Logbook 库是一个强大而灵活的日志记录工具,提供了丰富的功能和易用的接口。本文将深入探讨 Logbook 库的特性、用法,并通过丰富的示例代码展示其在实际项目中的应用。 Logbook 简介 Logbook 是一个为 P…

本地搭建llama大模型及对话UI

环境说明&#xff1a;MBP 2023 M2Pro芯片 用到的工具/组件/技术&#xff1a;ollama、llama3:8b、docker、open-webui 1.下载ollama ollama官网下载地址&#xff1a;https://ollama.com/download 到ollama官网地址下载对应操作系统版本的ollama平台&#xff0c;按照安装指引…

springboot 获取maven打包时间

springboot 获取maven打包时间 pom <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.13.RELEASE</version><relativePath /> <!-- lookup parent…

民航电子数据库:mysql与cae(insert语法差异)

目录 示例1、cae插入数据时不支持value关键字&#xff0c;只能使用values2、insert时&#xff0c;就算是自增主键&#xff0c;只要新增时包含了主键&#xff0c;该主键就必须有值&#xff0c;否则会报错&#xff1a;字段xxx不能取空值 对接民航电子数据库&#xff0c;本篇记录i…

用栈实现队列——leetcode刷题

题目要求我们只用栈的基本操作 push to top 入栈&#xff0c;peek from top 返回栈顶元素&#xff0c;pop from top 移除并返回栈顶元素&#xff0c;size 栈的大小&#xff0c;is_empty 判断栈是否为空&#xff0c;这几个函数来实现队列&#xff0c;也就是说&#xff0c;我们在…

salesforce 如何访问lwc组件

访问lwc有哪些途径呢? Action ButtonTabAura use lwc(拓展)如何区分是新建页面还是编辑页面 Action Button xml文件中要配置tab<?xml version"1.0" encoding"UTF-8"?> <LightningComponentBundle xmlns"http://soap.sforce.com/2006/04/…

全景剖析阿里云容器网络数据链路(七):Terway DataPath V2(Terway≥1.8.0)

作者&#xff1a;余凯 前言 近几年&#xff0c;企业基础设施云原生化的趋势越来越强烈&#xff0c;从最开始的IaaS化到现在的微服务化&#xff0c;客户的颗粒度精细化和可观测性的需求更加强烈。容器网络为了满足客户更高性能和更高的密度&#xff0c;也一直在高速的发展和演…

Docker 简单使用及安装常用软件

一、Docker 安装、配置与卸载 1.1、Docker 安装 # 1.安装gcc环境 yum -y install gcc gcc-c && \# 2. 卸载docker旧版本&#xff08;可能之前有安装&#xff09; yum -y remove docker docker-common docker-selinux docker-engine && \# 3. 安装依赖的软件包…

PHP源码_最新在线工具箱网站系统源码

项目运行截图 源码贡献 https://githubs.xyz/boot?app41 部分数据库表 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for toolbox_category -- ---------------------------- DROP TABLE IF EXISTS toolbox_category…