作者:京东物流 张晓旭
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、@Inherited2.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应该应用其领有的元正文,而不是应用指标正文,从而实现了自定义脱敏实现设计指标。
5.总结
以上就是利用Jackson序列化实现数据脱敏的全过程,如有此类需要的同学能够借鉴下面的实现办法。