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

利用,jackson,序列化,实现,数据,脱敏 · 浏览次数 : 53

小编点评

**代码描述** 该代码展示了如何使用 Jackson 元注解实现自定义脱敏实现。代码包含以下部分: * **DefaultDesensitize** 类:定义了默认脱敏实现,使用 Jackson 元注解进行自定义配置。 * **Symbol** 类:定义了脱敏符号支持,例如 *。 * **MobileNoDesensitization** 类:实现了手机号脱敏,使用 Jackson 元注解进行自定义配置。 **关键代码** * **DefaultDesensitize** 中的 **@JacksonAnnotationsInside** 注解用于指定 Jackson元注解应该应用于哪个类属性。 * **MobileNoDesensitization** 中的 **@JacksonAnnotationsInside** 注解用于指定 Jackson元注解应该应用于哪个类属性。 * **Symbol** 中的 **@JacksonAnnotationsInside** 注解用于指定 Jackson元注解应该应用于哪个类属性。 **实现** * **DefaultDesensitize** 中使用 Jackson 元注解配置了自定义属性,例如脱敏符号。 * **MobileNoDesensitization** 中使用 Jackson 元注解配置了手机号脱敏逻辑。 * **Symbol** 中定义了脱敏符号支持。 **示例** 假设我们有一个类名为 **Mobile**,它带有以下属性: * **mobile**: string * **symbol**: string 我们可以使用 Jackson 元注解实现自定义脱敏: ```java @JacksonAnnotationsInside public class Mobile { private String mobile; private String symbol; } ``` **完整代码** ```java @JacksonAnnotationsInside public class DefaultDesensitize { @JacksonAnnotationsInside private String mobile; } @JacksonAnnotationsInside public class MobileNoDesensitization { @JacksonAnnotationsInside private String mobile; } @JacksonAnnotationsInside public class Symbol { @JacksonAnnotationsInside private String star; } ```

正文

作者:京东物流 张晓旭

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应该使用其拥有的元注释,而不是使用目标注释,从而实现了自定义脱敏实现设计目标。

5.总结

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

与利用Jackson序列化实现数据脱敏相似的内容:

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

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

利用FastAPI和OpenAI-Whisper打造高效的语音转录服务

最近好久没有写博客了,浅浅记录下如何将OpenAI-Whisper做成Web服务吧 介绍 在这篇指导性博客中,我们将探讨如何在Python中结合使用FastAPI和OpenAI-Whisper。OpenAI-Whisper是一个前沿的语音识别模型,而FastAPI是一个高性能的现代Web框架,专

利用SpringBoot+rabbitmq 实现邮件异步发送,保证100%投递成功

在之前的文章中,我们详细介绍了 SpringBoot 整合 mail 实现各类邮件的自动推送服务。 但是这类服务通常不稳定,当出现网络异常的时候,会导致邮件推送失败。 本篇文章将介绍另一种高可靠的服务架构,实现邮件 100% 被投递成功。类似的短信自动发送等服务也大体相同。 一、先来一张流程图 本文

利用Wireshark抓包分析DNS域名解析过程

一、DNS协议概述 DNS协议也可以称为DNS服务,全称是Domain Name System,即域名系统,和HTTP协议一样,也是一个位于应用层的协议(服务),它是基于运输层的UDP协议的。从DNS的名字我们就可以知道,它提供域名映射到IP地址的服务。 二、实验目的 掌握DNS域名解析过程 熟悉D

利用大型语言模型轻松打造浪漫时刻

在这篇文章中,我们介绍了如何利用大型语言模型为情人节营造难忘的氛围。通过上传图片并进行风格转化,我们可以为对方呈现一幅独特的作品,增添浪漫的色彩。同时,借助搜索功能,我们能够轻松获取与情人节相关的信息,为策划活动提供更多灵感和建议。

利用英特尔 Gaudi 2 和至强 CPU 构建经济高效的企业级 RAG 应用

检索增强生成 (Retrieval Augmented Generation,RAG) 可将存储在外部数据库中的新鲜领域知识纳入大语言模型以增强其文本生成能力。其提供了一种将公司数据与训练期间语言模型学到的知识分开的方式,有助于我们在性能、准确性及安全隐私之间进行有效折衷。 通过本文,你将了解到英特

利用深度循环神经网络对心电图降噪

具体的软硬件实现点击 http://mcu-ai.com/ MCU-AI技术网页_MCU-AI 我们提出了一种利用由长短期记忆 (LSTM) 单元构建的深度循环神经网络来降 噪心电图信号 (ECG) 的新方法。该网络使 用动态模型 ECG 生成的合成数据进行预训 练,并使用来自 Physionet

利用pearcmd实现裸文件包含

title: 利用pearcmd实现裸文件包含 tags: [web,文件包含] categories: [CTF,web] 利用pearcmd实现裸文件包含 在 ctf 中,常常有这样一类题: 题目很简单,一般围绕一个 include 函数展开。 例: ctfshow 元旦水友赛 easy_inc

利用pip/conda安装库时,出现requires XXX, which is not installed/incompatible

博客地址:https://www.cnblogs.com/zylyehuo/ 出现以下提示警告时 step1 step2 step3 总结 利用pip/conda安装库时,出现requires XXX, which is not installed/incompatible 依次执行安装所缺的库即可

利用队列的内置模块(deque)模拟 Linux 下的 tail 命令(输出文件中最后几行的内容)

博客地址:https://www.cnblogs.com/zylyehuo/ # -*- coding: utf-8 -*- from collections import deque def tail(n): # n:指定输出文件中最后几行 with open('test.txt', 'r') a