Fastjson之数据脱敏

很多业务数据在展示上需要进行脱敏处理,保护重要的敏感信息。如电话号码脱敏,期望展示的数据格式是156****7837;如身份证号码脱敏,期望展示的数据格式是420***********113X。

当然在记录操作日志时对密码等信息进行过滤,保证其安全。那么可以采用Fastjson进行配置(本文所用的是SpringBoot环境)

1.Fastjson实现数据脱敏

采用的fastjson的版本是1.2.83,依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

后续步骤如下:

1)定义枚举类,列举所有需要脱敏的字段类型

package com.zxh.test.enums;

/**
 * @author zhongyushi
 */

public enum DesensitionTypeEnum {
    /**
     * 中文名
     */
    CHINA_NAME,
    /**
     * 16或者18身份证号
     */
    ID_CARD,
    /**
     * 11位手机号
     */
    MOBILE_PHONE,
    /**
     * 固定电话
     */
    FIXED_PHONE,
    /**
     * 银行卡号
     */
    BANK_CARD,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 密码
     */
    PASSWORD,
    /**
     * 车牌号
     */
    CAR_NUMBER,
    /**
     * 地址
     */
    ADDRESS,


}

2)定义注解,用来标注需要的字段

package com.zxh.test.annotation;

import com.zxh.test.enums.DesensitionTypeEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 数据脱敏注解
 *
 * @author zhongyushi
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {

    /**
     * 脱敏规则类型
     *
     * @return
     */
    DesensitionTypeEnum value();
}

3)添加脱敏的工具类

package com.zxh.test.util;

import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import org.apache.commons.lang.StringUtils;

/**
 * 数据脱敏工具类
 */public class DesensitizedUtils {

    /**
     * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
     *
     * @param fullName
     * @return
     */
    public static String chineseName(String fullName) {
        if (StringUtils.isBlank(fullName)) {
            return "";
        }
        String name = StringUtils.left(fullName, 1);
        return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
    }

    /**
     * 【身份证号】前三位 和后四位
     *
     * @param idCardNum
     * @return
     */
    public static String idCardNum(String idCardNum) {
        return idCardNum(idCardNum, 3, 4);
    }

    /**
     * 【身份证号】前三位 和后四位
     *
     * @param front
     * @param end
     * @return
     */
    public static String idCardNum(String idCardNum, int front, int end) {
        //身份证不能为空
        if (StringUtils.isEmpty(idCardNum)) {
            return "";
        }
        //需要截取的长度不能大于身份证号长度
        if ((front + end) > idCardNum.length()) {
            return "";
        }
        //需要截取的不能小于0
        if (front < 0 || end < 0) {
            return "";
        }
        //计算*的数量
        int asteriskCount = idCardNum.length() - (front + end);
        StringBuffer asteriskStr = new StringBuffer();
        for (int i = 0; i < asteriskCount; i++) {
            asteriskStr.append("*");
        }
        String regex = "(\\w{" + String.valueOf(front) + "})(\\w+)(\\w{" + String.valueOf(end) + "})";
        return idCardNum.replaceAll(regex, "$1" + asteriskStr + "$3");
    }

    /**
     * 【固定电话】 前四位,后两位
     *
     * @param num
     * @return
     */
    public static String fixedPhone(String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.left(num, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 2), StringUtils.length(num), "*"), "****"));
    }

    /**
     * 【手机号码】前三位,后四位,其他隐藏,比如135****4310
     *
     * @param num
     * @return
     */
    public static String mobilePhone(String num) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), "***"));
    }

    /**
     * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
     *
     * @param address
     * @param sensitiveSize 敏感信息长度
     * @return
     */
    public static String address(String address, int sensitiveSize) {
        if (StringUtils.isBlank(address)) {
            return "";
        }
        int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }

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

    /**
     * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234
     *
     * @param cardNum
     * @return
     */
    public static String bankCard(String cardNum) {
        if (StringUtils.isBlank(cardNum)) {
            return "";
        }
        return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"), "******"));
    }

    /**
     * 【密码】密码的全部字符都用*代替,比如:******
     *
     * @param password
     * @return
     */
    public static String password(String password) {
        if (StringUtils.isBlank(password)) {
            return "";
        }
        String pwd = StringUtils.left(password, 0);
        return StringUtils.rightPad(pwd, StringUtils.length(password), "*");
    }

    /**
     * 【车牌号】前两位后一位,比如:苏M****5
     *
     * @param carNumber
     * @return
     */
    public static String carNumber(String carNumber) {
        if (StringUtils.isBlank(carNumber)) {
            return "";
        }
        return StringUtils.left(carNumber, 2).
                concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(carNumber, 1), StringUtils.length(carNumber), "*"), "**"));
    }

    /**
     * 忽略敏感属性,即过滤掉不显示
     *
     * @param propertyName 要忽略的字段名称
     * @return
     */
    public static PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter(String... propertyName) {
        return new PropertyPreFilters().addFilter().addExcludes(propertyName);
    }
}

4)新建过滤器实现VueFilter,对字段进行过滤

package com.zxh.test.filter;

import com.alibaba.fastjson.serializer.ValueFilter;
import com.zxh.test.annotation.Desensitization;
import com.zxh.test.enums.DesensitionTypeEnum;
import com.zxh.test.util.DesensitizedUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;

/**
 * fastjson序列化过滤器
 *
 * @author zhongyushi
 */
@Slf4j
public class ValueDesensitizeFilter implements ValueFilter {
    @Override
    public Object process(Object object, String name, Object value) {
        if (null == value || !(value instanceof String) || ((String) value).length() == 0) {
            return value;
        }
        try {
            Desensitization annotation;
            //获取字段
            Field field = object.getClass().getDeclaredField(name);
            if (String.class != field.getType() || (annotation = field.getAnnotation(Desensitization.class)) == null) {
                return value;
            }
            //获取字段中注解的类型
            DesensitionTypeEnum typeEnum = annotation.value();
            String body = (String) value;
            switch (typeEnum) {
                case CHINA_NAME:
                    value = DesensitizedUtils.chineseName(body);
                    break;
                case ID_CARD:
                    value = DesensitizedUtils.idCardNum(body);
                    break;
                case MOBILE_PHONE:
                    value = DesensitizedUtils.mobilePhone(body);
                    break;
                case FIXED_PHONE:
                    value = DesensitizedUtils.fixedPhone(body);
                    break;
                case BANK_CARD:
                    value = DesensitizedUtils.bankCard(body);
                    break;
                case EMAIL:
                    value = DesensitizedUtils.email(body);
                    break;
                case PASSWORD:
                    value = DesensitizedUtils.password(body);
                    break;
                case CAR_NUMBER:
                    value = DesensitizedUtils.carNumber(body);
                    break;
                case ADDRESS:
                    value = DesensitizedUtils.address(body, 6);
                    break;
            }
        } catch (NoSuchFieldException e) {
            log.error("通过反射获取字段异常:{}", e);
        }
        return value;

    }
}

5)自定义消息转换对象,实现数据脱敏

package com.zxh.test.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.zxh.test.filter.ValueDesensitizeFilter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义消息转换对象
 *
 * @author zhongyushi
 */
@Configuration
public class FastJsonWebSerializationConfiguration implements WebMvcConfigurer {

    @Bean(name = "httpMessageConverters")
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1.定义一个converters转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        // 中文乱码解决方案
        List<MediaType> mediaTypes = new ArrayList<>();
        //设定json格式且编码为UTF-8
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConverter.setSupportedMediaTypes(mediaTypes);
        //添加自己写的拦截器,实现脱敏
        fastJsonConfig.setSerializeFilters(new ValueDesensitizeFilter());
        // 3.在converter中添加配置信息
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4.将converter赋值给HttpMessageConverter
        HttpMessageConverter<?> converter = fastConverter;
        // 5.返回HttpMessageConverters对象
        return new HttpMessageConverters(converter);
    }
}

6)新建实体类,使用注解

package com.zxh.test;

import com.zxh.test.annotation.Desensitization;
import com.zxh.test.enums.DesensitionTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVo {
    //id主键
    private String id;
    //中文名
    @Desensitization(DesensitionTypeEnum.CHINA_NAME)
    private String chineseName;
    //密码
    @Desensitization(DesensitionTypeEnum.PASSWORD)
    private String password;
    //确认密码
    private String passwd;
    //手机号
    @Desensitization(DesensitionTypeEnum.MOBILE_PHONE)
    private String mobilePhone;
    //固定电话
    @Desensitization(DesensitionTypeEnum.FIXED_PHONE)
    private String fixedPhone;
    //身份证号码
    @Desensitization(DesensitionTypeEnum.ID_CARD)
    private String idCard;
    //地址
    @Desensitization(DesensitionTypeEnum.ADDRESS)
    private String address;
    //邮箱
    @Desensitization(DesensitionTypeEnum.EMAIL)
    private String email;
    //银行卡号
    @Desensitization(DesensitionTypeEnum.BANK_CARD)
    private String bankCard;
    //车牌号
    @Desensitization(DesensitionTypeEnum.CAR_NUMBER)
    private String carNumber;

}

7)新建controller接口,设置对象属性值

package com.zxh.test.controller;

import com.zxh.test.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {

    @GetMapping("/set")
    public UserVo set() {
        UserVo userVo = new UserVo();
        userVo.setId("3");
        userVo.setMobilePhone("15623347837");
        userVo.setIdCard("42032119990909113X");
        userVo.setAddress("湖北省武汉市洪山区光谷大道1号");
        userVo.setCarNumber("鄂W6669S");
        userVo.setBankCard("6212267877021329");
        userVo.setPassword("wwwbaidu999@");
        userVo.setPasswd("wwwbaidu999@");
        userVo.setChineseName("钟小嘿");
        userVo.setEmail("zhongxiaohei@123.com");
        userVo.setFixedPhone("027-82829991");
        return userVo;
    }
}

这里返回的是实体对象,实际上返回包含此对象的集合或其他类型,都是可以的。

8)启动项目,在浏览器访问后返回结果如下

此时已基本实现了敏感数据的脱敏。而对于非controller的数据,需要手动转换进行脱敏,另外可过滤掉不需要的字段,如以下的测试方法

    @Test
    public void test3() {
        //非controller调用序列化脱敏.手动进行转换
        UserVo userVo = new UserVo();
        userVo.setId("3");
        userVo.setMobilePhone("15623347837");
        userVo.setIdCard("42032119990909113X");
        userVo.setAddress("湖北省武汉市洪山区光谷大道1号");
        userVo.setCarNumber("鄂W6669S");
        userVo.setBankCard("6212267877021329");
        userVo.setPassword("wwwbaidu999@");
        userVo.setPasswd("wwwbaidu999@");
        userVo.setChineseName("钟小嘿");
        userVo.setEmail("zhongxiaohei@123.com");
        userVo.setFixedPhone("027-82829991");
        //同时指定了两个过滤器,分别对数据进行格式化和字段的过滤(假设去掉字段passwd)
        SerializeFilter[] filters = {new ValueDesensitizeFilter(), DesensitizedUtils.excludePropertyPreFilter( "passwd")};
        System.out.println(JSON.toJSONString(userVo, filters));
    }

打印的结果经json格式化后如下:

可以看出已实现了数据脱敏和字段过滤。

2.Fastjson2实现数据脱敏

Fastjson2与Fastjson实现脱敏的方式相同,只是Fastjson2的有些类做了调整,只说明其差异的部分。

1)依赖的不同

除了要导入Fastjson2外,还需要导入其扩展包

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.21</version>
</dependency>
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2-extension</artifactId>
    <version>2.0.21</version>
</dependency>

2)自定义消息转换对象的方式不同

    @Bean(name = "httpMessageConverters")
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1.定义一个converters转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        // 中文乱码解决方案
        List<MediaType> mediaTypes = new ArrayList<>();
        //设定json格式且编码为UTF-8
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConverter.setSupportedMediaTypes(mediaTypes);
        //添加自己写的拦截器,实现脱敏
        fastJsonConfig.setWriterFilters(new ValueDesensitizeFilter());
        // 3.在converter中添加配置信息
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4.将converter赋值给HttpMessageConverter
        HttpMessageConverter<?> converter = fastConverter;
        // 5.返回HttpMessageConverters对象
        return new HttpMessageConverters(converter);
    }

不再设置序列化的类型,另外,过滤器也配置在了writeFilters中。另外,包名发生了变化

import com.alibaba.fastjson2.support.config.FastJsonConfig;
import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;

3)没有PropertyPreFilters类,因此无法实现忽略某些字段。

posted @ 2023-01-06 10:34  钟小嘿  阅读(1890)  评论(0编辑  收藏  举报