java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot数据脱敏

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

作者: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 方法:

五、总结

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

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

您可能感兴趣的文章:
阅读全文