当前位置: 首页 > news >正文

SpringBoot中优雅的实现隐私数据脱敏(提供Gitee源码)

前言:在实际项目开发中,可能会对一些用户的隐私信息进行脱敏操作,传统的方式很多都是用replace方法进行手动替换,这样会由很多冗余的代码并且后续也不好维护,本期就讲解一下如何在SpringBoot中优雅的通过序列化的方式去实现数据的脱敏操作!

目录

一、导入pom依赖

二、DesensitizationEnum枚举类

三、Desensitization自定义注解

四、DesensitizationSerialize脱敏序列化器

五、User实体类

六、UserController请求层

七、运行测试

八、Gitee源码

九、总结


一、导入pom依赖

完整代码:

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.18</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

二、DesensitizationEnum枚举类

在DesensitizationSerialize序列化类中,会根据脱敏注解的type值,也就是DesensitizationEnum 中的类型,来判断需要使用哪种脱敏方式。

这边我就简单定义了5个枚举类型:

完整代码:

package com.example.desensitization.constant;public enum DesensitizationEnum {/*** 自定义*/CUSTOM_RULE,/*** 身份证号码*/ID_CARD_NO,/*** 电话号码*/PHONE,/*** 地址*/ADDRESS,/*** 银行卡号*/BANK_CARD_NO,
}

三、Desensitization自定义注解

这个是自定义的注解@Desensitization,用于标注需要进行脱敏的字段。

主要包含以下元注解和属性

1、@Target(ElementType.FIELD):表示该注解只能用于字段上。

2、@Retention(RetentionPolicy.RUNTIME):表示该注解可以保留到运行时。

3、@JacksonAnnotationsInside:是一个Jackson的元注解,表示该注解可以作为Json序列化的注解。

4、@JsonSerialize:标注使用DesensitizationSerialize来进行序列化。

5、DesensitizationEnum type:需要脱敏的类型,对应枚举中的脱敏类型。

6、int start/end:可选的起始位置和结束位置,对于脱敏类型为字符串时有效。

完整代码:

package com.example.desensitization.annotation;import com.example.desensitization.constant.DesensitizationEnum;
import com.example.desensitization.serialize.DesensitizationSerialize;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {DesensitizationEnum type();int start() default 0;int end() default 0;}

四、DesensitizationSerialize脱敏序列化器

1、继承JsonSerializer<String>JsonSerializer是Jackson的序列化器基类,实现了将对象序列化为JSON的核心方法,这里继承它是为了实现字符串的自定义序列化。

2、实现ContextualSerializer接口ContextualSerializer可以让序列化器基于上下文环境进行定制化,实现这个接口后,可以实现createContextual()方法。

关键代码:

@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {private DesensitizationEnum type;private Integer start;private Integer end;
}

自定义序列化器方式可以实现非侵入式的灵活脱敏,对业务代码零侵入,且不依赖Spring等框架,更适合编写独立的应用服务。当然,AOP实现也有其适用场景,可以作为另一种可选方案。

createContextual()方法:

1、Controller的user()方法被调用,构建并返回了一个User对象。

2、开始对User对象进行JSON序列化,会先调用我们定义的DesensitizationSerialize中createContextual()方法,如果这个实体类被createContextual()方法处理过,则以后不会再走该方法,直接走serialize()方法。

3、DesensitizationSerialize会检查当前类字段是否有@Desensitization注解,如果有N个,则根据注解的type、start和end参数创建N个DesensitizationSerialize实例。

关键代码:

@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)throws JsonMappingException {if (beanProperty != null) {// 获取当前正在处理的字段的类型,判断如果是 String 类型则进行后续脱敏逻辑处理if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {// 通过 beanProperty 获取在字段上标注的 @Desensitization 注解Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);// 如果没有就尝试获取类注解if (desensitization == null) {desensitization = beanProperty.getContextAnnotation(Desensitization.class);}// 不为nullif (desensitization != null) {// 如果获取到了注解,则根据注解的 type、start 和 end 参数创建 DesensitizationSerialize 实例,这是脱敏处理的序列化器return new DesensitizationSerialize(desensitization.type(), desensitization.start(),desensitization.end());}}// 直接返回默认的序列化器return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}// 直接返回默认的序列化器return serializerProvider.findNullValueSerializer(null);}

serialize方法: 

JsonGenerator是Jackjson提供的JSON生成器类。在自定义序列化器的serialize()方法中,会传入JsonGenerator实例。serialize()方法需要通过JsonGenerator将脱敏后的字符串写入到结果JSON中。

CharSequenceUtil和DesensitizedUtil都是hutool提供的工具类。

整体流程是:

1、根据注解的参数动态选择脱敏策略。

2、调用对应 Hutool 的脱敏函数处理字符串。

3、将脱敏结果写入JSON。

关键代码:

    @Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)throws IOException {switch (type){//自定义case CUSTOM_RULE:jsonGenerator.writeString(CharSequenceUtil.hide(s, start, end));break;//身份证case ID_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.idCardNum(s, 2, 6));break;//手机号case PHONE:jsonGenerator.writeString(DesensitizedUtil.mobilePhone(s));break;//地址case ADDRESS:jsonGenerator.writeString(DesensitizedUtil.address(s, 2));break;// 银行卡脱敏case BANK_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.bankCard(s));break;default:}}

综上,整体的执行逻辑如下:

1、Controller返回一个实体对象。

2、如果实体对象是第一次进行脱敏,则会调用createContextual()方法。

3、获取当前实体对象所有使用Desensitization注解的字符串字段,创建对应的DesensitizationSerialize实例,实现脱敏处理的序列化器。

4、执行serialize()方法中switch的处理逻辑,由JsonGenerator将脱敏后的字符串写入到结果JSON中。

5、返回Json数据。

完整代码:

package com.example.desensitization.serialize;import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.example.desensitization.annotation.Desensitization;
import com.example.desensitization.constant.DesensitizationEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Objects;@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {private DesensitizationEnum type;private Integer start;private Integer end;@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)throws IOException {switch (type){//自定义case CUSTOM_RULE:jsonGenerator.writeString(CharSequenceUtil.hide(s, start, end));break;//身份证case ID_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.idCardNum(s, 2, 6));break;//手机号case PHONE:jsonGenerator.writeString(DesensitizedUtil.mobilePhone(s));break;//地址case ADDRESS:jsonGenerator.writeString(DesensitizedUtil.address(s, 2));break;// 银行卡脱敏case BANK_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.bankCard(s));break;default:}}@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)throws JsonMappingException {if (beanProperty != null) {// 获取当前正在处理的字段的类型,判断如果是 String 类型则进行后续脱敏逻辑处理if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {// 通过 beanProperty 获取在字段上标注的 @Desensitization 注解Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);// 如果没有就尝试获取类注解if (desensitization == null) {desensitization = beanProperty.getContextAnnotation(Desensitization.class);}// 不为nullif (desensitization != null) {// 如果获取到了注解,则根据注解的 type、start 和 end 参数创建 DesensitizationSerialize 实例,这是脱敏处理的序列化器return new DesensitizationSerialize(desensitization.type(), desensitization.start(),desensitization.end());}}// 直接返回默认的序列化器return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}// 直接返回默认的序列化器return serializerProvider.findNullValueSerializer(null);}
}

五、User实体类

给想要脱敏的字段加上 @Desensitization(type = DesensitizationEnum.枚举类型)注解即可。

完整代码:

package com.example.desensitization.domain;import com.example.desensitization.annotation.Desensitization;
import com.example.desensitization.constant.DesensitizationEnum;
import lombok.Builder;
import lombok.Data;@Data
@Builder
public class User {/*** 主键*/private String id;/*** 用户名*/private String name;/*** 身份证号码*/@Desensitization(type = DesensitizationEnum.ID_CARD_NO)private String idCardNo;/*** 电话号码*/@Desensitization(type = DesensitizationEnum.PHONE)private String phone;/*** 地址*/@Desensitization(type = DesensitizationEnum.CUSTOM_RULE,start = 2,end = 5)private String address;/*** 银行卡号*/@Desensitization(type = DesensitizationEnum.BANK_CARD_NO)private String bankCardNo;}

六、UserController请求层

完整代码:

package com.example.desensitization.controller;import com.example.desensitization.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.UUID;@RestController
@RequestMapping
public class UserController {@GetMapping("/user")public User user(){User user = User.builder().id(UUID.randomUUID().toString()).name("张三").idCardNo("32089809285012823").phone("13919819285").bankCardNo("62427292012731238812").address("江苏省南通市").build();return user;}}

七、运行测试

浏览器直接访问:http://localhost:8080/user

可以看到隐私的信息都进行了数据脱敏的处理!

八、Gitee源码

源码地址:SpringBoot中优雅的实现隐私数据脱敏

九、总结

以上就是我对于SpringBoot中如何优雅的实现隐私数据脱敏的完整教程,如有问题,欢迎评论区留言! 

相关文章:

SpringBoot中优雅的实现隐私数据脱敏(提供Gitee源码)

前言&#xff1a;在实际项目开发中&#xff0c;可能会对一些用户的隐私信息进行脱敏操作&#xff0c;传统的方式很多都是用replace方法进行手动替换&#xff0c;这样会由很多冗余的代码并且后续也不好维护&#xff0c;本期就讲解一下如何在SpringBoot中优雅的通过序列化的方式去…...

Elasticsearch集群shard过多后导致的性能问题分析

1.问题现象 上午上班以后发现ES日志集群状态不正确&#xff0c;集群频繁地重新发起选主操作。对外不能正常提供数据查询服务&#xff0c;相关日志数据入库也产生较大延时 2.问题原因 相关日志 查看ES集群日志如下&#xff1a; 00:00:51开始集群各个节点与当时的master节点…...

Unity框架学习--5 事件中心管理器

作用&#xff1a;访问其它脚本时&#xff0c;不直接访问&#xff0c;而是通过发送一条“命令”&#xff0c;让监听了这条“命令”的脚本自动执行对应的逻辑。 原理&#xff1a; 1、让脚本向事件中心添加事件&#xff0c;监听对应的“命令”。 2、发送“命令”&#xff0c;事件…...

(二)结构型模式:3、过滤器模式(Filter、Criteria Pattern)(C++示例)

目录 1、过滤器模式&#xff08;Filter、Criteria Pattern&#xff09;含义 2、过滤器模式应用场景 3、过滤器模式主要几个关键角色 4、C实现过滤器模式的示例 1、过滤器模式&#xff08;Filter、Criteria Pattern&#xff09;含义 &#xff08;1&#xff09;过滤器模式是…...

谷歌在Chrome浏览器中推进抗量子加密技术

近日&#xff0c;Chromium博客上发表的一篇博文称&#xff0c;为了加强网络安全&#xff0c;应对迫在眉睫的量子计算机威胁&#xff0c;谷歌各个团队密切合作&#xff0c;为网络向抗量子密码学的过渡做好准备。 谷歌的Chrome团队在博客中写道&#xff0c;该项目涉及修订技术标准…...

Kotlin的数组

在 Kotlin 中&#xff0c;数组是一种固定大小的有序集合&#xff0c;可以存储相同类型的元素。Kotlin 提供了两种类型的数组&#xff1a;原生数组和数组类。以下是 Kotlin 中数组的详细使用方法&#xff1a; 1.创建数组 Kotlin 支持使用 arrayOf() 函数来创建数组&#xff1a;…...

centos 安装docker

1.更新你的系统: sudo yum update -y2.安装必需的软件包: Docker 需要 yum-utils, device-mapper-persistent-data 和 lvm2 软件包来运行。安装它们&#xff1a; sudo yum install -y yum-utils device-mapper-persistent-data lvm23.设置 Docker 的仓库: 使用以下命令添加 D…...

Oracle-如何判断字符串包含中文字符串(汉字),删除中文内容及保留中文内容

今天遇见一个问题需要将字段中包含中文字符串的筛选出来 --建表 CREATE TABLE HADOOP1.AAA ( ID VARCHAR2(255) ); --添加字段INSERT INTO HADOOP1.AAA(ID)VALUES(理解);....--查询表内容SELECT * FROM HADOOP1.AAA;在网上查找了一下有以下三种方式&#xff1a; 第一种&#…...

File 类的用法, InputStream和Reader, OutputStream和Writer 的用法

前言 普通的文件长这样&#xff1a; 其实目录也是一种特殊文件&#xff1a; 一、文件前缀知识 &#xff08;一&#xff09;绝对路径和相对路径 以盘符开头的的路径&#xff0c;叫做绝对路径&#xff0c;如&#xff1a;D:\360Downloads\cat.jpg 以.或..开头的路径&#xff0c…...

AtCoder Beginner Contest 315 Task:A/B/C/E

A - tcdr 处理字符串简单题&#xff0c;题目要求去除字符串中的a,e,i,o,u即可 #include<iostream> using namespace std; int main() {string s;cin>>s;for(int i0;i<s.length();i){if(s[i]a||s[i]e||s[i]i||s[i]o||s[i]u)continue;cout<<s[i];} }B - T…...

【项目实践】基于LSTM的一维数据扩展与预测

基于LSTM的一维数据拟合扩展 一、引(fei)言(hua) 我在做Sri Lanka生态系统服务价值计算时&#xff0c;中间遇到了一点小问题。从世界粮农组织(FAO)上获得Sri Lanka主要农作物产量和价格数据时&#xff0c;其中的主要作物Sorghum仅有2001-2006年的数据&#xff0c;而Millet只有…...

webshell实践,在nginx上实现负载均衡

1、配置多台虚拟机&#xff0c;用作服务器 在不同的虚拟机上安装httpd服务 我采用了三台虚拟机进行服务器设置&#xff1a;192.168.240.11、192.168.240.12、192.168.240.13 [rootnode0-8 /]# yum install httpd -y #使用yum安装httpd服务#开启httpd服务 [rootnode0-8 /]# …...

LVS+Keepalived集群

keepalived Keepalived及其工作原理 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题 在一个LVS服务集群中通常有主服务器&#xff08;MASTER&#xff09;和备份服务器&#xff08;BACKUP&#xff09;两种角色的服务…...

Java的网络编程

网络编程 两台设备之间通过网络实现数据传输&#xff0c;将数据通过网络从一台设备传输到另一台设备 网络 两台或多台设备通过一定物理设备连接起来构成了网络 网络又分为: 局域网:覆盖范围最小&#xff0c;仅仅覆盖一个教室或一个机房城域网:覆盖范围较大&#xff0c;可以…...

kafka配置远程连接

要想实现在本地连接服务器的kafka&#xff0c;则必须在远程kafka配置远程连接 默认的 kafka 配置是无法远程访问的&#xff0c;解决该问题有几个方案。 方案1 advertised.listenersPLAINTEXT://IP:9092 注意必须是 ip&#xff0c;不能是 hostname 方案2 advertised.listene…...

css实现渐变色border

方式1 div {border: 4px solid;border-image: linear-gradient(to right, #8f41e9, #578aef) 1; }/* 或者 */ div {border: 4px solid;border-image-source: linear-gradient(to right, #8f41e9, #578aef);border-image-slice: 1; }作者&#xff1a;MudOnTire 链接&#xff1a…...

管理 IBM Spectrum LSF

管理 IBM Spectrum LSF 了解如何管理 IBM Spectrum LSF 集群&#xff0c;控制守护程序&#xff0c;更改集群配置以及使用主机和队列。 管理 LSF 作业和作业调度策略。 查看作业信息和控制作业。 了解如何配置资源并将其分配给 LSF 作业。 了解如何在 LSF 集群中提交&#xff0…...

117页数字化转型与产业互联网发展趋势及机会分析报告PPT

导读&#xff1a;原文《》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 喜欢文章&#xff0c;您可以点赞评论转发本文&#xff0c;了解更多内容请私信&#xff1a;方…...

【JavaWeb】实训的长篇笔记(上)

JavaWeb的实训是学校的一门课程&#xff0c;老师先讲解一些基础知识&#xff0c;然后让我们自己开发一个比较简单的Web程序。可涉及的知识何其之多&#xff0c;不是实训课的 3 周时间可以讲得完的&#xff0c;只是快速带过。他说&#xff1a;重点是Web开发的流程。 我的实训草草…...

如何使用Docker安装AWVS?

前言 还记得很早的时候使用AWVS&#xff0c;还需要找位置&#xff0c;贴补丁&#xff0c;放文件&#xff0c;现在慢慢掌握Docker后发现&#xff0c;使用Docker去部署一些东西就很方便&#xff0c;当然也包括AWVS。 我们今天带大家通过Docker部署AWVS&#xff08;有中文哦&…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...