Java实现敏感日志脱敏

2023-09-16 18:01:36

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

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

大家可以自己试试。

更多推荐

【SLAM】视觉SLAM简介

【SLAM】视觉SLAM简介task04主要了解了SLAM的主流框架,清楚VSALM中间接法与直接法的主要区别在什么地方,其各自的优势是什么,了解前端与后端的关系是什么1.什么是SLAM2.VSALM中间接法与直接法的主要区别在什么地方,其各自的优势是什么?在SLAM(SimultaneousLocalizationa

Rsync学习笔记1

企业架构Web服务器的文件及时同步:1)能够理解为何要服务器的文件同步;2)能够简单描述实现文件同步的几种方式;3)能够实现服务器文件实时同步的案例;服务器同步文件的必要性:当业务代码发生改变,上传到web服务器的时候,因为架构已经不是单台服务器提供服务,而是由多台Web服务器提供服务,业务代码需要同时上传到多台Web

2023数学建模研赛华为杯E题思路-出血性脑卒中临床智能诊疗建模

E题出血性脑卒中临床智能诊疗建模三、请建模回答如下问题1血肿扩张风险相关因素探索建模。a)请根据“表1”(字段:入院首次影像检查流水号,发病到首次影像检查时间间隔),“表2”(字段:各时间点流水号及对应的HM_volume),判断患者sub001至sub100发病后48小时内是否发生血肿扩张事件。结果填写规范:1是0否

【Spring】BeanName 的自动生成原理

🎈博客主页:🌈我的主页🌈🎈欢迎点赞👍收藏🌟留言📝欢迎讨论!👏🎈本文由【泠青沼~】原创,首发于CSDN🚩🚩🚩🎈由于博主是在学小白一枚,难免会有错误,有任何问题欢迎评论区留言指出,感激不尽!🌠个人主页目录🌟一、默认name生成原理🌟二、id和name属性处理原理🌟一、默认name生成原理在

【C++深入浅出】日期类的实现

目录一.前言二.日期类的框架三.日期类的实现3.1构造函数3.2析构函数3.3赋值运算符重载3.4关系运算符重载3.5日期+/-天数3.6自增与自减运算符重载3.7日期-日期四.完整代码一.前言通过前面两期类和对象的学习,我们已经对C++的类有了一定的了解。本期我们的目标是实现一个完整的日期类,通过实现日期类的构造函数

蓝桥杯 题库 简单 每日十题 day7

01啤酒和饮料题目描述本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。啤酒每罐2.3元,饮料每罐1.9元。小明买了若干啤酒和饮料,一共花了82.3元。我们还知道他买的啤酒比饮料的数量少,请你计算他买了几罐酒。#include<stdio.h>#include<stdlib.h>intmain()

基于SpringBoot的教师工作量管理系统

目录前言一、技术栈二、系统功能介绍管理员模块的实现教师模块的实现三、核心代码1、登录模块2、文件上传模块3、代码封装前言随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了教师工作量管理系统的开发全过程。通过分析教师工作量管理系统管理的不足,创建了一个计算机管理教师工作量管理系统

深眸科技迭代深度学习算法,以AI机器视觉技术扩围工业应用场景

智能制造是制造业数智化转型升级的发展方向,在当前以高端装备制造为核心的工业4.0时代背景下,越来越多的制造企业意识到机器视觉对于提高效率、降低成本,从而提升企业效益的意义。目前,机器视觉已成为制造业迈向智能制造过程中极其关键的一项技术,且通过融合人工智能,能够实现该技术的再一次升级,以此切入更多差异化工业应用场景,并以

软件设计模式

1.UML1.1类图表示法uml类图中,类使用包含类名、属性、方法属性或方法前的加好和减号表示了这个方法的可见性,可见性的符号有三种:+表示public-表示private#表示protected1.2类与类之间关系关联关系单向关联双向关系自关联聚合关系聚合关系是关联关系的一种,是强关联关系,是整体和部分的关系聚合关系

海艺互娱与亚马逊云科技合作,在生成式AI领域探索更多的训练方向

面对生成式AI(GenerativeAI)新浪潮,如何把握机遇,加速创新发展,智胜蓝海?通过与亚马逊云科技合作,海艺互娱使用云上便捷部署的生成式AI解决方案,快速构建起可以服务全球用户的seaart.ai艺术创作平台,让用户将灵感快速转化为作品的同时,实现成本优化。当前,随着人工智能的快速发展,生成式AI正以其惊人的创

海外媒体发稿:海外汽车媒体推广9个方式解析

根据下列9个国外汽车媒体推广方式,企业能够在国际范围内突破边界,获得领域关心。这将帮助企业完成国际化发展发展战略,扩展市场占有率和提升盈利空间。【华媒舍】国外全媒体发表文章将会成为企业完成这一目标的重要方式,为企业带来新的机遇与挑战,助推企业与时俱进和成长!1.提升国界线,开拓视野!国外汽车媒体是汽车业务领域散播信息的

热文推荐