SpringBoot基于Jackson实现数据脱敏

原理

  • SpringBoot中,默认使用Jackson进行序列化数据为JSON,故只需要在Jackson序列化时,将String对应数据进行脱敏处理即可

脱敏工具Hutool

方式一:利用@JsonSerialize注解

  • 使用@JsonSerialize注解,将某个字段设置使用自定义的序列化类
  • 然后,在自定义序列化类中,实现数据脱敏逻辑
示例
  • 自定义序列化类,并实现脱敏逻辑
package gk.springboot.preheat.plugin;

import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class MySerializerPlugin extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(DesensitizedUtil.chineseName(value));
    }
}
  • 使用,给UserInfo实体类中name字段进行注解,指定使用自定义MySerializerPlugin序列化类
package gk.springboot.preheat.model;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import gk.springboot.preheat.plugin.MySerializerPlugin;
import lombok.Data;

import java.util.Date;
import java.util.List;

@Data
public class UserInfo {
    private Integer id;
    @JsonSerialize(using = MySerializerPlugin.class)
    private String name;
    private Integer age;
    private Character sex;
    private Date createTime = new Date();
    private Date updateTime = new Date();
    private List<Account> account;
}
测试
  • name字段被脱敏
    在这里插入图片描述

方式二:自定义注解 + @JsonSerialize注解

  • @JsonSerialize注解是支持在注解上使用,利用这点,可以自定义注解
  • 将字段中存在自定义注解的,全部设置为使用自定义序列化类
  • 然后,利用自定义注解中定义脱敏类型,完成针对不同的数据(如:手机号、Email、姓名、银行卡等)进行脱敏工作
示例
  • 自定义注解@Desensitize
    • 其中@JsonSerialize是设置自定义序列化类
    • typestartend是自定义属性,用于后续脱敏逻辑处理
package gk.springboot.preheat.annotation;

import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.annotation.JacksonAnnotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import gk.springboot.preheat.plugin.DesensitizedSerializerPlugin;

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 = DesensitizedSerializerPlugin.class)
public @interface Desensitize {
	// 定义枚举类,主要用于设置启用哪种脱敏方式对数据脱敏
    DesensitizedSerializerPlugin.DesensitizedType type() default DesensitizedSerializerPlugin.DesensitizedType.CUSTOM_RULE;

    int start() default 0;

    int end() default 1;
}
  • 自定义序列化类
    • 继承JsonSerializer并重写serialize()方法,完成序列化时,数据脱敏逻辑
    • 实现ContextualSerializer接口,重写createContextual()方法,获取创建序列化程序的上下文(简单理解:就是获取字段创建序列化时的信息)
package gk.springboot.preheat.plugin;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
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 gk.springboot.preheat.annotation.Desensitize;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

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

@AllArgsConstructor
@NoArgsConstructor
public class DesensitizedSerializerPlugin extends JsonSerializer<String> implements ContextualSerializer {
    public enum DesensitizedType {
        // 自定义从start -> end 进行脱敏
        CUSTOM_RULE,
        //用户id
        USER_ID,
        //中文名
        CHINESE_NAME,
        //身份证号
        ID_CARD,
        //座机号
        FIXED_PHONE,
        //手机号
        MOBILE_PHONE,
        //地址
        ADDRESS,
        //电子邮件
        EMAIL,
        //密码
        PASSWORD,
        //中国大陆车牌,包含普通车辆、新能源车辆
        CAR_LICENSE,
        //银行卡
        BANK_CARD
    }

    private DesensitizedType typeEnum;
    private Integer start;
    private Integer end;

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        String newStr;
        switch (typeEnum) {
            case USER_ID:
                newStr = String.valueOf(DesensitizedUtil.userId());
                break;
            case CHINESE_NAME:
                newStr = DesensitizedUtil.chineseName(value);
                break;
            case ID_CARD:
                newStr = DesensitizedUtil.idCardNum(value, 1, 2);
                break;
            case FIXED_PHONE:
                newStr = DesensitizedUtil.fixedPhone(value);
                break;
            case MOBILE_PHONE:
                newStr = DesensitizedUtil.mobilePhone(value);
                break;
            case ADDRESS:
                newStr = DesensitizedUtil.address(value, 8);
                break;
            case EMAIL:
                newStr = DesensitizedUtil.email(value);
                break;
            case PASSWORD:
                newStr = DesensitizedUtil.password(value);
                break;
            case CAR_LICENSE:
                newStr = DesensitizedUtil.carLicense(value);
                break;
            case BANK_CARD:
                newStr = DesensitizedUtil.bankCard(value);
                break;
            case CUSTOM_RULE:
                if (StrUtil.isBlank(value))
                    newStr = value;
                else
                    newStr = CharSequenceUtil.hide(value, start, end);
                break;
            default:
                newStr = value;
        }
        gen.writeString(newStr);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (null != property) {
            if (Objects.equals(property.getType().getRawClass(), String.class)) {
                // 获取Desensitization注解
                Desensitize desensition = property.getAnnotation(Desensitize.class);
                if (null == desensition) {
                    desensition = property.getContextAnnotation(Desensitize.class);
                }
                if (null != desensition) {
                    // 使用自定义的JsonSerializer
                    return new DesensitizedSerializerPlugin(desensition.type(), desensition.start(), desensition.end());
                }
            }
            // 值非String类型或没有Desensitize注解,返回原有类型的JsonSerializer
            return prov.findValueSerializer(property.getType(), property);
        }
        // 值为null,返回null的JsonSerializer
        return prov.findNullValueSerializer(null);
    }
}
  • 在UserInfo的VO上使用@Desensitize注解
    • @Desensitize中的type指定了使用哪种方式进行数据脱敏
package gk.springboot.preheat.model;

import gk.springboot.preheat.annotation.Desensitize;
import gk.springboot.preheat.plugin.DesensitizedSerializerPlugin;
import lombok.Data;

import java.util.Date;
import java.util.List;

@Data
public class UserInfo {
    private Integer id;
    @Desensitize(type = DesensitizedSerializerPlugin.DesensitizedType.CHINESE_NAME)
    private String name;
    private Integer age;
    private Character sex;
    private Date createTime = new Date();
    private Date updateTime = new Date();
    private List<Account> account;
}
测试

在这里插入图片描述

注意

  • 该方式需要使用Jackson作为Http消息转换器的JSON序列化类,如果使用fastJson则不能实现
  • 一般微服务系统中,在网关中进行数据脱敏,因为考虑动态脱敏,即:根据不同角色,对不同的数据进行脱敏。比如:
    • 超级管理员,就不需要脱敏
    • 普通用户角色,需要对敏感数据进行脱敏
    • 部门主管,对自己所在部门数据无需脱敏,但是其他部门数据需要脱敏

参考文章

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值