前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 实现日志脱敏处理

Java 实现日志脱敏处理

作者头像
周三不加班
发布2019-09-03 10:53:29
3.4K0
发布2019-09-03 10:53:29
举报
文章被收录于专栏:程序猿杂货铺程序猿杂货铺

引言

在日常工作中,日志处理是我们每一个程序员必备的素质,但是在有些场景下客户信息敏感,需要进行某些字段,或者某部分字段的脱敏处理。接到需求我们开始操刀!

需求分析

处理字段的方式多种多样,如何方便,高效才是关键,众所周知在java中最好的处理方式就是封装,即,对程序员暴露出的最好是一个统一的API,不关心具体的处理逻辑,能拿到想要的返回值就好。

实现第一版

由于在RPC调用过程当中,大部分接口的参数封装数据类型都是Map,所以在此先针对Map形式实现日志脱敏功能

实现思路:

有两种实现方法:

代码语言:javascript
复制
第一种:写死配置
第二种:使用注解驱动
由于写死配置的扩展性实在是差,所以我们本次实现主要是注解驱动

定义注解

代码语言:javascript
复制
/**
 * @ClassName: DesensitizedAnnotation
 * @Description: 注解类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 17:42
 * @Version: 1.0
 */
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DesensitizedAnnotation {
    /*脱敏数据类型(规则)*/
    TypeEnum type();
    /*判断注解是否生效,暂时没有用到*/
    String isEffictiveMethod() default "";

}

引入枚举

主要是便于统一处理同类型的字段

代码语言:javascript
复制
public enum TypeEnum {
    /**客户名称**/
    PERSON_NAME,
    /**客户证件号**/
    PERSON_CERT_NO,
    /**客户手机号**/
    PERSON_PHONE_NO,
    /**客户银行卡名称**/
    PERSON_BANK_NAME,
    /**客户银行卡号**/
    PERSON_BANK_NO,
    /**密码**/
    PASSWORD,
}

定义基本数据模板类

主要作用是定义待过滤字段集合

代码语言:javascript
复制
/**
 * @ClassName: BaseInfo
 * @Description: 日志过滤字段基类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 17:38
 * @Version: 1.0
 */
public class BaseInfo implements Serializable {

    private static final long serialVersionUID = 1L;

    @DesensitizedAnnotation(type = TypeEnum.PERSON_NAME)
    private String custName;

    @DesensitizedAnnotation(type = TypeEnum.PERSON_CERT_NO)
    private String certNo;
}

定义处理工具类

代码语言:javascript
复制
/**
 * @ClassName: DesensitizedUtils
 * @Description: 日志脱敏工具类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 17:52
 * @Version: 1.0
 */
public class DesensitizedUtils {

    private static final Logger logger = LoggerFactory.getLogger(DesensitizedUtils.class);

    private static final Map<String, TypeEnum> annotationMaps = new HashMap<>();

    /**
     * 类加载时装配待脱敏字段
     */
    static {
        try {
            Class<?> clazz = Class.forName(BaseInfo.class.getName());
            Field[] fields = clazz.getDeclaredFields();
            for (int i = ; i < fields.length; i++) {
                fields[i].setAccessible(true);
                DesensitizedAnnotation annotation = fields[i].getAnnotation(DesensitizedAnnotation.class);
                if (annotation != null) {
                    TypeEnum type = annotation.type();
                    String name = fields[i].getName();
                    //name为注解字段名称,value为注解类型。方便后续根据注解类型扩展
                    annotationMaps.put(name, type);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.error("类加载时装配待脱敏字段异常,异常信息:[{}]", new Object[]{e});
        }
    }


    /**
     * 脱敏处理方法
     *
     * @param object
     * @return
     */
    public static String getConverent(Map<String,Object> object) {
        try {
            // 1.处理Map数据类型
            if (object instanceof Map) {
                HashMap<String, Object> reqMap = (HashMap) object;
                Iterator<String> iterator = annotationMaps.keySet().iterator();
                iterator.forEachRemaining(annotationName -> {
                    if (reqMap.keySet().contains(annotationName)) {
                        doconverentForMap(reqMap, annotationName);
                    }
                });
                return JSON.toJSONString(reqMap);
            }      
            return JSON.toJSONString(object);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("日志脱敏处理失败,回滚,详细信息:[{}]", new Object[]{e});
            return JSON.toJSONString(object);
        }
    }

    /**
     * 脱敏数据源为Map时处理方式
     *
     * @param reqMap
     * @param annotationName
     * @return
     */
    private static void doconverentForMap(HashMap<String, Object> reqMap, String annotationName) {
        String value = String.valueOf(reqMap.get(annotationName));
        if (StringUtils.isNotEmpty(value)) {
            value = doConverentByType(value, annotationName);
        }
        reqMap.put(annotationName, value);
    }


    /**
     * 根据不同注解类型处理不同字段
     *
     * @param value
     * @param annotationName
     * @return
     */
    private static String doConverentByType(String value, String annotationName) {
        TypeEnum typeEnum = annotationMaps.get(annotationName);
        switch (typeEnum) {
            case PERSON_NAME:
                value = getStringByLength(value);
                break;
            case PERSON_CERT_NO:
                value = getStringByLength(value);
            default:
                value = getStringByLength(value);
        }
        return value;
    }

    /**
     * 根据value长度取值(切分)
     *
     * @param value
     * @return
     */
    private static String getStringByLength(String value) {
        int length = value.length();
        if (length == ){
            value = value.substring(, ) + "*";
        }else if (length == ){
            value = value.substring(,) + "*" + value.substring(length -);
        }else if (length >  && length <= ){
            value = value.substring(,) + "**" + value.substring(length -);
        }else if (length >  && length <= ){
            value = value.substring(,) + "***" + value.substring(length -);
        }else if (length > ){
            value = value.substring(,) + "*****" + value.substring(length -);
        }
        return value;
    }

}

定义测试类

测试第一版实现的针对Map处理的脱敏操作

代码语言:javascript
复制
/**
 * @ClassName: TestDeaensitized
 * @Description: 日志脱敏测试类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 18:27
 * @Version: 1.0
 */
public class TestDeaensitized {

    public static void main(String[] args) {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("custName", "小妮儿");
        hashMap.put("certNo", "12345678909876543");
        hashMap.put("phone", "12345678909");
        System.out.println("脱敏前:" + hashMap);
        String converent1 = DesensitizedUtils.getConverent(hashMap);
        System.out.println("脱敏后:" + converent1);
    }
}

第一版实现测试结果

代码语言:javascript
复制
针对Map实现的脱敏结果
脱敏前:{certNo=12345678909876543, phone=12345678909, custName=小妮儿}
脱敏后:{"certNo":"123*****543","phone":"12345678909","custName":"小*儿"}

至此第一版功能实现顺利完成。

实现第二版

由于在RPC调用过程当中,大部分接口的参数封装数据类型都是Map,但是部分接口还是使用Java Bean所以在此针对Java Bean形式实现日志脱敏功能

实现思路:

代码语言:javascript
复制
根据不同的数据类型进行不同判断,屏蔽上层调用者的可见度,在底层动态实现分情况处理
在结果处理完之后,统一返回调用者序列化完成的数据信息

在第一版实现的基础之上,我们开始第二版的实现

添加实体类

主要是为了封装模拟RPC调用过程中参数实体的属性

代码语言:javascript
复制
/**
 * @ClassName: Person
 * @Description: Person实体类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 17:50
 * @Version: 1.0
 */
public class Person {

    private String custName;

    private int idNo;

    private String certNo;

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public int getIdNo() {
        return idNo;
    }

    public void setIdNo(int idNo) {
        this.idNo = idNo;
    }

    public String getCertNo() {
        return certNo;
    }

    public void setCertNo(String certNo) {
        this.certNo = certNo;
    }

    @Override
    public String toString() {
        return "Person{" +
                "custName='" + custName + ''' +
                ", idNo=" + idNo +
                ", certNo='" + certNo + ''' +
                '}';
    }
}

改造处理工具类

代码语言:javascript
复制
/**
 * @ClassName: DesensitizedUtils
 * @Description: 日志脱敏工具类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 17:52
 * @Version: 1.0
 */
public class DesensitizedUtils {

    private static final Logger logger = LoggerFactory.getLogger(DesensitizedUtils.class);

    private static final Map<String, TypeEnum> annotationMaps = new HashMap<>();

    /**
     * 类加载时装配待脱敏字段
     */
    static {
        try {
            Class<?> clazz = Class.forName(BaseInfo.class.getName());
            Field[] fields = clazz.getDeclaredFields();
            for (int i = ; i < fields.length; i++) {
                fields[i].setAccessible(true);
                DesensitizedAnnotation annotation = fields[i].getAnnotation(DesensitizedAnnotation.class);
                if (annotation != null) {
                    TypeEnum type = annotation.type();
                    String name = fields[i].getName();
                    //name为注解字段名称,value为注解类型。方便后续根据注解类型扩展
                    annotationMaps.put(name, type);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.error("类加载时装配待脱敏字段异常,异常信息:[{}]", new Object[]{e});
        }
    }


    /**
     * 脱敏处理方法
     *
     * @param object
     * @return
     */
    public static String getConverent(Object object) {

        String objClassName = object.getClass().getName();

        try {
            // 1.处理Map数据类型
            if (object instanceof Map) {
                HashMap<String, Object> reqMap = (HashMap) object;
                Iterator<String> iterator = annotationMaps.keySet().iterator();
                iterator.forEachRemaining(annotationName -> {
                    if (reqMap.keySet().contains(annotationName)) {
                        doconverentForMap(reqMap, annotationName);
                    }
                });
                return JSON.toJSONString(reqMap);
            }
            // 2.处理Object数据类型
            Object val = new Object();
            Class<?> objClazz = Class.forName(objClassName);
            Field[] declaredFields = objClazz.getDeclaredFields();
            for (int j = ; j < declaredFields.length; j++) {
                Iterator<String> iterator = annotationMaps.keySet().iterator();
                while (iterator.hasNext()) {
                    String annotationName = iterator.next();
                    if (declaredFields[j].getName().equals(annotationName)) {
                        declaredFields[j].setAccessible(true);
                        val = declaredFields[j].get(object);
                        //获取属性后现在默认处理的是String类型,其他类型数据可扩展
                        String value = doconverentForObject(val, annotationName);
                        declaredFields[j].set(object, value);
                    }
                }
            }
            return JSON.toJSONString(object);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("日志脱敏处理失败,回滚,详细信息:[{}]", new Object[]{e});
            return JSON.toJSONString(object);
        }
    }

    /**
     * 脱敏数据源为Object时处理方式
     *
     * @param val
     * @param annotationName
     * @return
     */
    private static String doconverentForObject(Object val, String annotationName) {

        String value = String.valueOf(val);
        if (StringUtils.isNotEmpty(value)) {
            value = doConverentByType(value, annotationName);
        }
        return value;
    }

    /**
     * 脱敏数据源为Map时处理方式
     *
     * @param reqMap
     * @param annotationName
     * @return
     */
    private static void doconverentForMap(HashMap<String, Object> reqMap, String annotationName) {
        String value = String.valueOf(reqMap.get(annotationName));
        if (StringUtils.isNotEmpty(value)) {
            value = doConverentByType(value, annotationName);
        }
        reqMap.put(annotationName, value);
    }


    /**
     * 根据不同注解类型处理不同字段
     *
     * @param value
     * @param annotationName
     * @return
     */
    private static String doConverentByType(String value, String annotationName) {
        TypeEnum typeEnum = annotationMaps.get(annotationName);
        switch (typeEnum) {
            case PERSON_NAME:
                value = getStringByLength(value);
                break;
            case PERSON_CERT_NO:
                value = getStringByLength(value);
            default:
                value = getStringByLength(value);
        }
        return value;
    }

    /**
     * 根据value长度取值(切分)
     *
     * @param value
     * @return
     */
    private static String getStringByLength(String value) {
        int length = value.length();
        if (length == ){
            value = value.substring(, ) + "*";
        }else if (length == ){
            value = value.substring(,) + "*" + value.substring(length -);
        }else if (length >  && length <= ){
            value = value.substring(,) + "**" + value.substring(length -);
        }else if (length >  && length <= ){
            value = value.substring(,) + "***" + value.substring(length -);
        }else if (length > ){
            value = value.substring(,) + "*****" + value.substring(length -);
        }
        return value;
    }

}

定义测试类

测试第二版实现的针对Object处理的脱敏操作

代码语言:javascript
复制
/**
 * @ClassName: TestDeaensitized
 * @Description: 日志脱敏测试类
 * @Author: 尚先生
 * @CreateDate: 2019/1/24 18:27
 * @Version: 1.0
 */
public class TestDeaensitized {

    public static void main(String[] args) {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("custName", "小妮儿");
        hashMap.put("certNo", "12345678909876543");
        hashMap.put("phone", "12345678909");
        System.out.println("脱敏前:" + hashMap);
        String converent1 = DesensitizedUtils.getConverent(hashMap);
        System.out.println("脱敏后:" + converent1);
        Person person = new Person();
        person.setCertNo("12345678909876541");
        person.setCustName("小妮儿真可爱!");
        System.out.println("脱敏前:" + person);
        String converent2 = DesensitizedUtils.getConverent(person);
        System.out.println("脱敏后:" + converent2);
    }

第二版实现测试结果

代码语言:javascript
复制
针对Map实现的脱敏结果
脱敏前:{certNo=12345678909876543, phone=12345678909, custName=小妮儿}
脱敏后:{"certNo":"123*****543","phone":"12345678909","custName":"小*儿"}
针对Object实现的脱敏结果
脱敏前:Person{custName='小妮儿真可爱!', idNo=0, certNo='12345678909876541'}
脱敏后:{"certNo":"123*****541","custName":"小妮***爱!","idNo":0}

至此所有功能实现顺利完成。

完整代码请参考Github

代码语言:javascript
复制
https://github.com/dwyanewede/project-learn/tree/master/src/main/java/com/learn/demo/desensitization

博客链接:

代码语言:javascript
复制
https://blog.csdn.net/shang_xs/article/details/86632071
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员啊粥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 需求分析
  • 实现第一版
  • 实现思路:
  • 定义注解
  • 引入枚举
  • 定义基本数据模板类
  • 定义处理工具类
  • 定义测试类
  • 第一版实现测试结果
  • 实现第二版
  • 实现思路:
  • 添加实体类
  • 改造处理工具类
  • 定义测试类
  • 第二版实现测试结果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档