SpringBoot自定义注解+Jackson序列化器实现数据脱敏

 更新时间:2026年08月23日 10:08:23   作者:Java编程爱好者  
在项目开发中,数据库经常会存储大量用户敏感信息,因此企业项目普遍需要做数据脱敏,这篇文章主要为大家详细介绍了SpringBoot如何通过自定义注解和Jackson序列化器实现数据脱敏,有需要的小伙伴可以参考下

一、引言

本人在写用户信息模块的时候,觉得手机号、邮箱等内容不应该直接明文返回给前端。

{
    "username": "测试用户",
    "phone":    "13812346789",    ❌明文暴露
    "email":    "test@qq.com"     ❌
}

一旦接口被抓包或日志泄露,用户的隐私就全暴露了。正确的做法是返回脱敏后的数据

{
    "username": "测试用户",
    "phone":    "138****6789",    ✅
    "email":    "t***@qq.com"     ✅
}    

阅读网上资料,我觉得把脱敏做到序列化出参的这一层比较好,下面是具体实现

二、核心实现:四个组件

组件1:脱敏类型枚举 SensitiveType

先枚举出所有需要脱敏的字段类型:

public enum SensitiveType{
    MOBILe,    //手机号
    EMAIL,     //邮箱
    ID_CARD    //身份证
}

组件2:打码规则 SensitiveUtils

public class SensitiveUtils {

    /** 手机号:13812346789 -> 138****6789(保留前3后4) */
    public static String maskMobile(String mobile) {
        if (mobile == null || mobile.length() != 11) {
            return mobile;   // 格式不符时原样返回,不抛异常
        }
        return mobile.substring(0, 3) + "****" + mobile.substring(7);
    }

    /** 邮箱:test@qq.com -> t****@qq.com(保留首字母 + @后面) */
    public static String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        int atIndex = email.indexOf("@");
        return email.substring(0, 1) + "****" + email.substring(atIndex);
    }

    /** 身份证:110101199001011234 -> 110***********1234(保留前3后4) */
    public static String maskIdCard(String idCard) {
        if (idCard == null || idCard.length() != 18) {
            return idCard;
        }
        return idCard.substring(0, 3) + "***********" + idCard.substring(14);
    }
}

组件3:脱敏序列化器 SensitiveJsonSerializer

public class SensitiveJsonSerializer extends JsonSerializer<String>
        implements ContextualSerializer {

    /** 当前字段的脱敏类型 */
    private SensitiveType type;

    /** 无参构造:Jackson 反射创建实例时调用 */
    public SensitiveJsonSerializer() {
    }

    /** 有参构造:内部使用,携带脱敏类型 */
    private SensitiveJsonSerializer(SensitiveType type) {
        this.type = type;
    }

    /** 核心方法:序列化时打码 */
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException {
        if (value == null) {
            gen.writeNull();
            return;
        }
        gen.writeString(mask(value, type));
    }

    /** 关键方法:读取字段上的 @Sensitive 注解,拿到脱敏类型 */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        if (property != null) {
            Sensitive annotation = property.getAnnotation(Sensitive.class);
            if (annotation != null) {
                return new SensitiveJsonSerializer(annotation.type());
            }
        }
        return this;
    }

    /** 根据类型调用对应的打码方法 */
    private String mask(String value, SensitiveType type) {
        switch (type) {
            case MOBILE: return SensitiveUtils.maskMobile(value);
            case EMAIL: return SensitiveUtils.maskEmail(value);
            case ID_CARD: return SensitiveUtils.maskIdCard(value);
            default: return value;
        }
    }
}

组件4:脱敏注解 @Sensitive

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside                       // 关键:允许组合 @JsonSerialize
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {

    /** 脱敏类型,默认手机号 */
    SensitiveType type() default SensitiveType.MOBILE;
}

完成上面四步后,只需要在 vo 字段上标注,序列化时自动生效。

public class UserProfileVO{

    @Sensitive(type = SensitiveType.MOBILE)
    private String phone;

    @Sensitive(type = SensitiveType.EMAIL)
    private String email;

    //其他字段...
}

三、完整数据流

  数据库(User 表)
   phone = 13812346789(完整值)
        │
        ▼
  User 实体(entity,对应数据库,没有 @Sensitive)
   user.phone = 13812346789
        │
        ▼
  Controller 组装:user.getPhone() 把完整值 copy 进 VO
   vo.phone = 13812346789
        │
        ▼
  返回 Result.success(vo) —— vo 是 UserProfileVO
   UserProfileVO.phone 字段上有 @Sensitive 注解
        │
        ▼
  Jackson 序列化 UserProfileVO 时
   发现 phone 字段有 @Sensitive(type = MOBILE)
   → 触发 SensitiveJsonSerializer
   → 调用 maskMobile 打码
        │
        ▼
  前端收到 JSON
   phone = "138****6789"

脱敏实际上只发生在序列化出参时,数据库里存的一直是完整值,脱敏是一个展示的动作,而存储层要保护数据应该使用加密或者哈希

四、两个关键技术点

1、@JacksonAnnotationsInside 是什么?

Jackson默认不认识我们自定义的 @Sensitive 注解,@JacksonAnnotationsInside 的作用是:允许把 @JsonSerialize 组合进自定义注解。

加了它之后,@Sensitive 就等价于 @JsonSerialize(using = SensitiveJsonSerializer.class) ,Jackson才能识别并触发我们的序列化器。

2、ContextualSerializer 有什么用?

序列化器怎么知道是 手机号? 邮箱? 答案是 createContextual 方法:

  • Jackson 序列化某个字段前,会调用 createContextual
  • 它通过 property.getAnnotation(Sensitive.class) 读字段上的注解
  • 拿到 type 后,返回一个携带类型的序列化实例

五、总结

本文实现了一个完整的 Spring 数据脱敏方案,核心链路是:枚举定义类型 -> 工具类实现规则 -> 序列化器打码 -> 注解标记字段

到此这篇关于SpringBoot自定义注解+Jackson序列化器实现数据脱敏的文章就介绍到这了,更多相关SpringBoot数据脱敏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SLF4J报错解决:No SLF4J providers were found的问题

    SLF4J报错解决:No SLF4J providers were found的

    这篇文章主要介绍了SLF4J报错解决:No SLF4J providers were found的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • java如何通过流读取图片做base64编码

    java如何通过流读取图片做base64编码

    这篇文章主要介绍了java如何通过流读取图片做base64编码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • SpringBoot接收请求参数的四种方式总结

    SpringBoot接收请求参数的四种方式总结

    这篇文章主要给大家介绍了关于SpringBoot接收请求参数的四种方式,文中通过代码以及图文介绍的非常详细,对大家学习或者使用SpringBoot具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • Java微服务分布式调度Elastic-job环境搭建及配置

    Java微服务分布式调度Elastic-job环境搭建及配置

    Elastic-Job在配置中提供了JobEventConfiguration,支持数据库方式配置,会在数据库中自动创建JOB_EXECUTION_LOG和JOB_STATUS_TRACE_LOG两张表以及若干索引,来记录作业的相关信息
    2023-02-02
  • Linux中使用shell脚本管理Java应用程序

    Linux中使用shell脚本管理Java应用程序

    在日常开发和运维工作中,管理基于Java的应用程序是一项基础且频繁的任务,本文将通过一个示例脚本,展示如何利用Shell脚本简化这一流程,实现Java应用的一键式启动、停止与重启操作,本脚本不仅提升了工作效率,还确保了操作的标准化与可靠性
    2024-06-06
  • 解决springboot没有启动标识,启动类也没有启动标识的问题

    解决springboot没有启动标识,启动类也没有启动标识的问题

    这篇文章主要介绍了解决springboot没有启动标识,启动类也没有启动标识的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 简单的用java实现读/写文本文件的示例

    简单的用java实现读/写文本文件的示例

    同时也展示了如果从输入流中读出来内容写入输出流中(仅限文本流) 三个例子可以独立存在,所以根据需要只看其中一个就行了。
    2008-07-07
  • Java OOP三大特征之封装继承与多态详解

    Java OOP三大特征之封装继承与多态详解

    本文主要讲述的是面向对象的三大特性:封装,继承,多态,内容含括从封装到继承再到多态的所有重点内容以及使用细节和注意事项,内容有点长,请大家耐心看完
    2022-07-07
  • Java 10的10个新特性总结

    Java 10的10个新特性总结

    这篇文章我们给大家整理了关于Java 10的10个新特性以及相关要点内容,有兴趣的朋友们可以参考下。
    2018-08-08
  • java中容器(顶层容器和中间容器)的布局管理器详解

    java中容器(顶层容器和中间容器)的布局管理器详解

    这篇文章主要介绍了java中容器(顶层容器和中间容器)的布局管理器详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12

最新评论