一、前言
在实际项目中,可能需要对日志中的一些敏感数据脱敏,比如使用遮掩算法,只显示部分数据。
二、具体实现
1.首先定义一个工具类,对常见的一些敏感数据脱敏
public class DesensitizedUtils {
/**
* 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
*/
public static String chineseName(String fullName) {
if (StringUtils.isBlank(fullName)) {
return "";
}
String name = StringUtils.left(fullName, 1);
return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
}
/**
* 【身份证号】显示最后四位,其他隐藏。共计18位或者15位,比如:*************1234
*/
public static String idCardNum(String id) {
if (StringUtils.isBlank(id)) {
return "";
}
String num = StringUtils.right(id, 4);
return StringUtils.leftPad(num, StringUtils.length(id), "*");
}
/**
* 【固定电话 后四位,其他隐藏,比如1234
*/
public static String fixedPhone(String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
}
/**
* 【手机号码】前三位,后四位,其他隐藏,比如135****6810
*/
public static String mobilePhone(String num) {
if (StringUtils.isBlank(num)) {
return "";
}
return StringUtils.left(num, 3).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), "***"));
}
/**
* 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
*/
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>
*/
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)));
}
}
/**
* 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234>
*/
public static String bankCard(String cardNum) {
if (StringUtils.isBlank(cardNum)) {
return "";
}
return StringUtils.left(cardNum, 6).concat(StringUtils
.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"),
"******"));
}
}
2.使用反射实现脱敏
public class LogDesensitizedUtils {
public static Field[] getAllFields(Object objSource) {
List<Field> fieldList = new ArrayList<Field>();
Class tempClass = objSource.getClass();
while (tempClass != null && !tempClass.getName().toLowerCase()
.equals("java.lang.object")) {//当父类为null的时候说明到达了最上层的父类(Object类).
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass(); //得到父类,然后赋给自己
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
private static void replace(Field[] fields, Object javaBean, Set<Integer> referenceCounter)
throws IllegalArgumentException, IllegalAccessException {
if (null != fields && fields.length > 0) {
for (Field field : fields) {
field.setAccessible(true);
if (null != field && null != javaBean) {
Object value = field.get(javaBean);
if (null != value) {
Class<?> type = value.getClass();
//处理子属性,包括集合中的
if (type.isArray()) {//对数组类型的字段进行递归过滤
int len = Array.getLength(value);
for (int i = 0; i < len; i++) {
Object arrayObject = Array.get(value, i);
if (null != arrayObject && isNotGeneralType(arrayObject.getClass(), arrayObject,
referenceCounter)) {
replace(getAllFields(arrayObject), arrayObject, referenceCounter);
}
}
} else if (value instanceof Collection<?>) {//对集合类型的字段进行递归过滤
Collection<?> c = (Collection<?>) value;
Iterator<?> it = c.iterator();
while (it.hasNext()) {
Object collectionObj = it.next();
if (isNotGeneralType(collectionObj.getClass(), collectionObj, referenceCounter)) {
replace(getAllFields(collectionObj), collectionObj, referenceCounter);
}
}
} else if (value instanceof Map<?, ?>) {//对Map类型的字段进行递归过滤
Map<?, ?> m = (Map<?, ?>) value;
Set<?> set = m.entrySet();
for (Object o : set) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
Object mapVal = entry.getValue();
if (isNotGeneralType(mapVal.getClass(), mapVal, referenceCounter)) {
replace(getAllFields(mapVal), mapVal, referenceCounter);
}
}
} else if (value instanceof Enum<?>) {
continue;
}
/*除基础类型、jdk类型的字段之外,对其他类型的字段进行递归过滤*/
else {
if (!type.isPrimitive()
&& type.getPackage() != null
&& !StringUtils.startsWith(type.getPackage().getName(), "javax.")
&& !StringUtils.startsWith(type.getPackage().getName(), "java.")
&& !StringUtils.startsWith(field.getType().getName(), "javax.")
&& !StringUtils.startsWith(field.getName(), "java.")
&& referenceCounter.add(value.hashCode())) {
replace(getAllFields(value), value, referenceCounter);
}
}
}
//脱敏操作
setNewValueForField(javaBean, field, value);
}
}
}
}
private static boolean isNotGeneralType(Class<?> clazz, Object value, Set<Integer> referenceCounter) {
return !clazz.isPrimitive()
&& clazz.getPackage() != null
&& !clazz.isEnum()
&& !StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
&& !StringUtils.startsWith(clazz.getPackage().getName(), "java.")
&& !StringUtils.startsWith(clazz.getName(), "javax.")
&& !StringUtils.startsWith(clazz.getName(), "java.")
&& referenceCounter.add(value.hashCode());
}
public static void setNewValueForField(Object javaBean, Field field, Object value) throws IllegalAccessException {
LogDesensitized annotation = field.getAnnotation(LogDesensitized.class);
if (field.getType().equals(String.class) && null != annotation) {
String valueStr = (String) value;
if (StringUtils.isNotBlank(valueStr)) {
switch (annotation.type()) {
case CHINESE_NAME: {
field.set(javaBean, LogDesensitizedUtils.chineseName(valueStr));
break;
}
case ID_CARD: {
field.set(javaBean, DesensitizedUtils.idCardNum(valueStr));
break;
}
case FIXED_PHONE: {
field.set(javaBean, DesensitizedUtils.fixedPhone(valueStr));
break;
}
case MOBILE_PHONE: {
field.set(javaBean, DesensitizedUtils.mobilePhone(valueStr));
break;
}
case ADDRESS: {
field.set(javaBean, DesensitizedUtils.address(valueStr, 8));
break;
}
case EMAIL: {
field.set(javaBean, DesensitizedUtils.email(valueStr));
break;
}
case BANK_CARD: {
field.set(javaBean, DesensitizedUtils.bankCard(valueStr));
break;
}
}
}
}
}
/**
* 脱敏对象
*/
public static String getJson(Object javaBean) {
String json = null;
if (null != javaBean) {
try {
if (javaBean.getClass().isInterface()) {
return json;
}
Object clone = new FastClone().clone(javaBean);
Set<Integer> referenceCounter = new HashSet<>();
LogDesensitizedUtils.replace(getAllFields(clone), clone, referenceCounter);
json = PojoDec.toJson(clone);
referenceCounter.clear();
} catch (Throwable e) {
e.printStackTrace();
}
}
return json;
}
}
3.注解如下:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogDesensitized {
SensitiveTypeEnum type();
}
敏感类型枚举类
public enum SensitiveTypeEnum {
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD;
}
Json序列化类
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PojoDec {
private static ObjectMapper mapper;
/**
* 序列化级别,默认只序列化不为空的字段
*/
protected static final JsonInclude.Include DEFAULT_PROPERTY_INCLUSION = JsonInclude.Include.NON_NULL;
/**
* 是否缩进JSON格式
*/
protected static final boolean IS_ENABLE_INDENT_OUTPUT = false;
static {
try {
//初始化
mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//配置序列化级别
mapper.setSerializationInclusion(DEFAULT_PROPERTY_INCLUSION);
//配置JSON缩进支持
mapper.configure(SerializationFeature.INDENT_OUTPUT, IS_ENABLE_INDENT_OUTPUT);
//配置普通属性
//序列化导致long的精度丢失
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
mapper.registerModule(module);
} catch (Exception e) {
log.error("jackson config error", e);
}
}
public static ObjectMapper getObjectMapper() {
return mapper;
}
/**
* 序列化为JSON
*/
public static <V> String toJson(List<V> list) {
try {
return mapper.writeValueAsString(list);
} catch (JsonProcessingException e) {
log.error("jackson to error, data: {}", list, e);
throw new RuntimeException(String.format("jackson to error, data: %s ", list), e);
}
}
/**
* 序列化为JSON
*/
public static <V> String toJson(V v) {
try {
return mapper.writeValueAsString(v);
} catch (JsonProcessingException e) {
log.error("jackson to error, data: {}", v, e);
throw new RuntimeException(String.format("jackson to error, data: %s ", v), e);
}
}
/**
* 格式化Json(美化)
* @return json
*/
public static String format(String json) {
try {
JsonNode node = mapper.readTree(json);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
} catch (IOException e) {
log.error("jackson format json error, json: {}", json, e);
throw new RuntimeException(String.format("jackson to error, data: %s ", e), e);
}
}
}
3.接着测试一下。
public class Test{
@Data
public static class BaseUserInfo {
/**
* 需要脱敏 中文名
*/
@LogDesensitized(type = SensitiveTypeEnum.CHINESE_NAME)
private String realName;
/**
* 需要脱敏 身份正好
*/
@LogDesensitized(type = SensitiveTypeEnum.ID_CARD)
private String idCardNo;
/**
* 需要脱敏 手机号
*/
@LogDesensitized(type = SensitiveTypeEnum.MOBILE_PHONE)
private String mobileNo;
@LogDesensitized(type = SensitiveTypeEnum.CHINESE_NAME)
private String account;
/**
* 需要脱敏 银行卡号
*/
@LogDesensitized(type = SensitiveTypeEnum.BANK_CARD)
private String bankCardNo;
/**
* 需要脱敏 邮箱
*/
@LogDesensitized(type = SensitiveTypeEnum.EMAIL)
private String email;
}
public static void testUserInfo() {
/*单个实体*/
BaseUserInfo baseUserInfo = new BaseUserInfo();
baseUserInfo.setRealName("王晓");
baseUserInfo.setIdCardNo("152191199023154120");
baseUserInfo.setMobileNo("13179246810");
baseUserInfo.setAccount("dannyhoo123457");
baseUserInfo.setBankCardNo("6121000212090651212");
baseUserInfo.setEmail("h2226688@126.com");
System.out.println("脱敏前:" + PojoDec.toJson(baseUserInfo));
System.out.println("脱敏后:" + LogDesensitizedUtils.getJson(baseUserInfo));
}
public static void main(String[] args) {
testUserInfo();
}
}
大家可以自己试试。