前言:在实际项目开发中,可能会对一些用户的隐私信息进行脱敏操作,传统的方式很多都是用replace方法进行手动替换,这样会由很多冗余的代码并且后续也不好维护,本期就讲解一下如何在SpringBoot中优雅的通过序列化的方式去实现数据的脱敏操作!
目录
一、导入pom依赖
二、DesensitizationEnum枚举类
三、Desensitization自定义注解
四、DesensitizationSerialize脱敏序列化器
五、User实体类
六、UserController请求层
七、运行测试
八、Gitee源码
九、总结
一、导入pom依赖
完整代码:
<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>
二、DesensitizationEnum枚举类
在DesensitizationSerialize序列化类中,会根据脱敏注解的type值,也就是DesensitizationEnum 中的类型,来判断需要使用哪种脱敏方式。
这边我就简单定义了5个枚举类型:
完整代码:
package com.example.desensitization.constant;
public enum DesensitizationEnum {
/**
* 自定义
*/
CUSTOM_RULE,
/**
* 身份证号码
*/
ID_CARD_NO,
/**
* 电话号码
*/
PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 银行卡号
*/
BANK_CARD_NO,
}
三、Desensitization自定义注解
这个是自定义的注解@Desensitization,用于标注需要进行脱敏的字段。
主要包含以下元注解和属性:
1、@Target(ElementType.FIELD):表示该注解只能用于字段上。
2、@Retention(RetentionPolicy.RUNTIME):表示该注解可以保留到运行时。
3、@JacksonAnnotationsInside:是一个Jackson的元注解,表示该注解可以作为Json序列化的注解。
4、@JsonSerialize:标注使用DesensitizationSerialize来进行序列化。
5、DesensitizationEnum type:需要脱敏的类型,对应枚举中的脱敏类型。
6、int start/end:可选的起始位置和结束位置,对于脱敏类型为字符串时有效。
完整代码:
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;
}
四、DesensitizationSerialize脱敏序列化器
1、继承JsonSerializer<String>JsonSerializer是Jackson的序列化器基类,实现了将对象序列化为JSON的核心方法,这里继承它是为了实现字符串的自定义序列化。
2、实现ContextualSerializer接口ContextualSerializer可以让序列化器基于上下文环境进行定制化,实现这个接口后,可以实现createContextual()方法。
关键代码:
@AllArgsConstructor
@NoArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {
private DesensitizationEnum type;
private Integer start;
private Integer end;
}
自定义序列化器方式可以实现非侵入式的灵活脱敏,对业务代码零侵入,且不依赖Spring等框架,更适合编写独立的应用服务。当然,AOP实现也有其适用场景,可以作为另一种可选方案。
createContextual()方法:
1、Controller的user()方法被调用,构建并返回了一个User对象。
2、开始对User对象进行JSON序列化,会先调用我们定义的DesensitizationSerialize中createContextual()方法,如果这个实体类被createContextual()方法处理过,则以后不会再走该方法,直接走serialize()方法。
3、DesensitizationSerialize会检查当前类字段是否有@Desensitization注解,如果有N个,则根据注解的type、start和end参数创建N个DesensitizationSerialize实例。
关键代码:
@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方法:
JsonGenerator是Jackjson提供的JSON生成器类。在自定义序列化器的serialize()方法中,会传入JsonGenerator实例。serialize()方法需要通过JsonGenerator将脱敏后的字符串写入到结果JSON中。
CharSequenceUtil和DesensitizedUtil都是hutool提供的工具类。
整体流程是:
1、根据注解的参数动态选择脱敏策略。
2、调用对应 Hutool 的脱敏函数处理字符串。
3、将脱敏结果写入JSON。
关键代码:
@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:
}
}
综上,整体的执行逻辑如下:
1、Controller返回一个实体对象。
2、如果实体对象是第一次进行脱敏,则会调用createContextual()方法。
3、获取当前实体对象所有使用Desensitization注解的字符串字段,创建对应的DesensitizationSerialize实例,实现脱敏处理的序列化器。
4、执行serialize()方法中switch的处理逻辑,由JsonGenerator将脱敏后的字符串写入到结果JSON中。
5、返回Json数据。
完整代码:
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);
}
}
五、User实体类
给想要脱敏的字段加上 @Desensitization(type = DesensitizationEnum.枚举类型)注解即可。
完整代码:
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;
}
六、UserController请求层
完整代码:
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;
}
}
七、运行测试
浏览器直接访问:http://localhost:8080/user
可以看到隐私的信息都进行了数据脱敏的处理!
八、Gitee源码
源码地址:SpringBoot中优雅的实现隐私数据脱敏
九、总结
以上就是我对于SpringBoot中如何优雅的实现隐私数据脱敏的完整教程,如有问题,欢迎评论区留言!