SpringBoot 一个注解实现数据脱敏

什么是数据脱敏

数据脱敏是指对某些敏感信息,例如姓名、身份证号码、手机号、固定电话、银行卡号、邮箱等个人信息,通过脱敏算法进行数据变形,以保护敏感隐私数据。

SpringBoot 一个注解实现数据脱敏_第1张图片

数据脱敏通常涉及以下几种主要方法:

  1. 替换: 将原始数据中的敏感信息替换为不敏感的等效数据。例如,将真实姓名替换为随机生成的名称,将电话号码替换为虚构的号码。
  2. 扰动: 对数据进行微小的变化,以使其仍然保持某种程度的统计一致性,但不足以使个人身份可被轻松识别。这包括添加噪声或对数值进行微小的随机化。
  3. 屏蔽: 将敏感信息从数据中删除或隐藏。例如,用特定字符或占位符替换敏感文本。
  4. 一般化: 减少数据的精确度,使得数据更加模糊。例如,将年龄精确到天数的数据一般化为年龄范围。
  5. 删除: 完全删除不必要的敏感信息。

Spring Boot自定义注解实现数据脱敏

依赖版本

  • JDK 17
  • Spring Boot 3.2.0
  • Hutool-core 5.8.24 (非必须)

源码地址:Gitee

导入依赖

<dependencies>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-coreartifactId>
        <version>5.8.24version>
    dependency>
dependencies>

自定义脱敏类型枚举

/**
 * @description 数据脱敏策略枚举
 */
public enum DesensitizationTypeEnum {
    //自定义
    CUSTOM,
    //用户id
    USER_ID,
    //中文名
    CHINESE_NAME,
    //身份证号
    ID_CARD,
    //座机号
    FIXED_PHONE,
    //手机号
    MOBILE_PHONE,
    //地址
    ADDRESS,
    //电子邮件
    EMAIL,
    //密码
    PASSWORD,
    //中国大陆车牌,包含普通车辆、新能源车辆
    CAR_LICENSE,
    //银行卡
    BANK_CARD
}

自定义脱敏序列化器

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
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 com.yiyan.study.annotation.Desensitization;
import com.yiyan.study.enums.DesensitizationTypeEnum;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

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

/**
 * @description 自定义脱敏序列化类
 */
@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {

    private DesensitizationTypeEnum type;

    private Integer startInclude;

    private Integer endExclude;

    @Override
    public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        switch (type) {
            // 自定义类型脱敏
            case CUSTOM -> jsonGenerator.writeString(CharSequenceUtil.hide(String.valueOf(str), startInclude,
                    endExclude >= startInclude ? endExclude : str.length() + endExclude));
            // userId脱敏
            case USER_ID -> jsonGenerator.writeString(String.valueOf(DesensitizedUtil.userId()));
            // 中文姓名脱敏
            case CHINESE_NAME -> jsonGenerator.writeString(CharSequenceUtil.hide(String.valueOf(str), 1, str.length()));
            // 身份证脱敏
            case ID_CARD -> jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2));
            // 固定电话脱敏
            case FIXED_PHONE -> jsonGenerator.writeString(DesensitizedUtil.fixedPhone(String.valueOf(str)));
            // 手机号脱敏
            case MOBILE_PHONE -> jsonGenerator.writeString(DesensitizedUtil.mobilePhone(String.valueOf(str)));
            // 地址脱敏
            case ADDRESS -> jsonGenerator.writeString(DesensitizedUtil.address(String.valueOf(str), 8));
            // 邮箱脱敏
            case EMAIL -> jsonGenerator.writeString(DesensitizedUtil.email(String.valueOf(str)));
            // 密码脱敏
            case PASSWORD -> jsonGenerator.writeString(DesensitizedUtil.password(String.valueOf(str)));
            // 中国车牌脱敏
            case CAR_LICENSE -> jsonGenerator.writeString(DesensitizedUtil.carLicense(String.valueOf(str)));
            // 银行卡脱敏
            case BANK_CARD -> jsonGenerator.writeString(DesensitizedUtil.bankCard(String.valueOf(str)));
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            // 判断数据类型是否为String类型
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                // 获取定义的注解
                Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
                if (desensitization == null) {
                    desensitization = beanProperty.getContextAnnotation(Desensitization.class);
                }
                if (desensitization != null) {
                    return new DesensitizationSerialize(desensitization.type(), desensitization.startInclude(),
                            desensitization.endExclude());
                }
            }

            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }
}

自定义脱敏注解

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.yiyan.study.enums.DesensitizationTypeEnum;
import com.yiyan.study.serialize.DesensitizationSerialize;

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

/**
 * 脱敏注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {
    /**
     * 脱敏数据类型,CUSTOM注解下,startInclude和endExclude生效
     */
    DesensitizationTypeEnum type() default DesensitizationTypeEnum.CUSTOM;

    /**
     * 脱敏开始位置(包含)
     */
    int startInclude() default 0;

    /**
     * 脱敏结束位置(不包含)
     */
    int endExclude() default 0;
}

数据脱敏测试

定义测试对象

import com.yiyan.study.annotation.Desensitization;
import com.yiyan.study.enums.DesensitizationTypeEnum;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

@Data
@Builder
public class DesensitizationDTO implements Serializable {

    @Desensitization(type = DesensitizationTypeEnum.CHINESE_NAME)
    private String username;

    @Desensitization(type = DesensitizationTypeEnum.EMAIL)
    private String email;

    @Desensitization(type = DesensitizationTypeEnum.ADDRESS)
    private String address;

    @Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE)
    private String phoneNumber;

    @Desensitization(type = DesensitizationTypeEnum.CUSTOM, startInclude = 1, endExclude = -2)
    private String note;
}

测试接口

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yiyan.study.model.DesensitizationDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 启动类
 */
@SpringBootApplication
@Slf4j
@RestController
public class DesensitizationApplication {

    private static final DesensitizationDTO dto = com.yiyan.study.model.DesensitizationDTO.builder()
            .username("小李")
            .phoneNumber("12300000456")
            .email("[email protected]")
            .address("XX.YY.DD.FF")
            .note("123456789")
            .build();

    public static void main(String[] args) throws JsonProcessingException {
        SpringApplication.run(DesensitizationApplication.class, args);

        ObjectMapper mapper = new ObjectMapper();
        String s = mapper.writeValueAsString(dto);
        log.info("Json : {}", s);
    }

    @GetMapping("/test")
    public DesensitizationDTO desensitizationTest() {
        return dto;
    }
}

你可能感兴趣的:(Spring,Boot,spring,boot,后端,java,spring,web安全)