遍历JSONObject和XML实现简单的字段脱敏

由于最近需要实现一个日志脱敏,而公司的日志API是公司自己封装的,不好扩展,于是想从打印的内容上想点办法。
我们的通讯报文一般是JSON格式的,或者XML格式的,于是,只要打印的时候,创建打印对象的副本,在副本的基础上进行字段屏蔽,然后再打印该副本。
首先,我们得制定一个脱敏规则,这个规则我这里放在配置文件里面,desensitization.properties
格式就是 [脱敏字段名]=[规则1],[规则2]
下面是样例文件:

#index从1开始,请勿输0
password=1-3,6-7
username=4-8
telephone=4-8
idCard=7-100

思路:
我的思路很简单,就是递归遍历JSONObject或者XML的Document对象,然后遍历时,查找这个字段是否是脱敏规则里的字段,如果是,就按照规则替换部分字符串。

然后就是代码实现了,导入两个包,一个是FastJSON的包另外一个是Dom4j的包,

直接贴代码了:
JSONUtils.java 用于处理JSON字段的脱敏

package com.hgd;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;

public class JSONUtils {

    //用于保存字段屏蔽的规则
    private static Map<String,List<RulesItem>> rules;

    //屏蔽规则提前加载到内存
    static {
        rules = new HashMap<>();
        //password=1-2,5-6
        Map<String,String> map = loadProperties(JSONUtils.class.getClassLoader().getResource("").getPath()+"/desensitization.properties");
        map.forEach((key,value)->{
            String[] propertyRules = value.split(",");
            List<RulesItem> arrays = new ArrayList<>(propertyRules.length);
             for (String propertyRule : propertyRules) {
                String[] tempArgs = propertyRule.split("-");
                RulesItem rulesArray = new RulesItem();
                int start = Integer.parseInt(tempArgs[0]);
                int end = Integer.parseInt(tempArgs[1]);
                rulesArray.setStart(start);
                rulesArray.setEnd(end);
                arrays.add(rulesArray);
            }
             rules.put(key,arrays);
        });
        System.out.println("rules"+ rules);
    }

    /**
     * 将JSONObject 转化成我们自定义的CustomerJSON
     * @param jsonObject 原始json
     * @return
     */
    public static CustomerJson toCustomerJson(JSONObject jsonObject){
        return new CustomerJson(jsonObject);
    }

    /**
     * 递归遍历JSONObject
     * @param object
     * @return
     */
    public static JSONObject changeJSONObject(JSONObject object){
        JSONObject result = new JSONObject(object.size());
        object.forEach((key,value)->{
           if(value instanceof JSONObject){
               result.put(key,changeJSONObject((JSONObject) value));
           }else if(value instanceof JSONArray){
               result.put(key,changeJSONArray((JSONArray)value));
           }else{
               //这里进行字段脱敏
               result.put(key,desensitization(key,String.valueOf(value)));
           }
        });
        return result;
    }

    /**
     * 递归遍历JOSNArray
     * @param jsonArray
     * @return
     */
    public static JSONArray changeJSONArray(JSONArray jsonArray){
        if(jsonArray!=null){
            JSONArray array = new JSONArray(jsonArray.size());
            jsonArray.forEach(object->{
                if(object instanceof JSONObject){
                    array.add(changeJSONObject((JSONObject)object));
                }else{
                    array.add(object);
                }
            });
            return  array;
        }else {
            return null;
        }
    }


    /**
     * 对字段进行脱敏
     * @param key 字段名称
     * @param value 字段值
     * @return
     */
    public static String desensitization(String key,String value){
        if(null == value || "".equals(value)){
            return value;
        }
        if(rules.containsKey(key)){
            List<RulesItem> arrays = rules.get(key);
            for (RulesItem temp : arrays) {
                value = desensiString(value, temp);
            }
        }
        return value;
    }

    /**
     * 对原始字符串进行替换操作
     * @param value 字段值
     * @param item 屏蔽规则
     * @return 屏蔽后的字段值
     */
    public static String desensiString(String value, RulesItem item){
        StringBuilder sb = new StringBuilder(value);
        int length = value.length();
        int filledLength = item.getEnd() - item.getStart() + 1 ;
        //保护,当问文件中的配置不是正数时,赋默认值1
        if(item.getStart()<=0){
            item.setStart(1);
        }
        if(item.getEnd() <=0){
            item.setEnd(1);
        }
        //保护,当原始字符串太短时,特殊处理,不处理,或者全部替换成*;
        if(item.getStart()>sb.length()){
            //return sb.toString();
            return getFilledString(‘*‘,sb.length());
        }
        System.out.println("start:"+(item.getStart()-1)+"end:"+(item.getEnd()-1));
        sb.replace(item.getStart()-1,item.getEnd()-1,getFilledString(‘*‘, Math.min(filledLength, length)));
        return sb.toString();
    }

    /**
     * 生成固定长度的填充串
     * @param filledChar 填充串的字符
     * @param length 填充串的长度
     * @return 生成好的字符串
     */
    public static String getFilledString(char filledChar,int length){
        StringBuilder sb = new StringBuilder();
        for (int i= 0 ;i<length;i++){
            sb.append(filledChar);
        }
        return sb.toString();
    }

    /**
     * 读取配置文件
     * @param path 配置文件路径
     * @return
     */
    public static Map<String,String> loadProperties(String path){
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream(new File(path)));
        } catch (IOException e) {
            e.printStackTrace();
        }
        Map<String,String> result = new HashMap<>(properties.size());
        Set<Map.Entry<Object, Object>> entrySet = properties.entrySet();
        for (Map.Entry<Object, Object> entry : entrySet) {
            result.put((String) entry.getKey(), (String) entry.getValue());
        }
        return result;
    }

}

XMLUtils.java 用于处理XML格式的报文脱敏

package com.hgd;

import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.util.List;

public class XMLUtils {

    public static Element getNodes(Element node) {
        List<Element> listElement = node.elements();
        Element result = DocumentHelper.createElement(node.getName());
        if (listElement == null || listElement.size() == 0) {//
            System.out.println(node.getTextTrim());
            //这里进行特定的修改
            Element element = DocumentHelper.createElement(node.getName());
            element.setText(JSONUtils.desensitization(node.getName(), node.getTextTrim()));
            return element;
        } else {
            //所有一级子节点的list
            for (Element e : listElement) {//遍历所有一级子节点
                result.add(getNodes(e));
            }
        }
        return result;
    }
}

脱敏规则的原子类,用于存储单个字段的脱敏规则
RulesItem.java

package com.hgd;

public class RulesItem {
    private int start;
    private int end;

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getEnd() {
        return end;
    }

    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "RulesItem{" +
                "start=" + start +
                ", end=" + end +
                ‘}‘;
    }
}

最终写个测试类,测试一下:
Main.java

package com.hgd;

import com.alibaba.fastjson.JSON;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

public class Main {

    public static void main(String[] args) {
        String oriJSON = "{\n" +
                "    \"username\":\"abcdefghijklmn\",\n" +
                "    \"password\":\"helloWorld\",\n" +
                "    \"telephone\":\"17789098907\",\n" +
                "    \"idCard\":\"441279199012043817\",\n" +
                "    \"innerUser\":[\n" +
                "        {\n" +
                "            \"telephone\":\"18979089876\"\n" +
                "        }\n" +
                "    ]\n" +
                "}";
        System.out.println(JSONUtils.changeJSONObject(JSON.parseObject(oriJSON)).toJSONString());
        String utils = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<root>\n" +
                "\t<user editor=\"chenleixing\" date=\"2015-02-15\">\n" +
                "\t\t<username>张三</username>\n" +
                "\t\t<telephone>17774570620</telephone>\n" +
                "\t\t<sex>男</sex>\n" +
                "\t</user>\n" +
                "\t<user editor=\"zhangxiaochao\" date=\"2015-02-15\">\n" +
                "\t\t<name>李四</name>\n" +
                "\t\t<year>24</year>\n" +
                "\t\t<sex>女</sex>\n" +
                "\t</user>\n" +
                "</root>";
        try {
            Document document = DocumentHelper.parseText(utils);
            Element result = XMLUtils.getNodes(document.getRootElement());
            Document resultDoc = DocumentHelper.createDocument(result);
            System.out.println(resultDoc.asXML());
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

}

输出结果:

rules{password=[RulesItem{start=1, end=3}], idCard=[RulesItem{start=7, end=100}], telephone=[RulesItem{start=4, end=8}], username=[RulesItem{start=4, end=8}]}
start:0end:2
start:6end:99
start:3end:7
start:3end:7
start:3end:7
{"innerUser":[{"telephone":"189*****9876"}],"password":"***lloWorld","telephone":"177*****8907","idCard":"441279******************","username":"abc*****hijklmn"}
张三
17774570620
start:3end:7
男
李四
24
女
<?xml version="1.0" encoding="UTF-8"?>
<root><user><username>**</username><telephone>177*****0620</telephone><sex>男</sex></user><user><name>李四</name><year>24</year><sex>女</sex></user></root>

遍历JSONObject和XML实现简单的字段脱敏

上一篇:Astra Service Broker:在Kubernetes上尽情使用Cassandra


下一篇:网页学习(六)盒模型