SpringBoot實(shí)現(xiàn)返回值數(shù)據(jù)脫敏的步驟詳解
介紹
SpringBoot實(shí)現(xiàn)返回?cái)?shù)據(jù)脫敏
有時(shí),敏感數(shù)據(jù)返回時(shí),需要進(jìn)行隱藏處理,但是如果一個(gè)字段一個(gè)字段的進(jìn)行硬編碼處理的話,不僅增加了工作量,而且后期需求變動(dòng)的時(shí)候,更加是地獄般的工作量變更。
下面,通過(guò)身份證,姓名,密碼,手機(jī)號(hào)等等示例去演示脫敏的流程,當(dāng)然你也可以在此基礎(chǔ)上添加自己的實(shí)現(xiàn)方式
原理
- 項(xiàng)目使用的是SpringBoot,所以需要在序列化的時(shí)候,進(jìn)行脫敏處理,springboot內(nèi)置的序列化工具為jackson
- 通過(guò)實(shí)現(xiàn)com.fasterxml.jackson.databind.JsonSerializer進(jìn)行自定義序列化
- 通過(guò)重寫com.fasterxml.jackson.databind.ser.ContextualSerializer.createContextual獲取自定義注解的信息
實(shí)現(xiàn)
自定義注解類
@Target(ElementType.FIELD) //作用于字段上 @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside // 表示自定義自己的注解Sensitive @JsonSerialize(using = SensitiveInfoSerialize.class) // 該注解使用序列化的方式 public @interface Sensitive { SensitizedType value(); }
創(chuàng)建脫敏字段類型枚舉
public enum SensitizedType { /** * 用戶id */ USER_ID, /** * 中文名 */ CHINESE_NAME, /** * 身份證號(hào) */ ID_CARD, /** * 座機(jī)號(hào) */ FIXED_PHONE, /** * 手機(jī)號(hào) */ MOBILE_PHONE, /** * 地址 */ ADDRESS, /** * 電子郵件 */ EMAIL, /** * 密碼 */ PASSWORD, /** * 中國(guó)大陸車牌,包含普通車輛、新能源車輛 */ CAR_LICENSE, /** * 銀行卡 */ BANK_CARD, /** * IPv4地址 */ IPV4, /** * IPv6地址 */ IPV6, /** * 定義了一個(gè)first_mask的規(guī)則,只顯示第一個(gè)字符。 */ FIRST_MASK }
脫敏工具類
import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; /** * @Auther: wu * @Date: 2023/7/11 * @Description: com.wu.demo.common.my_sensitive */ public class SensitizedUtil { public static String desensitized(CharSequence str, SensitizedType desensitizedType) { if (StrUtil.isBlank(str)) { return StrUtil.EMPTY; } String newStr = String.valueOf(str); switch (desensitizedType) { case USER_ID: newStr = String.valueOf(userId()); break; case CHINESE_NAME: newStr = chineseName(String.valueOf(str)); break; case ID_CARD: newStr = idCardNum(String.valueOf(str), 3, 4); break; case FIXED_PHONE: newStr = fixedPhone(String.valueOf(str)); break; case MOBILE_PHONE: newStr = mobilePhone(String.valueOf(str)); break; case ADDRESS: newStr = address(String.valueOf(str), 8); break; case EMAIL: newStr = email(String.valueOf(str)); break; case PASSWORD: newStr = password(String.valueOf(str)); break; case CAR_LICENSE: newStr = carLicense(String.valueOf(str)); break; case BANK_CARD: newStr = bankCard(String.valueOf(str)); break; case IPV4: newStr = ipv4(String.valueOf(str)); break; case IPV6: newStr = ipv6(String.valueOf(str)); break; case FIRST_MASK: newStr = firstMask(String.valueOf(str)); break; default: } return newStr; } /** * 【用戶id】不對(duì)外提供userId * * @return 脫敏后的主鍵 */ public static Long userId() { return 0L; } /** * 定義了一個(gè)first_mask的規(guī)則,只顯示第一個(gè)字符。<br> * 脫敏前:123456789;脫敏后:1********。 * * @param str 字符串 * @return 脫敏后的字符串 */ public static String firstMask(String str) { if (StrUtil.isBlank(str)) { return StrUtil.EMPTY; } return StrUtil.hide(str, 1, str.length()); } /** * 【中文姓名】只顯示第一個(gè)漢字,其他隱藏為2個(gè)星號(hào),比如:李** * * @param fullName 姓名 * @return 脫敏后的姓名 */ public static String chineseName(String fullName) { return firstMask(fullName); } /** * 【身份證號(hào)】前1位 和后2位 * * @param idCardNum 身份證 * @param front 保留:前面的front位數(shù);從1開始 * @param end 保留:后面的end位數(shù);從1開始 * @return 脫敏后的身份證 */ public static String idCardNum(String idCardNum, int front, int end) { //身份證不能為空 if (StrUtil.isBlank(idCardNum)) { return StrUtil.EMPTY; } //需要截取的長(zhǎng)度不能大于身份證號(hào)長(zhǎng)度 if ((front + end) > idCardNum.length()) { return StrUtil.EMPTY; } //需要截取的不能小于0 if (front < 0 || end < 0) { return StrUtil.EMPTY; } return StrUtil.hide(idCardNum, front, idCardNum.length() - end); } /** * 【固定電話 前四位,后兩位 * * @param num 固定電話 * @return 脫敏后的固定電話; */ public static String fixedPhone(String num) { if (StrUtil.isBlank(num)) { return StrUtil.EMPTY; } return StrUtil.hide(num, 4, num.length() - 2); } /** * 【手機(jī)號(hào)碼】前三位,后4位,其他隱藏,比如135****2210 * * @param num 移動(dòng)電話; * @return 脫敏后的移動(dòng)電話; */ public static String mobilePhone(String num) { if (StrUtil.isBlank(num)) { return StrUtil.EMPTY; } return StrUtil.hide(num, 3, num.length() - 4); } /** * 【地址】只顯示到地區(qū),不顯示詳細(xì)地址,比如:北京市海淀區(qū)**** * * @param address 家庭住址 * @param sensitiveSize 敏感信息長(zhǎng)度 * @return 脫敏后的家庭地址 */ public static String address(String address, int sensitiveSize) { if (StrUtil.isBlank(address)) { return StrUtil.EMPTY; } int length = address.length(); return StrUtil.hide(address, length - sensitiveSize, length); } /** * 【電子郵箱】郵箱前綴僅顯示第一個(gè)字母,前綴其他隱藏,用星號(hào)代替,@及后面的地址顯示,比如:d**@126.com * * @param email 郵箱 * @return 脫敏后的郵箱 */ public static String email(String email) { if (StrUtil.isBlank(email)) { return StrUtil.EMPTY; } int index = StrUtil.indexOf(email, '@'); if (index <= 1) { return email; } return StrUtil.hide(email, 1, index); } /** * 【密碼】密碼的全部字符都用*代替,比如:****** * * @param password 密碼 * @return 脫敏后的密碼 */ public static String password(String password) { if (StrUtil.isBlank(password)) { return StrUtil.EMPTY; } return StrUtil.repeat('*', password.length()); } /** * 【中國(guó)車牌】車牌中間用*代替 * eg1:null -》 "" * eg1:"" -》 "" * eg3:蘇D40000 -》 蘇D4***0 * eg4:陜A12345D -》 陜A1****D * eg5:京A123 -》 京A123 如果是錯(cuò)誤的車牌,不處理 * * @param carLicense 完整的車牌號(hào) * @return 脫敏后的車牌 */ public static String carLicense(String carLicense) { if (StrUtil.isBlank(carLicense)) { return StrUtil.EMPTY; } // 普通車牌 if (carLicense.length() == 7) { carLicense = StrUtil.hide(carLicense, 3, 6); } else if (carLicense.length() == 8) { // 新能源車牌 carLicense = StrUtil.hide(carLicense, 3, 7); } return carLicense; } /** * 銀行卡號(hào)脫敏 * eg: 1101 **** **** **** 3256 * * @param bankCardNo 銀行卡號(hào) * @return 脫敏之后的銀行卡號(hào) * @since 5.6.3 */ public static String bankCard(String bankCardNo) { if (StrUtil.isBlank(bankCardNo)) { return bankCardNo; } bankCardNo = StrUtil.trim(bankCardNo); if (bankCardNo.length() < 9) { return bankCardNo; } final int length = bankCardNo.length(); final int midLength = length - 8; final StringBuilder buf = new StringBuilder(); buf.append(bankCardNo, 0, 4); for (int i = 0; i < midLength; ++i) { if (i % 4 == 0) { buf.append(CharUtil.SPACE); } buf.append('*'); } buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length); return buf.toString(); } /** * IPv4脫敏,如:脫敏前:192.0.2.1;脫敏后:192.*.*.*。 * * @param ipv4 IPv4地址 * @return 脫敏后的地址 */ public static String ipv4(String ipv4) { return StrUtil.subBefore(ipv4, '.', false) + ".*.*.*"; } /** * IPv4脫敏,如:脫敏前:2001:0db8:86a3:08d3:1319:8a2e:0370:7344;脫敏后:2001:*:*:*:*:*:*:* * * @param ipv6 IPv4地址 * @return 脫敏后的地址 */ public static String ipv6(String ipv6) { return StrUtil.subBefore(ipv6, ':', false) + ":*:*:*:*:*:*:*"; } }
上述枚舉類和脫敏工具類,我使用了hutool中的代碼,如果hutool滿足你的需求,可以直接把上述自定義注解類和自定義序列化類使用到的SensitizedType類直接替換為hutool中的cn.hutool.core.util.DesensitizedUtil.DesensitizedType的枚舉類,
添加自定義序列化實(shí)現(xiàn)類
public class SensitiveInfoSerialize extends JsonSerializer<String> implements ContextualSerializer { private SensitizedType sensitizedType; /** * 步驟一 * 方法來(lái)源于ContextualSerializer,獲取屬性上的注解屬性,同時(shí)返回一個(gè)合適的序列化器 */ @Override public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException { // 獲取自定義注解 Sensitive annotation = beanProperty.getAnnotation(Sensitive.class); // 注解不為空,且標(biāo)注的字段為String if(Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())){ this.sensitizedType = annotation.value(); //自定義情況,返回本序列化器,將順利進(jìn)入到該類中的serialize方法中 return this; } // 注解為空,字段不為String,尋找合適的序列化器進(jìn)行處理 return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); } /** * 步驟二 * 方法來(lái)源于JsonSerializer<String>:指定返回類型為String類型,serialize()將修改后的數(shù)據(jù)返回 */ @Override public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { if(Objects.isNull(sensitizedType)){ // 定義策略為空,返回原字符串 jsonGenerator.writeString(str); }else { // 定義策略不為空,返回策略處理過(guò)的字符串 jsonGenerator.writeString(SensitizedUtil.desensitized(str,sensitizedType)); } } }
測(cè)試驗(yàn)證
在需要的脫敏的實(shí)體類字段上加上相應(yīng)的注解
@Data public class SensitiveBody { private String name; @Sensitive(SensitizedType.MOBILE_PHONE) private String mobile; @Sensitive(SensitizedType.ID_CARD) private String idCard; }
@ApiOperation(value = "脫敏測(cè)試處理") @GetMapping("sensitiveTest") public AjaxResult sensitiveTest(){ SensitiveBody body = new SensitiveBody(); body.setMobile("13041064026"); body.setIdCard("411126189912355689"); body.setName("Tom"); return AjaxResult.success(body); }
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)返回值數(shù)據(jù)脫敏的步驟詳解的文章就介紹到這了,更多相關(guān)SpringBoot返回值數(shù)據(jù)脫敏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot使用自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏過(guò)程詳細(xì)解析
- Springboot+Jackson自定義注解數(shù)據(jù)脫敏的項(xiàng)目實(shí)踐
- Springboot+Hutool自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏
- SpringBoot利用自定義注解實(shí)現(xiàn)隱私數(shù)據(jù)脫敏(加密顯示)的解決方案
- SpringBoot利用自定義json序列化器實(shí)現(xiàn)敏感字段數(shù)據(jù)脫敏詳解
- 淺析如何在SpringBoot中實(shí)現(xiàn)數(shù)據(jù)脫敏
- SpringBoot數(shù)據(jù)脫敏的實(shí)現(xiàn)示例
- SpringBoot接口返回?cái)?shù)據(jù)脫敏(Mybatis、Jackson)
相關(guān)文章
Java利用ElasticSearch實(shí)現(xiàn)增刪改功能
這篇文章主要為大家詳細(xì)介紹了Java如何利用ElasticSearch實(shí)現(xiàn)增刪改功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-08-08Java中的世界時(shí)區(qū)如何自動(dòng)計(jì)算及生成?
在?Java?中,處理時(shí)區(qū)和時(shí)間計(jì)算是一個(gè)非常常見的需求,尤其是在涉及全球應(yīng)用時(shí),Java?提供了一些強(qiáng)大的?API?來(lái)處理世界時(shí)區(qū)(如?java.time?包),下面將介紹如何基于?Java?自動(dòng)計(jì)算時(shí)區(qū)并生成相應(yīng)的時(shí)間2025-01-01Java使用備忘錄模式實(shí)現(xiàn)過(guò)關(guān)類游戲功能詳解
這篇文章主要介紹了Java使用備忘錄模式實(shí)現(xiàn)過(guò)關(guān)類游戲功能,結(jié)合實(shí)例形式詳細(xì)分析了java備忘錄模式的概念、原理及其在過(guò)關(guān)類游戲中的具體應(yīng)用方法,需要的朋友可以參考下2018-04-04完美解決SpringCloud-OpenFeign使用okhttp替換不生效問(wèn)題
這篇文章主要介紹了完美解決SpringCloud-OpenFeign使用okhttp替換不生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Feign+mybatisplus搭建項(xiàng)目遇到的坑及解決
這篇文章主要介紹了Feign+mybatisplus搭建項(xiàng)目遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03SpringBoot異常處理之異常顯示的頁(yè)面問(wèn)題
這篇文章主要介紹了SpringBoot異常處理異常顯示的頁(yè)面的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系
這篇文章主要介紹了簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03spring如何實(shí)現(xiàn)依賴注入DI(spring-test方式)
本文主要介紹如何實(shí)現(xiàn)spring 的依賴注入,并且淺顯的講述一下注入需要注意的事項(xiàng)。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java的無(wú)參構(gòu)造函數(shù)用法實(shí)例分析
這篇文章主要介紹了Java的無(wú)參構(gòu)造函數(shù)用法,結(jié)合實(shí)例形式分析了java無(wú)參構(gòu)造函數(shù)基本原理、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-09-09