当前位置: 首页 > news >正文

spring boot实现接口数据脱敏,整合jackson实现敏感信息隐藏脱敏

文章目录

      • 整合 jackson 实现接口数据脱敏
        • JsonSerializer类
        • ContextualSerializer接口
            • createContextual方法
        • 完整代码实现

整合 jackson 实现接口数据脱敏

目的:整合 jackson 实现接口数据脱敏(对涉及到敏感信息的字段进行部分隐藏或替换处理,以防止敏感数据泄露)



先了解部分代码涉及到的知识点,再来看完整代码实现:

1、 自定义序列化器的时候,需要 继承 JsonSerializer 类 和 实现 ContextualSerializer 接口,如下:

这里只贴出结构,代码的逻辑部分没有贴出(后面会讲解)

public class MySensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {// 来自 JsonSerializer 重写@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {}// 来自 ContextualSerializer 重写@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {return null;}
}

JsonSerializer类
  1. JsonSerializer<T> 是 Jackson 用来 控制 Java 对象序列化为 JSON 的核心接口。泛型 <T> 指的是这个序列化器能处理的数据类型。

    • JsonSerializer<String> → 这个序列化器只处理 String 类型字段。

    • JsonSerializer<Object> → 可以处理任何类型,适合通用序列化器。

  2. 当 Jackson 遇到一个字段需要序列化成 JSON 时:

    1. 找到对应字段类型的序列化器:默认会有 Jackson 提供的内置序列化器,比如:

      • StringSerializer 处理 String
      • IntegerSerializer 处理 Integer
    2. 如果字段上标注了 自定义序列化器(比如:@JsonSerialize(using = YourSerializer.class),就会用你提供的序列化器。


ContextualSerializer接口

ContextualSerializer 是 Jackson 提供的接口,允许序列化器在运行时动态生成,根据 字段上的注解或者上下文,生成一个“带配置”的序列化器实例。

这句话到底什么意思?----> 假设现在需要脱敏:

  • 手机号 → 138****5678
  • 邮箱 → u***@example.com
  • 姓名 → 张*丰

希望同一个 序列化器 能处理不同字段的不同脱敏规则。

  • 如果不使用 ContextualSerializer
    • 每个字段都要写一个单独的序列化器类(比如:PhoneSerializerEmailSerializerNameSerializer)。不灵活,代码冗余。
  • 使用 ContextualSerializer
    • Jackson 在序列化字段时,会调用 createContextual方法 (后面会详细讲解这个方法)
    • 可以读取字段上的注解(比如 @SensitiveWrapped(SensitiveEnum.MOBILE)
    • 根据注解动态生成序列化器实例,实现 同一个类,不同字段,不同脱敏逻辑

举例:

public class User {@SensitiveWrapped(SensitiveEnum.MOBILE)private String mobile;@SensitiveWrapped(SensitiveEnum.EMAIL)private String email;
}

序列化流程:@SensitiveWrapped、SensitiveEnum均为自定义的类(在后续整个代码实现中会写出,这里只是为了看知识点)

  1. Jackson 扫描 User 的字段
  2. 遇到 mobile 字段,调用 SensitiveSerialize.createContextual(serializerProvider, property)
    • 读取字段上的注解 @SensitiveWrapped(SensitiveEnum.MOBILE)
    • 返回一个 带 SensitiveEnum.MOBILE 配置的序列化器实例
  3. 调用 serialize 方法:
    • 传入 mobile 字段的值
    • 根据 SensitiveEnum.MOBILE 执行脱敏 → "138****5678"
  4. 遇到 email 字段,同样流程,但注解是 SensitiveEnum.EMAIL
    • 返回一个带 SensitiveEnum.EMAIL 配置的序列化器实例
    • 输出 "u***@example.com"

2、 自定义注解,如下:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface SensitiveWrapped {// 使用注解时,给注解中添加值SensitiveEnum value();
}

1.1 没有 @Target 注解,默认可以应用到所有 Java 元素(相当于没有限制)

1.2 @JacksonAnnotationsInside 注解

这是 Jackson 提供的一个元注解。它的效果是:把当前注解视作“组合注解”。即当某个字段上标注了 @SensitiveWrapped,Jackson 会把 @SensitiveWrapped 当作内部声明的 Jackson 注解的代理。

正常情况下,Jackson 只认它自己的注解,如果没有 @JacksonAnnotationsInside,Jackson 不会自动识别出这个注解。

这里就是:@SensitiveWrapped 内部又标注了 @JsonSerialize,所以 Jackson 会像直接在字段上写 @JsonSerialize(using = SensitiveSerialize.class) 那样处理。

好处:把 Jackson 的配置封装到自定义注解中,使用方只要写 @SensitiveWrapped(SensitiveEnum.MOBILE) 就可以了,语义更清晰、代码更简洁。


createContextual方法

3、 详细分析 ContextualSerializer 接口中实现的 createContextual 方法:

@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {// 为空直接跳过if (beanProperty != null) {// 非String类直接跳过:因为这里验证的类型都是Stringif (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {// 拿到注解SensitiveWrapped annotation = beanProperty.getAnnotation(SensitiveWrapped.class);if (annotation == null) {annotation = beanProperty.getContextAnnotation(SensitiveWrapped.class);}if (annotation != null) {// 如果能得到注解,就将注解的value传入序列化器中return new MySensitiveSerialize(annotation.value());}}return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}return serializerProvider.findNullValueSerializer(beanProperty);
}

① 代码目的:在序列化某个字段之前,根据 字段的类型 与 注解 决定返回哪一个 JsonSerializer

  • 若字段是 String 且标注了 @SensitiveWrapped 注解,就返回一个“带脱敏配置”的自定义序列化器实例;
  • 否则返回 Jackson 默认的序列化器。

方法的核心是把注解信息(annotation.value())传入序列化器,从而实现“同一个序列化器类,不同字段不同脱敏规则”的能力(ContextualSerializer 的典型用法)

createContextual方法签名分析:

  • 参数分析:

    • SerializerProvider serializerProvider:Jackson 的序列化提供者,能用于查找默认序列化器或做其他上下文相关操作。

    • BeanProperty beanProperty:表示当前正在序列化的 bean 的属性信息(字段/方法/构造参数等)。从 beanProperty 可以读取属性类型、注解、名称等元数据。(后面会详细分析)

  • 返回值分析:一个 JsonSerializer<?> 序列化实例:这个序列化器会给 serialize(...) 调用。返回值可以是 this、一个新的序列化器实例,或 Jackson 的默认序列化器。

③ 代码分析:

3.1 beanProperty.getType().getRawClass()beanProperty.getType() 返回 JavaTypegetRawClass() 返回原始 Class

3.2 读取注解:(包含两步)

SensitiveAnno annotation = beanProperty.getAnnotation(SensitiveWrapped.class);
if (annotation == null) {annotation = beanProperty.getContextAnnotation(SensitiveWrapped.class);
}

含义:尝试从 beanProperty 上读取 @SensitiveWrapped 注解,顺序为:

  1. beanProperty.getAnnotation(...):直接读取属性本身上的注解(即标在字段 或 getter/setter上的注解)。
  2. beanProperty.getContextAnnotation(...):如果前者没找到,再检查“上下文注解”(比如类级别、接口、方法上的注解等视具体实现而定)。

为什么检查 2 次注解:

  • 注解可能直接标在字段或 getter(getAnnotation 能拿到)。但有时注解放在类级别或更高级的上下文位置(或由于代理/继承关系),getContextAnnotation 能发现某些在上下文中可见的注解。确保在不同标注位置也能生效。

**3.3 ** 如果找到注解,返回带配置的序列化器:

if (annotation != null) {// 如果能得到注解,就将注解的value传入序列化器中return new MySensitiveSerialize(annotation.value());
}

如果没找到注解或不是 String,则交给 Jackson 的默认序列化器去处理(即按照普通规则序列化该字段)

return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);

findValueSerializer 的作用:根据字段类型和属性元信息,返回 Jackson 已注册或内建的序列化器(例如 StringSerializerIntegerSerializer 等)

3.4beanProperty == null 时:表示当前上下文不是某个 bean 的具体属性。

// 不应该这么写:应该返回默认的序列化器
return serializerProvider.findNullValueSerializer(beanProperty);

这里不应该这么写,应该返回默认的序列化器:

return serializerProvider.findValueSerializer(String.classs, beanProperty(或者直接写null,因为beanProperty值就是null));

问题1:为什么一定是 String.class,原来的类型不可能是别的类型吗?----> 因为自定义的序列化器(当前这个序列化器)是针对 String 类型的,所以我就返回原先的默认序列化即可:

public class MySensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {......
}

或者更明确一点,让 Jackson 自动根据泛型推导出,改为:

return serializerProvider.findValueSerializer(this.handledType(), beanProperty);

问题2:可以直接返回 this 不?

直接返回 this,Jackson 会继续用你当前这个序列化器。比如:

/* 假如要序列化的值是 ”123456" 
然后 beanProperty = null(因为是根元素)
*/
// 假设你返回this:
return this;
// 则继续当前的序列化器
// 那还是我们自定义的序列化器
// 结果:就会将123456脱敏// 假如你写
return serializerProvider.findValueSerializer(this.handledType(), beanProperty);
// 那么 Jackson 会还原为系统自带的 StringSerializer,不会脱敏

3.4.1 再次分析下 BeanPropertyBeanProperty 表示 当前被序列化的属性(字段)

1、如果 Jackson 正在序列化一个 Java Bean(例如 User 对象)的字段 name,那么 beanProperty 就会包含这个字段的元信息。

例如:

public class User {private String name;
}

当 Jackson 序列化 user.name 时:beanProperty ≠ null。它包含的信息包括:

  • 字段名(name
  • 字段类型(String
  • 该字段上的注解(比如你的 @SensitiveWrapped
  • 所属类(User.class

2、什么时候 beanProperty == null?----> 当 Jackson 在序列化的值不是某个类的“字段”,而是一个独立的值(根对象(直接字符串或对象)、集合元素、Map 值、null值等),就拿不到 Bean 属性信息,因此 beanProperty == null

例如:

// 根对象String:也就是直接定义字符串
String = "ok";
// List<String>、Map<String, String> 
List<String> list = Arrays.asList("java","C"); 

相当于 Jackson 只知道这是一个什么,但具体不知道这个东西是属于哪个类的哪个字段。但假如集合中是某个具体的对象(比如:List<User>),则就知道了是哪个 bean,此时就不为 null


总结:根据上面的分析给出更清晰的版本

@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
// 如果beanProperty为null
if (beanProperty == null) {// 返回默认的字符串序列化器return serializerProvider.findValueSerializer(this.handledType(), null);
}// 只对String类型处理
JavaType type = beanProperty.getType();
if (!Object.equals(type.getRawClass(), String.class)) {// 非String类型使用默认的序列化器return serializerProvider.findValueSerializer(type, beanProperty);
}// 尝试读取注解:先读属性本身,再读上下文注解
SensitiveAnno ann = property.getAnnotation(SensitiveWrapped.class);
if (ann == null) {ann = property.getContextAnnotation(SensitiveWrapped.class);
}if (ann != null) {// 找到注解 -> 返回带具体脱敏配置的序列化器实例(应为不可变、线程安全)return new MySensitiveSerialize(ann.value());
}// 没找到注解 -> 返回默认的 String 序列化器
return serializerProvider.findValueSerializer(type, beanProperty);

完整代码实现

可以参考上传到 Gitee 中的代码:完整代码

运行结果:

在这里插入图片描述

结束,不要忘记关注收藏哦,不懂请评论!

http://www.dtcms.com/a/490984.html

相关文章:

  • 基于单片机的汽车多参数安全检测与报警系统设计
  • C++设计模式_行为型模式_备忘录模式Memento
  • 温州h5建站关于网站建设的文章
  • 大连专业做网站wordpress 4.5 汉化主题
  • Spring Boot 3零基础教程,Spring Boot 日志分组,笔记20
  • 【单调向量 单调栈】3676. 碗子数组的数目|1848
  • 【JUnit实战3_01】第一章:JUnit 起步
  • 公司门户网站该怎么做用模块做网站
  • 合肥网站定制公司宁波做网站公司哪家好
  • Banana Script,一个C99实现的,类JavaScript极简语法的脚本引擎
  • 14-机器学习与大模型开发数学教程-第1章 1-6 费马定理与极值判定
  • 写的网站怎么做接口php在网站上怎么做充值
  • nginx报400bad request 请求头过大异常处理
  • react+springboot云上部署
  • Google 地图类型
  • 免费网站做企业的网站都要准备什么
  • 网站建设往年的高考题免费看电视的网站有哪些
  • STM32N6 KEIL IDE 调试XIP 应用的一种方法 LAT1575
  • 大模型微调(二):使微调保持稳定的策略
  • 前端调优23大规则(Part 4)
  • SpringBoot-入门介绍
  • 如何推动AI技术在企业管理中的商业化落地?
  • 淘宝网站建设的策划书产品软文案例
  • 复制带随机指针的链表
  • Promise 与 async/await
  • win11 字体变宽问题
  • 最好的做网站机械加工网站色彩搭配
  • Pytorch Yolov11目标检测+Android部署 留贴记录
  • iis 发布网站 404archlinux+wordpress
  • leetcode 2598. 执行操作后的最大 MEX 中等