Hibernate如何为ColumnTransformer编程式提供读写能力并外置加密密钥?
实现方案
核心思路
在应用启动阶段优先从第三方密钥服务器拉取AES密钥,再通过动态注入的方式将密钥传入Hibernate的加密/解密逻辑中,全程不需要硬编码或者在本地配置文件存储密钥。
具体实现方案
方案1:自定义Hibernate UserType(更灵活,推荐)
该方案可以完全控制加密解密的SQL生成逻辑,无需依赖注解占位符解析。
- 第一步:封装密钥拉取工具
实现密钥拉取工具类,在应用启动阶段提前从第三方服务器拉取密钥,缓存到静态变量中,拉取失败直接终止启动避免后续异常:public class AesKeyFetcher { private static String aesKey; public static String getAesKey() { return aesKey; } // 应用启动时调用该方法拉取密钥 public static void initKey() { // 调用第三方密钥服务器接口拉取密钥逻辑 aesKey = remoteKeyServer.fetchKey(); if (aesKey == null || aesKey.isEmpty()) { throw new RuntimeException("AES密钥拉取失败,应用启动终止"); } } } - 第二步:实现自定义加密类型
实现Hibernate的UserType接口,在读写数据时自动拼接带密钥的AES函数:public class AesEncryptedType implements UserType<String> { @Override public int[] sqlTypes() { return new int[]{Types.VARBINARY}; // 加密后存储为二进制,也可转为VARCHAR存储 } @Override public Class<String> returnedClass() { return String.class; } @Override public void nullSafeSet(PreparedStatement st, String value, int index, SharedSessionContractImplementor session) throws SQLException { if (value == null) { st.setNull(index, sqlTypes()[0]); return; } // 写操作使用AES_ENCRYPT,传入动态拉取的密钥 st.setString(index, value); session.getJdbcCoordinator().getStatementPreparer().prepareStatement("SELECT AES_ENCRYPT(?, ?)"); // 实际使用时直接在预编译SQL里传密钥参数即可,此处为简化写法 } @Override public String nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException { // 读操作使用AES_DECRYPT,传入动态拉取的密钥 return rs.getString("AES_DECRYPT(" + getColumnName() + ", '" + AesKeyFetcher.getAesKey() + "')"); } // 其余接口方法按默认逻辑实现即可 } - 第三步:实体类字段标注
直接在需要加密的字段上标注自定义类型即可:@Entity @Table(name = "user") public class User { @Id private Long id; @Type(value = AesEncryptedType.class) private String phone; // 需要加密存储的字段 } - 第四步:启动预加载密钥
配置应用启动监听器,在Hibernate初始化之前调用AesKeyFetcher.initKey()完成密钥拉取。
方案2:动态注入配置配合原生@ColumnTransformer
如果不想自定义类型,也可以通过动态注入系统属性的方式实现,无需修改现有注解逻辑:
- 应用启动拉取密钥后,直接将密钥写入系统属性:
// 拉取到密钥后执行 System.setProperty("custom.aes.secret", fetchedAesKey); - 实体类字段直接用占位符写@ColumnTransformer即可:
@ColumnTransformer( read = "AES_DECRYPT(phone, '${custom.aes.secret}')", write = "AES_ENCRYPT(?, '${custom.aes.secret}')" ) private String phone; - 注意要保证密钥拉取逻辑在Hibernate初始化之前执行,避免占位符无法解析。
注意事项
- 密钥拉取请求必须走HTTPS协议,防止传输过程中被窃听
- 密钥不要永久缓存,可设置定期过期重新拉取,降低泄露风险
- 做好日志敏感信息屏蔽,避免密钥被打印到日志文件中
内容的提问来源于stack exchange,提问作者Harshit Vijayvargia
相关产品推荐
相关产品推荐

