前言:在實際項目開發中,可能會對一些用戶的隱私信息進行脫敏操作,傳統的方式很多都是用replace方法進行手動替換,這樣會由很多冗余的代碼并且后續也不好維護,本期就講解一下如何在SpringBoot中優雅的通過序列化的方式去實現數據的脫敏操作!
目錄
一、導入pom依賴
二、DesensitizationEnum枚舉類
三、Desensitization自定義注解
四、DesensitizationSerialize脫敏序列化器
五、User實體類
六、UserController請求層
七、運行測試
八、Gitee源碼
九、總結
一、導入pom依賴
完整代碼:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.18</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
二、DesensitizationEnum枚舉類
在DesensitizationSerialize序列化類中,會根據脫敏注解的type值,也就是DesensitizationEnum 中的類型,來判斷需要使用哪種脫敏方式。
這邊我就簡單定義了5個枚舉類型:
完整代碼:
package com.example.desensitization.constant;public enum DesensitizationEnum {/*** 自定義*/CUSTOM_RULE,/*** 身份證號碼*/ID_CARD_NO,/*** 電話號碼*/PHONE,/*** 地址*/ADDRESS,/*** 銀行卡號*/BANK_CARD_NO,
}
三、Desensitization自定義注解
這個是自定義的注解@Desensitization,用于標注需要進行脫敏的字段。
主要包含以下元注解和屬性:
1、@Target(ElementType.FIELD):表示該注解只能用于字段上。
2、@Retention(RetentionPolicy.RUNTIME):表示該注解可以保留到運行時。
3、@JacksonAnnotationsInside:是一個Jackson的元注解,表示該注解可以作為Json序列化的注解。
4、@JsonSerialize:標注使用DesensitizationSerialize來進行序列化。
5、DesensitizationEnum type:需要脫敏的類型,對應枚舉中的脫敏類型。
6、int start/end:可選的起始位置和結束位置,對于脫敏類型為字符串時有效。
完整代碼:
package com.example.desensitization.annotation;import com.example.desensitization.constant.DesensitizationEnum;
import com.example.desensitization.serialize.DesensitizationSerialize;
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;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {DesensitizationEnum type();int start() default 0;int end() default 0;}
四、DesensitizationSerialize脫敏序列化器
1、繼承JsonSerializer<String>JsonSerializer是Jackson的序列化器基類,實現了將對象序列化為JSON的核心方法,這里繼承它是為了實現字符串的自定義序列化。
2、實現ContextualSerializer接口ContextualSerializer可以讓序列化器基于上下文環境進行定制化,實現這個接口后,可以實現createContextual()方法。
關鍵代碼:
@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {private DesensitizationEnum type;private Integer start;private Integer end;
}
自定義序列化器方式可以實現非侵入式的靈活脫敏,對業務代碼零侵入,且不依賴Spring等框架,更適合編寫獨立的應用服務。當然,AOP實現也有其適用場景,可以作為另一種可選方案。
createContextual()方法:
1、Controller的user()方法被調用,構建并返回了一個User對象。
2、開始對User對象進行JSON序列化,會先調用我們定義的DesensitizationSerialize中createContextual()方法,如果這個實體類被createContextual()方法處理過,則以后不會再走該方法,直接走serialize()方法。
3、DesensitizationSerialize會檢查當前類字段是否有@Desensitization注解,如果有N個,則根據注解的type、start和end參數創建N個DesensitizationSerialize實例。
關鍵代碼:
@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)throws JsonMappingException {if (beanProperty != null) {// 獲取當前正在處理的字段的類型,判斷如果是 String 類型則進行后續脫敏邏輯處理if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {// 通過 beanProperty 獲取在字段上標注的 @Desensitization 注解Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);// 如果沒有就嘗試獲取類注解if (desensitization == null) {desensitization = beanProperty.getContextAnnotation(Desensitization.class);}// 不為nullif (desensitization != null) {// 如果獲取到了注解,則根據注解的 type、start 和 end 參數創建 DesensitizationSerialize 實例,這是脫敏處理的序列化器return new DesensitizationSerialize(desensitization.type(), desensitization.start(),desensitization.end());}}// 直接返回默認的序列化器return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}// 直接返回默認的序列化器return serializerProvider.findNullValueSerializer(null);}
serialize方法:?
JsonGenerator是Jackjson提供的JSON生成器類。在自定義序列化器的serialize()方法中,會傳入JsonGenerator實例。serialize()方法需要通過JsonGenerator將脫敏后的字符串寫入到結果JSON中。
CharSequenceUtil和DesensitizedUtil都是hutool提供的工具類。
整體流程是:
1、根據注解的參數動態選擇脫敏策略。
2、調用對應 Hutool 的脫敏函數處理字符串。
3、將脫敏結果寫入JSON。
關鍵代碼:
@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)throws IOException {switch (type){//自定義case CUSTOM_RULE:jsonGenerator.writeString(CharSequenceUtil.hide(s, start, end));break;//身份證case ID_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.idCardNum(s, 2, 6));break;//手機號case PHONE:jsonGenerator.writeString(DesensitizedUtil.mobilePhone(s));break;//地址case ADDRESS:jsonGenerator.writeString(DesensitizedUtil.address(s, 2));break;// 銀行卡脫敏case BANK_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.bankCard(s));break;default:}}
綜上,整體的執行邏輯如下:
1、Controller返回一個實體對象。
2、如果實體對象是第一次進行脫敏,則會調用createContextual()方法。
3、獲取當前實體對象所有使用Desensitization注解的字符串字段,創建對應的DesensitizationSerialize實例,實現脫敏處理的序列化器。
4、執行serialize()方法中switch的處理邏輯,由JsonGenerator將脫敏后的字符串寫入到結果JSON中。
5、返回Json數據。
完整代碼:
package com.example.desensitization.serialize;import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.example.desensitization.annotation.Desensitization;
import com.example.desensitization.constant.DesensitizationEnum;
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;@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {private DesensitizationEnum type;private Integer start;private Integer end;@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)throws IOException {switch (type){//自定義case CUSTOM_RULE:jsonGenerator.writeString(CharSequenceUtil.hide(s, start, end));break;//身份證case ID_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.idCardNum(s, 2, 6));break;//手機號case PHONE:jsonGenerator.writeString(DesensitizedUtil.mobilePhone(s));break;//地址case ADDRESS:jsonGenerator.writeString(DesensitizedUtil.address(s, 2));break;// 銀行卡脫敏case BANK_CARD_NO:jsonGenerator.writeString(DesensitizedUtil.bankCard(s));break;default:}}@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)throws JsonMappingException {if (beanProperty != null) {// 獲取當前正在處理的字段的類型,判斷如果是 String 類型則進行后續脫敏邏輯處理if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {// 通過 beanProperty 獲取在字段上標注的 @Desensitization 注解Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);// 如果沒有就嘗試獲取類注解if (desensitization == null) {desensitization = beanProperty.getContextAnnotation(Desensitization.class);}// 不為nullif (desensitization != null) {// 如果獲取到了注解,則根據注解的 type、start 和 end 參數創建 DesensitizationSerialize 實例,這是脫敏處理的序列化器return new DesensitizationSerialize(desensitization.type(), desensitization.start(),desensitization.end());}}// 直接返回默認的序列化器return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);}// 直接返回默認的序列化器return serializerProvider.findNullValueSerializer(null);}
}
五、User實體類
給想要脫敏的字段加上?@Desensitization(type = DesensitizationEnum.枚舉類型)注解即可。
完整代碼:
package com.example.desensitization.domain;import com.example.desensitization.annotation.Desensitization;
import com.example.desensitization.constant.DesensitizationEnum;
import lombok.Builder;
import lombok.Data;@Data
@Builder
public class User {/*** 主鍵*/private String id;/*** 用戶名*/private String name;/*** 身份證號碼*/@Desensitization(type = DesensitizationEnum.ID_CARD_NO)private String idCardNo;/*** 電話號碼*/@Desensitization(type = DesensitizationEnum.PHONE)private String phone;/*** 地址*/@Desensitization(type = DesensitizationEnum.CUSTOM_RULE,start = 2,end = 5)private String address;/*** 銀行卡號*/@Desensitization(type = DesensitizationEnum.BANK_CARD_NO)private String bankCardNo;}
六、UserController請求層
完整代碼:
package com.example.desensitization.controller;import com.example.desensitization.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.UUID;@RestController
@RequestMapping
public class UserController {@GetMapping("/user")public User user(){User user = User.builder().id(UUID.randomUUID().toString()).name("張三").idCardNo("32089809285012823").phone("13919819285").bankCardNo("62427292012731238812").address("江蘇省南通市").build();return user;}}
七、運行測試
瀏覽器直接訪問:http://localhost:8080/user
可以看到隱私的信息都進行了數據脫敏的處理!
八、Gitee源碼
源碼地址:SpringBoot中優雅的實現隱私數據脫敏
九、總結
以上就是我對于SpringBoot中如何優雅的實現隱私數據脫敏的完整教程,如有問題,歡迎評論區留言!?