1

Preface

What is data desensitization

Data desensitization refers to the deformation of certain sensitive information through desensitization rules to achieve reliable protection of sensitive privacy data

Common desensitization rules

Replace, rearrange, encrypt, truncate, mask

Good data masking implementation

1. As far as possible for the application after desensitization, keep the meaningful information before desensitization
2. Prevent hackers from cracking to the greatest extent

Today we talk about how to customize data desensitization

the whole idea

This example achieves desensitization by replacing it, and then cooperates with common framework features, such as the interceptor mechanism of mybatis or serialization of json to quickly achieve desensitization

Concrete landing

1. Define a desensitization tool class

You can directly reference the hutool toolkit, but it only provides this tool after version 5.6+
https://www.hutool.cn/docs/#/core/Tools/Information Desensitization Tool-DesensitizedUtil

Otherwise, implement one yourself, the shape is as follows

public class DesensitizedUtils {



    /**
     * 脱敏,使用默认的脱敏策略
     * <pre>
     * DesensitizedUtil.desensitized("100", DesensitizedUtils.DesensitizedType.USER_ID)) =  "0"
     * DesensitizedUtil.desensitized("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)) = "段**"
     * DesensitizedUtil.desensitized("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)) = "5***************1X"
     * DesensitizedUtil.desensitized("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)) = "0915*****79"
     * DesensitizedUtil.desensitized("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)) = "180****1999"
     * DesensitizedUtil.desensitized("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)) = "北京市海淀区马********"
     * DesensitizedUtil.desensitized("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)) = "d*************@gmail.com.cn"
     * DesensitizedUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********"
     * DesensitizedUtil.desensitized("苏D40000", DesensitizedUtils.DesensitizedType.CAR_LICENSE)) = "苏D4***0"
     * DesensitizedUtil.desensitized("11011111222233333256", DesensitizedUtils.DesensitizedType.BANK_CARD)) = "1101 **** **** **** 3256"
     * </pre>
     *
     * @param str              字符串
     * @param desensitizedType 脱敏类型;可以脱敏:用户id、中文名、身份证号、座机号、手机号、地址、电子邮件、密码
     * @return 脱敏之后的字符串
     * @author dazer and neusoft and qiaomu
     * @since 5.6.2
     */
    public static String desensitized(CharSequence str, DesensitizedType desensitizedType) {
        if (StrUtil.isBlank(str)) {
            return StrUtil.EMPTY;
        }
        String newStr = String.valueOf(str);
        switch (desensitizedType) {
            case USER_ID:
                newStr = String.valueOf(DesensitizedUtils.userId());
                break;
            case CHINESE_NAME:
                newStr = DesensitizedUtils.chineseName(String.valueOf(str));
                break;
            case ID_CARD:
                newStr = DesensitizedUtils.idCardNum(String.valueOf(str), 1, 2);
                break;
            case FIXED_PHONE:
                newStr = DesensitizedUtils.fixedPhone(String.valueOf(str));
                break;
            case MOBILE_PHONE:
                newStr = DesensitizedUtils.mobilePhone(String.valueOf(str));
                break;
            case ADDRESS:
                newStr = DesensitizedUtils.address(String.valueOf(str), 8);
                break;
            case EMAIL:
                newStr = DesensitizedUtils.email(String.valueOf(str));
                break;
            case PASSWORD:
                newStr = DesensitizedUtils.password(String.valueOf(str));
                break;
            case CAR_LICENSE:
                newStr = DesensitizedUtils.carLicense(String.valueOf(str));
                break;
            case BANK_CARD:
                newStr = DesensitizedUtils.bankCard(String.valueOf(str));
                break;
            default:
        }
        return newStr;
    }

    /**
     * 【用户id】不对外提供userId
     *
     * @return 脱敏后的主键
     */
    public static Long userId() {
        return 0L;
    }

    /**
     * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
     *
     * @param fullName 姓名
     * @return 脱敏后的姓名
     */
    public static String chineseName(String fullName) {
        if (StrUtil.isBlank(fullName)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(fullName, 1, fullName.length());
    }

    /**
     * 【身份证号】前1位 和后2位
     *
     * @param idCardNum 身份证
     * @param front     保留:前面的front位数;从1开始
     * @param end       保留:后面的end位数;从1开始
     * @return 脱敏后的身份证
     */
    public static String idCardNum(String idCardNum, int front, int end) {
        //身份证不能为空
        if (StrUtil.isBlank(idCardNum)) {
            return StrUtil.EMPTY;
        }
        //需要截取的长度不能大于身份证号长度
        if ((front + end) > idCardNum.length()) {
            return StrUtil.EMPTY;
        }
        //需要截取的不能小于0
        if (front < 0 || end < 0) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(idCardNum, front, idCardNum.length() - end);
    }

    /**
     * 【固定电话 前四位,后两位
     *
     * @param num 固定电话
     * @return 脱敏后的固定电话;
     */
    public static String fixedPhone(String num) {
        if (StrUtil.isBlank(num)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(num, 4, num.length() - 2);
    }

    /**
     * 【手机号码】前三位,后4位,其他隐藏,比如135****2210
     *
     * @param num 移动电话;
     * @return 脱敏后的移动电话;
     */
    public static String mobilePhone(String num) {
        if (StrUtil.isBlank(num)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.hide(num, 3, num.length() - 4);
    }

    /**
     * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
     *
     * @param address       家庭住址
     * @param sensitiveSize 敏感信息长度
     * @return 脱敏后的家庭地址
     */
    public static String address(String address, int sensitiveSize) {
        if (StrUtil.isBlank(address)) {
            return StrUtil.EMPTY;
        }
        int length = address.length();
        return StrUtil.hide(address, length - sensitiveSize, length);
    }

    /**
     * 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
     *
     * @param email 邮箱
     * @return 脱敏后的邮箱
     */
    public static String email(String email) {
        if (StrUtil.isBlank(email)) {
            return StrUtil.EMPTY;
        }
        int index = StrUtil.indexOf(email, '@');
        if (index <= 1) {
            return email;
        }
        return StrUtil.hide(email, 1, index);
    }

    /**
     * 【密码】密码的全部字符都用*代替,比如:******
     *
     * @param password 密码
     * @return 脱敏后的密码
     */
    public static String password(String password) {
        if (StrUtil.isBlank(password)) {
            return StrUtil.EMPTY;
        }
        return StrUtil.repeat('*', password.length());
    }

    /**
     * 【中国车牌】车牌中间用*代替
     * eg1:null       -》 ""
     * eg1:""         -》 ""
     * eg3:苏D40000   -》 苏D4***0
     * eg4:陕A12345D  -》 陕A1****D
     * eg5:京A123     -》 京A123     如果是错误的车牌,不处理
     *
     * @param carLicense 完整的车牌号
     * @return 脱敏后的车牌
     */
    public static String carLicense(String carLicense) {
        if (StrUtil.isBlank(carLicense)) {
            return StrUtil.EMPTY;
        }
        // 普通车牌
        if (carLicense.length() == 7) {
            carLicense = StrUtil.hide(carLicense, 3, 6);
        } else if (carLicense.length() == 8) {
            // 新能源车牌
            carLicense = StrUtil.hide(carLicense, 3, 7);
        }
        return carLicense;
    }

    /**
     * 银行卡号脱敏
     * eg: 1101 **** **** **** 3256
     *
     * @param bankCardNo 银行卡号
     * @return 脱敏之后的银行卡号
     * @since 5.6.3
     */
    public static String bankCard(String bankCardNo) {
        if (StrUtil.isBlank(bankCardNo)) {
            return bankCardNo;
        }
        bankCardNo = StrUtil.trim(bankCardNo);
        if (bankCardNo.length() < 9) {
            return bankCardNo;
        }

        final int length = bankCardNo.length();
        final int midLength = length - 8;
        final StringBuilder buf = new StringBuilder();

        buf.append(bankCardNo, 0, 4);
        for (int i = 0; i < midLength; ++i) {
            if (i % 4 == 0) {
                buf.append(CharUtil.SPACE);
            }
            buf.append('*');
        }
        buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length);
        return buf.toString();
    }
}

In fact, when this step is normal, desensitization can be completed by replacing it. You can call this tool directly in the program. But as a programmer who knows how to be lazy, he is definitely not satisfied with this. So we will further encapsulate

2. Custom desensitization annotation
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Sensitive {

    DesensitizedType strategy() default DesensitizedType.NONE;

    /**
     * 是否使用dfa算法
     * @return
     */
    boolean useDFA() default false;

    /**
     * dfa敏感字符替换,默认替换成 "*"
     * @return
     */
    String dfaReplaceChar() default "*";


    /**
     * dfa敏感字符替换次数
     * @return
     */
    int dfaReplaceCharRepeatCount() default 1;

}
3. Use some framework features to improve efficiency

a. If the project already uses mybatis, you can use the mybatis interceptor feature. The realization principle is to intercept the results of the response, and then desensitize the results

@Intercepts(@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class))
public class DesensitizedInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> list = (List<Object>) invocation.proceed();
        list.forEach(EntityUtils::desensitized);

        return list;
    }

}

b. If the project is a web project based on springboot, you can use the jackson custom serialization implementation that comes with springboot. Its implementation turned out to be desensitization when json was serialized and rendered to the front end.

If this is the case, you need to modify the custom annotations and add

@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedJsonSerializer.class)

annotation. The shape is as follows

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedJsonSerializer.class)
public @interface Sensitive {

    DesensitizedType strategy() default DesensitizedType.NONE;

    /**
     * 是否使用dfa算法
     * @return
     */
    boolean useDFA() default false;

    /**
     * dfa敏感字符替换,默认替换成 "*"
     * @return
     */
    String dfaReplaceChar() default "*";


    /**
     * dfa敏感字符替换次数
     * @return
     */
    int dfaReplaceCharRepeatCount() default 1;

}

The core code of serialization desensitization logic is as follows

public class DesensitizedJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private Sensitive sensitive;
    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(EntityUtils.getDesensitizedValue(sensitive,s));

    }

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

        sensitive = beanProperty.getAnnotation(Sensitive.class);

        if(!ObjectUtils.isEmpty(sensitive) && String.class.isAssignableFrom(beanProperty.getType().getRawClass())){
            return this;
        }
        return serializerProvider.findValueSerializer(beanProperty.getType(),beanProperty);
    }
}

Example

Take json as an example

1. To define the entity object, add desensitization annotations to the attributes that need to be desensitized
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDTO {

    private Integer id;

    private String username;

    @Sensitive(strategy = DesensitizedType.PASSWORD)
    private String password;

    @Sensitive(strategy = DesensitizedType.CHINESE_NAME)
    private String fullname;

    @Sensitive(strategy = DesensitizedType.MOBILE_PHONE)
    private String mobile;

    @Sensitive(strategy = DesensitizedType.EMAIL)
    private String email;

    @Sensitive(useDFA = true,dfaReplaceChar = "#",dfaReplaceCharRepeatCount = 3)
    private String remark;
}
2. Write a test controller
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;


    @GetMapping(value="/list")
    public AjaxResult listUsers(){
       return AjaxResult.success(userService.listUserDTO());
    }

}
Test Results


Desensitization has been carried out as shown in the picture

Other options

1. Realize data desensitization based on Sharding Sphere

For specific implementation, please refer to the following article

https://jaskey.github.io/blog/2020/03/18/sharding-sphere-data-desensitization/

2. Custom annotation formatting

The main implementation steps are as follows

  • 1. Implement the AnnotationFormatterFactory interface
  • 2. Create a desensitized formatting class to implement Formatter
  • 3. Register the interface implemented by AnnotationFormatterFactory to FormatterRegistry

For specific implementation, please refer to the following article

https://blog.csdn.net/qq_27081015/article/details/103295983

4. Use fastjson for desensitization

The main implementation steps are as follows

  • 1. Implement the ValueFilter interface and desensitize in the process
  • 2. Configure fastjson as the default JSON conversion
/**
     * 配置fastjson为默认JSON转换
     *
     * @return
     */
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1.定义一个converters转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastJsonConfig.setSerializeFilters(new ValueDesensitizeFilter());//添加自己写的拦截器
        // 3.在converter中添加配置信息
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4.将converter赋值给HttpMessageConverter
        HttpMessageConverter<?> converter = fastConverter;
        // 5.返回HttpMessageConverters对象
        return new HttpMessageConverters(converter);
    }

For specific implementation, please refer to the following article

https://blog.csdn.net/qq_27081015/article/details/103297316

5. Use mybatis-mate

Mybatis-plus Enterprise (Elegant Data Processing) module, you need to configure the authorization code when using it. as follows

mybatis-mate:
  cert:
    grant: jinTianYiXueKe
    license: GKXP9r4MCJhGID/DTGigcBcLmZjb1YZGjE4GXaAoxbtGsPC20sxpEtiUr2F7Nb1ANTUekvF6Syo6DzraA4M4oacwoLVTglzfvaEyUogW8L7mydqlsZ4+hlm20kK85eLJK1QsskrSJmreMnEaNh9lsV7Lpbxy9JeGCeM0HPEbRvq8Y+8dUt5bQYLklsa3ZIBexir+4XykZY15uqn1pYIp4pEK0+aINTa57xjJNoWuBIqm7BdFIb4l1TAcPYMTsMXhF5hfMmKD2h391HxWTshJ6jbt4YqdKD167AgeoM+B+DE1jxlLjcpskY+kFs9piOS7RCcmKBBUOgX2BD/JxhR2gQ==

His implementation mechanism is to use json serialization, if you are interested, you can refer to the following link

https://gitee.com/baomidou/mybatis-mate-examples

The demo of this article is also based on mybatis-mate to achieve desensitization, the link is as follows
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-desensitization/springboot-desensitzation-mybatis-mate

Summarize

Sometimes there are many ways to implement business scenarios, and everyone needs to know how to make a decision. For example, if your project does not use mybatis in the above scheme, but you introduce mybatis for desensitization, this scheme adds additional complexity. , The maintenance is estimated to have to toss

demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-desensitization


linyb极客之路
336 声望193 粉丝