Utilisez des annotations personnalisées dans SpringBoot pour réaliser avec élégance la désensibilisation des données de confidentialité (affichage crypté)

Avant-propos
Pendant les deux jours de rectification et autres tests de sécurité, il existe un élément de risque de "fuite d'informations utilisateur" (c'est-à-dire que certaines données privées des utilisateurs dans le système d'arrière-plan sont directement affichées en texte brut), qui fait en fait référence à la désensibilisation des données .

Désensibilisation des données : cryptez certaines données sensibles dans le système, puis renvoyez-les pour protéger la confidentialité
effet de désensibilisation
. Effectuez un processus de cryptage avant de renvoyer les données. Bien sûr, cette méthode doit être très rentable. Il est recommandé d'utiliser des annotations pour y parvenir, ce qui est efficace et élégant, permet d'économiser du temps et des efforts et prend en charge les extensions.

En fait, il existe généralement deux solutions :
1. Désensibiliser les données lorsque vous les obtenez (comme utiliser la fonction d'insertion pour les masquer lors de l'interrogation de mysql) 2.
Désensibiliser les données lorsque vous obtenez les données (comme Utiliser fastjson, jackson)
la solution que je choisis ici est la deuxième, c'est-à-dire qu'avant que l'interface ne renvoie les données, les valeurs des champs sensibles sont traitées lors de la sérialisation, et la sérialisation de jackson est utilisée pour y parvenir (recommandé)

1. Créez une énumération de type de données de confidentialité : PrivacyTypeEnum

import lombok.Getter;

/**
 * 隐私数据类型枚举
 */
@Getter
public enum PrivacyTypeEnum {
    
    

  /** 自定义(此项需设置脱敏的范围)*/
  CUSTOMER,

  /** 姓名 */
  NAME,

  /** 身份证号 */
  ID_CARD,

  /** 手机号 */
  PHONE,

  /** 邮箱 */
  EMAIL,
}

2. Créez une annotation de confidentialité personnalisée : PrivacyEncrypt

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

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) // class文件中保留,运行时也保留,能通过反射读取到
@JacksonAnnotationsInside // 表示自定义自己的注解PrivacyEncrypt
@JsonSerialize(using = PrivacySerializer.class) // 该注解使用序列化的方式
public @interface PrivacyEncrypt {
    
    

    /**
     * 脱敏数据类型(没给默认值,所以使用时必须指定type)
     */
    PrivacyTypeEnum type();

    /**
     * 前置不需要打码的长度
     */
    int prefixNoMaskLen() default 1;

    /**
     * 后置不需要打码的长度
     */
    int suffixNoMaskLen() default 1;

    /**
     * 用什么打码
     */
    String symbol() default "*";
}

3. Créez un sérialiseur personnalisé : PrivacySerializer

import cn.hutool.core.util.StrUtil;
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 lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

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

@NoArgsConstructor
@AllArgsConstructor
public class PrivacySerializer extends JsonSerializer<String> implements ContextualSerializer {
    
    

    // 脱敏类型
    private PrivacyTypeEnum privacyTypeEnum;
    // 前几位不脱敏
    private Integer prefixNoMaskLen;
    // 最后几位不脱敏
    private Integer suffixNoMaskLen;
    // 用什么打码
    private String symbol;

    @Override
    public void serialize(String origin, final JsonGenerator jsonGenerator,
                          final SerializerProvider serializerProvider) throws IOException {
    
    
        if (StrUtil.isEmpty(origin)) {
    
    
            origin = null;
        }
        if (null != privacyTypeEnum) {
    
    
            switch (privacyTypeEnum) {
    
    
                case CUSTOMER:
                    jsonGenerator.writeString(PrivacyUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
                    break;
                case NAME:
                    jsonGenerator.writeString(PrivacyUtil.hideChineseName(origin));
                    break;
                case ID_CARD:
                    jsonGenerator.writeString(PrivacyUtil.hideIDCard(origin));
                    break;
                case PHONE:
                    jsonGenerator.writeString(PrivacyUtil.hidePhone(origin));
                    break;
                case EMAIL:
                    jsonGenerator.writeString(PrivacyUtil.hideEmail(origin));
                    break;
                default:
                    throw new IllegalArgumentException("unknown privacy type enum " + privacyTypeEnum);
            }
        }
    }

    @Override
    public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
                                              final BeanProperty beanProperty) throws JsonMappingException {
    
    
        if (null != beanProperty) {
    
    
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
    
    
                PrivacyEncrypt privacyEncrypt = beanProperty.getAnnotation(PrivacyEncrypt.class);
                if (null == privacyEncrypt) {
    
    
                    privacyEncrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class);
                }
                if (null != privacyEncrypt) {
    
    
                    return new PrivacySerializer(privacyEncrypt.type(), privacyEncrypt.prefixNoMaskLen(),
                            privacyEncrypt.suffixNoMaskLen(), privacyEncrypt.symbol());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }

}

Voici le processus de mise en œuvre spécifique, car les données à désensibiliser sont de type String, donc lors de l'héritage de JsonSerializer, remplissez le type de String et
réécrivez la méthode de sérialisation est au cœur de la désensibilisation, et définissez la valeur sérialisée en fonction du type Le la méthode createContextual réécrite
consiste à lire nos annotations personnalisées PrivacyEncrypt pour créer un environnement contextuel

[ Important ] Mise à jour du 04/07/2023 : Le code ci-dessus a été optimisé et modifié, ce qui résout le problème anormal de rapport de sérialisation de Jackson lorsque la valeur du champ dans la table de la base de données est une chaîne vide ou non nulle

4. Classe d'outil de masquage des données de confidentialité : PrivacyUtil

public class PrivacyUtil {
    
    

    /**
     * 中文名脱敏
     */
    public static String hideChineseName(String chineseName) {
    
    
        if (StrUtil.isEmpty(chineseName)) {
    
    
            return null;
        }
        if (chineseName.length() <= 2) {
    
    
            return desValue(chineseName, 1, 0, "*");
        }
        return desValue(chineseName, 1, 1, "*");
    }

    /**
     * 手机号脱敏
     */
    public static String hidePhone(String phone) {
    
    
        if (StrUtil.isEmpty(phone)) {
    
    
            return null;
        }
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); // 隐藏中间4位
//        return desValue(phone, 3, 4, "*"); // 隐藏中间4位
//        return desValue(phone, 7, 0, "*"); // 隐藏末尾4位
    }

    /**
     * 邮箱脱敏
     */
    public static String hideEmail(String email) {
    
    
        if (StrUtil.isEmpty(email)) {
    
    
            return null;
        }
        return email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
    }

    /**
     * 身份证号脱敏
     */
    public static String hideIDCard(String idCard) {
    
    
        if (StrUtil.isEmpty(idCard)) {
    
    
            return null;
        }
        return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2");
    }

    /**
     * 对字符串进行脱敏操作
     * @param origin          原始字符串
     * @param prefixNoMaskLen 左侧需要保留几位明文字段
     * @param suffixNoMaskLen 右侧需要保留几位明文字段
     * @param maskStr         用于遮罩的字符串, 如'*'
     * @return 脱敏后结果
     */
    public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
    
    
        if (StrUtil.isEmpty(origin)) {
    
    
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = origin.length(); i < n; i++) {
    
    
            if (i < prefixNoMaskLen) {
    
    
                sb.append(origin.charAt(i));
                continue;
            }
            if (i > (n - suffixNoMaskLen - 1)) {
    
    
                sb.append(origin.charAt(i));
                continue;
            }
            sb.append(maskStr);
        }
        return sb.toString();
    }

}

En fait, cette classe d'outils peut être personnalisée par vous-même et étendue en fonction de votre propre activité.Deux points sont mentionnés :

  1. Quelle que soit la désensibilisation de cette classe d'outils, vous devez d'abord déterminer si le paramètre est vide. S'il est vide, retournez directement null, afin que jsonGenerator.writeString() puisse être exécuté normalement
  2. Dans l'annotation personnalisée PrivacyEncrypt, uniquement lorsque la valeur de type est PrivacyTypeEnum.CUSTOMER (personnalisé), la plage de désensibilisation doit être spécifiée, c'est-à-dire les valeurs de prefixNoMaskLen et suffixNoMaskLen.Les formats cachés tels que l'adresse e-mail et le téléphone mobile le nombre peut être fixé

5. Utilisation des annotations

Ajoutez directement des annotations aux champs qui doivent être désensibilisés et spécifiez la valeur de type (généralement utilisée sur les champs de la classe d'entité VO renvoyée), comme suit :

@Data
public class People {
    
    

    private Integer id;

    private String name;

    private Integer sex;

    private Integer age;

    @PrivacyEncrypt(type = PrivacyTypeEnum.PHONE) // 隐藏手机号
    private String phone;

    @PrivacyEncrypt(type = PrivacyTypeEnum.EMAIL) // 隐藏邮箱
    private String email;

    private String sign;
}

À ce stade, le travail de désensibilisation est terminé. Vous pouvez utiliser cette annotation globalement, une fois pour toutes, et prendre en charge l'expansion. L'effet de test est le suivant :

Effet

Le code ci-dessus a été testé et utilisé en production réelle, vous pouvez donc l'utiliser en toute confiance
Article de référence : https://blog.csdn.net/a792396951/article/details/117993344

Je suppose que tu aimes

Origine blog.csdn.net/qq_36737803/article/details/122366043
conseillé
Classement