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
Two, DesensitizationEnum enumeration class
3. Desensitization custom annotation
Four, DesensitizationSerialize desensitization serializer
6. UserController request layer
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!