用 Java 注解对结果集中包含身份证号码的数据进行脱敏处理
文章转载自:用 Java 注解对结果集中包含身份证号码的数据进行脱敏处理
背景
项目涉及导用户手机号码、身份证号等隐私信息,因此期望对响应到客户端结果集中的敏感信息进行脱敏处理,如身份证号码中间 8 位用*
代替,手机号码中间 4 位用*
代替
处理思路
- 定义枚举类,处理不同类型的数据(身份证号、手机号、邮件等)
- 定义注解,用在要处理的字段上,关键是该注解上使用 @JacksonAnnotationsInside 主键,因为 springboot 项目默认使用 jackson 序列化数据(题外话:如果使用 FastJsonHttpMessageConverter 序列化数据,则需要实现 com.alibaba.fastjson.serializer.ValueFilter 类)
- 定义实现类,extends JsonSerializer implements ContextualSerializer
- 脱敏处理帮助类
具体实现
具体实现代码和上面思路一样,由 4 个类完成,最后是要脱敏的实体类
脱敏类型枚举类
/**
* @Author ScarletDrop
* @Time 2020-12-16 19:14
* @Description 需要脱敏字段类型,对以下类型都可以自定义处理
*/
public enum DesensitionType {
//用户id
USER_ID,
//中文名
CHINESE_NAME,
//身份证号
ID_CARD,
//座机号
FIXED_PHONE,
//手机号
MOBILE_PHONE,
//地址
ADDRESS,
//电子邮件
EMAIL,
//密码
PASSWORD;
}
脱敏注解类
import com.kanade.demo.common.enums.DesensitionType;
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;
/**
* @Author ScarletDrop
* @Time 2020-12-16 19:16
* @Description 脱敏注解
*/
@JacksonAnnotationsInside
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {
/**
* 脱敏类型规则
* @return
*/
DesensitionType value();
}
脱敏具体实现类
import com.kanade.demo.common.enums.DesensitionType;
import com.kanade.demo.common.utils.DesensitionType;
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;
/**
* @Author ScarletDrop
* @Time 2020-12-16 19:18
* @Description 自定义脱敏序列化类
*/
public class DesensitizationSerialize extends JsonSerializer implements ContextualSerializer {
private DesensitionType type;
public DesensitizationSerialize(){}
public DesensitizationSerialize(final DesensitionType type){
this.type = type;
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty beanProperty) throws JsonMappingException {
if(beanProperty != null){
//获取字段是否有脱敏注解,有则创建一个序列化对象,并调用serialize方法
Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
if(desensitization == null){
desensitization = beanProperty.getContextAnnotation(Desensitization.class);
}
// 如果定义了脱敏注解,就将需要脱敏的类型传入DesensitizationSerialize构造函数
if(desensitization != null){
return new DesensitizationSerialize(desensitization.value());
}
return provider.findValueSerializer(beanProperty.getType() , beanProperty);
}
return provider.findNullValueSerializer(beanProperty);
}
@Override
public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
switch (this.type){
case USER_ID:
jsonGenerator.writeNumber(DesensitizedUtils.userId());
break;
case CHINESE_NAME:
jsonGenerator.writeString(DesensitizedUtils.chineseName(String.valueOf(value)));
break;
case ID_CARD:
jsonGenerator.writeString(DesensitizedUtils.idCardNum(String.valueOf(value),3,3));
break;
case FIXED_PHONE:
jsonGenerator.writeString(DesensitizedUtils.fixedPhone(String.valueOf(value)));
break;
case MOBILE_PHONE:
jsonGenerator.writeString(DesensitizedUtils.mobilePhone(String.valueOf(value)));
break;
case ADDRESS:
jsonGenerator.writeString(DesensitizedUtils.address(String.valueOf(value), 8));
break;
case EMAIL:
jsonGenerator.writeString(DesensitizedUtils.email(String.valueOf(value)));
break;
case PASSWORD:
jsonGenerator.writeString(DesensitizedUtils.password(String.valueOf(value)));
break;
default:
}
}
}
脱敏处理帮助类
import org.apache.commons.lang.StringUtils;
/**
* @Author ScarletDrop
* @Time 2020-12-16 19:21
* @Description 脱敏规则具体实现
*/
public class DesensitizedUtils {
/**
* 【用户id】不对外提供userId
* @return
*/
public static Long userId(){
return Long.valueOf(0);
}
/**
* 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
*
* @param fullName
* @return
*/
public static String chineseName(String fullName) {
if (StringUtils.isBlank(fullName)) {
return "";
}
String name = StringUtils.left(fullName, 1);
return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
}
/**
* 【身份证号】前三位 和后三位
*
* @param front
* @param end
* @return
*/
public static String idCardNum(String idCardNum, int front, int end) {
//身份证不能为空
if (StringUtils.isEmpty(idCardNum)) {
return "";
}
//需要截取的长度不能大于身份证号长度
if ((front + end) > idCardNum.length()) {
return "";
}
//需要截取的不能小于0
if (front < 0 || end < 0) {
return "";
}
//计算*的数量
int asteriskCount = idCardNum.length() - (front + end);
StringBuffer asteriskStr = new StringBuffer();
for (int i = 0; i < asteriskCount; i++) {
asteriskStr.append("*");
}
String regex = "(\\w{" + String.valueOf(front) + "})(\\w+)(\\w{" + String.valueOf(end) + "})";
return idCardNum.replaceAll(regex, "$1" + asteriskStr + "$3");
}
/**
* 【固定电话 前四位,后两位
*
* @param num
* @return
*/
public static String fixedPhone(String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.left(num, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 2), StringUtils.length(num), "*"), "****"));
}
/**
* 【手机号码】前三位,后两位,其他隐藏,比如135******10
*
* @param num
* @return
*/
public static String mobilePhone(String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 2), StringUtils.length(num), "*"), "***"));
}
/**
* 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
*
* @param address
* @param sensitiveSize 敏感信息长度
* @return
*/
public static String address(String address, int sensitiveSize) {
if (StringUtils.isBlank(address)) {
return "";
}
int length = StringUtils.length(address);
return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
}
/**
* 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com>
*
* @param email
* @return
*/
public static String email(String email) {
if (StringUtils.isBlank(email)) {
return "";
}
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)));
}
}
/**
* 【密码】密码的全部字符都用*代替,比如:******
*
* @param password
* @return
*/
public static String password(String password) {
if (StringUtils.isBlank(password)) {
return "";
}
String pwd = StringUtils.left(password, 0);
return StringUtils.rightPad(pwd, StringUtils.length(password), "*");
}
}
要脱敏处理实体类,使用自定义脱敏注解
@Data
@Accessors(chain = true)
public class XXXXXPo {
@Id
@ApiModelProperty(value = "主键")
private Long affairsInfoId;
@ApiModelProperty(value = "身份证号")
@Desensitization(value = DesensitionType.ID_CARD)
private String credentialNo;
@ApiModelProperty("用户电话")
@Desensitization(value = DesensitionType.MOBILE_PHONE)
private String phone;
@ApiModelProperty("邮箱")
@Desensitization(value = DesensitionType.EMAIL)
private String email;
}
简单用法就是这些,可以自形扩展,比如自定义 id 取哪几位等等
Q.E.D.