Java使用Hutool+自定义注解实现数据脱敏

 更新时间:2023年09月07日 09:31:53   作者:Bummon  
我们在使用手机银行的时候经常能看到APP上会将银行卡的卡号中间部分给隐藏掉使用 ***** 来代替,在某些网站上查看一些业务密码时(例如签到密码等)也会使用 ***** 来隐藏掉真正的密码,那么这种方式是如何实现的呢,本文将给大家介绍使用Hutool+自定义注解实现数据脱敏

前言

我们在使用手机银行的时候经常能看到APP上会将银行卡的卡号中间部分给隐藏掉使用 ***** 来代替,在某些网站上查看一些业务密码时(例如签到密码等)也会使用 ***** 来隐藏掉真正的密码,那么这种方式是如何实现的呢?

Hutool

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。

Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;

Hutool是项目中 util 包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。

我们这篇文章的实现思路就基于Hutool来实现,在Hutool中提供了一个名为 DesensitizedUtil 的工具类,我们使用这个工具类来加密。

首先我们先来看一下这个类里的具体实现,如下:

我们可以看到映入眼帘的除了一个无参构造之外就是一个名为 desensitized 的方法,这个方法就是我们加密的主要方法,里面利用了 switch…case 方法来区分不同的加密方法。我们可以来写一个单元测试来测试一下通过这个方法加密后是什么样的。

以上为加密后的信息,里面我使用了不同的类型来进行加密,目前最新版的Hutool支持脱敏加密的类型如下:

  • 用户ID
  • 中文名
  • 密码
  • 地址
  • 邮箱
  • 座机号
  • 手机号
  • 中国大陆的车牌号
  • 银行卡号
  • IPv4地址
  • IPv6地址
  • 自定义脱敏

实现

通过以上的示例我们就可以开始编写我们自己的脱敏操作了,首先我们要先根据以上Hutool中提供的脱敏类型来编写我们自己的类型**(如嫌麻烦也可省略此步骤,直接使用DesensitizedUtil中的DesensitizedType)**

编写数据脱敏类型

/**
 * @author Bummon
 * @description 数据脱敏策略
 * @date 2023-09-01 17:43
 */
public enum DataMaskingType {
    /**
     * 用户ID
     */
    USER_ID,
    /**
     * 中文名
     */
    CHINESE_NAME,
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 座机
     */
    FIXED_PHONE,
    /**
     * 手机号
     */
    MOBILE_PHONE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 邮箱
     */
    EMAIL,
    /**
     * 密码
     */
    PASSWORD,
    /**
     * 中国大陆车牌号
     */
    CAR_LICENSE,
    /**
     * 银行卡号
     */
    BANK_CARD,
    /**
     * IPv4地址
     */
    IPV4,
    /**
     * IPv6地址
     */
    IPV6,
    /**
     * 自定义类型
     */
    CUSTOM;
}

编写自定义注解

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @author Bummon
 * @description 数据脱敏自定义注解
 * @date 2023-09-01 18:01
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DataMaskingSerialize.class)
public @interface DataMasking {
    /**
     * 数据脱敏类型
     */
    DataMaskingType type() default DataMaskingType.CUSTOM;
    /**
     * 脱敏开始位置(包含)
     */
    int start() default 0;
    /**
     * 脱敏结束位置(不包含)
     */
    int end() default 0;
}

需要注意的是:当DataMaskingType为 CUSTOM 时,才需要填写 start 和 end ,且这两个参数才会生效,且 start 中是包含当前下标的字符的,而 end 不包含当前下标的字符。

编写自定义序列化类

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Objects;
/**
 * @author Bummon
 * @description
 * @date 2023-09-01 18:14
 */
@AllArgsConstructor
@NoArgsConstructor
public class DataMaskingSerialize extends JsonSerializer implements ContextualSerializer {
    private DataMaskingType type;
    private Integer start;
    private Integer end;
    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        String value = (String) o;
        switch (type) {
            //userId
            case USER_ID:
                jsonGenerator.writeString(String.valueOf(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.USER_ID)));
                break;
            //中文名
            case CHINESE_NAME:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.CHINESE_NAME));
                break;
            //身份证号
            case ID_CARD:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.ID_CARD));
                break;
            //座机
            case FIXED_PHONE:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.FIXED_PHONE));
                break;
            //手机号
            case MOBILE_PHONE:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.MOBILE_PHONE));
                break;
            //地址
            case ADDRESS:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.ADDRESS));
                break;
            //邮箱
            case EMAIL:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.EMAIL));
                break;
            case BANK_CARD:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.BANK_CARD));
                break;
            //密码
            case PASSWORD:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.PASSWORD));
                break;
            //中国大陆车牌号
            case CAR_LICENSE:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.CAR_LICENSE));
                break;
            case IPV4:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.IPV4));
                break;
            case IPV6:
                jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.IPV6));
                break;
            //自定义
            case CUSTOM:
                jsonGenerator.writeString(CharSequenceUtil.hide(value, start, end));
                break;
            default:
                break;
        }
    }
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (Objects.nonNull(beanProperty)) {
            //判断是否为string类型
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                DataMasking anno = beanProperty.getAnnotation(DataMasking.class);
                if (Objects.isNull(anno)) {
                    anno = beanProperty.getContextAnnotation(DataMasking.class);
                }
                if (Objects.nonNull(anno)) {
                    return new DataMaskingSerialize(anno.type(), anno.start(), anno.end());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }
}

我们继承于 JsonSerializer 并实现了 ContextualSerializer 中的方法,并对我们自定义注解声明的字段进行拦截和脱敏加密操作,接下来我们可以来测试一下效果。

测试

因为是实例化的时候才会被脱敏,那我们就创建一个实体类来存放我们需要加密的信息。

编写测试实体类

import com.bummon.mask.DataMasking;
import com.bummon.mask.DataMaskingType;
import lombok.*;
/**
 * @author Bummon
 * @description
 * @date 2023-09-01 18:29
 */
@Data
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class TestEntity {
    @DataMasking(type = DataMaskingType.USER_ID)
    private Integer userId;
    @DataMasking(type = DataMaskingType.CHINESE_NAME)
    private String userName;
    @DataMasking(type = DataMaskingType.ADDRESS)
    private String address;
    @DataMasking(type = DataMaskingType.ID_CARD)
    private String idCard;
    @DataMasking(type = DataMaskingType.FIXED_PHONE)
    private String fixedPhone;
    @DataMasking(type = DataMaskingType.MOBILE_PHONE)
    private String mobilePhone;
    @DataMasking(type = DataMaskingType.EMAIL)
    private String email;
    @DataMasking(type = DataMaskingType.PASSWORD)
    private String password;
    @DataMasking(type = DataMaskingType.CAR_LICENSE)
    private String carLicense;
    @DataMasking(type = DataMaskingType.BANK_CARD)
    private String bankCard;
    @DataMasking(type = DataMaskingType.IPV4)
    private String ipv4;
    @DataMasking(type = DataMaskingType.IPV6)
    private String ipv6;
    @DataMasking(type = DataMaskingType.CUSTOM,start = 3,end = 9)
    private String custom;
    /**
     * 不进行数据脱敏的字段
     */
    private String noMask;
}

编写测试Controller

import com.bummon.entity.TestEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author Bummon
 * @description
 * @date 2023-09-01 18:39
 */
@RestController
public class TestController {
    @GetMapping("/test")
    public TestEntity test() {
        return TestEntity.builder()
                .userId(1234567890)
                .userName("张三")
                .password("12")
                .address("河南省郑州市中原区")
                .email("xxxx@xx.com")
                .fixedPhone("0838-5553792")
                .mobilePhone("13888888888")
                .carLicense("豫P3U253")
                .bankCard("1679374639283740")
                .idCard("412711223344556677")
                .ipv4("192.168.1.236")
                .ipv6("abcd:1234:aCA9:123:4567:089:0:0000")
                .custom("289073458794")
                .noMask("我是不需要数据脱敏的字段")
                .build();
    }
}

接下来我们启动项目来看测试一下得到的是否为我们预期的数据:

我们可以看到,我们加了注解的字段都被正确的脱敏了,而没加注解的字段会正常显示。

总结

我们使用了HutoolDesensitizedUtil中的 desensitized 方法来实现数据脱敏,在 CUSTOM 类型的脱敏字段中,startend 两个属性是必填的,且 start 包含当前下标,而 end 不包含当前下标。

以上就是Java使用Hutool+自定义注解实现数据脱敏的详细内容,更多关于Hutool+自定义注解数据脱敏的资料请关注脚本之家其它相关文章!

相关文章

  • Java中spring boot 字符串判断是否为空方法小结

    Java中spring boot 字符串判断是否为空方法小结

    这篇文章主要介绍了Java中spring boot字符串判断是否为空,通过安装依赖,结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-11-11
  • java7钻石语法知识点总结

    java7钻石语法知识点总结

    在本篇文章里小编给大家整理的是关于java7钻石语法的相关知识点内容,有需要的朋友们参考下。
    2019-11-11
  • 详解Quartz 与 Spring框架集成的三种方式

    详解Quartz 与 Spring框架集成的三种方式

    这篇文章主要介绍了详解Quartz 与 Spring框架集成的三种方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • Java装饰者模式的深入了解

    Java装饰者模式的深入了解

    这篇文章主要为大家介绍了Java装饰者模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • Spring Boot示例分析讲解自动化装配机制核心注解

    Spring Boot示例分析讲解自动化装配机制核心注解

    这篇文章主要分析了Spring Boot 自动化装配机制核心注解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-07-07
  • MybatisPlus更新时部分失败的问题解决

    MybatisPlus更新时部分失败的问题解决

    这篇文章主要为大家详细介绍了MybatisPlus更新时部分失败的问题分析和解决方法,文中的代码示例讲解的非常详细,需要的朋友可以参考下
    2023-06-06
  • Maven远程仓库地址修改实现解析

    Maven远程仓库地址修改实现解析

    这篇文章主要介绍了Maven远程仓库地址修改实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • java JVM原理与常识知识点

    java JVM原理与常识知识点

    在本文中小编给大家分享的是关于java的JVM原理和java常识,有兴趣的朋友们可以学习下
    2018-12-12
  • Java数据结构学习之二叉树

    Java数据结构学习之二叉树

    今天给大家带来的是关于Java数据结构的相关知识,文章围绕着Java二叉树展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Spring Boot中的微信支付全过程(小程序)

    Spring Boot中的微信支付全过程(小程序)

    微信支付是企业级项目中经常使用到的功能,作为后端开发人员,完整地掌握该技术是十分有必要的。今天通过本文给大家介绍Spring Boot中的微信支付全过程,感兴趣的朋友一起看看吧
    2022-05-05

最新评论