1.背景在项目中有些敏感信息不能直接展示,比如客户手机号、身份证、车牌号等信息,展示时均需要进行数据脱敏,防止泄露客户隐私。脱敏即是对数据的部分信息用脱敏符号(*)处理。
2.目标在服务端返回数据时,利用jackson序列化完成数据脱敏,达到对敏感信息脱敏展示。
降低重复开发量,提升开发效率
形成统一有效的脱敏规则
可基于重写默认脱敏实现的desensitize方法,实现可扩展、可自定义的个性化业务场景的脱敏需求
3.主要实现3.1基于jackson的自定义脱敏序列化实现stdserializer:所有标准序列化程序所使用的基类,这个是编写自定义序列化程序所推荐使用的基类。
contextualserializer: 是jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制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可以获得字段的类型以及注解。当字段拥有自定义注解时,取出注解中的值创建定制的序列化方式,这样在serialize方法中便可以得到这个值了。createcontextual方法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会改变),所以无需关心性能问题。
3.2定义脱敏接口、以及工厂实现3.2.1脱敏器接口定义
package com.jd.ccmp.ctm.constraints.desensitization;/** * 脱敏器 * * @author zhangxiaoxu15 * @date 2022/2/8 10:56 */public interface desensitization<t> { /** * 脱敏实现 * * @param target 脱敏对象 * @return 脱敏返回结果 */ t desensitize(t target);}
3.2.2脱敏器工厂实现
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常用的脱敏器实现3.3.1默认脱敏实现
可基于默认实现,扩展实现个性化场景
package com.jd.ccmp.ctm.constraints.desensitization;/** * 默认脱敏实现 * * @author zhangxiaoxu15 * @date 2022/2/8 11:01 */public interface defaultdesensitization extends desensitization<string> {}
3.3.2手机号脱敏器
实现对手机号中间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实现自定义注解,提高易用性
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)@documentedpublic @interface desensitize { /** * 对象脱敏器实现 */ @suppresswarnings("all") class<? extends desensitization<?>> desensitization();
3.4.1默认脱敏注解
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.*;/** * 默认脱敏注解 * * @author zhangxiaoxu15 * @date 2022/2/8 11:14 */@target({elementtype.field})@retention(retentionpolicy.runtime)@jacksonannotationsinside@desensitize(desensitization = defaultdesensitization.class)@documentedpublic @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)@documentedpublic @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 符号个数 * @param symbol 符号 */ public static string getsymbol(int number, string symbol) { return intstream.range(0, number).maptoobj(i -> symbol).collect(collectors.joining()); }
4.使用样例&执行流程剖析
程序类图
**执行流程剖析**
1.调用jsonutil.tojsonstring()开始执行序列化
2.识别属性mobile上的注解@mobilenodesensitize(上文3.4.2)
3.调用objectdesensitizeserializer#createcontextual(上文3.1 & 3.2),返回jsonserializer
4.调用手机号脱敏实现mobilenodesensitization#desensitize(上文3.3.2)
5.输出脱敏后的序列化结果,{"mobile":"133****5678"}
不难发现核心执行流程是第3步,但是@mobilenodesensitize与objectdesensitizeserializer又是如何联系起来的呢?
尝试梳理下引用链路:@mobilenodesensitize -> @desensitize -> @jsonserialize -> objectdesensitizeserializer
但是,在objectdesensitizeserializer的实现中,我们似乎却没有发现上述链路的直接调用关系
这就不得不说下jackson元注解的概念
//**jackson元注解**//1.提到元注解这个词,大家会想到@target、@retention、@documented、@inherited//2.jackson也以同样的思路设计了@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)@jacksonannotationpublic @interface jacksonannotationsinside{}
正是通过”combo-annotations”(组合注解、捆绑注解)的机制,实现了指示jackson应该使用其拥有的元注释,而不是使用目标注释,从而实现了自定义脱敏实现设计目标。
以上就是java怎么用jackson序列化实现数据脱敏的详细内容。