1.定义注解

创建Spring Boot项目添加以下依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.7.22</version>
        </dependency>
    </dependencies>

然后定义Sensitive 注解,@JacksonAnnotationsInside的作用是将@JacksonAnnotation标注的注解作为一个组合注解,这里使用@JacksonAnnotationsInside在@Sensitive 注解中组合@JsonSerialize注解,这样就可以使用JsonSerialize的功能,并且可以拓展自定义属性和隐式得指定自定义序列化器SensitiveSerializer了。

@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
    /**
     * 脱敏的类型,默认手机号
     * @return
     */
    Type value();

    /**
     * CUSTOM_HIDE/CUSTOM_OVERLAY 时生效
     * 开始位置(包含)
     * @return
     */
    int startInclude() default 0;

    /**
     * CUSTOM_HIDE/CUSTOM_OVERLAY 时生效
     * 结束位置(不包含)
     * @return
     */
    int endExclude() default 0;


    /**
     * CUSTOM_OVERLAY 时生效,*重复的次数
     * @return
     */
    int overlayRepeat() default 4;


    /**
     * Enumeration used with {@link Sensitive}
     */
    public enum Type {
        /**
         * 手机号
         */
        MOBILE,
        /**
         * 中文名
         */
        CHINESE_NAME,
        /**
         * 身份证号
         */
        ID_CARD,
        /**
         * 座机号
         */
        FIXED_PHONE,
        /**
         * 地址
         */
        ADDRESS,
        /**
         * 电子邮件
         */
        EMAIL,
        /**
         * 银行卡
         */
        BANK_CARD,
        /**
         * 自定义,有多少个字符替换成多少个*
         * e.g: startInclude=3,endExclude=7,隐藏第3个到第7个的字符
         */
        CUSTOM_HIDE,
        /**
         *保留方式隐藏
         * e.g: startInclude=3,endExclude=4 ,保留前面3个和后面的4个
         */
        CUSTOM_RETAIN_HIDE,
        /**
         * 自定义,只替换成指定个*
         */
        CUSTOM_OVERLAY,
    }

}

2. 脱敏工具类

创建脱敏工具类SensitiveUtil ,统一封装数据脱敏的方法。

public class SensitiveUtil {


    /**
     * [手机号码] 前3位后4位明码,中间4位掩码用****显示,如138****0000
     * @param mobile 手机号码
     * @return
     */
    public static String handlerMobile(String mobile) {
        if(StringUtil.isEmpty(mobile)){
            return null;
        }
         return hide(mobile,3,mobile.length() - 4);
    }

    /**
     * [手机号码] 只显示后四位, 如:*8856
     * @param phone 手机号码
     * @return
     */
    public static String handlerPhone(String phone) {
        if(StringUtil.isEmpty(phone)){
            return null;
        }
        return overlay(phone,StringPool.ASTERISK,1,0,phone.length() - 4);
    }


    /**
     * [身份证号] 前6位后4位明码,中间掩码用***显示,如511623********0537
     * @param idNum
     * @return
     */
    public static String handlerIdCard(String idNum) {
        if(StringUtil.isEmpty(idNum)){
            return null;
        }
        return hide(idNum,6,idNum.length() - 4);
    }

    /**
     * [银行卡] 前6位后4位明码,中间部分****代替,如622848*********5579
     * @param cardNum
     * @return
     */
    public static String handlerBankCard(String cardNum) {
        if(StringUtil.isEmpty(cardNum)){
            return null;
        }
        return hide(cardNum,6,cardNum.length() - 4);
    }

    /**
     * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:广东省广州市天河区****>
     * @param address
     */
    public static String handlerAddress(String address) {
        if(StringUtil.isEmpty(address)){
            return null;
        }
        return overlay(address,StringPool.ASTERISK,4,9,address.length());
    }


    /**
     * [用户名] 只显示第一位 <例子:黄**>
     * @param username
     * @return
     */
    public static String handlerUsername(String username) {
        if(StringUtil.isEmpty(username)){
            return null;
        }
        return hide(username,1,username.length());
    }

    /**
     * [固定电话] 后四位,其他隐藏<例子:****1234>
     */
    public static String handlerFixedPhone(final String num) {
        if(StringUtil.isEmpty(num)){
            return null;
        }
        return overlay(num, StringPool.ASTERISK,4, 0,num.length()-4);
    }

    /**
     * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>
     * @param email
     * @return
     */
    public static String handlerEmail(final String email) {
        if(StringUtil.isEmpty(email)){
            return null;
        }
        final int index = StringUtil.indexOf(email, StringPool.AT_CHAR);
        if (index <= 1) {
            return email;
        } else {
            return hide(email, 1, index);
        }
    }

    /**
     * 用另一个字符串覆盖一个字符串的一部分
     * @param str  需要替换的字符串
     * @param overlay  将被替换成的字符串
     * @param overlayRepeat  overlay重复的次数
     * @param start  开始位置
     * @param end  结束位置
     * @return
     */
    public static String overlay(String str, String overlay,int overlayRepeat, int start, int end) {
        if (StringUtil.isEmpty(str)) {
            return StringUtil.str(str);
        }
        if (StringUtil.isEmpty(overlay)) {
            overlay = StringPool.EMPTY;
        }
        final int len = str.length();
        if (start < 0) {
            start = 0;
        }
        if (start > len) {
            start = len;
        }
        if (end < 0) {
            end = 0;
        }
        if (end > len) {
            end = len;
        }
        if (start > end) {
            final int temp = start;
            start = end;
            end = temp;
        }
        return str.substring(0, start) + StringUtil.repeat(overlay, overlayRepeat) + str.substring(end);
    }

    /**
     * 替换指定字符串的指定区间内字符为"*"
     *
     * @param str 字符串
     * @param startInclude 开始位置(包含)
     * @param endExclude 结束位置(不包含)
     * @return 替换后的字符串
     */
    public static String hide(String str, int startInclude, int endExclude) {
        return hide(str, startInclude, endExclude,StringPool.ASTERISK_CHAR);

    }

    /**
     * 替换指定字符串的指定区间内字符为"*"
     *
     * @param str 字符串
     * @param startInclude 开始位置(包含)
     * @param endExclude 结束位置(不包含)
     * @param replacedChar
     * @return 替换后的字符串
     */
    public static String hide(String str, int startInclude, int endExclude, char replacedChar) {
        if (StringUtil.isEmpty(str)) {
            return StringUtil.str(str);
        }
        final int strLength = str.length();
        if (startInclude > strLength) {
            return StringUtil.str(str);
        }
        if (endExclude > strLength) {
            endExclude = strLength;
        }
        if (startInclude > endExclude) {
            // 如果起始位置大于结束位置,不替换
            return StringUtil.str(str);
        }

        final char[] chars = new char[strLength];
        for (int i = 0; i < strLength; i++) {
            if (i >= startInclude && i < endExclude) {
                chars[i] = replacedChar;
            } else {
                chars[i] = str.charAt(i);
            }
        }
        return new String(chars);
    }

}

3. 自定义Jackson序列化器

参考Jackson自带的StringSerializer,继承JsonSerializer,重写serialize方法,自定义Jackson序列化器SensitiveSerializer针对String类型数据进行数据脱敏处理。

public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private Sensitive.Type type;
    private int startInclude;
    private int endExclude;
    private int overlayRepeat;

    public SensitiveSerializer() {}
    public SensitiveSerializer(final Sensitive sensitive) {
        this.type = sensitive.value();
        this.startInclude = sensitive.startInclude();
        this.endExclude = sensitive.endExclude();
        this.overlayRepeat = sensitive.overlayRepeat();
    }


    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if(ObjectUtil.isEmpty(value)){
            gen.writeString(value);
            return;
        }else {
            switch (this.type) {
                case MOBILE:
                    gen.writeString(SensitiveUtil.handlerMobile(value));
                    break;
                case ID_CARD:
                    gen.writeString(SensitiveUtil.handlerIdCard(value));
                    break;
                case BANK_CARD:
                    gen.writeString(SensitiveUtil.handlerBankCard(value));
                    break;
                case CHINESE_NAME:
                    gen.writeString(SensitiveUtil.handlerUsername(value));
                    break;
                case FIXED_PHONE:
                    gen.writeString(SensitiveUtil.handlerFixedPhone(value));
                    break;
                case ADDRESS:
                    gen.writeString(SensitiveUtil.handlerAddress(value));
                    break;
                case EMAIL:
                    gen.writeString(SensitiveUtil.handlerEmail(value));
                    break;
                case CUSTOM_HIDE:
                    gen.writeString(SensitiveUtil.hide(value,startInclude,endExclude));
                    break;
                case CUSTOM_RETAIN_HIDE:
                    gen.writeString(SensitiveUtil.hide(value,startInclude,(value.length()-endExclude)));
                    break;
                case CUSTOM_OVERLAY:
                    gen.writeString(SensitiveUtil.overlay(value, StringPool.ASTERISK,overlayRepeat,startInclude,endExclude));
                    break;
                default:
                    gen.writeString(value);
            }
        }

    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if(Objects.isNull(property)){
            return prov.getDefaultNullValueSerializer();
        }
        if(Objects.equals(property.getType().getRawClass(), String.class)){
            Sensitive sensitive = property.getAnnotation(Sensitive.class);
            if (Objects.isNull(sensitive)) {
                sensitive = property.getContextAnnotation(Sensitive.class);
            }
            if (Objects.nonNull(sensitive)) {
                return new SensitiveSerializer(sensitive);
            }
        }
        return prov.findValueSerializer(property.getType(), property);
        //Sensitive sensitive = property.getAnnotation(Sensitive.class);
        //type = sensitive.value();
        //return this;
    }
}

4. 测试使用

创建实体类Student

public class Student {

    @Sensitive(value = Sensitive.Type.CHINESE_NAME)
    private String name;

    @Sensitive(value = Sensitive.Type.ID_CARD)
    private String idCard;

    @Sensitive(value = Sensitive.Type.BANK_CARD)
    private String bankCard;

    @Sensitive(value = Sensitive.Type.FIXED_PHONE)
    private String fixedPhone;

    @Sensitive(value = Sensitive.Type.ADDRESS)
    private String address;

    @Sensitive(value = Sensitive.Type.EMAIL)
    private String email;

    @Sensitive(value = Sensitive.Type.CUSTOM_RETAIN_HIDE,startInclude = 3,endExclude = 10)
    private String remark;

    public Student() {
    }

    public String getBankCard() {
        return bankCard;
    }

    public void setBankCard(String bankCard) {
        this.bankCard = bankCard;
    }

    public String getFixedPhone() {
        return fixedPhone;
    }

    public void setFixedPhone(String fixedPhone) {
        this.fixedPhone = fixedPhone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Student(String name, String idCard, String bankCard, String fixedPhone, String address, String email, String remark) {
        this.name = name;
        this.idCard = idCard;
        this.bankCard = bankCard;
        this.fixedPhone = fixedPhone;
        this.address = address;
        this.email = email;
        this.remark = remark;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }
}

定义接口/test/student

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/student")
    public Student getStudent(){
        Student student = new Student();
        student.setName("张三三");
        student.setIdCard("44082199612054343");
        student.setBankCard("62173300255879654448");
        student.setFixedPhone("3110026");
        student.setAddress("广东省广州市天河区");
        student.setEmail("1258398545@qq.com");
        student.setRemark("sadhaonsdoasnodnaonodsn是大祭司大祭司你");
        return student;
    }
}

浏览器请求接口/test/student,查看接口返回的数据

spring boot postHandle 脱敏 springboot 日志脱敏_spring