数据脱敏
使用注解实现关键数据脱敏处理
需求前景
有的时候,前端页面需要展示身份证,手机号等这种关键个人信息的数据,明文显示肯定是不适合的,我们需要对其中的数据做脱敏处理,下面记录一下通过注解来实现关键信息的脱敏。
代码实例
1、定义脱敏有关枚举
/**
* 脱敏枚举策略
*
* @author LiJunYi
*/
public enum SensitiveStrategy {
/**
* Username sensitive strategy.
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* Id card sensitive type.
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
/**
* Phone sensitive type.
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* Address sensitive type.
*/
ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));
private final Desensitizer desensitizer;
SensitiveStrategy(Desensitizer desensitizer) {
this.desensitizer = desensitizer;
}
/**
* Gets desensitizer.
*
* @return the desensitizer
*/
public Desensitizer getDesensitizer() {
return desensitizer;
}
}
2、定义具体脱敏策略的函数
/**
* 具体脱敏策略的函数
*/
public interface Desensitizer extends Function<String,String> {
}
3、定义一个JSON序列化器
/**
*
* @author LiJunYi
*/
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveStrategy strategy;
/**
* serialize方法执行脱敏序列化逻辑
*/
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(strategy.getDesensitizer().apply(value));
}
/***
* createContextual方法用来获取实体类上的@Sensitive注解并根据条件初始化对应的JsonSerializer对象;
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation)&&Objects.equals(String.class, property.getType().getRawClass())) {
this.strategy = annotation.strategy();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
4、脱敏注解
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;
/**
* Jackson的捆绑注解@JacksonAnnotationsInside,它的作用是将多个注解组合到一起
* 序列化注解@JsonSerialize,它的作用是声明使用我上面自定义的序列化方法。
* @author LiJunYi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
SensitiveStrategy strategy();
}
5、具体的脱敏组件方法
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.stream.Stream;
@Intercepts(@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}))
public class SensitivePlugin implements Interceptor {
@SuppressWarnings("unchecked")
@Override
public Object intercept(Invocation invocation) throws Throwable {
List<Object> records = (List<Object>) invocation.proceed();
// 对结果集脱敏
records.forEach(this::sensitive);
return records;
}
private void sensitive(Object source) {
// 拿到返回值类型
Class<?> sourceClass = source.getClass();
// 初始化返回值类型的 MetaObject
MetaObject metaObject = SystemMetaObject.forObject(source);
// 捕捉到属性上的标记注解 @Sensitive 并进行对应的脱敏处理
Stream.of(sourceClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Sensitive.class))
.forEach(field -> doSensitive(metaObject, field));
}
private void doSensitive(MetaObject metaObject, Field field) {
// 拿到属性名
String name = field.getName();
// 获取属性值
Object value = metaObject.getValue(name);
// 只有字符串类型才能脱敏 而且不能为null
if (String.class == metaObject.getGetterType(name) && value != null) {
Sensitive annotation = field.getAnnotation(Sensitive.class);
// 获取对应的脱敏策略 并进行脱敏
SensitiveStrategy type = annotation.strategy();
Object o = type.getDesensitizer().apply((String) value);
// 把脱敏后的值塞回去
metaObject.setValue(name, o);
}
}
}
6、实体类添加脱敏注解
@Data
public class User {
/**
* 真实姓名
*/
@Sensitive(strategy = SensitiveStrategy.USERNAME)
private String realName;
/**
* 地址
*/
@Sensitive(strategy = SensitiveStrategy.ADDRESS)
private String address;
/**
* 电话号码
*/
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phoneNumber;
/**
* 身份证号码
*/
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard;
}
测试
public class Test
{
public static void main(String[] args) {
User user = new User();
user.setRealName("张三丰");
user.setPhoneNumber("13333333333");
user.setAddress("浙江省杭州市西湖区西溪湿地");
user.setIdCard("4333333333334334333");
ObjectMapper objectMapper = new ObjectMapper();
String json = null;
try {
json = objectMapper.writeValueAsString(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println(json);
}
}
结果输出
{
"realName":"张*丰",
"address":"浙江省****市西湖区西溪****",
"phoneNumber":"133****3333",
"idCard":"4333****34333"
}