You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 02:57:02