Home >Java >javaTutorial >How to use Jackson serialization to achieve data desensitization in Java
Some sensitive information in the project cannot be displayed directly, such as customer mobile phone number, ID card, license plate number and other information. Data desensitization is required to prevent leakage of customer privacy. Desensitization means treating part of the data with desensitization symbols (*).
When the server returns data, use Jackson serialization to complete data desensitization and achieve desensitized display of sensitive information.
Reduce the amount of repeated development and improve development efficiency
Form unified and effective desensitization rules
It can be based on the desensitize method of overriding the default desensitization implementation to realize the desensitization requirements of scalable and customizable personalized business scenarios
StdSerializer: The base class used by all standard serializers. This is the recommended base for writing custom serializers. kind.
ContextualSerializer: is another serialization-related interface provided by Jackson. Its function is to customize JsonSerializer through the context information known by the field.
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); }
Note: createContextual can obtain the field type and annotations. When a field has a custom annotation, take the value in the annotation and create a customized serialization method, so that the value can be obtained in the serialize method. The createContextual method will only be called the first time a field is serialized (because the context information of the field will not change during runtime), so there is no need to worry about performance issues.
3.2.1 Desensitization interface definition
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 Desensitization Device factory implementation
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.1Default desensitization implementation
can be based on the default implementation, Expand the implementation of personalized scenarios
package com.jd.ccmp.ctm.constraints.desensitization; /** * 默认脱敏实现 * * @author zhangxiaoxu15 * @date 2022/2/8 11:01 */ public interface DefaultDesensitization extends Desensitization<String> { }
3.3.2 Mobile phone number desensitizer
Achieve desensitization of the middle 4 digits of the mobile phone number
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;
Implement custom annotations through @JacksonAnnotationsInside to improve ease of use
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 { /** * 对象脱敏器实现 */ @SuppressWarnings("all") Class<? extends Desensitization<?>> desensitization();
3.4.1 Default desensitization annotation
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) @Documented public @interface DefaultDesensitize {
3.4.2 Mobile phone number desensitization annotation
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 { }
supports specified desensitization symbols, such as * or ^_^
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()); }
Program class diagram
**Execution Process Analysis**
1. Call JsonUtil.toJsonString() to start serialization
2. Identify the annotation @MobileNoDesensitize (3.4.2 above) on the attribute mobile
3. Call ObjectDesensitizeSerializer#createContextual (3.1 & 3.2 above), return JsonSerializer
4. Call the mobile phone number to desensitize MobileNoDesensitization#desensitize (3.3.2 above)
5. Output The serialization result after desensitization, {"mobile":"133****5678"}
It is not difficult to find that the core execution process is step 3, but how are @MobileNoDesensitize and ObjectDesensitizeSerializer connected? Woolen cloth?
Try to sort out the reference link: @MobileNoDesensitize -> @Desensitize -> @JsonSerialize -> ObjectDesensitizeSerializer
However, in In the implementation of ObjectDesensitizeSerializer, we seem to have not found the direct calling relationship of the above link
This has to talk about the concept of Jackson meta-annotation
//**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) @JacksonAnnotation public @interface JacksonAnnotationsInside { }
It is through the mechanism of "combo-annotations" (combined annotations, bundled annotations) that it is possible to instruct Jackson to use its own meta-annotations instead of using target annotations, thereby achieving custom desensitization to achieve design goals.
The above is the detailed content of How to use Jackson serialization to achieve data desensitization in Java. For more information, please follow other related articles on the PHP Chinese website!