引言
在当今数字化时代,数据安全至关重要。随着各类应用系统的广泛使用,大量的用户数据被收集、存储和传输,其中不乏包含敏感信息的数据,如身份证号、手机号、邮箱地址、银行卡号等 。一旦这些敏感信息泄露,将给用户带来极大的风险,可能导致隐私泄露、身份被盗用、财产损失等严重后果。在 Spring Boot 项目中,当我们将包含敏感字段的数据进行序列化并返回给前端或第三方系统时,必须采取有效的措施对这些敏感字段进行脱敏处理,以确保数据的安全性。例如,在一个电商系统中,用户的个人信息(包括手机号、地址等)在展示给商家或其他用户时需要进行脱敏;在一个金融系统中,用户的银行卡号、交易密码等敏感信息在任何情况下都不能以明文形式暴露。接下来,本文将详细介绍在 Spring Boot 中实现敏感字段序列化脱敏的方法。
认识数据脱敏
什么是数据脱敏
数据脱敏,简单来说,就是对敏感数据进行变形处理,使其在不泄露原始数据内容的情况下,仍能保持一定的可用性 。通过特定的脱敏规则和算法,将敏感信息转化为非敏感信息,从而达到保护数据隐私的目的。例如,将身份证号中的部分数字替换为星号,将手机号中间几位隐藏等。数据脱敏并非简单地删除敏感数据,而是在保证数据可用性的前提下,对敏感信息进行隐藏或变形,使数据在开发、测试、分析等场景中能够安全使用 。
为什么需要数据脱敏
- 保护用户隐私:在各类应用中,用户的个人信息如姓名、身份证号、手机号、邮箱等都属于敏感数据。一旦这些数据泄露,用户可能面临骚扰电话、诈骗信息、身份被盗用等风险,给用户的生活和财产安全带来严重威胁。比如,在电商平台上,如果用户的收货地址和手机号被泄露,可能会导致用户收到大量垃圾邮件和骚扰电话。
- 满足合规要求:随着数据安全和隐私保护相关法律法规的不断完善,如《中华人民共和国网络安全法》、《通用数据保护条例》(GDPR)等,企业和组织必须采取有效的措施保护用户的敏感数据,否则将面临巨额罚款和法律责任。例如,GDPR 规定,对于违反数据保护规定的企业,最高可处以全球年营业额 4% 的罚款。
- 防止数据泄露风险:在数据的存储、传输和使用过程中,存在各种潜在的数据泄露风险,如黑客攻击、内部人员违规操作等。对敏感数据进行脱敏处理,可以降低数据泄露带来的危害,即使数据被非法获取,攻击者也难以从脱敏后的数据中获取到有价值的敏感信息 。例如,2017 年 Equifax 公司的数据泄露事件,导致约 1.43 亿美国消费者的个人信息被泄露,包括姓名、社保号码、出生日期、地址等敏感信息,给公司带来了巨大的声誉损失和法律风险。
- 便于数据共享和测试:在与第三方合作或进行数据测试时,为了避免敏感数据的泄露,需要对数据进行脱敏处理。脱敏后的数据既可以满足业务需求,又能保证数据的安全性 。比如,在数据分析项目中,需要使用大量的真实数据进行模型训练,但这些数据中可能包含敏感信息,通过脱敏处理,可以在不影响数据分析结果的前提下,安全地使用数据。
Spring Boot 中实现敏感字段序列化脱敏的原理
Jackson 序列化机制
在 Spring Boot 中,Jackson 是默认的数据序列化和反序列化框架,它在将 Java 对象转换为 JSON 格式的数据时发挥着关键作用。当我们使用@ResponseBody注解返回一个 Java 对象,或者将对象作为 HTTP 响应体发送时,Jackson 会自动介入并完成序列化操作 。
Jackson 的序列化过程可以简单理解为:它首先会通过反射获取 Java 对象的所有属性,然后根据属性的类型和配置,将这些属性转换为对应的 JSON 格式 。例如,对于一个包含String、Integer、Date等基本类型属性的 Java 对象,Jackson 会将String类型的属性直接转换为 JSON 字符串,Integer类型转换为 JSON 数字,而Date类型则会按照默认或指定的日期格式转换为字符串 。对于复杂类型,如对象嵌套、集合等,Jackson 会递归地处理其中的每个元素,将整个对象图转换为层次结构的 JSON 数据 。例如,假设有一个User类,包含name(String类型)、age(Integer类型)和birthDate(Date类型)属性,当使用 Jackson 将User对象序列化为 JSON 时,它会生成类似{"name":"张三","age":30,"birthDate":"2024-10-01"}的字符串(假设日期格式为yyyy-MM-dd) 。
自定义注解和序列化器
为了实现敏感字段的序列化脱敏,我们需要借助自定义注解和自定义序列化器。自定义注解就像是一个标记,我们可以将其添加到 Java 类的字段上,用来告诉系统这个字段是敏感字段,需要进行脱敏处理 。例如,我们可以创建一个@Sensitive注解,当我们在User类的phoneNumber字段上使用@Sensitive注解时,就表示该phoneNumber字段是敏感字段 。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
}
自定义序列化器则是实现脱敏逻辑的核心部分。它是 Jackson 序列化机制的扩展,当 Jackson 遇到被标记为敏感字段的属性时,会调用我们自定义的序列化器来进行处理 。在自定义序列化器中,我们可以根据不同的敏感字段类型(如手机号、身份证号、邮箱等),编写相应的脱敏规则 。例如,对于手机号,我们可以将中间四位替换为星号;对于身份证号,可以隐藏部分数字 。通过这种方式,我们能够在不影响其他正常字段序列化的前提下,对敏感字段进行有效的脱敏处理 ,确保敏感信息在序列化过程中得到保护 。
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class SensitiveSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 这里编写具体的脱敏逻辑,例如手机号脱敏
if (value != null && value.length() > 7) {
value = value.substring(0, 3) + "****" + value.substring(7);
}
gen.writeString(value);
}
}
通过结合自定义注解和自定义序列化器,我们在 Spring Boot 中实现了对敏感字段的序列化脱敏,保障了数据在传输和展示过程中的安全性 。
实战演练
准备工作
- 搭建 Spring Boot 项目:使用 Spring Initializr 快速创建一个 Spring Boot 项目。在 IDEA 中,选择File -> New -> Project,然后在弹出的窗口中选择Spring Initializr,点击Next。在下一步中,填写项目的基本信息,如Group、Artifact等,然后点击Next。在依赖选择界面,勾选Spring Web依赖,该依赖包含了 Web 开发所需的基本组件,如 Tomcat 容器、Jackson(用于处理 JSON 字符串的类库)以及 Spring 和 Spring MVC 的依赖 。完成选择后,点击Finish,项目创建完成。
- 引入相关依赖:在pom.xml文件中添加所需的依赖。除了 Spring Boot Starter Web 依赖外,我们还需要引入 Hutool 工具包,它提供了丰富的工具类,方便我们进行数据脱敏操作 。在pom.xml中添加如下依赖:
<dependencies>
<!-- Spring Boot Starter Web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Hutool工具包依赖 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
</dependencies>
添加完依赖后,Maven 会自动下载并引入这些依赖到项目中。
定义脱敏注解
- 创建自定义脱敏注解:在项目的src/main/java目录下,创建一个新的 Java 类,用于定义脱敏注解。假设我们将注解命名为Sensitive,代码如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
// 脱敏类型,如手机号、身份证号、邮箱等,可根据需求扩展为枚举类型
SensitiveType type() default SensitiveType.DEFAULT;
// 前置不需要脱敏的长度
int prefixNoMaskLen() default 0;
// 后置不需要脱敏的长度
int suffixNoMaskLen() default 0;
// 用于脱敏的字符,默认为星号
String symbol() default "*";
}
// 定义脱敏类型枚举
enum SensitiveType {
DEFAULT,
PHONE,
ID_CARD,
EMAIL
}
- 注解属性解释:
-
- Retention(RetentionPolicy.RUNTIME):表示该注解在运行时仍然有效,可以通过反射获取 。
-
- Target(ElementType.FIELD):表示该注解只能作用于类的字段上 。
-
- type():定义脱敏类型,通过枚举SensitiveType来指定,默认值为DEFAULT,可以根据不同的敏感字段类型进行选择,如PHONE表示手机号,ID_CARD表示身份证号,EMAIL表示邮箱 。
-
- prefixNoMaskLen():指定字段开头不需要脱敏的字符长度,例如对于手机号,设置为 3 表示前三位不脱敏 。
-
- suffixNoMaskLen():指定字段结尾不需要脱敏的字符长度,例如对于手机号,设置为 4 表示后四位不脱敏 。
-
- symbol():指定用于脱敏的字符,默认是星号*,可以根据需求修改为其他字符 。通过这些属性,我们可以灵活地定义不同敏感字段的脱敏规则 。
创建自定义序列化器
- 编写自定义序列化器代码:创建一个实现JsonSerializer接口的自定义序列化器,用于对被@Sensitive注解标记的字段进行脱敏处理 。假设我们将序列化器命名为SensitiveSerializer,代码如下:
import cn.hutool.core.util.DesensitizedUtil;
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 java.io.IOException;
import java.util.Objects;
public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveType type;
private int prefixNoMaskLen;
private int suffixNoMaskLen;
private String symbol;
public SensitiveSerializer() {
}
public SensitiveSerializer(SensitiveType type, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
this.type = type;
this.prefixNoMaskLen = prefixNoMaskLen;
this.suffixNoMaskLen = suffixNoMaskLen;
this.symbol = symbol;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
switch (type) {
case PHONE:
value = DesensitizedUtil.mobilePhone(value);
break;
case ID_CARD:
value = DesensitizedUtil.idCardNum(value, prefixNoMaskLen, suffixNoMaskLen);
break;
case EMAIL:
value = DesensitizedUtil.email(value);
break;
default:
value = DesensitizedUtil.hide(value, prefixNoMaskLen, value.length() - suffixNoMaskLen, symbol);
}
gen.writeString(value);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
if (sensitive == null) {
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
}
if (sensitive != null) {
return new SensitiveSerializer(sensitive.type(), sensitive.prefixNoMaskLen(), sensitive.suffixNoMaskLen(), sensitive.symbol());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
- 关键方法分析:
-
- serialize(String value, JsonGenerator gen, SerializerProvider serializers):该方法是实现脱敏的核心逻辑 。它接收要序列化的值value、JsonGenerator用于生成 JSON 内容以及SerializerProvider提供序列化相关的上下文信息 。在方法中,首先判断值是否为空,为空则直接写入null 。然后根据type属性的值,调用 Hutool 工具包中的不同脱敏方法进行处理,如DesensitizedUtil.mobilePhone用于手机号脱敏,DesensitizedUtil.idCardNum用于身份证号脱敏,DesensitizedUtil.email用于邮箱脱敏 。如果是默认类型,则使用DesensitizedUtil.hide方法,根据指定的前后不脱敏长度和脱敏字符进行脱敏处理 。最后,将脱敏后的值通过gen.writeString方法写入 JSON 。
-
- createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty):该方法用于创建上下文相关的序列化器 。它接收SerializerProvider和BeanProperty,BeanProperty包含了当前被序列化的字段信息 。首先判断beanProperty是否为空以及字段类型是否为String类型 。然后尝试获取字段上的@Sensitive注解,如果获取到注解,则根据注解的属性创建一个新的SensitiveSerializer实例并返回 。如果没有获取到注解,则通过serializerProvider.findValueSerializer方法查找默认的序列化器 。如果beanProperty为空,则通过serializerProvider.findNullValueSerializer方法查找处理null值的序列化器 。通过这两个关键方法,实现了根据自定义注解对敏感字段进行灵活的脱敏序列化处理 。
在实体类中应用注解
- 添加脱敏注解到实体类字段:在需要进行脱敏处理的实体类字段上添加@Sensitive注解 。例如,我们有一个User实体类,包含name、phoneNumber、idCard和email字段,其中phoneNumber、idCard和email是敏感字段,需要进行脱敏处理,代码如下:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
@Data
public class User {
private String name;
@JsonSerialize(using = SensitiveSerializer.class)
@Sensitive(type = SensitiveType.PHONE)
private String phoneNumber;
@JsonSerialize(using = SensitiveSerializer.class)
@Sensitive(type = SensitiveType.ID_CARD, prefixNoMaskLen = 3, suffixNoMaskLen = 4)
private String idCard;
@JsonSerialize(using = SensitiveSerializer.class)
@Sensitive(type = SensitiveType.EMAIL)
private String email;
}
- 注解生效原理:当 Jackson 进行序列化操作时,遇到被@JsonSerialize(using = SensitiveSerializer.class)注解标记的字段,会使用我们自定义的SensitiveSerializer进行序列化 。而@Sensitive注解则为SensitiveSerializer提供了具体的脱敏规则,如脱敏类型、前后不脱敏长度和脱敏字符等 。通过这种方式,实现了对实体类中敏感字段的脱敏处理 。在上述例子中,phoneNumber字段会按照手机号的脱敏规则进行处理,idCard字段会按照身份证号的脱敏规则进行处理,前三位和后四位不脱敏,中间部分用星号替换,email字段会按照邮箱的脱敏规则进行处理 。
测试脱敏效果
- 编写测试代码:创建一个 Controller 类,用于提供接口返回包含敏感字段的User对象 。假设我们将 Controller 命名为UserController,代码如下:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/user")
public User getUser() {
User user = new User();
user.setName("张三");
user.setPhoneNumber("13800138000");
user.setIdCard("11010519491231002X");
user.setEmail("zhangsan@example.com");
return user;
}
}
- 测试结果展示:启动 Spring Boot 项目,访问http://localhost:8080/user接口(假设项目端口为 8080) 。返回的 JSON 数据如下:
{
"name": "张三",
"phoneNumber": "138****8000",
"idCard": "110****1231002X",
"email": "z****@example.com"
}
可以看到,phoneNumber、idCard和email字段已经按照我们设定的脱敏规则进行了脱敏处理,有效保护了敏感信息 。
优化与拓展
统一配置与管理
在实际项目中,随着业务的发展,可能会有越来越多的敏感字段需要进行脱敏处理,而且脱敏规则也可能会根据不同的业务需求和安全要求进行调整。为了便于管理和修改这些脱敏规则,我们可以将它们进行统一配置。
一种常见的做法是使用配置文件(如application.yml或application.properties)来存储脱敏规则 。例如,在application.yml中添加如下配置:
sensitivity:
rules:
phone:
type: phone
prefixNoMaskLen: 3
suffixNoMaskLen: 4
symbol: "*"
idCard:
type: idCard
prefixNoMaskLen: 3
suffixNoMaskLen: 4
symbol: "*"
email:
type: email
prefixNoMaskLen: 1
suffixNoMaskLen: 0
symbol: "*"
然后,通过 Spring 的@ConfigurationProperties注解将这些配置加载到一个 Java 类中 。创建一个SensitivityProperties类:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "sensitivity.rules")
public class SensitivityProperties {
private Map < String, Rule > rules;
public Map < String, Rule > getRules() {
return rules;
}
public void setRules(Map < String, Rule > rules) {
this.rules = rules;
}
public static class Rule {
private String type;
private int prefixNoMaskLen;
private int suffixNoMaskLen;
private String symbol;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getPrefixNoMaskLen() {
return prefixNoMaskLen;
}
public void setPrefixNoMaskLen(int prefixNoMaskLen) {
this.prefixNoMaskLen = prefixNoMaskLen;
}
public int getSuffixNoMaskLen() {
return suffixNoMaskLen;
}
public void setSuffixNoMaskLen(int suffixNoMaskLen) {
this.suffixNoMaskLen = suffixNoMaskLen;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
}
}
在自定义序列化器SensitiveSerializer中,可以注入SensitivityProperties,并根据配置获取相应的脱敏规则 。修改SensitiveSerializer的构造函数和serialize方法:
import cn.hutool.core.util.DesensitizedUtil;
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 org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.Objects;
public class SensitiveSerializer extends JsonSerializer < String > implements ContextualSerializer {
private SensitiveProperties.Rule rule;
@Autowired
private SensitiveProperties sensitiveProperties;
public SensitiveSerializer() {
}
public SensitiveSerializer(SensitiveProperties.Rule rule) {
this.rule = rule;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
switch (rule.getType()) {
case "phone":
value = DesensitizedUtil.mobilePhone(value);
break;
case "idCard":
value = DesensitizedUtil.idCardNum(value, rule.getPrefixNoMaskLen(), rule.getSuffixNoMaskLen());
break;
case "email":
value = DesensitizedUtil.email(value);
break;
default:
value = DesensitizedUtil.hide(value, rule.getPrefixNoMaskLen(), value.length() - rule.getSuffixNoMaskLen(), rule.getSymbol());
}
gen.writeString(value);
}
@Override
public JsonSerializer < ? > createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
if (sensitive == null) {
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
}
if (sensitive != null) {
String fieldName = beanProperty.getName();
SensitiveProperties.Rule rule = sensitiveProperties.getRules().get(fieldName);
if (rule != null) {
return new SensitiveSerializer(rule);
}
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
通过这种方式,我们将脱敏规则集中管理在配置文件中,当需要修改脱敏规则时,只需要在配置文件中进行修改,而不需要修改代码,提高了系统的可维护性和灵活性 。
支持更多脱敏类型
在实际应用中,除了手机号、身份证号和邮箱等常见的敏感信息外,还可能会遇到其他类型的敏感数据,如银行卡号、密码、地址等 。为了支持更多类型的敏感数据脱敏,我们可以扩展自定义序列化器 。
以银行卡号为例,银行卡号通常有 16 位或 19 位,常见的脱敏规则是保留前 6 位和后 4 位,中间部分用星号替换 。我们可以在自定义序列化器SensitiveSerializer的switch语句中添加对银行卡号的处理 。假设我们在SensitiveType枚举中添加一个BANK_CARD类型:
enum SensitiveType {
DEFAULT,
PHONE,
ID_CARD,
EMAIL,
BANK_CARD
}
然后在SensitiveSerializer的serialize方法中添加如下代码:
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
switch (type) {
case PHONE:
value = DesensitizedUtil.mobilePhone(value);
break;
case ID_CARD:
value = DesensitizedUtil.idCardNum(value, prefixNoMaskLen, suffixNoMaskLen);
break;
case EMAIL:
value = DesensitizedUtil.email(value);
break;
case BANK_CARD:
if (value.length() >= 10) {
value = value.substring(0, 6) + "******" + value.substring(value.length() - 4);
}
break;
default:
value = DesensitizedUtil.hide(value, prefixNoMaskLen, value.length() - suffixNoMaskLen, symbol);
}
gen.writeString(value);
}
这样,当我们在实体类的字段上使用@Sensitive(type = SensitiveType.BANK_CARD)注解时,就可以对银行卡号进行脱敏处理了 。对于其他类型的敏感数据,也可以按照类似的方式进行扩展,通过不断丰富自定义序列化器中的脱敏逻辑,使其能够满足各种不同敏感数据的脱敏需求 。
注意事项
性能影响
虽然自定义序列化器为敏感字段脱敏提供了有效的解决方案,但在实际应用中,我们也需要关注它对系统性能的潜在影响。
自定义序列化器在处理敏感字段时,通常需要执行额外的逻辑,如字符串替换、正则表达式匹配等,这些操作会消耗一定的 CPU 和内存资源 。例如,在处理大量包含敏感字段的数据时,频繁的字符串操作可能会导致 CPU 使用率升高,从而影响系统的整体响应速度。在对身份证号进行脱敏时,需要使用正则表达式来匹配和替换部分数字,这个过程会占用一定的计算资源 。如果系统中存在大量的敏感字段需要脱敏,并且这些字段的数据量较大,那么性能问题可能会更加明显 。
为了优化性能,我们可以采取以下措施:
- 缓存脱敏结果:对于一些固定不变的敏感数据,如某些用户的身份证号、手机号等,我们可以在第一次脱敏后将结果缓存起来 。当下次需要序列化相同的数据时,直接从缓存中获取脱敏后的结果,避免重复执行脱敏逻辑,从而提高性能 。可以使用 Spring 的缓存注解@Cacheable来实现缓存功能 。例如:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class SensitiveDataService {
@Cacheable("sensitiveDataCache")
public String desensitizeSensitiveData(String sensitiveData) {
// 执行脱敏逻辑
// ...
return desensitizedData;
}
}
- 优化脱敏算法:尽量选择高效的脱敏算法和数据结构 。例如,在字符串替换操作中,使用StringBuilder或StringBuffer来代替String的直接拼接,因为String是不可变对象,每次拼接都会创建新的对象,而StringBuilder和StringBuffer是可变的,性能更高 。对于复杂的脱敏规则,避免使用过于复杂的正则表达式,因为正则表达式的解析和匹配过程可能会消耗较多的资源 。可以将复杂的脱敏规则拆分成多个简单的步骤,或者使用更高效的算法来实现 。
- 批量处理:如果有多个敏感字段需要脱敏,可以考虑将它们批量处理,而不是逐个处理 。例如,将多个敏感字段封装成一个对象,然后在自定义序列化器中对这个对象进行一次性的脱敏处理,这样可以减少方法调用和对象创建的开销 。假设我们有一个包含多个敏感字段的UserInfo类,可以在SensitiveSerializer中对整个UserInfo对象进行处理:
public class UserInfo {
private String phoneNumber;
private String idCard;
private String email;
// 省略getter和setter方法
}
public class SensitiveSerializer extends JsonSerializer < UserInfo > implements ContextualSerializer {
@Override
public void serialize(UserInfo userInfo, JsonGenerator gen, SerializerProvider serializers) throws IOException {
userInfo.setPhoneNumber(DesensitizedUtil.mobilePhone(userInfo.getPhoneNumber()));
userInfo.setIdCard(DesensitizedUtil.idCardNum(userInfo.getIdCard(), 3, 4));
userInfo.setEmail(DesensitizedUtil.email(userInfo.getEmail()));
gen.writeObject(userInfo);
}
// 省略createContextual方法
}
兼容性问题
在 Spring Boot 项目中使用自定义序列化器实现敏感字段脱敏时,还可能会遇到与其他框架或库的兼容性问题 。
例如,当项目中同时使用了其他依赖于 Jackson 的库时,这些库可能对 Jackson 的配置有自己的要求,这可能会与我们自定义的序列化器产生冲突 。有些库可能会自定义 Jackson 的 ObjectMapper 配置,导致我们注册的自定义序列化器无法生效 。在使用 Spring Data Elasticsearch 时,它可能会对 Jackson 进行一些默认配置,这些配置可能会覆盖我们为敏感字段脱敏所做的自定义配置 。
解决兼容性问题的思路如下:
- 检查依赖冲突:使用mvn dependency:tree命令查看项目的依赖树,检查是否存在版本冲突或重复依赖 。如果发现有冲突的依赖,可以尝试排除不需要的依赖,或者升级到兼容的版本 。例如,如果发现 Jackson 的版本冲突,可以在pom.xml中排除冲突的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
然后重新引入正确版本的 Jackson 依赖 。
2. 配置优先级调整:了解不同框架或库对 Jackson 配置的优先级规则,确保我们的自定义序列化器配置具有较高的优先级 。在 Spring Boot 中,可以通过Jackson2ObjectMapperBuilderCustomizer来定制 ObjectMapper 的配置,并且确保我们的定制在其他库的配置之前生效 。例如:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Autowired
private SensitiveSerializer sensitiveSerializer;
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.serializerByType(String.class, sensitiveSerializer);
// 其他配置
};
}
}
- 使用 SPI 机制:如果某些库不允许直接修改 Jackson 的配置,可以考虑使用 Java 的 SPI(Service Provider Interface)机制 。通过 SPI,我们可以在不影响其他库的前提下,将自定义序列化器注册到 Jackson 中 。在src/main/resources/META-INF/services目录下创建一个名为com.fasterxml.jackson.databind.SerializerProvider的文件,文件内容为自定义序列化器的全限定类名 。例如:
com.example.demo.SensitiveSerializer
这样,Jackson 在启动时会自动加载并使用我们的自定义序列化器 。通过这些方法,可以有效地解决自定义序列化器与其他框架或库之间的兼容性问题,确保敏感字段脱敏功能的正常运行 。
总结与展望
在 Spring Boot 中实现敏感字段序列化脱敏是保障数据安全的重要手段。通过深入理解 Jackson 序列化机制,利用自定义注解和序列化器,我们能够有效地对敏感字段进行脱敏处理,确保敏感信息在序列化过程中不被泄露 。在实战过程中,我们从搭建项目、引入依赖,到定义脱敏注解、创建自定义序列化器,并在实体类中应用注解进行测试,每一步都紧密相连,共同构建了一个完整的脱敏体系 。
同时,我们还对实现方案进行了优化与拓展,包括统一配置与管理脱敏规则,使其更易于维护和修改;支持更多脱敏类型,以满足不同业务场景下的需求 。在实际应用中,也不能忽视性能影响和兼容性问题,需要采取相应的措施进行优化和解决 。
展望未来,随着数据安全重要性的不断提升,数据脱敏技术也将不断发展和完善 。一方面,脱敏算法和技术将更加智能化和高效化,能够自动识别和处理各种复杂的敏感数据 。例如,利用人工智能和机器学习技术,根据数据的特征和使用场景,动态地生成最优的脱敏策略 。另一方面,随着云计算、大数据、物联网等新兴技术的广泛应用,数据脱敏将面临更多的挑战和机遇 。在云计算环境下,如何保证脱敏数据在不同云平台之间的安全性和一致性;在大数据场景中,如何快速处理海量数据的脱敏需求等,都将成为未来研究的重点方向 。
此外,随着数据安全法律法规的不断完善,企业和组织对数据脱敏的合规性要求也将越来越高 。未来的数据脱敏技术不仅要满足技术层面的需求,还要确保符合相关法律法规的规定,为企业和用户提供更加全面的数据安全保护 。相信在技术的不断创新和发展下,数据脱敏技术将在数据安全领域发挥更加重要的作用,为数字化时代的数据安全保驾护航 。