Home  >  Article  >  Java  >  How to use Jackson serialization to achieve data desensitization in Java

How to use Jackson serialization to achieve data desensitization in Java

王林
王林forward
2023-04-18 09:46:10698browse

    1. Background

    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 (*).

    2. Goal

    • 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

    3. Main implementation

    3.1 Custom desensitized serialization implementation based on Jackson

    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 Define desensitization interface and factory implementation

    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 Commonly used desensitizer implementation

    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;

    3.4 Annotation definition

    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 {
    }

    3.5 Define desensitization symbols

    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 {
        /**
         * &#39;*&#39;脱敏符
         */
        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 .Usage sample & execution process analysis

    How to use Jackson serialization to achieve data desensitization in Java

    Program class diagram

    How to use Jackson serialization to achieve data desensitization in Java

    **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 &#39;contains&#39;.
     * 
     * @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!

    Statement:
    This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete