随着数据安全的重要性日益凸显,保障用户敏感信息的安全已成为开发者必须重视的课题。诸如手机号码、身份证号、银行卡号等关键信息,若以明文形式直接存储于数据库中,一旦发生数据泄露,将可能引发严重的安全后果。
以往常见的处理方式是在每次进行数据插入或查询时手动实现加密与解密操作。然而,这种做法不仅使代码结构变得冗杂,还极易因疏忽导致某些字段未被加密,从而埋下安全隐患。为此,本文介绍一种基于注解机制的自动加解密方案,结合 Spring Boot 与 MyBatis 实现,能够对标注的敏感字段实现自动加密存储与透明化解密读取。
姓名: 张三
手机: nTuVgMWime1:hFGa9as6JHxLT2vG8dpiRmu4wtxDnkTEr/1x
邮箱: mK7pL9xQ2rS8vN3w:jKxL9mN2pQ7rS8vT3wX4yZ6aB8cD1eF2g
身份证: X1Y2Z3A4B5C6D7E8:F9G0H1I2J3K4L5M6N7O8P9Q0R1S2T3U4V
首先定义一个用于标识需要加密字段的注解 Encrypted:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypted {
// 是否支持模糊查询
boolean supportFuzzyQuery() default false;
}
在实体类中使用该注解标记敏感字段,例如:
public class User {
private Long id;
private String username;
@Encrypted // 此字段将自动加密
private String phone;
@Encrypted // 此字段也将自动加密
private String email;
}
加密过程采用 AES-GCM 算法,具备较高的安全性与完整性校验能力。以下是核心加解密工具类的实现:
public class CryptoUtil {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int IV_LENGTH = 12;
public static String encrypt(String plaintext) {
// 生成随机IV
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
// 初始化加密器并执行加密
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
// 合并IV与密文,并进行Base64编码
byte[] encryptedData = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encryptedData, 0, iv.length);
System.arraycopy(ciphertext, 0, encryptedData, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(encryptedData);
}
public static String decrypt(String encryptedText) {
// Base64解码
byte[] encryptedData = Base64.getDecoder().decode(encryptedText);
// 分离IV和密文
byte[] iv = Arrays.copyOfRange(encryptedData, 0, IV_LENGTH);
byte[] ciphertext = Arrays.copyOfRange(encryptedData, IV_LENGTH, encryptedData.length);
// 初始化解密器并执行解密
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext);
}
// 判断是否已加密,防止重复加密
public static boolean isEncrypted(String value) {
return value != null && value.contains(":");
}
}
加密后的数据格式为:Base64(IV):Base64(密文),确保每次加密的随机性与安全性。
整个方案的核心在于 MyBatis 拦截器的实现,它负责在 SQL 执行前后自动识别并处理带有 @Encrypted 注解的字段:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class EncryptionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
// 处理 UPDATE/INSERT 操作:对传入参数进行加密
if ("update".equals(methodName)) {
Object parameter = getParameter(invocation);
if (shouldEncrypt(parameter)) {
encryptFields(parameter);
}
}
// 执行原始SQL操作
Object result = invocation.proceed();
// 处理 SELECT 操作:对查询结果进行解密
if ("query".equals(methodName)) {
decryptResult(result);
}
return result;
}
姓名: 张三
手机: nTuVgMWime1:hFGa9as6JHxLT2vG8dpiRmu4wtxDnkTEr/1x
邮箱: mK7pL9xQ2rS8vN3w:jKxL9mN2pQ7rS8vT3wX4yZ6aB8cD1eF2g
身份证: X1Y2Z3A4B5C6D7E8:F9G0H1I2J3K4L5M6N7O8P9Q0R1S2T3U4V
/**
* 对目标对象中的字段进行加密处理
* 仅处理带有 @Encrypted 注解的字符串类型字段
*/
private void encryptFields(Object obj) {
if (obj == null) return;
// 排除基础类型、Map 及 Collection 类型
if (isBasicType(obj.getClass()) || obj instanceof Map || obj instanceof Collection) {
return;
}
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Encrypted.class)) {
try {
field.setAccessible(true);
Object value = field.get(obj);
// 仅对未加密的字符串进行加密
if (value instanceof String && !isEncrypted((String) value)) {
String encrypted = CryptoUtil.encrypt((String) value);
field.set(obj, encrypted);
}
} catch (Exception e) {
log.error("加密字段失败: {}", field.getName(), e);
}
}
}
}
/**
* 解密查询返回的结果数据
* 支持单个对象和列表类型的解密
*/
private void decryptResult(Object result) {
if (result instanceof List) {
for (Object item : (List<?>) result) {
decryptFields(item);
}
} else if (result != null) {
decryptFields(result);
}
}
/**
* 执行字段级别的解密逻辑
* 与加密过程相反,对已加密的字符串进行还原
*/
private void decryptFields(Object obj) {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Encrypted.class)) {
try {
field.setAccessible(true);
Object value = field.get(obj);
// 确保是已加密的字符串才执行解密
if (value instanceof String && isEncrypted((String) value)) {
String decrypted = CryptoUtil.decrypt((String) value);
field.set(obj, decrypted);
}
} catch (Exception e) {
log.error("解密字段失败: {}", field.getName(), e);
}
}
}
}
}
/**
* 自动配置类:注册加密拦截器使其生效
*/
@Configuration
@Configuration
@ConditionalOnProperty(name = "encryption.enabled", havingValue = "true", matchIfMissing = true)
public class EncryptionAutoConfiguration {
@Bean
public ConfigurationCustomizer encryptionConfigurationCustomizer() {
return configuration -> {
configuration.addInterceptor(new EncryptionInterceptor());
};
}
}
只需在配置文件中添加以下内容即可启用自动加解密功能:
encryption: enabled: true
数据库存储示例
原始明文数据:
用户信息:
- 姓名: 张三
- 手机: 13812345678
- 邮箱: zhangsan@example.com
- 身份证: 110101199001011234
写入数据库后的加密状态:
用户信息:
姓名: 张三
手机: nTuVgMWime1:hFGa9as6JHxLT2vG8dpiRmu4wtxDnkTEr/1x
邮箱: mK7pL9xQ2rS8vN3w:jKxL9mN2pQ7rS8vT3wX4yZ6aB8cD1eF2g
身份证: X1Y2Z3A4B5C6D7E8:F9G0H1I2J3K4L5M6N7O8P9Q0R1S2T3U4V
业务层使用方式
通过注解驱动的拦截机制,业务代码无需处理加解密细节。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 创建用户:敏感字段将由拦截器自动加密
public void createUser(User user) {
user.setPhone("13812345678");
user.setEmail("zhangsan@example.com");
userMapper.insert(user);
}
// 查询用户:读取时字段已自动解密
public User getUser(Long id) {
User user = userMapper.findById(id);
System.out.println(user.getPhone()); // 输出: 13812345678
System.out.println(user.getEmail()); // 输出: zhangsan@example.com
return user;
}
}
加密密钥配置管理
密钥可通过外部化配置注入,提升安全性与灵活性。
@Configuration
public class EncryptionConfig {
@Value("${encryption.key}")
private String encryptionKey;
@Bean
public SecretKey getSecretKey() {
// 支持从环境变量、配置中心或专用密钥服务加载密钥
byte[] keyBytes = Base64.getDecoder().decode(encryptionKey);
return new SecretKeySpec(keyBytes, "AES");
}
}
日志脱敏处理工具
防止敏感信息泄露至日志系统,提供通用遮蔽方法。
public class SensitiveDataLogger {
public String maskPhone(String phone) {
if (phone == null || phone.length() != 11) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
public String maskEmail(String email) {
if (email == null) return email;
int atIndex = email.indexOf("@");
if (atIndex <= 2) return "***" + email.substring(atIndex);
return email.substring(0, 2) + "***" + email.substring(atIndex);
}
}
方案优势
- 使用简便:仅需添加注解,无需额外编码
- 逻辑解耦:业务代码不掺杂加解密逻辑,保持简洁清晰
- 安全合规:采用标准AES等加密算法,保障数据机密性
- 集中管控:统一管理加密策略和密钥体系,便于审计和升级
适用场景
- 用户信息管理系统
- 支付与金融交易系统
- 医疗健康数据平台
- 任何涉及个人隐私或敏感信息存储的应用系统
限制说明
- 对性能极度敏感的高并发系统可能受影响
- 加密字段无法支持模糊查询或范围检索等复杂操作
- 超大规模数据场景下需评估加解密开销
该方案实现成本低,防护效果显著。若系统存在敏感数据保护需求,是一种值得采纳的轻量级解决方案。


雷达卡


京公网安备 11010802022788号







