1.自定义脱敏注解
标注在实体类 具体 需要脱敏 字段上
其中用到Jackson的两个注解, 并标明使用我们自定义脱敏策略即序列化后脱敏
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;/*** 对象脱敏注解** @version v1.0**/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)@JacksonAnnotationsInside@JsonSerialize(using = SensitiveSerialize.class)public @interface Sensitive {/*** 脱敏数据类型, 非Customer时, 将忽略 refixNoMaskLen 和 suffixNoMaskLen 和 maskStr*/SensitiveTypeEnum type() default SensitiveTypeEnum.CUSTOMER;/*** 前置不需要打码的长度*/int prefixNoMaskLen() default 0;/*** 后置不需要打码的长度*/int suffixNoMaskLen() default 0;/*** 用什么打码*/String maskStr() default "*";}
注解按生命周期来划分可分为3类:
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
2.自定义脱敏逻辑
借助Jackson类和接口实现序列化才脱敏
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 com.pofly.air.common.core.util.DesensitizedUtils;import lombok.AllArgsConstructor;import lombok.NoArgsConstructor;import java.io.IOException;import java.util.Objects;/*** <p>* 脱敏序列化*/@NoArgsConstructor@AllArgsConstructorpublic class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {private SensitiveTypeEnum type;private Integer prefixNoMaskLen;private Integer suffixNoMaskLen;private String maskStr;@Overridepublic void serialize(final String origin, final JsonGenerator jsonGenerator,final SerializerProvider serializerProvider) throws IOException {switch (type) {case CHINESE_NAME:jsonGenerator.writeString(DesensitizedUtils.chineseName(origin));break;case ID_CARD:jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin));break;case FIXED_PHONE:jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin));break;case MOBILE_PHONE:jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin));break;case ADDRESS:jsonGenerator.writeString(DesensitizedUtils.address(origin));break;case EMAIL:jsonGenerator.writeString(DesensitizedUtils.email(origin));break;case BANK_CARD:jsonGenerator.writeString(DesensitizedUtils.bankCard(origin));break;case PASSWORD:jsonGenerator.writeString(DesensitizedUtils.password(origin));break;case KEY:jsonGenerator.writeString(DesensitizedUtils.key(origin));break;case CUSTOMER:jsonGenerator.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr));break;default:throw new IllegalArgumentException("Unknow sensitive type enum " + type);}}@Overridepublic JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,final BeanProperty beanProperty) throws JsonMappingException {if (beanProperty != null) {if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);if (sensitive == null) {sensitive = beanProperty.getContextAnnotation(Sensitive.class);}if (sensitive != null) {return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(),sensitive.suffixNoMaskLen(), sensitive.maskStr());}}return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}return serializerProvider.findNullValueSerializer(null);}}
3.脱敏枚举类
/*** 敏感信息枚举类** @version v1.0**/public enum SensitiveTypeEnum {/*** 自定义*/CUSTOMER,/*** 用户名, 刘*华, 徐**/CHINESE_NAME,/*** 身份证号, 110110********1234*/ID_CARD,/*** 座机号, ****1234*/FIXED_PHONE,/*** 手机号, 176****1234*/MOBILE_PHONE,/*** 地址, 北京*********/ADDRESS,/*** 电子邮件, s*****o@xx.com*/EMAIL,/*** 银行卡, 622202************1234*/BANK_CARD,/*** 密码, 永远是 ******, 与长度无关*/PASSWORD,/*** 密钥, 【密钥】密钥除了最后三位其他都是***, 与长度无关*/KEY}
4.脱敏工具类
import cn.hutool.core.util.StrUtil;/*** 脱敏工具类** @version v1.0**/public class DesensitizedUtils {/*** 对字符串进行脱敏操作* @param origin 原始字符串* @param prefixNoMaskLen 左侧需要保留几位明文字段* @param suffixNoMaskLen 右侧需要保留几位明文字段* @param maskStr 用于遮罩的字符串, 如'*'* @return 脱敏后结果*/public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {if (origin == null) {return null;}StringBuilder sb = new StringBuilder();for (int i = 0, n = origin.length(); i < n; i++) {if (i < prefixNoMaskLen) {sb.append(origin.charAt(i));continue;}if (i > (n - suffixNoMaskLen - 1)) {sb.append(origin.charAt(i));continue;}sb.append(maskStr);}return sb.toString();}/*** 【中文姓名】只显示最后一个汉字,其他隐藏为星号,比如:**梦* @param fullName 姓名* @return 结果*/public static String chineseName(String fullName) {if (fullName == null) {return null;}return desValue(fullName, 0, 1, "*");}/*** 【身份证号】显示前六位, 四位,其他隐藏。共计18位或者15位,比如:340304*******1234* @param id 身份证号码* @return 结果*/public static String idCardNum(String id) {return desValue(id, 6, 4, "*");}/*** 【固定电话】后四位,其他隐藏,比如 ****1234* @param num 固定电话* @return 结果*/public static String fixedPhone(String num) {return desValue(num, 0, 4, "*");}/*** 【手机号码】前三位,后四位,其他隐藏,比如135****6810* @param num 手机号码* @return 结果*/public static String mobilePhone(String num) {return desValue(num, 3, 4, "*");}/*** 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区***** @param address 地址* @return 结果*/public static String address(String address) {return desValue(address, 6, 0, "*");}/*** 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com* @param email 电子邮箱* @return 结果*/public static String email(String email) {if (email == null) {return null;}int index = StrUtil.indexOf(email, '@');if (index <= 1) {return email;}String preEmail = desValue(email.substring(0, index), 1, 0, "*");return preEmail + email.substring(index);}/*** 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:622260**********1234* @param cardNum 银行卡号* @return 结果*/public static String bankCard(String cardNum) {return desValue(cardNum, 6, 4, "*");}/*** 【密码】密码的全部字符都用*代替,比如:******* @param password 密码* @return 结果*/public static String password(String password) {if (password == null) {return null;}return "******";}/*** 【密钥】密钥除了最后三位,全部都用*代替,比如:***xdS 脱敏后长度为6,如果明文长度不足三位,则按实际长度显示,剩余位置补** @param key 密钥* @return 结果*/public static String key(String key) {if (key == null) {return null;}int viewLength = 6;StringBuilder tmpKey = new StringBuilder(desValue(key, 0, 3, "*"));if (tmpKey.length() > viewLength) {return tmpKey.substring(tmpKey.length() - viewLength);}else if (tmpKey.length() < viewLength) {int buffLength = viewLength - tmpKey.length();for (int i = 0; i < buffLength; i++) {tmpKey.insert(0, "*");}return tmpKey.toString();}else {return tmpKey.toString();}}}
