fastjson 是阿里巴巴开源的一款优秀的java生态下序列化/反序列化产品,我们可以在不同的服务之间进行数据交互时使用它,同样可以输出日志时使用fasnjson把对象转化为String, 然后再进行采集,比调用对象的toString方法靠谱,比如如果对象内部没有实现toString方法就不能正确输出想要的信息, 另一方面json格式的日志数据,后续可观性也会好一些;


问题

使用fastjson序列化对象输出日志, 引出了另一个问题,由于我司是做支付业务的,对于用户的一些敏感信息(姓名, 身份证号, 手机号, 银行卡号, 卡有效期等)是绝对不能直接原文输出的,因此对于这部分数据就要进行脱敏处理;

分析

现状我们项目代码中是各自独立处理每个字段,自定义加工进行脱敏,这种方式要求每个使用的地方重复处理,工作量大同时不能保证脱敏规则统一,也容易出错;首先脱敏这件事情是一个共有的需求,各个程序中会有很多地方都有这样的需要,因此我们解决这个事情最好是有一个通用的办法;使用fastjson进行对象到字符串的转化是一个统一处理点,可以实现脱敏统一处理,从此切入试下

调研

查看了fastjson官方的文档,发现其中提供filter机制,可以在序列化过程中加入自定义的拦截处理,对json数据中的key-value加工处理;那我们就可以创建自定义的脱敏处理filter实现我们需要的功能嘛

实施

自定义filter进行脱敏处理,那在filter的逻辑中,我们要知道对象的哪些字段需要以什么规则进行脱敏呢?
可以通过给脱敏对象属性添加自定义注解,然后注解属性上再指定定义脱敏规则枚举,最后在filter中检测注解进而进行脱敏处理;

  1. 脱敏注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 需要脱敏的对象
 * @author Hinsteny
 * @version $ID: Desensitization 2018-12-12 20:12 All rights reserved.$
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {

    /**
     * 脱敏规则类型
     * @return
     */
    DesensitionType type();

    /**
     * 附加值, 自定义正则表达式等
     * @return
     */
    String[] attach() default "";

}

  1. 脱敏规则枚举
/**
 * @author Hinsteny
 * @version $ID: DesensitionType 2018-12-12 20:40 All rights reserved.$
 */
public enum DesensitionType {

    PHONE("phone", "11位手机号", "^(\\d{3})\\d{4}(\\d{4})$", "$1****$2"),
    IDENTITYNO("identityNo", "16或者18身份证号", "^(\\d{4})\\d{8,10}(\\d{4})$", "$1****$2"),
    BANKCARDNO("bankCardNo", "银行卡号", "^(\\d{4})\\d*(\\d{4})$", "$1****$2"),

    CUSTOM("custom", "自定义正则处理", ""),
    TRUNCATE("truncate", "字符串截取处理", ""),
    ;

    String type;

    String describe;

    String[] regular;

    DesensitionType(String type, String describe, String... regular) {
        this.type = type;
        this.describe = describe;
        this.regular = regular;
    }

    public String getType() {
        return type;
    }

    public String getDescribe() {
        return describe;
    }

    public String[] getRegular() {
        return regular;
    }

}

  1. 自定义脱敏处理filter

import com.alibaba.fastjson.serializer.ValueFilter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 在fastjson中使用此过滤器进行脱敏操作
 * @author Hinsteny
 * @version $ID: ValueDesensitzeiFilter 2018-12-12 10:06 All rights reserved.$
 */
public class ValueDesensitizeFilter implements ValueFilter {

    private Logger logger = LoggerFactory.getLogger(ValueDesensitizeFilter.class);

    @Override
    public Object process(Object object, String name, Object value) {
        if (null == value || !(value instanceof String) || ((String) value).length() == 0) {
            return value;
        }
        try {
            Field field = object.getClass().getDeclaredField(name);
            Desensitization desensitization;
            if (String.class != field.getType() || (desensitization = field.getAnnotation(Desensitization.class)) == null) {
                return value;
            }
            List<String> regular;
            DesensitionType type = desensitization.type();
            switch (type) {
                case CUSTOM:
                    regular = Arrays.asList(desensitization.attach());
                    break;
                case TRUNCATE:
                    regular = truncateRender(desensitization.attach());
                    break;
                default:
                    regular = Arrays.asList(type.getRegular());
            }
            if (regular.size() > 1) {
                String match = regular.get(0);
                String result = regular.get(1);
                if (null != match && result != null && match.length() > 0) {
                    return ((String) value).replaceAll(match, result);
                }
            }
        } catch (NoSuchFieldException e) {
            logger.warn("ValueDesensitizeFilter the class {} has no field {}", object.getClass(), name);
        }
        return value;
    }

    private List<String> truncateRender(String[] attachs) {
        List<String> regular = new ArrayList<>();
        if (null != attachs && attachs.length >1) {
            String rule = attachs[0];
            String size = attachs[1];
            String template, result;
            if ("0".equals(rule)) {
                template = "^(\\S{%s})(\\S+)$";
                result = "$1";
            } else if ("1".equals(rule)) {
                template = "^(\\S+)(\\S{%s})$";
                result = "$2";
            } else {
                return regular;
            }
            try {
                if (Integer.parseInt(size) > 0) {
                    regular.add(0, String.format(template, size));
                    regular.add(1, result);
                }
            } catch (Exception e) {
                logger.warn("ValueDesensitizeFilter truncateRender size {} exception", size);
            }
        }
        return regular;
    }
}

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐