原理
- 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
是设置自定义序列化类 type
、start
、end
是自定义属性,用于后续脱敏逻辑处理
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 {
CUSTOM_RULE,
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)) {
Desensitize desensition = property.getAnnotation(Desensitize.class);
if (null == desensition) {
desensition = property.getContextAnnotation(Desensitize.class);
}
if (null != desensition) {
return new DesensitizedSerializerPlugin(desensition.type(), desensition.start(), desensition.end());
}
}
return prov.findValueSerializer(property.getType(), property);
}
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则不能实现
- 一般微服务系统中,在网关中进行数据脱敏,因为考虑动态脱敏,即:根据不同角色,对不同的数据进行脱敏。比如:
- 超级管理员,就不需要脱敏
- 普通用户角色,需要对敏感数据进行脱敏
- 部门主管,对自己所在部门数据无需脱敏,但是其他部门数据需要脱敏
参考文章