利用Jackson序列化实现数据脱敏

作者:京东物流 张晓旭

1.背景

在项目中有些敏感信息不能直接展示,比如客户手机号、身份证、车牌号等信息,展示时均需要进行数据脱敏,防止泄露客户隐私。脱敏即是对数据的部分信息用脱敏符号(*)处理。

2.目标

  • 在服务端返回数据时,利用Jackson序列化完成数据脱敏,达到对敏感信息脱敏展示。
  • 降低重复开发量,提升开发效率
  • 形成统一有效的脱敏规则
  • 可基于重写默认脱敏实现的desensitize方法,实现可扩展、可自定义的个性化业务场景的脱敏需求

3.主要实现

3.1基于Jackson的自定义脱敏序列化实现

StdSerializer:所有标准序列化程序所使用的基类,这个是编写自定义序列化程序所推荐使用的基类。
**ContextualSerializer:**是Jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer。

1package com.jd.ccmp.ctm.constraints.serializer; 2 3 4 5 6import com.fasterxml.jackson.core.JsonGenerator; 7import com.fasterxml.jackson.databind.BeanProperty; 8import com.fasterxml.jackson.databind.JsonSerializer; 9import com.fasterxml.jackson.databind.SerializerProvider; 10import com.fasterxml.jackson.databind.ser.ContextualSerializer; 11import com.fasterxml.jackson.databind.ser.std.StdSerializer; 12import com.jd.ccmp.ctm.constraints.Symbol; 13import com.jd.ccmp.ctm.constraints.annotation.Desensitize; 14import com.jd.ccmp.ctm.constraints.desensitization.Desensitization; 15import com.jd.ccmp.ctm.constraints.desensitization.DesensitizationFactory; 16import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization; 17 18 19 20 21import java.io.IOException; 22 23 24 25 26/** 27 * 脱敏序列化器 28 * 29 * @author zhangxiaoxu15 30 * @date 2022/2/8 11:10 31 */ 32public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer { 33 private static final long serialVersionUID = -7868746622368564541L; 34 private transient Desensitization<Object> desensitization; 35 protected ObjectDesensitizeSerializer() { 36 super(Object.class); 37 } 38 public Desensitization<Object> getDesensitization() { 39 return desensitization; 40 } 41 public void setDesensitization(Desensitization<Object> desensitization) { 42 this.desensitization = desensitization; 43 } 44 @Override 45 public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) { 46//获取属性注解 47 Desensitize annotation = property.getAnnotation(Desensitize.class); 48 return createContextual(annotation.desensitization()); 49 } 50 @SuppressWarnings("unchecked") 51 public JsonSerializer<Object> createContextual(Class<? extends Desensitization<?>> clazz) { 52 ObjectDesensitizeSerializer serializer = new ObjectDesensitizeSerializer(); 53 if (clazz != DefaultDesensitization.class) { 54 serializer.setDesensitization((Desensitization<Object>) DesensitizationFactory.getDesensitization(clazz)); 55 } 56 return serializer; 57 } 58 @Override 59 public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { 60 Desensitization<Object> objectDesensitization = getDesensitization(); 61 if (objectDesensitization != null) { 62 try { 63 gen.writeObject(objectDesensitization.desensitize(value)); 64 } catch (Exception e) { 65 gen.writeObject(value); 66 } 67 } else if (value instanceof String) { 68 gen.writeString(Symbol.getSymbol(((String) value).length(), Symbol.STAR)); 69 } else { 70 gen.writeObject(value); 71 }

注:createContextual可以获得字段的类型以及注解。当字段拥有自定义注解时,取出注解中的值创建定制的序列化方式,这样在serialize方法中便可以得到这个值了。createContextual方法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会改变),所以无需关心性能问题。

3.2定义脱敏接口、以及工厂实现

3.2.1脱敏器接口定义

1package com.jd.ccmp.ctm.constraints.desensitization; 2 3 4/** 5 * 脱敏器 6 * 7 * @author zhangxiaoxu15 8 * @date 2022/2/8 10:56 9 */ 10public interface Desensitization<T> { 11 /** 12 * 脱敏实现 13 * 14 * @param target 脱敏对象 15 * @return 脱敏返回结果 16 */ 17 T desensitize(T target); 18}

3.2.2脱敏器工厂实现

1package com.jd.ccmp.ctm.constraints.desensitization; 2 3 4import java.util.HashMap; 5import java.util.Map; 6 7 8/** 9 * 工厂方法 10 * 11 * @author zhangxiaoxu15 12 * @date 2022/2/8 10:58 13 */ 14public class DesensitizationFactory { 15 private DesensitizationFactory() { 16 } 17 private static final Map<Class<?>, Desensitization<?>> map = new HashMap<>(); 18 19 20 21 22 @SuppressWarnings("all") 23 public static Desensitization<?> getDesensitization(Class<?> clazz) { 24 if (clazz.isInterface()) { 25 throw new UnsupportedOperationException("desensitization is interface, what is expected is an implementation class !"); 26 } 27 return map.computeIfAbsent(clazz, key -> { 28 try { 29 return (Desensitization<?>) clazz.newInstance(); 30 } catch (InstantiationException | IllegalAccessException e) { 31 throw new UnsupportedOperationException(e.getMessage(), e); 32 } 33 });

3.3常用的脱敏器实现

3.3.1默认脱敏实现

可基于默认实现,扩展实现个性化场景

1package com.jd.ccmp.ctm.constraints.desensitization; 2 3 4/** 5 * 默认脱敏实现 6 * 7 * @author zhangxiaoxu15 8 * @date 2022/2/8 11:01 9 */ 10public interface DefaultDesensitization extends Desensitization<String> { 11}

3.3.2手机号脱敏器

实现对手机号中间4位号码脱敏

1package com.jd.ccmp.ctm.constraints.desensitization; 2import com.jd.ccmp.ctm.constraints.Symbol; 3import java.util.regex.Matcher; 4import java.util.regex.Pattern; 5 6 7/** 8 * 手机号脱敏器,保留前3位和后4位 9 * 10 * @author zhangxiaoxu15 11 * @date 2022/2/8 11:02 12 */ 13public class MobileNoDesensitization implements DefaultDesensitization { 14 /** 15 * 手机号正则 16 */ 17 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}"); 18 19 20 21 22 @Override 23 public String desensitize(String target) { 24 Matcher matcher = DEFAULT_PATTERN.matcher(target); 25 while (matcher.find()) { 26 String group = matcher.group(); 27 target = target.replace(group, group.substring(0, 3) + Symbol.getSymbol(4, Symbol.STAR) + group.substring(7, 11)); 28 } 29 return target;

3.4注解定义

通过@JacksonAnnotationsInside实现自定义注解,提高易用性

1package com.jd.ccmp.ctm.constraints.annotation; 2import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; 3import com.fasterxml.jackson.databind.annotation.JsonSerialize; 4import com.jd.ccmp.ctm.constraints.desensitization.Desensitization; 5import com.jd.ccmp.ctm.constraints.serializer.ObjectDesensitizeSerializer; 6import java.lang.annotation.*; 7 8 9/** 10 * 脱敏注解 11 * 12 * @author zhangxiaoxu15 13 * @date 2022/2/8 11:09 14 */ 15@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) 16@Retention(RetentionPolicy.RUNTIME) 17@JacksonAnnotationsInside 18@JsonSerialize(using = ObjectDesensitizeSerializer.class) 19@Documented 20public @interface Desensitize { 21 /** 22 * 对象脱敏器实现 23 */ 24 @SuppressWarnings("all") 25 Class<? extends Desensitization<?>> desensitization();

3.4.1默认脱敏注解

1package com.jd.ccmp.ctm.constraints.annotation; 2import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; 3import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization; 4import java.lang.annotation.*; 5 6 7 8 9/** 10 * 默认脱敏注解 11 * 12 * @author zhangxiaoxu15 13 * @date 2022/2/8 11:14 14 */ 15@Target({ElementType.FIELD}) 16@Retention(RetentionPolicy.RUNTIME) 17@JacksonAnnotationsInside 18@Desensitize(desensitization = DefaultDesensitization.class) 19@Documented 20public @interface DefaultDesensitize {

3.4.2手机号脱敏注解

1package com.jd.ccmp.ctm.constraints.annotation; 2import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; 3import com.jd.ccmp.ctm.constraints.desensitization.MobileNoDesensitization; 4import java.lang.annotation.*; 5 6 7/** 8 * 手机号脱敏 9 * 10 * @author zhangxiaoxu15 11 * @date 2022/2/8 11:18 12 */ 13@Target({ElementType.FIELD}) 14@Retention(RetentionPolicy.RUNTIME) 15@JacksonAnnotationsInside 16@Desensitize(desensitization = MobileNoDesensitization.class) 17@Documented 18public @interface MobileNoDesensitize { 19}

3.5定义脱敏符号

支持指定脱敏符号,例如* 或是 ^_^

1package com.jd.ccmp.ctm.constraints; 2import java.util.stream.Collectors; 3import java.util.stream.IntStream; 4 5 6/** 7 * 脱敏符号 8 * 9 * @author zhangxiaoxu15 10 * @date 2022/2/8 10:53 11 */ 12public class Symbol { 13 /** 14 * '*'脱敏符 15 */ 16 public static final String STAR = "*"; 17 private Symbol() {} 18 /** 19 * 获取符号 20 * 21 * @param number 符号个数 22 * @param symbol 符号 23 */ 24 public static String getSymbol(int number, String symbol) { 25 return IntStream.range(0, number).mapToObj(i -> symbol).collect(Collectors.joining()); 26 }

4.使用样例&执行流程剖析

程序类图

1**执行流程剖析** 2 1.调用JsonUtil.toJsonString()开始执行序列化 3 2.识别属性mobile上的注解@MobileNoDesensitize(上文3.4.2) 4 3.调用ObjectDesensitizeSerializer#createContextual(上文3.1 & 3.2),返回JsonSerializer 5 4.调用手机号脱敏实现MobileNoDesensitization#desensitize(上文3.3.2) 6 5.输出脱敏后的序列化结果,{"mobile":"133****5678"}

不难发现核心执行流程是第3步,但是@MobileNoDesensitize与ObjectDesensitizeSerializer又是如何联系起来的呢?

  • 尝试梳理下引用链路:@MobileNoDesensitize -> @Desensitize -> @JsonSerialize -> ObjectDesensitizeSerializer
  • 但是,在ObjectDesensitizeSerializer的实现中,我们似乎却没有发现上述链路的直接调用关系
  • 这就不得不说下Jackson元注解的概念
1**Jackson元注解** 21.提到元注解这个词,大家会想到@Target、@Retention、@Documented、@Inherited 32.Jackson也以同样的思路设计了@JacksonAnnotationsInside 4 5 6/** 7 * Meta-annotation (annotations used on other annotations) 8 * used for indicating that instead of using target annotation 9 * (annotation annotated with this annotation), 10 * Jackson should use meta-annotations it has. 11 * This can be useful in creating "combo-annotations" by having 12 * a container annotation, which needs to be annotated with this 13 * annotation as well as all annotations it 'contains'. 14 * 15 * @since 2.0 16 */ 17@Target({ElementType.ANNOTATION_TYPE}) 18@Retention(RetentionPolicy.RUNTIME) 19@JacksonAnnotation 20public @interface JacksonAnnotationsInside 21{ 22}

正是通过”combo-annotations”(组合注解、捆绑注解)的机制,实现了指示Jackson应该使用其拥有的元注释,而不是使用目标注释,从而实现了自定义脱敏实现设计目标。

5.总结

以上就是利用Jackson序列化实现数据脱敏的全过程,如有此类需求的同学可以借鉴上面的实现方法。

点赞
收藏

评论区

加载中...

相关推荐

java 日志的数据脱敏

思路1。在model层进行处理,直接重写get方法,在写一个getPlain获取明文方法。(缺点:数据库写入和json序列化传递时使用的都是密文)  2.利用日志组件过滤特定的key,去进行脱敏(缺点:对所有的日志输出全部要正则匹配,非常耗时。)由1,2的利弊,肯定会选择1,然后考虑一种实现(在model层定义方法,获取它的一个复制类,复制

Oracle汉字用户名数据脱敏长度不变,rpad函数使用

信息安全考虑,有时需要对用户名称进行数据脱敏。针对Oracle数据库,进行取数数据脱敏处理脱敏规则:长度小于9个字符,只保留前3个汉字与后3个汉字,中间全部由\填充。长度9个字及以上及奇数,隐去中间3个字;长度10个字及以上及奇数,隐去中间4个字。例如:公司名称:宇宙无敌厉害的超级大公司的杭州分公司 字段长度:18脱敏后:

Java日志脱敏框架 sensitive

项目介绍日志脱敏是常见的安全需求。普通的基于工具类方法的方式,对代码的入侵性太强。编写起来又特别麻烦。本项目提供基于注解的方式,并且内置了常见的脱敏方式,便于开发。特性基于注解的日志脱敏。可以自定义策略实现,策略生效条件。常见的脱敏内置方案。java深拷贝

一种配置化的数据脱敏与反脱敏框架实现 | 京东云技术团队

在现有的微服务技术架构背景下,敏感数据的使用存在许多痛点,基于此,tony提供了一套完整、安全、透明化、低改造成本的数据脱敏整合解决方案。

Hutool:一行代码搞定数据脱敏 | 京东云技术团队

本文主要介绍了数据脱敏的相关内容,首先介绍了数据脱敏的概念,在此基础上介绍了常用的数据脱敏规则;随后介绍了本文的重点Hutool工具及其使用方法,在此基础上进行了实操,分别演示了使用DesensitizedUtil工具类、配合Jackson通过注解的方式完成数据脱敏;最后,介绍了一些常见的数据脱敏方法,并附上了对应的教程链接供大家参考,本文内容如有不当之处,还请大家批评指正。

SpringBoot版本升级引起的FileNotFoundException——WebMvcConfigurerAdapter.class

缘起最近公司项目要求JDK从8升到17,SpringBoot版本从2.x升级到3.x,期间遇到了一个诡异的FileNotFoundException异常,日志如下(敏感信息使用xxx脱敏)logorg.springframework.beans.facto