前言
在开发中,我们经常会处理一些敏感信息,敏感信息包括但不限于手机号码、身份证号、银行卡号等,这些信息泄露可能导致用户个人信息的滥用、身份盗用等严重问题。脱敏是一种常用的保护用户隐私的方式,它的目的是减少潜在的风险,同时保持一定的用户信息可读性。
比如咱们在选择用户信息以及展示选座信息时,用户证件号码的脱敏展示
一、hutool工具类
信息脱敏工具-DesensitizedUtil | Hutool
1.1 简介
Hutool 是一个非常实用的 Java 工具库,它通过静态方法封装,旨在简化开发、提高效率。以下是 Hutool 的一些主要特点和功能:
简单易用:Hutool 的 API 设计简洁明了,易于理解和使用,降低了学习成本。
功能丰富:提供了大量的实用工具方法,包括文件操作、日期处理、网络请求、数据转换、加密解密、图片处理等。
性能高效:在设计和实现时注重性能优化,执行效率高。
持续更新:开发者团队会根据 Java 技术的发展和开发者的需求,不断更新和完善工具类库。
模块化:可以根据需求对每个模块单独引入,也可以通过引入
hutool-all
方式引入所有模块。
同时,hutool工具也支持常用的数据脱敏,基本覆盖了常见的敏感信息
用户id
中文姓名
身份证号
座机号
手机号
地址
电子邮件
密码
中国大陆车牌,包含普通车辆、新能源车辆
银行卡
1.2 引入maven配置
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
1.3 使用
import cn.hutool.core.util.DesensitizedUtil;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
*
* @description: Hutool实现数据脱敏
*/
@SpringBootTest
public class HuToolDesensitizationTest {
@Test
public void testPhoneDesensitization(){
String phone="13723231234";
System.out.println(DesensitizedUtil.mobilePhone(phone)); //输出:137****1234
}
@Test
public void testBankCardDesensitization(){
String bankCard="6217000130008255666";
System.out.println(DesensitizedUtil.bankCard(bankCard)); //输出:6217 **** **** *** 5666
}
@Test
public void testIdCardNumDesensitization(){
String idCardNum="411021199901102321";
//只显示前4位和后2位
System.out.println(DesensitizedUtil.idCardNum(idCardNum,4,2)); //输出:4110************21
}
@Test
public void testPasswordDesensitization(){
String password="www.jd.com_35711";
System.out.println(DesensitizedUtil.password(password)); //输出:****************
}
}
二、通过注解方式实现脱敏
2.1 脱敏策略的枚举
public enum DesensitizationTypeEnum {
//自定义
MY_RULE,
//用户id
USER_ID,
//中文名
CHINESE_NAME,
//身份证号
ID_CARD,
//座机号
FIXED_PHONE,
//手机号
MOBILE_PHONE,
//地址
ADDRESS,
//电子邮件
EMAIL,
//密码
PASSWORD,
//中国大陆车牌,包含普通车辆、新能源车辆
CAR_LICENSE,
//银行卡
BANK_CARD
}
2.2 自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {
/**
* 脱敏数据类型,在MY_RULE的时候,startInclude和endExclude生效
*/
DesensitizationTypeEnum type() default DesensitizationTypeEnum.MY_RULE;
/**
* 脱敏开始位置(包含)
*/
int startInclude() default 0;
/**
* 脱敏结束位置(不包含)
*/
int endExclude() default 0;
}
注:只有使用了自定义的脱敏枚举MY_RULE的时候,开始位置和结束位置才生效。
2.3 创建自定义的序列化类
这一步是我们实现数据脱敏的关键。自定义序列化类继承 JsonSerializer,实现ContextualSerializer接口,并重写两个方法。
@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {
private DesensitizationTypeEnum type;
private Integer startInclude;
private Integer endExclude;
@Override
public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
switch (type) {
// 自定义类型脱敏
case MY_RULE:
jsonGenerator.writeString(CharSequenceUtil.hide(str, startInclude, endExclude));
break;
// userId脱敏
case USER_ID:
jsonGenerator.writeString(String.valueOf(DesensitizedUtil.userId()));
break;
// 中文姓名脱敏
case CHINESE_NAME:
jsonGenerator.writeString(DesensitizedUtil.chineseName(String.valueOf(str)));
break;
// 身份证脱敏
case ID_CARD:
jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2));
break;
// 固定电话脱敏
case FIXED_PHONE:
jsonGenerator.writeString(DesensitizedUtil.fixedPhone(String.valueOf(str)));
break;
// 手机号脱敏
case MOBILE_PHONE:
jsonGenerator.writeString(DesensitizedUtil.mobilePhone(String.valueOf(str)));
break;
// 地址脱敏
case ADDRESS:
jsonGenerator.writeString(DesensitizedUtil.address(String.valueOf(str), 8));
break;
// 邮箱脱敏
case EMAIL:
jsonGenerator.writeString(DesensitizedUtil.email(String.valueOf(str)));
break;
// 密码脱敏
case PASSWORD:
jsonGenerator.writeString(DesensitizedUtil.password(String.valueOf(str)));
break;
// 中国车牌脱敏
case CAR_LICENSE:
jsonGenerator.writeString(DesensitizedUtil.carLicense(String.valueOf(str)));
break;
// 银行卡脱敏
case BANK_CARD:
jsonGenerator.writeString(DesensitizedUtil.bankCard(String.valueOf(str)));
break;
default:
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
// 判断数据类型是否为String类型
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
// 获取定义的注解
Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
// 如果该字段上没有注解,用默认的序列化器
if (desensitization == null) {
desensitization = beanProperty.getContextAnnotation(Desensitization.class);
}
// 如果该字段上有注解
if (desensitization != null) {
// 创建定义的序列化类的实例并且返回,入参为注解定义的type,开始位置,结束位置。
return new DesensitizationSerialize(desensitization.type(), desensitization.startInclude(),
desensitization.endExclude());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
2.4 使用
/**
*
* @description:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestPojo {
private String userName;
@Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE)
private String phone;
@Desensitization(type = DesensitizationTypeEnum.PASSWORD)
private String password;
@Desensitization(type = DesensitizationTypeEnum.MY_RULE, startInclude = 0, endExclude = 2)
private String address;
}
@RestController
public class TestController {
@RequestMapping("/test")
public TestPojo testDesensitization(){
TestPojo testPojo = new TestPojo();
testPojo.setUserName("我是用户名");
testPojo.setAddress("地球中国-北京市通州区京东总部2号楼");
testPojo.setPhone("13782946666");
testPojo.setPassword("123456");
System.out.println(testPojo);
return testPojo;
}
}
结果为:
{
"username": "我是用户名",
"phone": "137****6666",
"password": "******",
"address": "**中国-北京市通州区京东总部2号楼"
}
三、自定义序列化器实现脱敏
3.1 实现思路
在 SpringMVC 返回数据时,通过默认的 Jackson 序列化器进行指定,替换为咱们已经包装后的序列化器,这样就能依赖现有解决方案,降低技术复杂度
3.2 自定义序列化器
定义手机号和证件号的 Jackson 自定义序列化器,并在对应需要脱敏的敏感字段上指定自定义序列化器
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* 身份证号脱敏反序列化
*
*/
public class IdCardDesensitizationSerializer extends JsonSerializer<String> {
@Override
public void serialize(String idCard, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
//DesensitizedUtil Hutool工具类,专门用来实现数据脱敏
String phoneDesensitization = DesensitizedUtil.idCardNum(idCard, 4, 4);
jsonGenerator.writeString(phoneDesensitization);
}
}
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* 手机号脱敏反序列化
*
*/
public class PhoneDesensitizationSerializer extends JsonSerializer<String> {
@Override
public void serialize(String phone, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
//DesensitizedUtil Hutool工具类,专门用来实现数据脱敏
String phoneDesensitization = DesensitizedUtil.mobilePhone(phone);
jsonGenerator.writeString(phoneDesensitization);
}
}
3.3 使用
@Data
@Accessors(chain = true)
public class PassengerRespDTO {
/**
* 证件号码
*/
@JsonSerialize(using = IdCardDesensitizationSerializer.class)
private String idCard;
/**
* 手机号
*/
@JsonSerialize(using = PhoneDesensitizationSerializer.class)
private String phone;
}
四、拓展思考
对接前端的敏感数据脱敏展示功能做到上面这些就已经实现了。但是总感觉哪里不对,因为咱们调用详细信息接口获取到手机号、证件号等信息保存入库。如果我在后端服务里去调用乘车人的信息接口,那岂不是也是脱敏的?这样的话,存储到数据库的数据就不准确了,期望是真实的数据,但是实际是脱敏后的
基于这种真实情况,目前做法是拆分一个新的实体,叫做 xxxActualRespDTO
,返回的信息是不脱敏的。其实本质上就是复制了一个一模一样的实体,但是在证件号和手机号字段上不添加 @JsonSerialize
注解,以此满足业务需求
相关文章