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+自定义注解数据脱敏的资料请关注脚本之家其它相关文章!

相关文章

  • Maven scala和java混合打包方式

    Maven scala和java混合打包方式

    这篇文章主要介绍了Maven scala和java混合打包方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • @Conditional注解的使用场景和源码解析

    @Conditional注解的使用场景和源码解析

    这篇文章主要介绍了@Conditional注解的使用场景和源码解析,@Conditional是一个条件注解,它的作用是判断Bean是否满足条件,如果满足条件,则将Bean注册进IOC中,如果不满足条件,则不进行注册,需要的朋友可以参考下
    2023-11-11
  • 纯注解版spring与mybatis的整合过程

    纯注解版spring与mybatis的整合过程

    这篇文章主要介绍了纯注解版spring与mybatis的整合过程,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • Java使用钉钉创建企业内部机器人的实现

    Java使用钉钉创建企业内部机器人的实现

    钉钉的出现为企业节约了大量人力成本,其中也开放了大量的外部api接口方便企业使用。本文主要介绍了Java使用钉钉创建企业内部机器人的实现,感兴趣的可以了解一下
    2021-11-11
  • Python基础之如何使用multiprocessing模块

    Python基础之如何使用multiprocessing模块

    今天带大家学习python多进程的相关知识,文中对multiprocessing模块的使用作了非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • Java编程探索之泛型擦除实例解析

    Java编程探索之泛型擦除实例解析

    这篇文章主要介绍了Java编程探索之泛型擦除实例解析,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • Cookie 实现的原理

    Cookie 实现的原理

    我们在浏览器中,经常涉及到数据的交换,比如你登录邮箱,登录一个页面。我们经常会在此时设置30天内记住我,或者自动登录选项。那么它们是怎么记录信息的呢,答案就是今天的主角cookie了
    2021-06-06
  • 初探Java内部类的使用

    初探Java内部类的使用

    Java内部类一般可以分为以下三种:成员内部类、静态内部类和匿名内部类。这篇文章主要带大家初探一下Java内部类的使用,感兴趣的可以了解一下
    2022-09-09
  • SpringBoot中间件ORM框架实现案例详解(Mybatis)

    SpringBoot中间件ORM框架实现案例详解(Mybatis)

    这篇文章主要介绍了SpringBoot中间件ORM框架实现案例详解(Mybatis),本篇文章提炼出mybatis最经典、最精简、最核心的代码设计,来实现一个mini-mybatis,从而熟悉并掌握ORM框架的涉及实现,需要的朋友可以参考下
    2023-07-07
  • SpringBoot使用SSE进行实时通知前端的实现代码

    SpringBoot使用SSE进行实时通知前端的实现代码

    这篇文章主要介绍了SpringBoot使用SSE进行实时通知前端,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06

最新评论