当前位置:   article > 正文

springboot实现数据脱敏(重写序列化方法)_spring boot数据脱敏form表单没生效

spring boot数据脱敏form表单没生效

springboot实现数据脱敏(重写序列化方法)

前言:对敏感数据进行隐藏的做法成为脱敏,实际项目中为了符合等保要求,需要对重要数据进行脱敏显示,例如非管理员不能查看其他用户的手机号登信息。在实现脱敏的方式中,最直观就是通过在接口中对敏感字符串进行替换,但此方式会增加代码量,耦合大,增加维护成本,因此采用自定义序列化方式。

springboot中在controller返回后,向前端发送数据前,需要经过序列化,将数据转化为前端能接收的数据类型,例如json或者xml,通过修改属性的序列化方法,将敏感字段做处理,实现数据脱敏操作。

以下内容基本搬运自文章【SpringBoot中利用自定义注解优雅地实现隐私数据脱敏(加密显示)】

来源链接: https://blog.csdn.net/qq_36737803/article/details/122366043

1. 创建枚举类

public enum DesensitizeTypeEnum {

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

    /** 姓名(有默认脱敏规则,设置脱敏范围参数不生效) */
    NAME,

    /** 身份证号(有默认脱敏规则,设置脱敏范围参数不生效) */
    ID_CARD,

    /** 手机号(有默认脱敏规则,设置脱敏范围参数不生效) */
    PHONE,

    /** 邮箱(有默认脱敏规则,设置脱敏范围参数不生效) */
    EMAIL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这里枚举类的作用是定义默认脱敏行为,实现枚举类型的脱敏方式,因为像邮箱这种特殊格式,通过特定的正则表达式脱敏效果会更好。

2. 创建自定义注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside // 告诉jackson这是个自定义Jackson注解,在jackson序列化时被关注
@JsonSerialize(using = DesensitizeSerializer.class) //告诉jackson被改注解修饰的字段,应该使用自定义序列化器
public @interface DesensitizeField {

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

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

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

3. 创建自定义序列化器

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

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

    /**
     * 标有脱敏注解的字段调用的序列化方法
     * @param value 脱敏前字符串
     * @param gen 用于输出生成的Json内容的生成器
     * @param serializers 提供序列化对象的工厂,可提供当前序列化器和其他序列化器
     * @throws IOException
     */
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 对序列化对象判空处理,只有非空对象才进行json输出
        if (!ObjectUtils.isEmpty(value)) {
            // 写入序列化结果,DesensitizeUtils.desensitize()为自定义字符串脱敏方法
            gen.writeString(DesensitizeUtils.desensitize(value,this));
        }
    }

    /**
     * 构建自定义序列化器的上下文,判断序列化对象是否走自定义序列化器(通过判断是否带有自定义序列化注解)
     * @param serializerProvider 提供序列化对象的工厂
     * @param beanProperty 要序列化的对象
     * @return
     * @throws JsonMappingException
     */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (null != beanProperty) {
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                // 获取序列化对象上的自定义序列化注解
                DesensitizeField desensitizeField = beanProperty.getAnnotation(DesensitizeField.class);
                if (null == desensitizeField) {
                    desensitizeField = beanProperty.getContextAnnotation(DesensitizeField.class);
                }
                // 如果注解对象不为空,需要走自定义脱敏序列化器
                if (null != desensitizeField) {
                    return new DesensitizeSerializer(desensitizeField.type(), desensitizeField.prefixNoMaskLen(),
                            desensitizeField.suffixNoMaskLen(), desensitizeField.symbol());
                }
            }
            // 如果对象不为空或者没有自定义脱敏注解标注,走其他类型序列化器
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        // 如果对象为空,走空序列化器
        return serializerProvider.findNullValueSerializer(null);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

4.创建脱敏方法类

public class DesensitizeUtils {

    /**
     * 判断脱敏类型,根据不同脱敏类型选择不同脱敏方法进行脱敏
     * @param value 原始字符串数据
     * @param serializer  自定义的序列化其,主要是获取脱敏参数
     * @return 脱敏后字符串
     */
    public static String desensitize(String value, DesensitizeSerializer serializer){
        String desensitizeValue;
        switch (serializer.getType()){
            case CUSTOMER:
                desensitizeValue = desValue(value, serializer.getPrefixNoMaskLen(),
                        serializer.getSuffixNoMaskLen(), serializer.getSymbol());
                break;
            case NAME:
                desensitizeValue = hideChineseName(value);
                break;
            case EMAIL:
                desensitizeValue = hideEmail(value);
                break;
            case PHONE:
                desensitizeValue = hidePhone(value);
                break;
            case ID_CARD:
                desensitizeValue = hideIDCard(value);
                break;
            default:
                throw new IllegalArgumentException("unknown privacy type enum " + serializer.getType());
        }
        return desensitizeValue;
    }

    /**
     * 中文名脱敏
     */
    public static String hideChineseName(String chineseName) {
        if (ObjectUtils.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 (ObjectUtils.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 (ObjectUtils.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 (ObjectUtils.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 (ObjectUtils.isEmpty(origin)) {
            return origin;
        }
        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();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

至此,自定义序列化方法实现脱敏构建完成

5. 测试

构建实体类,使用自定义注解

@Data
@ToString
public class UserInfo {

    private String userId;
    @DesensitizeField(type = DesensitizeTypeEnum.PHONE)
    private String mobile;
    @DesensitizeField(type = DesensitizeTypeEnum.CUSTOMER,prefixNoMaskLen = 0,suffixNoMaskLen = 0)
    private String password;
    @DesensitizeField(type = DesensitizeTypeEnum.ID_CARD)
    private String identityId;
    @DesensitizeField(type = DesensitizeTypeEnum.EMAIL)
    private String email;
    private String createTime;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

测试接口

    @GetMapping("test")
    public UserInfo getUserInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId("100001");
        userInfo.setMobile("12345678910");
        userInfo.setPassword("Mm123456");
        userInfo.setIdentityId("430444199901017878");
        userInfo.setEmail("zhangsanlisi123@qq.com");
        userInfo.setCreateTime(null);
        return userInfo;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

测试结果如下图

image-20231129130055208.png

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/920920
推荐阅读
相关标签
  

闽ICP备14008679号