• Java实现敏感日志脱敏


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

    二、具体实现
    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), "*"),
                            "******"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145

    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();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    敏感类型枚举类

    public enum SensitiveTypeEnum {
        /**
         * 中文名
         */
        CHINESE_NAME,
        /**
         * 身份证号
         */
        ID_CARD,
        /**
         * 座机号
         */
        FIXED_PHONE,
        /**
         * 手机号
         */
        MOBILE_PHONE,
        /**
         * 地址
         */
        ADDRESS,
        /**
         * 电子邮件
         */
        EMAIL,
        /**
         * 银行卡
         */
        BANK_CARD;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    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();
            }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    大家可以自己试试。

  • 相关阅读:
    【OpenFOAM】-olaFlow-算例1- baseWaveFlume
    [翻译] 使用 TensorFlow 进行分布式训练
    如何为你的Python程序配置HTTP/HTTPS爬虫IP
    【力扣刷题】只出现一次的数字
    【Android 从入门到出门】第二章:使用声明式UI创建屏幕并探索组合原则
    Spring源码系列:核心概念解析
    [Mac软件]Adobe Substance 3D Stager 2.1.4 3D场景搭建工具
    项目管理系统(Java+Web+MySQL)
    面试20220803
    spring boot学习第十三篇:使用spring security控制权限
  • 原文地址:https://blog.csdn.net/qq_42077317/article/details/132921235