在 Spring Boot 结合 MyBatis 的项目中,实现字段脱敏(如手机号、身份证号、银行卡号等敏感信息的部分隐藏)可以通过以下方案实现。以下是分步说明和完整代码示例:
一、实现方案选择
1. 方案一:自定义注解 + Jackson 序列化脱敏
- 适用场景:数据返回给前端时动态脱敏,数据库存储原始数据。
- 优点:无侵入性,通过注解灵活控制脱敏字段,与业务逻辑解耦。
- 核心实现 :利用 Jackson 的
JsonSerializer
自定义序列化逻辑。
2. 方案二:MyBatis 类型处理器(TypeHandler)
- 适用场景:数据库存储时加密或脱敏,查询时解密。
- 优点:数据存储层直接处理,安全性高。
- 缺点:需处理加解密逻辑,可能影响查询性能。
3. 方案三:AOP 拦截 Service 层返回结果
- 适用场景:在业务层统一处理敏感字段。
- 优点:集中控制脱敏逻辑。
- 缺点:需遍历对象树,性能开销较大。
二、推荐方案一:Jackson 动态脱敏(完整代码)
1. 定义脱敏策略注解
java
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 {
/**
* 脱敏策略类型
*/
SensitiveStrategy strategy();
}
2. 定义脱敏策略枚举
java
public enum SensitiveStrategy {
/**
* 手机号脱敏(保留前3后4位)
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* 身份证脱敏(保留前1后4位)
*/
ID_CARD(s -> s.replaceAll("(\\d{1})\\d{13}(\\d{4})", "$1*************$2")),
/**
* 银行卡脱敏(保留前6后4位)
*/
BANK_CARD(s -> s.replaceAll("(\\d{6})\\d{9}(\\d{4})", "$1*********$2"));
private final Function<String, String> desensitizer;
SensitiveStrategy(Function<String, String> desensitizer) {
this.desensitizer = desensitizer;
}
public Function<String, String> getDesensitizer() {
return desensitizer;
}
}
3. 自定义 Jackson 序列化器
java
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 java.io.IOException;
public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveStrategy strategy;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (strategy != null && value != null) {
// 应用脱敏策略
String desensitizedValue = strategy.getDesensitizer().apply(value);
gen.writeString(desensitizedValue);
} else {
gen.writeString(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (annotation != null && property.getType().getRawClass() == String.class) {
SensitiveSerializer serializer = new SensitiveSerializer();
serializer.strategy = annotation.strategy();
return serializer;
}
return prov.findNullValueSerializer(property);
}
}
4. 在实体类中标记脱敏字段
java
public class User {
private String name;
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phone;
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard;
// Getters and Setters
}
5. 注册自定义序列化器到 Jackson
java
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public SimpleModule sensitiveModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new SensitiveSerializer());
return module;
}
}
三、测试结果
请求返回示例
json
{
"name": "张三",
"phone": "138****5678",
"idCard": "3***************1234"
}
四、方案二补充:MyBatis TypeHandler 脱敏(数据库存储加密)
1. 实现 TypeHandler
java
@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class EncryptTypeHandler implements TypeHandler<String> {
private static final String KEY = "your-secret-key-123";
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
// 存储时加密
ps.setString(i, encrypt(parameter));
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
// 查询时解密
return decrypt(rs.getString(columnName));
}
private String encrypt(String data) {
// 实现加密逻辑(如 AES)
return "encrypted_" + data;
}
private String decrypt(String data) {
// 实现解密逻辑
return data.replace("encrypted_", "");
}
}
2. 在字段上指定 TypeHandler
java
public class User {
@TableField(typeHandler = EncryptTypeHandler.class)
private String bankCard;
}
五、方案选择建议
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Jackson 动态脱敏 | 响应数据脱敏 | 无侵入性,灵活配置 | 不适用于非 JSON 输出场景 |
MyBatis TypeHandler | 数据库加密存储 | 数据存储安全 | 加解密影响查询性能 |
AOP 拦截 | 业务层统一处理 | 集中控制逻辑 | 性能开销较大 |
六、扩展优化
-
动态开关脱敏
通过
@Sensitive
注解增加enable
属性,结合配置中心动态控制是否脱敏:java@Sensitive(strategy = SensitiveStrategy.PHONE, enable = "${sensitive.enable:true}")
-
嵌套对象脱敏
自定义序列化器支持嵌套对象处理:
javapublic class User { @Sensitive(strategy = SensitiveStrategy.PHONE) private String phone; @Valid // 支持嵌套对象 private Address address; }
-
日志脱敏
结合 Logback 或 Log4j2 的
Converter
实现日志输出时的脱敏。
通过上述方案,可以灵活、安全地实现敏感字段的脱敏处理,根据实际需求选择合适的实现方式。