Java利用Jackson序列化实现数据脱敏详解
作者:京东云开发者 发布时间:2023-12-22 17:47:38
1.背景
在项目中有些敏感信息不能直接展示,比如客户手机号、身份证、车牌号等信息,展示时均需要进行数据脱敏,防止泄露客户隐私。脱敏即是对数据的部分信息用脱敏符号(*)处理。
2.目标
在服务端返回数据时,利用Jackson序列化完成数据脱敏,达到对敏感信息脱敏展示。
降低重复开发量,提升开发效率
形成统一有效的脱敏规则
可基于重写默认脱敏实现的desensitize方法,实现可扩展、可自定义的个性化业务场景的脱敏需求
3.主要实现
3.1基于Jackson的自定义脱敏序列化实现
StdSerializer:所有标准序列化程序所使用的基类,这个是编写自定义序列化程序所推荐使用的基类。
ContextualSerializer: 是Jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer。
package com.jd.ccmp.ctm.constraints.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.jd.ccmp.ctm.constraints.Symbol;
import com.jd.ccmp.ctm.constraints.annotation.Desensitize;
import com.jd.ccmp.ctm.constraints.desensitization.Desensitization;
import com.jd.ccmp.ctm.constraints.desensitization.DesensitizationFactory;
import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization;
import java.io.IOException;
/**
* 脱敏序列化器
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:10
*/
public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer {
private static final long serialVersionUID = -7868746622368564541L;
private transient Desensitization<Object> desensitization;
protected ObjectDesensitizeSerializer() {
super(Object.class);
}
public Desensitization<Object> getDesensitization() {
return desensitization;
}
public void setDesensitization(Desensitization<Object> desensitization) {
this.desensitization = desensitization;
}
@Override
public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) {
//获取属性注解
Desensitize annotation = property.getAnnotation(Desensitize.class);
return createContextual(annotation.desensitization());
}
@SuppressWarnings("unchecked")
public JsonSerializer<Object> createContextual(Class<? extends Desensitization<?>> clazz) {
ObjectDesensitizeSerializer serializer = new ObjectDesensitizeSerializer();
if (clazz != DefaultDesensitization.class) {
serializer.setDesensitization((Desensitization<Object>) DesensitizationFactory.getDesensitization(clazz));
}
return serializer;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
Desensitization<Object> objectDesensitization = getDesensitization();
if (objectDesensitization != null) {
try {
gen.writeObject(objectDesensitization.desensitize(value));
} catch (Exception e) {
gen.writeObject(value);
}
} else if (value instanceof String) {
gen.writeString(Symbol.getSymbol(((String) value).length(), Symbol.STAR));
} else {
gen.writeObject(value);
}
注:createContextual可以获得字段的类型以及注解。当字段拥有自定义注解时,取出注解中的值创建定制的序列化方式,这样在serialize方法中便可以得到这个值了。createContextual方法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会改变),所以无需关心性能问题。
3.2定义脱敏接口、以及工厂实现
3.2.1脱敏器接口定义
package com.jd.ccmp.ctm.constraints.desensitization;
/**
* 脱敏器
*
* @author zhangxiaoxu15
* @date 2022/2/8 10:56
*/
public interface Desensitization<T> {
/**
* 脱敏实现
*
* @param target 脱敏对象
* @return 脱敏返回结果
*/
T desensitize(T target);
}
3.2.2脱敏器工厂实现
package com.jd.ccmp.ctm.constraints.desensitization;
import java.util.HashMap;
import java.util.Map;
/**
* 工厂方法
*
* @author zhangxiaoxu15
* @date 2022/2/8 10:58
*/
public class DesensitizationFactory {
private DesensitizationFactory() {
}
private static final Map<Class<?>, Desensitization<?>> map = new HashMap<>();
@SuppressWarnings("all")
public static Desensitization<?> getDesensitization(Class<?> clazz) {
if (clazz.isInterface()) {
throw new UnsupportedOperationException("desensitization is interface, what is expected is an implementation class !");
}
return map.computeIfAbsent(clazz, key -> {
try {
return (Desensitization<?>) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new UnsupportedOperationException(e.getMessage(), e);
}
});
3.3常用的脱敏器实现
3.3.1默认脱敏实现
可基于默认实现,扩展实现个性化场景
package com.jd.ccmp.ctm.constraints.desensitization;
/**
* 默认脱敏实现
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:01
*/
public interface DefaultDesensitization extends Desensitization<String> {
}
3.3.2手机号脱敏器
实现对手机号中间4位号码脱敏
package com.jd.ccmp.ctm.constraints.desensitization;
import com.jd.ccmp.ctm.constraints.Symbol;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 手机号脱敏器,保留前3位和后4位
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:02
*/
public class MobileNoDesensitization implements DefaultDesensitization {
/**
* 手机号正则
*/
private static final Pattern DEFAULT_PATTERN = Pattern.compile("(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}");
@Override
public String desensitize(String target) {
Matcher matcher = DEFAULT_PATTERN.matcher(target);
while (matcher.find()) {
String group = matcher.group();
target = target.replace(group, group.substring(0, 3) + Symbol.getSymbol(4, Symbol.STAR) + group.substring(7, 11));
}
return target;
3.4注解定义
通过@JacksonAnnotationsInside实现自定义注解,提高易用性
package com.jd.ccmp.ctm.constraints.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.jd.ccmp.ctm.constraints.desensitization.Desensitization;
import com.jd.ccmp.ctm.constraints.serializer.ObjectDesensitizeSerializer;
import java.lang.annotation.*;
/**
* 脱敏注解
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:09
*/
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = ObjectDesensitizeSerializer.class)
@Documented
public @interface Desensitize {
/**
* 对象脱敏器实现
*/
@SuppressWarnings("all")
Class<? extends Desensitization<?>> desensitization();
3.4.1默认脱敏注解
package com.jd.ccmp.ctm.constraints.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization;
import java.lang.annotation.*;
/**
* 默认脱敏注解
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:14
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Desensitize(desensitization = DefaultDesensitization.class)
@Documented
public @interface DefaultDesensitize {
3.4.2手机号脱敏注解
package com.jd.ccmp.ctm.constraints.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.jd.ccmp.ctm.constraints.desensitization.MobileNoDesensitization;
import java.lang.annotation.*;
/**
* 手机号脱敏
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:18
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Desensitize(desensitization = MobileNoDesensitization.class)
@Documented
public @interface MobileNoDesensitize {
}
3.5定义脱敏符号
支持指定脱敏符号,例如* 或是 ^_^
package com.jd.ccmp.ctm.constraints;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 脱敏符号
*
* @author zhangxiaoxu15
* @date 2022/2/8 10:53
*/
public class Symbol {
/**
* '*'脱敏符
*/
public static final String STAR = "*";
private Symbol() {}
/**
* 获取符号
*
* @param number 符号个数
* @param symbol 符号
*/
public static String getSymbol(int number, String symbol) {
return IntStream.range(0, number).mapToObj(i -> symbol).collect(Collectors.joining());
}
4.使用样例&执行流程剖析
程序类图
**执行流程剖析**
1.调用JsonUtil.toJsonString()开始执行序列化
2.识别属性mobile上的注解@MobileNoDesensitize(上文3.4.2)
3.调用ObjectDesensitizeSerializer#createContextual(上文3.1 & 3.2),返回JsonSerializer
4.调用手机号脱敏实现MobileNoDesensitization#desensitize(上文3.3.2)
5.输出脱敏后的序列化结果,{"mobile":"133****5678"}
不难发现核心执行流程是第3步,但是@MobileNoDesensitize与ObjectDesensitizeSerializer又是如何联系起来的呢?
尝试梳理下引用链路:@MobileNoDesensitize -> @Desensitize -> @JsonSerialize -> ObjectDesensitizeSerializer
但是,在ObjectDesensitizeSerializer的实现中,我们似乎却没有发现上述链路的直接调用关系
这就不得不说下Jackson元注解的概念
//**Jackson元注解**
//1.提到元注解这个词,大家会想到@Target、@Retention、@Documented、@Inherited
//2.Jackson也以同样的思路设计了@JacksonAnnotationsInside
/**
* Meta-annotation (annotations used on other annotations)
* used for indicating that instead of using target annotation
* (annotation annotated with this annotation),
* Jackson should use meta-annotations it has.
* This can be useful in creating "combo-annotations" by having
* a container annotation, which needs to be annotated with this
* annotation as well as all annotations it 'contains'.
*
* @since 2.0
*/
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JacksonAnnotationsInside
{
}
正是通过”combo-annotations”(组合注解、捆绑注解)的机制,实现了指示Jackson应该使用其拥有的元注释,而不是使用目标注释,从而实现了自定义脱敏实现设计目标。
来源:https://juejin.cn/post/7216495812577591357
猜你喜欢
- 本文详细地介绍了Java内存管理的原理,以及内存泄露产生的原因,同时提供了一些列解决Java内存泄露的方案,希望对各位Java开发者有所帮助
- Android 集成FlutterFlutter 作为 Google 开源的新一代跨平台、高性能 UI 框架,旨在帮助开发者高效地构建出跨平
- 本文实例为大家分享了java实现计算器功能具体代码,供大家参考,具体内容如下效果图组成结构从结构上来说,一个简单的图形界面,需要由界面组件、
- 本文实例讲述了Java实现合并两个有序序列算法。分享给大家供大家参考,具体如下:问题描述输入:序列A<a0,a1,a2,...aq,a
- 先来回忆下在mybatis中的resultMap作用和是什么resultMap的作用是什么在使用传统的mybatis时,我们一般都会在xml
- 配置Servlet的方法有俩种,分别是传统web.xml文档中部署servlet和注解方式部署servlet,下面就先一起来学习 * 解方式部
- 全局配置无效依赖 <dependency> &n
- 在上一篇文章《驱动开发:内核字符串转换方法》中简单介绍了内核是如何使用字符串以及字符串之间的转换方法,本章将继续探索字符串的拷贝与比较,与应
- 前言动态调整线上日志级别是一个非常常见的场景,借助apollo这种配置中心组件非常容易实现。作为apollo的官方技术支持,博主经常在技术群
- using System.Collections.Generic;using System.Linq;using System.Refle
- 本文实例讲述了C#自定义RSA加密解密及RSA签名和验证类。分享给大家供大家参考。具体分析如下:这个C#类自定义RSA加密解密及RSA签名和
- 前言Android的编辑框控件EditText在平常编程时会经常用到,有时候会对编辑框增加某些限制,如限制只能输入数字,最大输入的文字个数,
- 本文实例为大家分享了C#实现聊天窗体以及抖动的具体代码,供大家参考,具体内容如下一、聊天窗体案例1、聊天窗体的要求?(1)由两个文本框组以及
- public class User { public
- 网络爬虫在信息检索与处理中有很大的作用,是收集网络信息的重要工具。接下来就介绍一下爬虫的简单实现。爬虫的工作流程如下爬虫自指定的URL地址开
- Cloneable这个接口设计得十分奇葩,不符合正常人的使用习惯,然而用这个接口的人很多也很有必要,所以还是有必要了解一下这套扭曲的机制。以
- 需求当游戏显示3d场景及其UI的时候。玩家左右晃动手机的时候,UI界面会随之左右偏移。上下晃动的时候,3D场景会随之上下偏移。手机停止晃动的
- 之前我们有介绍通过Spring Boot Admin来检测服务的上下线,然后进行通知功能。https://www.jb51.net/arti
- 有时候你希望在一个嵌套循环的外层循环中执行Continue操作。例如,假设你有一连串的标准,和一堆items。并且你希望找到一个符合每个标准
- 活锁与死锁活锁活锁同样会发生在多个相互协作的线程间,当他们为了彼此间的响应而相互礼让,使得没有一个线程能够继续前进,那么就发生了活锁。同死锁