有关于数据脱敏处理,小编也是在文章上面看到的,感觉很有意思,那么,便深入研究了一下,首先我们先来看一下数据脱敏之后的结果吧?
用结果说话更能深入人心!!
下面是数据库中的字段:请看Phone属性!数据库中存储的就是用户直接输入的数据!
但是,当我们对该phone字段脱敏处理以后,在前台我们看到的数据是:
这就能够有效的避免一些不必要的信息外泄!比如:银行卡?身份证号?地址?手机号码?等涉及到个人隐私信息!!真的很有用呢!!
那么,我们便来看一下实际的代码吧!!
首先添加依赖包
默认的情况下,如果当前项目已经添加了spring-web
包或者spring-boot-starter-web
包,因为这些jar
包已经集成了jackson
相关包,因此无需重复依赖。
如果当前项目没有jackson
包,可以通过如下方式进行添加相关依赖包
<!-- Jackson Databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>
<!-- Apache Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
编写脱敏类型枚举类,满足不同场景的处理
package com.example.desensitization;
public enum SensitiveEnum {
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD,
/**
* 公司开户银行联号
*/
CNAPS_CODE
}
编写脱敏注解类
package com.example.desensitization;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface SensitiveWrapped {
/**
* 脱敏类型
* @return
*/
SensitiveEnum value();
}
编写脱敏序列化类
package com.example.desensitization;
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 java.io.IOException;
import java.util.Objects;
/**
* 用于序列化字符串的脱敏处理类
* 继承自JsonSerializer<String>,同时实现ContextualSerializer接口
* 该类根据敏感信息类型对字符串进行脱敏处理
*/
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
/**
* 脱敏类型
*/
private SensitiveEnum type;
/**
* 根据脱敏类型对字符串进行脱敏处理
*
* @param s 输入的字符串
* @param jsonGenerator 用于写入处理后的字符串的Json生成器
* @param serializerProvider 序列化提供者
* @throws IOException 如果写入操作导致IO错误
*/
@Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 根据不同的敏感信息类型,调用相应的方法对敏感信息进行处理,并写入JSON
switch (this.type) {
case CHINESE_NAME: {
// 处理并写入中文姓名敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));
break;
}
case ID_CARD: {
// 处理并写入身份证号码敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s));
break;
}
case FIXED_PHONE: {
// 处理并写入固定电话敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));
break;
}
case MOBILE_PHONE: {
// 处理并写入移动电话敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s));
break;
}
case ADDRESS: {
// 处理并写入地址敏感信息,精度为4级
jsonGenerator.writeString(SensitiveInfoUtils.address(s, 4));
break;
}
case EMAIL: {
// 处理并写入电子邮件敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.email(s));
break;
}
case BANK_CARD: {
// 处理并写入银行账户敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));
break;
}
case CNAPS_CODE: {
// 处理并写入银行间清算系统(CNAPS)代码敏感信息
jsonGenerator.writeString(SensitiveInfoUtils.cnapsCode(s));
break;
}
}
}
/**
* 创建上下文相关的序列化器
*
* @param serializerProvider 序列化提供者
* @param beanProperty Bean属性
* @return 创建的JsonSerializer
* @throws JsonMappingException 如果出现映射异常
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
// 为空直接跳过
if (beanProperty != null) {
// 非 String 类直接跳过
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
SensitiveWrapped sensitiveWrapped = beanProperty.getAnnotation(SensitiveWrapped.class);
if (sensitiveWrapped == null) {
sensitiveWrapped = beanProperty.getContextAnnotation(SensitiveWrapped.class);
}
if (sensitiveWrapped != null) {
// 如果能得到注解,就将注解的 value 传入 SensitiveSerialize
return new SensitiveSerialize(sensitiveWrapped.value());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(beanProperty);
}
/**
* 默认构造函数
*/
public SensitiveSerialize() {}
/**
* 带脱敏类型的构造函数
*
* @param type 脱敏类型
*/
public SensitiveSerialize(final SensitiveEnum type) {
this.type = type;
}
}
其中createContextual
的作用是通过字段已知的上下文信息定制JsonSerializer
对象。
编写脱敏工具类
package com.example.desensitization;
import org.apache.commons.lang3.StringUtils;
public class SensitiveInfoUtils {
/**
* 将中文全名中的第一个汉字显示出来,其余部分用两个星号隐藏。
* 例如:李**。
*
* @param fullName 中文全名
* @return 格式化后的姓名,如果输入为空白,则返回空字符串
*/
public static String chineseName(final String fullName) {
if (StringUtils.isBlank(fullName)) {
return "";
}
final String name = StringUtils.left(fullName, 1);
return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
}
/**
* 将中文姓和名合并后,只显示第一个汉字,其他隐藏为2个星号。
* 例如:李**。
*
* @param familyName 姓氏
* @param givenName 名字
* @return 格式化后的姓名,如果输入为空白,则返回空字符串
*/
public static String chineseName(final String familyName, final String givenName) {
if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) {
return "";
}
return chineseName(familyName + givenName);
}
/**
* 对身份证号进行脱敏处理。
* 身份证号的前三位和后四位保持不变,中间部分用星号替换。
* 例如:4201**********5762
*
* @param id 身份证号码字符串,可以是15位或18位
* @return 脱敏后的身份证号码字符串 如果输入为空或者空白字符串,则返回空字符串
*/
public static String idCardNum(final String id) {
// 如果身份证号为空或空白字符串,则直接返回空字符串
if (StringUtils.isBlank(id)) {
return "";
}
// 拼接字符串,前三位加星号填充加后四位
// StringUtils.left(id, 3) 取字符串的前三位
// StringUtils.right(id, 4) 取字符串的后四位
// StringUtils.leftPad 在字符串左侧填充星号,直到达到原字符串长度
// StringUtils.removeStart 移除左侧开始的三个星号,避免星号覆盖前三位身份证号
return StringUtils.left(id, 3).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(id, 4), StringUtils.length(id), "*"),
"***"));
}
/**
* [固定电话] 后四位,其他隐藏<例子:****1234>
*
* @param num 电话号码字符串
* @return 格式化后的电话号码字符串
*/
public static String fixedPhone(final String num) {
if (StringUtils.isBlank(num)) {
return "";
}
// 除了最后四位号码外,其他位数用星号替换
return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
}
/**
* [手机号码] 前三位,后四位,其他隐藏<例子:138******1234>
*
* @param num 手机号码字符串
* @return 格式化后的手机号码字符串
*/
public static String mobilePhone(final String num) {
if (StringUtils.isBlank(num)) {
return "";
}
// 保留前三位和后四位,中间用星号替换
return StringUtils.left(num, 3).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"),
"***"));
}
/**
* [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>
*
* @param address 地址字符串
* @param sensitiveSize 敏感信息长度,即不显示的地址长度
* @return 格式化后的地址字符串
*/
public static String address(final String address, final int sensitiveSize) {
if (StringUtils.isBlank(address)) {
return "";
}
final int length = StringUtils.length(address);
// 地址的最后几位用星号替换,替换长度为敏感信息长度
return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
}
/**
* [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
*/
public static String email(final String email) {
if (StringUtils.isBlank(email)) {
return "";
}
final int index = StringUtils.indexOf(email, "@");
if (index <= 1) {
return email;
} else {
return StringUtils.rightPad(StringUtils.left(email, 1), index, "*")
.concat(StringUtils.mid(email, index, StringUtils.length(email)));
}
}
/**
* [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>
*/
public static String bankCard(final String cardNum) {
if (StringUtils.isBlank(cardNum)) {
return "";
}
return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(
StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"),
"******"));
}
/**
* [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>
*/
public static String cnapsCode(final String code) {
if (StringUtils.isBlank(code)) {
return "";
}
return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*");
}
}
编写测试实体类
最后,我们编写一个实体类UserEntity
,看看转换后的效果如何
package com.example.desensitization;
public class UserEntity {
/**
* 用户ID
*/
private Long userId;
/**
* 用户姓名
*/
private String name;
/**
* 手机号
*/
@SensitiveWrapped(SensitiveEnum.MOBILE_PHONE)
private String mobile;
/**
* 身份证号码
*/
@SensitiveWrapped(SensitiveEnum.ID_CARD)
private String idCard;
/**
* 年龄
*/
private String sex;
/**
* 性别
*/
private int age;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
当我们想要应用的时候,只需要在想要脱敏的字段上加入注解即可!
测试程序如下:
package com.example.desensitization;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SensitiveDemo {
public static void main(String[] args) throws JsonProcessingException {
UserEntity userEntity = new UserEntity();
userEntity.setUserId(1l);
userEntity.setName("张三");
userEntity.setMobile("18000000001");
userEntity.setIdCard("420117200001011000008888");
userEntity.setAge(20);
userEntity.setSex("男");
//通过jackson方式,将对象序列化成json字符串
ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.writeValueAsString(userEntity));
}
}
结果如下:
{"userId":1,"name":"张三","mobile":"180****0001","idCard":"420*****************8888","sex":"男","age":20}
很清晰的看到,转换结果成功!
如果你当前的项目是基于SpringMVC
框架进行开发的,那么在对象返回的时候,框架会自动帮你采用jackson
框架进行序列化。
@RequestMapping("/hello")
public UserEntity hello() {
UserEntity userEntity = new UserEntity();
userEntity.setUserId(1l);
userEntity.setName("张三");
userEntity.setMobile("18000000001");
userEntity.setIdCard("420117200001011000008888");
userEntity.setAge(20);
userEntity.setSex("男");
return userEntity;
}
请求网页http://127.0.0.1:8080/hello
,结果如下:
在实际的业务场景开发中,采用注解方式进行全局数据脱敏处理,可以有效的解决敏感数据隐私泄露的问题。
本文主要从实操层面对数据脱敏处理做了简单的介绍,可能有些网友还有更好的解决方案,欢迎下方留言,后面如果遇到了好的解决办法,也会分享给大家,愿对大家有所帮助!