Java使用Hutool+自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏
前言
我們在使用手機(jī)銀行的時(shí)候經(jīng)常能看到APP上會將銀行卡的卡號中間部分給隱藏掉使用 ***** 來代替,在某些網(wǎng)站上查看一些業(yè)務(wù)密碼時(shí)(例如簽到密碼等)也會使用 ***** 來隱藏掉真正的密碼,那么這種方式是如何實(shí)現(xiàn)的呢?
Hutool
Hutool是一個小而全的Java工具類庫,通過靜態(tài)方法封裝,降低相關(guān)API的學(xué)習(xí)成本,提高工作效率,使Java擁有函數(shù)式語言般的優(yōu)雅,讓Java語言也可以“甜甜的”。
Hutool中的工具方法來自每個用戶的精雕細(xì)琢,它涵蓋了Java開發(fā)底層代碼中的方方面面,它既是大型項(xiàng)目開發(fā)中解決小問題的利器,也是小型項(xiàng)目中的效率擔(dān)當(dāng);
Hutool是項(xiàng)目中 util 包友好的替代,它節(jié)省了開發(fā)人員對項(xiàng)目中公用類和公用工具方法的封裝時(shí)間,使開發(fā)專注于業(yè)務(wù),同時(shí)可以最大限度的避免封裝不完善帶來的bug。
我們這篇文章的實(shí)現(xiàn)思路就基于Hutool來實(shí)現(xiàn),在Hutool中提供了一個名為 DesensitizedUtil 的工具類,我們使用這個工具類來加密。
首先我們先來看一下這個類里的具體實(shí)現(xiàn),如下:

我們可以看到映入眼簾的除了一個無參構(gòu)造之外就是一個名為 desensitized 的方法,這個方法就是我們加密的主要方法,里面利用了 switch…case 方法來區(qū)分不同的加密方法。我們可以來寫一個單元測試來測試一下通過這個方法加密后是什么樣的。

以上為加密后的信息,里面我使用了不同的類型來進(jìn)行加密,目前最新版的Hutool支持脫敏加密的類型如下:
- 用戶ID
- 中文名
- 密碼
- 地址
- 郵箱
- 座機(jī)號
- 手機(jī)號
- 中國大陸的車牌號
- 銀行卡號
- IPv4地址
- IPv6地址
- 自定義脫敏
實(shí)現(xiàn)
通過以上的示例我們就可以開始編寫我們自己的脫敏操作了,首先我們要先根據(jù)以上Hutool中提供的脫敏類型來編寫我們自己的類型**(如嫌麻煩也可省略此步驟,直接使用DesensitizedUtil中的DesensitizedType)**
編寫數(shù)據(jù)脫敏類型
/**
* @author Bummon
* @description 數(shù)據(jù)脫敏策略
* @date 2023-09-01 17:43
*/
public enum DataMaskingType {
/**
* 用戶ID
*/
USER_ID,
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份證號
*/
ID_CARD,
/**
* 座機(jī)
*/
FIXED_PHONE,
/**
* 手機(jī)號
*/
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 數(shù)據(jù)脫敏自定義注解
* @date 2023-09-01 18:01
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DataMaskingSerialize.class)
public @interface DataMasking {
/**
* 數(shù)據(jù)脫敏類型
*/
DataMaskingType type() default DataMaskingType.CUSTOM;
/**
* 脫敏開始位置(包含)
*/
int start() default 0;
/**
* 脫敏結(jié)束位置(不包含)
*/
int end() default 0;
}需要注意的是:當(dāng)DataMaskingType為 CUSTOM 時(shí),才需要填寫 start 和 end ,且這兩個參數(shù)才會生效,且 start 中是包含當(dāng)前下標(biāo)的字符的,而 end 不包含當(dāng)前下標(biāo)的字符。
編寫自定義序列化類
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;
//座機(jī)
case FIXED_PHONE:
jsonGenerator.writeString(DesensitizedUtil.desensitized(value, DesensitizedUtil.DesensitizedType.FIXED_PHONE));
break;
//手機(jī)號
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 并實(shí)現(xiàn)了 ContextualSerializer 中的方法,并對我們自定義注解聲明的字段進(jìn)行攔截和脫敏加密操作,接下來我們可以來測試一下效果。
測試
因?yàn)槭菍?shí)例化的時(shí)候才會被脫敏,那我們就創(chuàng)建一個實(shí)體類來存放我們需要加密的信息。
編寫測試實(shí)體類
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;
/**
* 不進(jìn)行數(shù)據(jù)脫敏的字段
*/
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("河南省鄭州市中原區(qū)")
.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("我是不需要數(shù)據(jù)脫敏的字段")
.build();
}
}接下來我們啟動項(xiàng)目來看測試一下得到的是否為我們預(yù)期的數(shù)據(jù):

我們可以看到,我們加了注解的字段都被正確的脫敏了,而沒加注解的字段會正常顯示。
總結(jié)
我們使用了Hutool的DesensitizedUtil中的 desensitized 方法來實(shí)現(xiàn)數(shù)據(jù)脫敏,在 CUSTOM 類型的脫敏字段中,start 和 end 兩個屬性是必填的,且 start 包含當(dāng)前下標(biāo),而 end 不包含當(dāng)前下標(biāo)。
以上就是Java使用Hutool+自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏的詳細(xì)內(nèi)容,更多關(guān)于Hutool+自定義注解數(shù)據(jù)脫敏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Java實(shí)現(xiàn)修改圖片分辨率示例代碼
這篇文章主要介紹了一個可以修改圖片分辨率的java工具類,文中的示例代碼講解詳細(xì),對學(xué)習(xí)JAVA有一定的幫助,感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)吧2021-12-12
Spring 整合 MyBatis的實(shí)現(xiàn)步驟
SpringMVC 本來就是 Spring 框架的一部分,這兩者無須再做整合,所以 SSM 整合的關(guān)鍵就是Spring對MyBatis的整合,三大框架整合完成后,將以 Spring 為核心,調(diào)用有關(guān)資源,高效運(yùn)作,這篇文章主要介紹了 Spring 整合 MyBatis的實(shí)現(xiàn)步驟,需要的朋友可以參考下2023-02-02
Springboot+jwt實(shí)現(xiàn)在線用戶功能(示例代碼)
這篇文章主要介紹了Springboot+jwt實(shí)現(xiàn)在線用戶功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-12-12

