1. 为什么我们需要数据脱敏?
在开发企业级应用时,数据安全始终是重中之重。最近我接手的一个金融项目就遇到了典型场景:系统需要存储用户的身份证号、银行卡号等敏感信息,但直接明文存储显然不符合安全规范。经过技术调研,最终采用了Spring Boot + Jasypt + MyBatis-Plus的组合方案,实现了字段级别的数据脱敏和加密。
这套方案的核心价值在于:
- 存储加密:敏感字段入库前自动加密
- 查询脱敏:数据返回前端时自动隐藏关键部分
- 开发透明:业务代码几乎无需修改
- 性能可控:加解密过程对系统影响极小
重要提示:数据脱敏不是简单的字符串替换,需要考虑加密强度、性能开销、查询需求等多方面因素。下面我会详细拆解每个环节的实现细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Jasypt加密方案深度解析
2.1 Jasypt的工作原理
Jasypt(Java Simplified Encryption)是一个轻量级的加密库,它通过标准的加密算法(如PBEWithMD5AndDES、AES等)实现数据加密。其核心优势是:
- 提供统一的加密接口
- 支持配置化密钥管理
- 与Spring生态无缝集成
加密流程示例:
java复制StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("mySecretKey");
String encrypted = encryptor.encrypt("敏感数据");
2.2 Spring Boot集成实践
在Spring Boot项目中集成Jasypt只需三步:
- 添加依赖:
xml复制<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
- 配置加密密钥(推荐使用环境变量):
properties复制jasypt.encryptor.password=${JASYPT_PASSWORD}
- 在application.properties中使用加密值:
properties复制db.password=ENC(加密后的字符串)
避坑指南:千万不要把密钥硬编码在代码中!建议通过启动参数或K8s Secret注入。
3. MyBatis-Plus脱敏组件设计
3.1 脱敏处理器实现
MyBatis-Plus的TypeHandler机制非常适合实现字段脱敏。以下是核心实现类:
java复制public class SensitiveTypeHandler extends BaseTypeHandler<String> {
private static final String MASK = "****";
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) {
// 入库加密
ps.setString(i, EncryptUtils.encrypt(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) {
// 出库脱敏
String original = EncryptUtils.decrypt(rs.getString(columnName));
return StringUtils.overlay(original, MASK, 3, original.length()-4);
}
}
3.2 字段级注解配置
通过自定义注解实现细粒度控制:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@TypeHandler(SensitiveTypeHandler.class)
public @interface SensitiveField {
SensitiveType value() default SensitiveType.DEFAULT;
}
public enum SensitiveType {
ID_CARD, // 身份证
BANK_CARD, // 银行卡
PHONE // 手机号
}
实体类使用示例:
java复制public class User {
@SensitiveField(SensitiveType.ID_CARD)
private String idCard;
@SensitiveField(SensitiveType.PHONE)
private String mobile;
}
4. 实战中的性能优化
4.1 加密算法选型对比
| 算法类型 | 安全性 | 性能 | 适用场景 |
|---|---|---|---|
| AES-256 | 高 | 中 | 金融级数据 |
| PBEWithMD5AndDES | 中 | 高 | 一般敏感数据 |
| SM4 | 高 | 低 | 国密要求场景 |
4.2 缓存策略设计
频繁加解密会带来性能开销,我们采用Guava Cache实现解密缓存:
java复制LoadingCache<String, String> decryptCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
public String load(String encrypted) {
return encryptor.decrypt(encrypted);
}
});
缓存命中率监控建议:
java复制CacheStats stats = decryptCache.stats();
double hitRate = stats.hitRate();
5. 生产环境部署要点
5.1 密钥轮换方案
密钥需要定期更换,我们设计的轮换流程:
- 新密钥加密存量数据(后台任务)
- 双密钥并行运行1周
- 完全切换到新密钥
关键代码:
java复制public void rotateKey(String newKey) {
List<User> users = userMapper.selectList(null);
users.forEach(user -> {
String reEncrypted = reEncrypt(user.getIdCard(), newKey);
user.setIdCard(reEncrypted);
userMapper.updateById(user);
});
}
5.2 安全审计日志
所有敏感数据访问需要记录审计日志:
java复制@Aspect
@Component
public class SensitiveAccessAspect {
@AfterReturning(
pointcut="@annotation(com.example.SensitiveOperation)",
returning="result")
public void logAccess(JoinPoint jp, Object result) {
AuditLog log = new AuditLog();
log.setOperation(jp.getSignature().getName());
log.setSensitiveFields(extractSensitiveData(result));
auditLogRepository.save(log);
}
}
6. 常见问题排查指南
6.1 加密数据无法查询
问题现象:WHERE条件包含加密字段时查询失败
解决方案:
- 使用内存过滤:
java复制List<User> users = userMapper.selectList(null)
.stream()
.filter(u -> u.getIdCard().equals(encryptedInput))
.collect(Collectors.toList());
- 或使用SQL函数:
xml复制<select id="selectByEncryptedIdCard" resultType="User">
SELECT * FROM user
WHERE id_card = ENCRYPT_FUNC(#{idCard})
</select>
6.2 加解密性能瓶颈
优化方向:
- 启用Jasypt的pooling配置:
properties复制jasypt.encryptor.pool-size=4
- 考虑硬件加速:
java复制encryptor.setProvider(new SunJCE());
7. 扩展应用场景
7.1 接口返回值处理
通过Jackson的JsonSerializer实现自动脱敏:
java复制public class SensitiveSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider provider) {
gen.writeString(DesensitizationUtils.mobilePhone(value));
}
}
注册到字段:
java复制@JsonSerialize(using = SensitiveSerializer.class)
private String mobile;
7.2 多租户隔离方案
结合MyBatis-Plus的多租户功能,实现租户级密钥管理:
java复制public class TenantKeyResolver {
public static String getCurrentTenantKey() {
String tenantId = TenantContext.getCurrentTenant();
return keyService.getKeyByTenant(tenantId);
}
}
在加密器中动态设置:
java复制encryptor.setPassword(TenantKeyResolver.getCurrentTenantKey());
这套方案在我们多个生产系统中稳定运行超过2年,期间经历了等保三级测评的严格检验。最大的收获是:安全方案必须从一开始就纳入设计,后期补救的成本往往呈指数级增长。对于金融、医疗等敏感行业,建议每季度进行一次密钥轮换,并配合完整的审计日志体系。
