前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

这里有两种脱敏方式,一种是注解脱敏,另一种是字段名匹配脱敏

1、注解脱敏

1.1、注解

/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName SensitiveInfo
 * @date 2019/6/13  20:01.
 * @Description Json序列化脱敏注解
 * 不建议使用注解,建议使用JsonUtils工具类进行脱敏,因为注解会让我们需要必要时输出到前台的信息变的脱敏
 */

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveInfoSerialize.class)
public @interface SensitiveInfo {

    SensitiveTypeEnum value() ;

}

1.2、脱敏的类型枚举

package com.hlj.proj.utils.sensitivity;



/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName SensitiveTypeEnum
 * @date 2019/6/13  20:01.
 * @Description 敏感信息枚举类型
 */
public enum SensitiveTypeEnum {


    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 密码
     */
    PASSWORD,
    /**
     * 手机号
     */
    MOBILE_PHONE,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 真实姓名
     */
    NAME,
    /**
     * 账户信息
     */
    ACCOUNT_NO;



}

1.3、Json脱敏序列化

package com.hlj.proj.utils.sensitivity;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

import java.io.IOException;
import java.util.Objects;

/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName SensitiveInfoSerialize
 * @date 2019/6/13  20:01.
 * @Description Json脱敏序列化
 */
public class SensitiveInfoSerialize extends JsonSerializer<Object> implements ContextualSerializer {

    private SensitiveTypeEnum type;

    public SensitiveInfoSerialize() {
    }

    public SensitiveInfoSerialize(final SensitiveTypeEnum type) {
        this.type = type;
    }


    @Override
    public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
        switch (this.type) {
            case ID_CARD: {
                jsonGenerator.writeString(SensitiveInfoUtils.idCard(String.valueOf(value)));
                break;
            }
            case MOBILE_PHONE: {
                jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(String.valueOf(value)));
                break;
            }
            case EMAIL: {
                jsonGenerator.writeString(SensitiveInfoUtils.email(String.valueOf(value)));
                break;
            }
            case ACCOUNT_NO: {
                jsonGenerator.writeString(SensitiveInfoUtils.acctNo(String.valueOf(value)));
                break;
            }
            case PASSWORD: {
                jsonGenerator.writeString(SensitiveInfoUtils.password(String.valueOf(value)));
                break;
            }
            case NAME: {
                jsonGenerator.writeString(SensitiveInfoUtils.realName(String.valueOf(value)));
                break;
            }
            default:
                jsonGenerator.writeString(String.valueOf(value));

        }

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {

        if (beanProperty != null) {

            // 非 String 类直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                SensitiveInfo sensitiveInfo = beanProperty.getAnnotation(SensitiveInfo.class);
                if (sensitiveInfo == null) {
                    sensitiveInfo = beanProperty.getContextAnnotation(SensitiveInfo.class);
                }
                // 如果能得到注解,就将注解的 value 传入 SensitiveInfoSerialize
                if (sensitiveInfo != null) {

                    return new SensitiveInfoSerialize(sensitiveInfo.value());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);

    }
}

1.4、脱敏工具类

package com.hlj.proj.utils.sensitivity;

import org.apache.commons.lang3.StringUtils;


/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName SensitiveInfoUtils
 * @date 2019/6/13  20:01.
 * @Description 脱敏工具类
 */
public class SensitiveInfoUtils {

    /**
     * [真实姓名] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
     */
    public static String realName(final String realName) {
        if (StringUtils.isBlank(realName)) {
            return "";
        }
        return dealString(realName, 1, 0);
    }

    /**
     * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
     */
    public static String idCard(final String idCard) {
        if (StringUtils.isBlank(idCard)) {
            return "";
        }
        return dealString(idCard, 3, 4);
    }

    /**
     * [手机号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
     */
    public static String mobilePhone(final String idCard) {
        if (StringUtils.isBlank(idCard)) {
            return "";
        }
        return dealString(idCard, 3, 4);
    }

    /**
     * [邮箱] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
     */
    public static String email(final String email) {
        if (StringUtils.isBlank(email)) {
            return "";
        }
        int index = email.indexOf("@");
        return dealString(email, 3, email.length() - index);
    }

    /**
     * [账号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:*************5762>
     */
    public static String acctNo(final String idCard) {
        if (StringUtils.isBlank(idCard)) {
            return "";
        }
        final String name = StringUtils.left(idCard, 1);
        return StringUtils.rightPad(name, StringUtils.length(idCard), "*");
    }

    /**
     * [密码] 隐藏。<例子:*************>
     */
    public static String password(final String password) {
        if (StringUtils.isBlank(password)) {
            return "";
        }
        return "*";
    }


    private static String dealString(String str, int head_off, int tail_off) {
        int length = str.length();
        StringBuffer sb = new StringBuffer();
        final String head = StringUtils.left(str, head_off);
        String tail = StringUtils.right(str, tail_off);
        sb.append(head);
        int size = length - (head_off + tail_off);
        if (size > 0) {
            while (size > 0) {
                sb.append("*");
                size--;
            }
        }
        sb.append(tail);
        return sb.toString();
    }


    /**
     * 提供给外部进行直接脱敏处理
     * @param type
     * @param value
     * @return
     */
    public static String sensitveValue(SensitiveTypeEnum type, String value) {
        switch (type) {
            case NAME: {
                return realName(String.valueOf(value));
            }
            case ID_CARD: {
                return idCard(String.valueOf(value));
            }
            case MOBILE_PHONE: {
                return mobilePhone(String.valueOf(value));
            }
            case EMAIL: {
                return email(String.valueOf(value));
            }
            case ACCOUNT_NO: {
                return acctNo(String.valueOf(value));
            }
            case PASSWORD: {
                return password(String.valueOf(value));
            }
            default:
                return String.valueOf(value);
        }

    }


}

1.5、Bean

package com.hlj.proj.dto.Demo;


import com.fasterxml.jackson.annotation.JsonInclude;
import com.hlj.proj.utils.sensitivity.SensitiveInfo;
import com.hlj.proj.utils.sensitivity.SensitiveTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName DemoDTO
 * @date 2019/6/13  20:02.
 * @Description
 */
@Data
@Accessors(chain = true)
@ApiModel(value = "demo实体类")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DemoDTO   {

    @ApiModelProperty(value = "demo 主键", hidden = true)
    private Long id;

    @ApiModelProperty(value = "姓名")
    @SensitiveInfo(SensitiveTypeEnum.REAL_NAME)
    private String name;

    @ApiModelProperty(value = "年龄")
    private Integer age;

    @ApiModelProperty(value = "手机号")
    private String phone;

    @ApiModelProperty(value = "邮箱")
    private String email;

    @ApiModelProperty(value = "是否删除,10可用,99删除 ", hidden = true)
    private String delFlag;

    @ApiModelProperty(value = "创建人", hidden = true)
    private Long createUser;

    @ApiModelProperty(value = "创建人名字", hidden = true)
    private String createName;

    @ApiModelProperty(value = "创建时间", hidden = true)
    private java.util.Date createTime;

    @ApiModelProperty(value = "更新人", hidden = true)
    private Long updateUser;

    @ApiModelProperty(value = "更新人名称", hidden = true)
    private String updateName;

    @ApiModelProperty(hidden = true)
    private java.util.Date updateTime;

}

1.6、controller测试

    /**
     * 不建议使用注解
     *
     * @param demoDTO
     * @return
     */
    @ApiOperation(value = "注解脱敏:不建议使用",
            notes = "注解脱敏:不建议使用",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE,
            response = DemoDTO.class)
    @GetMapping(value = "sensitivity/anno", produces = "application/json; charset=utf-8")
    @ResponseBody
    public ResponseBean anno(DemoDTO demoDTO) {
        log.info("脱敏--------注解脱敏------数据信息{}", demoDTO);
        demoEntityService.getDemoDTO(demoDTO);
        return ResponseBean.buildSuccess(demoDTO);
    }



  /**
     * 正常JsonUtils :也会脱敏的
     */
    @ApiOperation(value = "正常JsonUtils",
            notes = "正常JsonUtils",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE,
            response = DemoDTO.class)
    @GetMapping(value = "sensitivity/normal", produces = "application/json; charset=utf-8")
    @ResponseBody
    public String normal(DemoDTO demoDTO) {
        log.info("脱敏--------Json工具脱敏------数据信息{}", demoDTO);
        demoEntityService.getDemoDTO(demoDTO);
        return JsonUtils.toJsonString(demoDTO);
    }

1.7、浏览器观察

http://localhost:8888/hlj/sensitivity/anno?age=1&email=healerjean%40gmail.com&name=%E5%BC%A0%E4%BC%9F%E5%A4%A7&phone=17610397651

{
  "success": true,
  "result": {
    "name": "张**",
    "age": 1,
    "phone": "17610397651",
    "email": "healerjean@gmail.com"
  },
  "msg": "",
  "code": 200,
  "date": "1564458518959"
}

2、Json脱敏

2.1、提供给JsonUtils工具类进行脱敏

package com.hlj.proj.utils.sensitivity;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;

import java.util.List;
import java.util.Map;


/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName MyBeanSerializerModifier
 * @date 2019/6/13  20:01.
 * @Description 提供给JsonUtils工具类进行脱敏
 */
public class SensitiveSerializerModifier extends BeanSerializerModifier {

    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
        // 循环所有的beanPropertyWriter
        for (int i = 0; i < beanProperties.size(); i++) {
            BeanPropertyWriter writer = beanProperties.get(i);
            //判断是否有注解
            SensitiveInfo sensitiveInfo = writer.getAnnotation(SensitiveInfo.class);
            if (sensitiveInfo != null) {
               continue;
            }
            final String propName = writer.getName();
            //正则匹配
            for (Map.Entry<String, SensitiveTypeEnum> entry : SensitivityConstants.sensitivityRules.entrySet()) {
                String rule = entry.getKey();
                int length = rule.length();
                int propLen = propName.length();
                if (propName.length() < length) {
                    continue;
                }
                int temp = rule.indexOf("*");
                String key = null;
                String substring = null;
                if (temp >= 0) {
                    if (temp < (length >> 2)) {
                        key = rule.substring(temp + 1, length);
                        substring = propName.substring(propLen - key.length(), propLen);
                    } else {
                        key = rule.substring(0, temp);
                        substring = propName.substring(0, temp);
                    }
                    if (substring.equals(key)) {
                        writer.assignSerializer(new SensitiveInfoSerialize(entry.getValue()));
                    }
                } else if (rule.equals(propName)) {
                    writer.assignSerializer(new SensitiveInfoSerialize(entry.getValue()));
                }
            }
        }
        return beanProperties;
    }
}

2.2、脱敏规则

package com.hlj.proj.utils.sensitivity;


import java.util.HashMap;
import java.util.Map;


/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName SensitivityConstants
 * @date 2019/6/13  20:01.
 * @Description 脱敏规则
 */
public class SensitivityConstants {

    public static Map<String, SensitiveTypeEnum> sensitivityRules = new HashMap<>();

    static {
        /** 真实姓名 */
        sensitivityRules.put("name", SensitiveTypeEnum.NAME);
        /** 身份证 */
        sensitivityRules.put("*IdCard", SensitiveTypeEnum.ID_CARD);
        sensitivityRules.put("idCard", SensitiveTypeEnum.ID_CARD);
        /** 手机号 */
        sensitivityRules.put("*Phone", SensitiveTypeEnum.MOBILE_PHONE);
        sensitivityRules.put("*phone", SensitiveTypeEnum.MOBILE_PHONE);
        sensitivityRules.put("phone", SensitiveTypeEnum.MOBILE_PHONE);
        sensitivityRules.put("*Mobile", SensitiveTypeEnum.MOBILE_PHONE);
        sensitivityRules.put("mobile", SensitiveTypeEnum.MOBILE_PHONE);
        /** 邮箱 */
        sensitivityRules.put("*Email", SensitiveTypeEnum.EMAIL);
        sensitivityRules.put("email", SensitiveTypeEnum.EMAIL);
        /** 密码 */
        sensitivityRules.put("passWord", SensitiveTypeEnum.PASSWORD);
        sensitivityRules.put("password", SensitiveTypeEnum.PASSWORD);
        sensitivityRules.put("*Password", SensitiveTypeEnum.PASSWORD);
        sensitivityRules.put("*PassWord", SensitiveTypeEnum.PASSWORD);
    }

}

2.3、Json脱敏


ObjectMapper objectMapperSensitivity = new ObjectMapper();

//脱敏日志创建
objectMapperSensitivity.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapperSensitivity.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapperSensitivity.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapperSensitivity.registerModule(javaTimeModule);
objectMapperSensitivity.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//脱敏
objectMapperSensitivity.setSerializerFactory(objectMapperSensitivity.getSerializerFactory().withSerializerModifier(new SensitiveSerializerModifier()));






    /**
     * 对象转Json格式字符串----脱敏处理(包含map)
     *
     * @return
     */
    public static String toJsonStringWithSensitivity(Object propName) {
        if (propName != null && propName instanceof Map) {
            Map map = (Map) propName;
            if (map != null && !map.isEmpty()) {
                Set<Map.Entry> set = map.entrySet();
                for (Map.Entry item : set) {
                    Object key = item.getKey();
                    Object value = item.getValue();
                    if (key instanceof String) {
                        String keyString = key.toString();
                        String s = dealSensitivity(keyString, value.toString());
                        map.put(keyString, s);
                    }
                }
            }
        }
        try {
            return objectMapperSensitivity.writeValueAsString(propName);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String dealSensitivity(String mapkey, String mapValue) {
        //正则匹配
        for (Map.Entry<String, SensitiveTypeEnum> entry : SensitivityConstants.sensitivityRules.entrySet()) {
            String rule = entry.getKey();
            int length = rule.length();
            int propLen = mapkey.length();
            if (mapkey.length() < length) {
                continue;
            }
            int temp = rule.indexOf("*");
            String key = null;
            String substring = null;
            if (temp >= 0) {
                if (temp < (length >> 2)) {
                    key = rule.substring(temp + 1, length);
                    substring = mapkey.substring(propLen - key.length(), propLen);
                } else {
                    key = rule.substring(0, temp);
                    substring = mapkey.substring(0, temp);
                }
                if (substring.equals(key)) {
                    return SensitiveInfoUtils.sensitveValue(entry.getValue(), mapValue);
                }
            } else if (rule.equals(mapkey)) {
                return SensitiveInfoUtils.sensitveValue(entry.getValue(), mapValue);
            }
        }
        return mapValue;
    }

2.4、Bean

package com.hlj.proj.dto.Demo;


import com.fasterxml.jackson.annotation.JsonInclude;
import com.hlj.proj.utils.sensitivity.SensitiveInfo;
import com.hlj.proj.utils.sensitivity.SensitiveTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @author HealerJean
 * @version 1.0v
 * @ClassName DemoDTO
 * @date 2019/6/13  20:02.
 * @Description
 */
@Data
@Accessors(chain = true)
@ApiModel(value = "demo实体类")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DemoDTO   {

    @ApiModelProperty(value = "demo 主键", hidden = true)
    private Long id;

    @ApiModelProperty(value = "姓名")
    @SensitiveInfo(SensitiveTypeEnum.REAL_NAME)
    private String name;

    @ApiModelProperty(value = "年龄")
    private Integer age;

    @ApiModelProperty(value = "手机号")
    private String phone;

    @ApiModelProperty(value = "邮箱")
    private String email;

    @ApiModelProperty(value = "是否删除,10可用,99删除 ", hidden = true)
    private String delFlag;

    @ApiModelProperty(value = "创建人", hidden = true)
    private Long createUser;

    @ApiModelProperty(value = "创建人名字", hidden = true)
    private String createName;

    @ApiModelProperty(value = "创建时间", hidden = true)
    private java.util.Date createTime;

    @ApiModelProperty(value = "更新人", hidden = true)
    private Long updateUser;

    @ApiModelProperty(value = "更新人名称", hidden = true)
    private String updateName;

    @ApiModelProperty(hidden = true)
    private java.util.Date updateTime;

}

2.5、controlelr

   /**
     * 建议使用工具类
     *
     * @param demoDTO
     * @return
     */
    @ApiOperation(value = "Json工具脱敏:建议使用",
            notes = "Json工具脱敏::建议使用",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE,
            response = DemoDTO.class)
    @GetMapping(value = "sensitivity/jsonUtils", produces = "application/json; charset=utf-8")
    @ResponseBody
    public String sensitivity(DemoDTO demoDTO) {
        log.info("脱敏--------Json工具脱敏------数据信息{}", demoDTO);
        demoEntityService.getDemoDTO(demoDTO);
        return ResponseBean.buildSensitivitySuccess(demoDTO);
    }


    /**
     * 脱敏
     * @param result
     * @return
     */
    public static String buildSensitivitySuccess(Object result){
        return JsonUtils.toJsonStringWithSensitivity(buildSuccess(result))  ;
    }



2.6、浏览器测试

{
  "success": true,
  "result": {
    "name": "张**",
    "age": 1,
    "phone": "176****7651",
    "email": "hea*******@gmail.com"
  },
  "msg": "",
  "code": 200,
  "date": "1564460586464"
}

ContactAuthor