Java利用Jackson序列化實現(xiàn)數(shù)據(jù)脫敏詳解
1.背景
在項目中有些敏感信息不能直接展示,比如客戶手機號、身份證、車牌號等信息,展示時均需要進(jìn)行數(shù)據(jù)脫敏,防止泄露客戶隱私。脫敏即是對數(shù)據(jù)的部分信息用脫敏符號(*)處理。
2.目標(biāo)
- 在服務(wù)端返回數(shù)據(jù)時,利用Jackson序列化完成數(shù)據(jù)脫敏,達(dá)到對敏感信息脫敏展示。
- 降低重復(fù)開發(fā)量,提升開發(fā)效率
- 形成統(tǒng)一有效的脫敏規(guī)則
- 可基于重寫默認(rèn)脫敏實現(xiàn)的desensitize方法,實現(xiàn)可擴展、可自定義的個性化業(yè)務(wù)場景的脫敏需求
3.主要實現(xiàn)
3.1基于Jackson的自定義脫敏序列化實現(xiàn)
StdSerializer:所有標(biāo)準(zhǔn)序列化程序所使用的基類,這個是編寫自定義序列化程序所推薦使用的基類。
ContextualSerializer: 是Jackson 提供的另一個序列化相關(guān)的接口,它的作用是通過字段已知的上下文信息定制JsonSerializer。
package com.jd.ccmp.ctm.constraints.serializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.jd.ccmp.ctm.constraints.Symbol; import com.jd.ccmp.ctm.constraints.annotation.Desensitize; import com.jd.ccmp.ctm.constraints.desensitization.Desensitization; import com.jd.ccmp.ctm.constraints.desensitization.DesensitizationFactory; import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization; import java.io.IOException; /** * 脫敏序列化器 * * @author zhangxiaoxu15 * @date 2022/2/8 11:10 */ public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer { private static final long serialVersionUID = -7868746622368564541L; private transient Desensitization<Object> desensitization; protected ObjectDesensitizeSerializer() { super(Object.class); } public Desensitization<Object> getDesensitization() { return desensitization; } public void setDesensitization(Desensitization<Object> desensitization) { this.desensitization = desensitization; } @Override public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) { //獲取屬性注解 Desensitize annotation = property.getAnnotation(Desensitize.class); return createContextual(annotation.desensitization()); } @SuppressWarnings("unchecked") public JsonSerializer<Object> createContextual(Class<? extends Desensitization<?>> clazz) { ObjectDesensitizeSerializer serializer = new ObjectDesensitizeSerializer(); if (clazz != DefaultDesensitization.class) { serializer.setDesensitization((Desensitization<Object>) DesensitizationFactory.getDesensitization(clazz)); } return serializer; } @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { Desensitization<Object> objectDesensitization = getDesensitization(); if (objectDesensitization != null) { try { gen.writeObject(objectDesensitization.desensitize(value)); } catch (Exception e) { gen.writeObject(value); } } else if (value instanceof String) { gen.writeString(Symbol.getSymbol(((String) value).length(), Symbol.STAR)); } else { gen.writeObject(value); }
注:createContextual可以獲得字段的類型以及注解。當(dāng)字段擁有自定義注解時,取出注解中的值創(chuàng)建定制的序列化方式,這樣在serialize方法中便可以得到這個值了。createContextual方法只會在第一次序列化字段時調(diào)用(因為字段的上下文信息在運行期不會改變),所以無需關(guān)心性能問題。
3.2定義脫敏接口、以及工廠實現(xiàn)
3.2.1脫敏器接口定義
package com.jd.ccmp.ctm.constraints.desensitization; /** * 脫敏器 * * @author zhangxiaoxu15 * @date 2022/2/8 10:56 */ public interface Desensitization<T> { /** * 脫敏實現(xiàn) * * @param target 脫敏對象 * @return 脫敏返回結(jié)果 */ T desensitize(T target); }
3.2.2脫敏器工廠實現(xiàn)
package com.jd.ccmp.ctm.constraints.desensitization; import java.util.HashMap; import java.util.Map; /** * 工廠方法 * * @author zhangxiaoxu15 * @date 2022/2/8 10:58 */ public class DesensitizationFactory { private DesensitizationFactory() { } private static final Map<Class<?>, Desensitization<?>> map = new HashMap<>(); @SuppressWarnings("all") public static Desensitization<?> getDesensitization(Class<?> clazz) { if (clazz.isInterface()) { throw new UnsupportedOperationException("desensitization is interface, what is expected is an implementation class !"); } return map.computeIfAbsent(clazz, key -> { try { return (Desensitization<?>) clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new UnsupportedOperationException(e.getMessage(), e); } });
3.3常用的脫敏器實現(xiàn)
3.3.1默認(rèn)脫敏實現(xiàn)
可基于默認(rèn)實現(xiàn),擴展實現(xiàn)個性化場景
package com.jd.ccmp.ctm.constraints.desensitization; /** * 默認(rèn)脫敏實現(xiàn) * * @author zhangxiaoxu15 * @date 2022/2/8 11:01 */ public interface DefaultDesensitization extends Desensitization<String> { }
3.3.2手機號脫敏器
實現(xiàn)對手機號中間4位號碼脫敏
package com.jd.ccmp.ctm.constraints.desensitization; import com.jd.ccmp.ctm.constraints.Symbol; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 手機號脫敏器,保留前3位和后4位 * * @author zhangxiaoxu15 * @date 2022/2/8 11:02 */ public class MobileNoDesensitization implements DefaultDesensitization { /** * 手機號正則 */ private static final Pattern DEFAULT_PATTERN = Pattern.compile("(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}"); @Override public String desensitize(String target) { Matcher matcher = DEFAULT_PATTERN.matcher(target); while (matcher.find()) { String group = matcher.group(); target = target.replace(group, group.substring(0, 3) + Symbol.getSymbol(4, Symbol.STAR) + group.substring(7, 11)); } return target;
3.4注解定義
通過@JacksonAnnotationsInside實現(xiàn)自定義注解,提高易用性
package com.jd.ccmp.ctm.constraints.annotation; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.jd.ccmp.ctm.constraints.desensitization.Desensitization; import com.jd.ccmp.ctm.constraints.serializer.ObjectDesensitizeSerializer; import java.lang.annotation.*; /** * 脫敏注解 * * @author zhangxiaoxu15 * @date 2022/2/8 11:09 */ @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @JsonSerialize(using = ObjectDesensitizeSerializer.class) @Documented public @interface Desensitize { /** * 對象脫敏器實現(xiàn) */ @SuppressWarnings("all") Class<? extends Desensitization<?>> desensitization();
3.4.1默認(rèn)脫敏注解
package com.jd.ccmp.ctm.constraints.annotation; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization; import java.lang.annotation.*; /** * 默認(rèn)脫敏注解 * * @author zhangxiaoxu15 * @date 2022/2/8 11:14 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @Desensitize(desensitization = DefaultDesensitization.class) @Documented public @interface DefaultDesensitize {
3.4.2手機號脫敏注解
package com.jd.ccmp.ctm.constraints.annotation; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.jd.ccmp.ctm.constraints.desensitization.MobileNoDesensitization; import java.lang.annotation.*; /** * 手機號脫敏 * * @author zhangxiaoxu15 * @date 2022/2/8 11:18 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @Desensitize(desensitization = MobileNoDesensitization.class) @Documented public @interface MobileNoDesensitize { }
3.5定義脫敏符號
支持指定脫敏符號,例如* 或是 ^_^
package com.jd.ccmp.ctm.constraints; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 脫敏符號 * * @author zhangxiaoxu15 * @date 2022/2/8 10:53 */ public class Symbol { /** * '*'脫敏符 */ public static final String STAR = "*"; private Symbol() {} /** * 獲取符號 * * @param number 符號個數(shù) * @param symbol 符號 */ public static String getSymbol(int number, String symbol) { return IntStream.range(0, number).mapToObj(i -> symbol).collect(Collectors.joining()); }
4.使用樣例&執(zhí)行流程剖析
程序類圖
**執(zhí)行流程剖析**
1.調(diào)用JsonUtil.toJsonString()開始執(zhí)行序列化
2.識別屬性mobile上的注解@MobileNoDesensitize(上文3.4.2)
3.調(diào)用ObjectDesensitizeSerializer#createContextual(上文3.1 & 3.2),返回JsonSerializer
4.調(diào)用手機號脫敏實現(xiàn)MobileNoDesensitization#desensitize(上文3.3.2)
5.輸出脫敏后的序列化結(jié)果,{"mobile":"133****5678"}
不難發(fā)現(xiàn)核心執(zhí)行流程是第3步,但是@MobileNoDesensitize與ObjectDesensitizeSerializer又是如何聯(lián)系起來的呢?
- 嘗試梳理下引用鏈路:@MobileNoDesensitize -> @Desensitize -> @JsonSerialize -> ObjectDesensitizeSerializer
- 但是,在ObjectDesensitizeSerializer的實現(xiàn)中,我們似乎卻沒有發(fā)現(xiàn)上述鏈路的直接調(diào)用關(guān)系
- 這就不得不說下Jackson元注解的概念
//**Jackson元注解** //1.提到元注解這個詞,大家會想到@Target、@Retention、@Documented、@Inherited //2.Jackson也以同樣的思路設(shè)計了@JacksonAnnotationsInside /** * Meta-annotation (annotations used on other annotations) * used for indicating that instead of using target annotation * (annotation annotated with this annotation), * Jackson should use meta-annotations it has. * This can be useful in creating "combo-annotations" by having * a container annotation, which needs to be annotated with this * annotation as well as all annotations it 'contains'. * * @since 2.0 */ @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotation public @interface JacksonAnnotationsInside { }
正是通過”combo-annotations”(組合注解、捆綁注解)的機制,實現(xiàn)了指示Jackson應(yīng)該使用其擁有的元注釋,而不是使用目標(biāo)注釋,從而實現(xiàn)了自定義脫敏實現(xiàn)設(shè)計目標(biāo)。
以上就是Java利用Jackson序列化實現(xiàn)數(shù)據(jù)脫敏詳解的詳細(xì)內(nèi)容,更多關(guān)于Java Jackson序列化數(shù)據(jù)脫敏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java基礎(chǔ)-Java的體系結(jié)構(gòu)
這篇文章主要介紹了Java的體系結(jié)構(gòu),Java幾乎成為了“開源”的代名詞。第三方開源軟件和框架。如Tomcat、Struts,MyBatis,Spring等,下面我們來看看文章具體的內(nèi)容介紹吧2022-01-01springboot+gradle 構(gòu)建多模塊項目的步驟
這篇文章主要介紹了springboot+gradle 構(gòu)建多模塊項目的步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05劍指Offer之Java算法習(xí)題精講字符串操作與數(shù)組及二叉搜索樹
跟著思路走,之后從簡單題入手,反復(fù)去看,做過之后可能會忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質(zhì)的變化2022-03-03Java基礎(chǔ)學(xué)習(xí)之字符緩沖流的應(yīng)用
這篇文章主要為大家詳細(xì)介紹了Java基礎(chǔ)中的字符緩沖流的相關(guān)應(yīng)用,例如復(fù)制Java文件等,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一2022-09-09