一、前言
在实际项目中,可能需要对日志中的一些敏感数据脱敏,比如使用遮掩算法,只显示部分数据。

二、具体实现
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();
        }  
}

大家可以自己试试。