应用场景
数据库中密文存储身份证、手机号等敏感信息时,Java需要将密文数据转换为明文并脱敏返回给前端。
脱敏方式枚举类
public enum DesensitizationTypeEnum {
/**
* 默认方式
*/
DEFAULT,
/**
* 头部脱敏
*/
HEAD,
/**
* 尾部脱敏
*/
TAIL,
/**
* 中间脱敏
*/
MIDDLE,
/**
* 头尾脱敏
*/
HEAD_TAIL,
/**
* 全部脱敏
*/
ALL,
/**
* 不脱敏,相当于没打这个注解
*/
NONE;
}
脱敏数据类型枚举类
@Getter
@NoArgsConstructor
public enum DesensitizationDataTypeEnum {
/**
* 用户ID
*/
USER_ID,
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 密码
*/
PASSWORD,
/**
* 车牌
*/
CAR_LICENSE,
/**
* 银行卡号
*/
BANK_CARD,
/**
* 其他
*/
OTHER;
}
脱敏工具类
public class DesensitizationUtil {
public DesensitizationUtil() {
}
public static String desensitized(CharSequence str, DesensitizationDataTypeEnum desensitizedType) {
if (StrUtil.isBlank(str)) {
return "";
} else {
String newStr = String.valueOf(str);
switch (desensitizedType.ordinal()) {
case 1:
newStr = String.valueOf(userId());
break;
case 2:
newStr = chineseName(String.valueOf(str));
break;
case 3:
newStr = idCardNum(String.valueOf(str), 1, 2);
break;
case 4:
newStr = fixedPhone(String.valueOf(str));
break;
case 5:
newStr = mobilePhone(String.valueOf(str));
break;
case 6:
newStr = address(String.valueOf(str));
break;
case 7:
newStr = email(String.valueOf(str));
break;
case 8:
newStr = password(String.valueOf(str));
break;
case 9:
newStr = carLicense(String.valueOf(str));
break;
case 10:
newStr = bankCard(String.valueOf(str));
}
return newStr;
}
}
public static Long userId() {
return 0L;
}
/**
* 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
*
* @param fullName 姓名
* @return 脱敏后的姓名
*/
public static String chineseName(String fullName) {
return StrUtil.isBlank(fullName) ? "" : StrUtil.hide(fullName, 1, fullName.length());
}
/**
* 【身份证号】显示前1位和后2位,其他隐藏。共计18位或者15位,比如:1*****************1
*
* @param idCardNum 身份证号
* @param front 保留:前面的front位数;从1开始
* @param end 保留:后面的end位数;从1开始
* @return 脱敏后的身份证号
*/
public static String idCardNum(String idCardNum, int front, int end) {
if (StrUtil.isBlank(idCardNum)) {
return "";
} else if (front + end > idCardNum.length()) {
return "";
} else {
return front >= 0 && end >= 0 ? StrUtil.hide(idCardNum, front, idCardNum.length() - end) : "";
}
}
/**
* 【固定电话】 前四位,后两位
*
* @param num 固定电话
* @return 脱敏后的固定电话
*/
public static String fixedPhone(String num) {
return StrUtil.isBlank(num) ? "" : StrUtil.hide(num, 4, num.length() - 2);
}
/**
* 【手机号码】前三位,后四位,其他隐藏,比如:138******1234
*
* @param num 手机号码
* @return 脱敏后的手机号码
*/
public static String mobilePhone(String num) {
return StrUtil.isBlank(num) ? "" : StrUtil.hide(num, 3, num.length() - 4);
}
/**
* 【地址】只显示到地区的三分之二,尾部脱敏,不显示详细地址,比如:北京市海淀区****
*
* @param address 地址
* @return 脱敏后的地址
*/
public static String address(String address) {
if (StrUtil.isBlank(address)) {
return "";
} else {
// 地址尾部脱敏三分之一
int length = address.length();
int end = length / 3;
return StrUtil.hide(address, length - end, length);
}
}
/**
* 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
*
* @param email 邮箱
* @return 脱敏后的邮箱
*/
public static String email(String email) {
if (StrUtil.isBlank(email)) {
return "";
} else {
int index = StrUtil.indexOf(email, '@');
return index <= 1 ? email : StrUtil.hide(email, 1, index);
}
}
/**
* 【密码】密码的全部字符都用*代替,比如:******
*
* @param password 密码
* @return 脱敏后的密码
*/
public static String password(String password) {
return StrUtil.isBlank(password) ? "" : StrUtil.repeat('*', password.length());
}
/**
* 【中国车牌】车牌中间用*代替 eg1:null -》 "" eg1:"" -》 "" eg3:苏D40000 -》 苏D4***0 eg4:陕A12345D -》 陕A1****D eg5:京A123 -》 京A123 如果是错误的车牌,不处理
*
* @param carLicense 完整的车牌号
* @return 脱敏后的车牌号
*/
public static String carLicense(String carLicense) {
if (StrUtil.isBlank(carLicense)) {
return "";
} else {
if (carLicense.length() == 7) {
carLicense = StrUtil.hide(carLicense, 3, 6);
} else if (carLicense.length() == 8) {
carLicense = StrUtil.hide(carLicense, 3, 7);
}
return carLicense;
}
}
/**
* 银行卡号脱敏 eg: 1101 **** **** **** 3256
*
* @param bankCardNo 银行卡号
* @return 脱敏后的银行卡号
*/
public static String bankCard(String bankCardNo) {
if (StrUtil.isBlank(bankCardNo)) {
return bankCardNo;
} else {
bankCardNo = StrUtil.trim(bankCardNo);
if (bankCardNo.length() < 9) {
return bankCardNo;
} else {
int length = bankCardNo.length();
int midLength = length - 8;
StringBuilder buf = new StringBuilder();
buf.append(bankCardNo, 0, 4);
for (int i = 0; i < midLength; ++i) {
if (i % 4 == 0) {
buf.append(' ');
}
buf.append('*');
}
buf.append(' ').append(bankCardNo, length - 4, length);
return buf.toString();
}
}
}
}
脱敏注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerializer.class)
public @interface Desensitization {
/**
* 脱敏数据类型
*
* @return
*/
DesensitizationDataTypeEnum dataType();
/**
* 是否解密,默认false
*/
boolean decrypt() default false;
/**
* 脱敏类型
*
* @return
*/
DesensitizationTypeEnum type() default DesensitizationTypeEnum.DEFAULT;
/**
* 尾部不脱敏的长度,默认1,当type为HEAD或HEAD_TAIL时使用
*/
int tailNoMaskLen() default 1;
/**
* 头部不脱敏的长度,默认1,当type为TAIL或HEAD_TAIL时使用
*/
int headNoMaskLen() default 1;
/**
* 中间不脱敏的长度,默认1,当type为MIDDLE时使用
*/
int middleNoMaskLen() default 1;
/**
* 脱敏字符,默认*
*/
char maskCode() default '*';
}
脱敏序列化器
@NoArgsConstructor
public class DesensitizationSerializer extends JsonSerializer implements ContextualSerializer {
private Desensitization desensitization;
public DesensitizationSerializer(Desensitization desensitization) {
this.desensitization = desensitization;
}
@Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(desensitize(s));
}
@Override
public JsonSerializer> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
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) {
return new DesensitizationSerializer(desensitization);
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
/**
* 脱敏处理
*/
private String desensitize(String s) {
if (StrUtil.isNotBlank(s)) {
DesensitizationDataTypeEnum dataType = desensitization.dataType();
DesensitizationTypeEnum type = desensitization.type();
// 解密处理
boolean decrypt = desensitization.decrypt();
if (decrypt) {
String value = PasswordUtil.decrypt(s);
// 正则匹配,如果匹配到身份证或手机号,则返回解密后的值,否则返回原值
if (ReUtil.contains(ReConstant.ID_CARD, value) || ReUtil.contains(ReConstant.MOBILE, value)) {
s = value;
}
}
switch (type) {
case DEFAULT:
// 默认方式,根据dataType自动选择脱敏方式
s = autoDesensitize(s, dataType);
break;
case HEAD:
// 头部脱敏
s = headDesensitize(s);
break;
case TAIL:
// 尾部脱敏
s = tailDesensitize(s);
break;
case MIDDLE:
s = middleDesensitize(s);
break;
case HEAD_TAIL:
s = headTailDesensitize(s);
break;
case ALL:
s = allDesensitize(s);
break;
case NONE:
// 不做脱敏
break;
default:
}
}
return s;
}
/**
* 全部脱敏
*/
private String allDesensitize(String s) {
return StrUtil.fillBefore(s, desensitization.maskCode(), s.length());
}
/**
* 头尾脱敏
*/
private String headTailDesensitize(String s) {
int middleNoMaskLen = desensitization.middleNoMaskLen();
if (middleNoMaskLen >= s.length()) {
// 如果中间不脱敏的长度大于等于字符串的长度,不进行脱敏
return s;
}
int len = s.length() - middleNoMaskLen;
// 头部脱敏
int headStart = 0;
int headEnd = len / 2;
s = StrUtil.replace(s, headStart, headEnd, desensitization.maskCode());
// 尾部脱敏
int tailStart = s.length() - (len - len / 2);
int tailEnd = s.length();
return StrUtil.replace(s, tailStart, tailEnd, desensitization.maskCode());
}
/**
* 中间脱敏
*/
private String middleDesensitize(String s) {
int headNoMaskLen = desensitization.headNoMaskLen();
int tailNoMaskLen = desensitization.tailNoMaskLen();
if (headNoMaskLen + tailNoMaskLen >= s.length()) {
// 如果头部不脱敏的长度+尾部不脱敏长度 大于等于字符串的长度,不进行脱敏
return s;
}
int start = headNoMaskLen;
int end = s.length() - tailNoMaskLen;
return StrUtil.replace(s, start, end, desensitization.maskCode());
}
/**
* 尾部脱敏
*/
private String tailDesensitize(String s) {
int headNoMaskLen = desensitization.headNoMaskLen();
if (headNoMaskLen >= s.length()) {
// 如果头部不脱敏的长度大于等于字符串的长度,不进行脱敏
return s;
}
int start = headNoMaskLen;
int end = s.length();
return StrUtil.replace(s, start, end, desensitization.maskCode());
}
/**
* 头部脱敏
*/
private String headDesensitize(String s) {
int tailNoMaskLen = desensitization.tailNoMaskLen();
if (tailNoMaskLen >= s.length()) {
// 如果尾部不脱敏的长度大于等于字符串的长度,不进行脱敏
return s;
}
int start = 0;
int end = s.length() - tailNoMaskLen;
return StrUtil.replace(s, start, end, desensitization.maskCode());
}
/**
* 根据数据类型自动脱敏
*/
private String autoDesensitize(String s, DesensitizationDataTypeEnum dataType) {
switch (dataType) {
case CHINESE_NAME:
s = DesensitizationUtil.chineseName(s);
break;
case FIXED_PHONE:
s = DesensitizationUtil.fixedPhone(s);
break;
case MOBILE_PHONE:
s = DesensitizationUtil.mobilePhone(s);
break;
case ADDRESS:
s = DesensitizationUtil.address(s);
break;
case PASSWORD:
s = DesensitizationUtil.password(s);
break;
case BANK_CARD:
s = DesensitizationUtil.bankCard(s);
break;
case EMAIL:
s = DesensitizationUtil.email(s);
break;
case ID_CARD:
s = DesensitizationUtil.idCardNum(s, 6, 4);
break;
case OTHER:
// 其他类型的不支持以默认方式脱敏,直接返回
break;
default:
}
return s;
}
}
返回对象
@ApiModelProperty(value = "证件号码")
@Desensitization(dataType = DesensitizationDataTypeEnum.ID_CARD, decrypt = true)
@Encrypt // 前端传参时对数据进行加密匹配数据库密文身份证
private String idcard;
@ApiModelProperty(value = "联系电话: 一般登记手机密码,将在找回密码、修改密码时用于发送验证短信。")
@Desensitization(dataType = DesensitizationDataTypeEnum.MOBILE_PHONE, decrypt = true)
@Encrypt
private String tel;
@ApiModelProperty(value = "住址")
@Desensitization(dataType = DesensitizationDataTypeEnum.ADDRESS)
private String address;