Elegant implementation of privacy data desensitization in SpringBoot (Gitee source code provided)

Foreword: In actual project development, some users' private information may be desensitized. Many traditional methods use the replace method to manually replace, which will result in a lot of redundant code and is not easy to maintain in the future. This article In this period, I will explain how to achieve desensitization of data elegantly through serialization in SpringBoot!

Table of contents

1. Import pom dependencies

Two, DesensitizationEnum enumeration class

3. Desensitization custom annotation

Four, DesensitizationSerialize desensitization serializer

Five, User entity class

6. UserController request layer

Seven, run the test

8. Gitee source code

Nine. Summary


1. Import pom dependencies

Full code:

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
    </dependencies>

Two, DesensitizationEnum enumeration class

In the DesensitizationSerialize serialization class, it will determine which desensitization method needs to be used according to the type value of the desensitization annotation, that is, the type in DesensitizationEnum.

Here I simply define 5 enumeration types:

Full code:

package com.example.desensitization.constant;

public enum DesensitizationEnum {

    /**
     * 自定义
     */
    CUSTOM_RULE,
    
    /**
     * 身份证号码
     */
    ID_CARD_NO,

    /**
     * 电话号码
     */
    PHONE,

    /**
     * 地址
     */
    ADDRESS,

    /**
     * 银行卡号
     */
    BANK_CARD_NO,
}

3. Desensitization custom annotation

This is a custom annotation @Desensitization, which is used to mark fields that need to be desensitized.

It mainly contains the following meta annotations and attributes :

1. @Target(ElementType.FIELD): Indicates that this annotation can only be used on fields.

2. @Retention(RetentionPolicy.RUNTIME): Indicates that the annotation can be retained until runtime.

3. @JacksonAnnotationsInside: It is a Jackson meta-annotation, indicating that the annotation can be used as a Json serialization annotation.

4. @JsonSerialize: The annotation uses DesensitizationSerialize for serialization.

5. DesensitizationEnum type: The type that needs desensitization, corresponding to the desensitization type in the enumeration.

6. int start/end: ​​optional start position and end position, valid when the desensitization type is a string.

Full code:

package com.example.desensitization.annotation;

import com.example.desensitization.constant.DesensitizationEnum;
import com.example.desensitization.serialize.DesensitizationSerialize;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {

    DesensitizationEnum type();

    int start() default 0;

    int end() default 0;

}

Four, DesensitizationSerialize desensitization serializer

1. Inherit JsonSerializer<String> JsonSerializer is the serializer base class of Jackson, which implements the core method of serializing objects into JSON. Here it is inherited to realize custom serialization of strings.

2. Implement the ContextualSerializer interface ContextualSerializer allows the serializer to be customized based on the context. After implementing this interface, the createContextual() method can be implemented.

key code:

@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {

    private DesensitizationEnum type;

    private Integer start;

    private Integer end;
}

The custom serializer method can achieve non-intrusive and flexible desensitization, zero intrusion to business code, and does not depend on frameworks such as Spring, which is more suitable for writing independent application services. Of course, AOP implementation also has its applicable scenarios, which can be used as another alternative.

createContextual() method:

1. The user() method of the Controller is called, and a User object is constructed and returned.

2. To start JSON serialization of the User object, the createContextual() method in the DesensitizationSerialize we defined will be called first. If this entity class has been processed by the createContextual() method, this method will not be used in the future, and serialize() will be used directly. method.

3. DesensitizationSerialize will check whether the current class field has @Desensitization annotation, if there are N, create N DesensitizationSerialize instances according to the type, start and end parameters of the annotation.

key code:

@Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)
            throws JsonMappingException {
        if (beanProperty != null) {
            // 获取当前正在处理的字段的类型,判断如果是 String 类型则进行后续脱敏逻辑处理
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                // 通过 beanProperty 获取在字段上标注的 @Desensitization 注解
                Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
                // 如果没有就尝试获取类注解
                if (desensitization == null) {
                    desensitization = beanProperty.getContextAnnotation(Desensitization.class);
                }
                // 不为null
                if (desensitization != null) {
                    // 如果获取到了注解,则根据注解的 type、start 和 end 参数创建 DesensitizationSerialize 实例,这是脱敏处理的序列化器
                    return new DesensitizationSerialize(desensitization.type(), desensitization.start(),
                            desensitization.end());
                }
            }
            // 直接返回默认的序列化器
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        // 直接返回默认的序列化器
        return serializerProvider.findNullValueSerializer(null);
    }

serialize method: 

JsonGenerator is a JSON generator class provided by Jackjson. In the serialize() method of the custom serializer, a JsonGenerator instance is passed in. The serialize() method needs to write the desensitized string into the result JSON through JsonGenerator.

Both CharSequenceUtil and DesensitizedUtil are tool classes provided by hutool.

The overall process is:

1. Dynamically select the desensitization strategy according to the parameters of the annotation.

2. Call the desensitization function corresponding to Hutool to process the string.

3. Write the desensitization result into JSON.

key code:

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException {
        switch (type){
            //自定义
            case CUSTOM_RULE:
                jsonGenerator.writeString(CharSequenceUtil.hide(s, start, end));
                break;
            //身份证
            case ID_CARD_NO:
                jsonGenerator.writeString(DesensitizedUtil.idCardNum(s, 2, 6));
                break;
            //手机号
            case PHONE:
                jsonGenerator.writeString(DesensitizedUtil.mobilePhone(s));
                break;
            //地址
            case ADDRESS:
                jsonGenerator.writeString(DesensitizedUtil.address(s, 2));
                break;
            // 银行卡脱敏
            case BANK_CARD_NO:
                jsonGenerator.writeString(DesensitizedUtil.bankCard(s));
                break;
            default:
        }
    }

In summary, the overall execution logic is as follows:

1. Controller returns an entity object.

2. If the entity object is desensitized for the first time, the createContextual() method will be called.

3. Get all the string fields annotated with Desensitization of the current entity object, create the corresponding DesensitizationSerialize instance, and realize the desensitization serializer.

4. Execute the processing logic of the switch in the serialize() method, and JsonGenerator writes the desensitized string into the result JSON.

5. Return Json data.

Full code:

package com.example.desensitization.serialize;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.example.desensitization.annotation.Desensitization;
import com.example.desensitization.constant.DesensitizationEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Objects;

@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {

    private DesensitizationEnum type;

    private Integer start;

    private Integer end;

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException {
        switch (type){
            //自定义
            case CUSTOM_RULE:
                jsonGenerator.writeString(CharSequenceUtil.hide(s, start, end));
                break;
            //身份证
            case ID_CARD_NO:
                jsonGenerator.writeString(DesensitizedUtil.idCardNum(s, 2, 6));
                break;
            //手机号
            case PHONE:
                jsonGenerator.writeString(DesensitizedUtil.mobilePhone(s));
                break;
            //地址
            case ADDRESS:
                jsonGenerator.writeString(DesensitizedUtil.address(s, 2));
                break;
            // 银行卡脱敏
            case BANK_CARD_NO:
                jsonGenerator.writeString(DesensitizedUtil.bankCard(s));
                break;
            default:
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty)
            throws JsonMappingException {
        if (beanProperty != null) {
            // 获取当前正在处理的字段的类型,判断如果是 String 类型则进行后续脱敏逻辑处理
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                // 通过 beanProperty 获取在字段上标注的 @Desensitization 注解
                Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
                // 如果没有就尝试获取类注解
                if (desensitization == null) {
                    desensitization = beanProperty.getContextAnnotation(Desensitization.class);
                }
                // 不为null
                if (desensitization != null) {
                    // 如果获取到了注解,则根据注解的 type、start 和 end 参数创建 DesensitizationSerialize 实例,这是脱敏处理的序列化器
                    return new DesensitizationSerialize(desensitization.type(), desensitization.start(),
                            desensitization.end());
                }
            }
            // 直接返回默认的序列化器
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        // 直接返回默认的序列化器
        return serializerProvider.findNullValueSerializer(null);
    }
}

Five, User entity class

Just add @Desensitization(type = DesensitizationEnum. enumeration type) annotation to the field you want to desensitize.

Full code:

package com.example.desensitization.domain;

import com.example.desensitization.annotation.Desensitization;
import com.example.desensitization.constant.DesensitizationEnum;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class User {

    /**
     * 主键
     */
    private String id;

    /**
     * 用户名
     */
    private String name;

    /**
     * 身份证号码
     */
    @Desensitization(type = DesensitizationEnum.ID_CARD_NO)
    private String idCardNo;

    /**
     * 电话号码
     */
    @Desensitization(type = DesensitizationEnum.PHONE)
    private String phone;

    /**
     * 地址
     */
    @Desensitization(type = DesensitizationEnum.CUSTOM_RULE,start = 2,end = 5)
    private String address;

    /**
     * 银行卡号
     */
    @Desensitization(type = DesensitizationEnum.BANK_CARD_NO)
    private String bankCardNo;

}

6. UserController request layer

Full code:

package com.example.desensitization.controller;

import com.example.desensitization.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@RequestMapping
public class UserController {

    @GetMapping("/user")
    public User user(){
        User user = User.builder()
                .id(UUID.randomUUID().toString())
                .name("张三").
                idCardNo("32089809285012823").
                phone("13919819285").
                bankCardNo("62427292012731238812").
                address("江苏省南通市").build();
        return user;
    }

}

Seven, run the test

Browser direct access: http://localhost:8080/user

You can see that the private information has been desensitized!

8. Gitee source code

Source code address: Elegant implementation of privacy data desensitization in SpringBoot

Nine. Summary

The above is my complete tutorial on how to elegantly desensitize private data in SpringBoot. If you have any questions, please leave a message in the comment area! 

Guess you like

Origin blog.csdn.net/HJW_233/article/details/132307837