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

如何让Spring JPA使用自定义ObjectMapper实现字段加密解密?

问题描述

我正在使用Spring Data JPA Repositories结合Hibernate,希望调用JpaRepository方法时触发自定义序列化/反序列化器。需求是:带有@Encrypted注解的实体属性,保存时自动加密存入数据库,查询时自动解密。我已经在客户端-服务端通信中实现了该功能,不想重复添加@JsonSerialize/@JsonDeserialize注解,想复用现有ObjectMapper配置(如下代码),请问如何让Spring JPA强制使用我的ObjectMapper?

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JsonEncryptionConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        ClientServerEncryptionService clientServerEncryptionService = new ClientServerEncryptionService(mapper);
        DbEncryptionService serverDbEncryptionService = new DbEncryptionService(mapper);

        SimpleModule cryptoModule = new SimpleModule() {
            @Override
            public void setupModule(SetupContext context) {
                context.addBeanSerializerModifier(new EncryptedJsonSerializer.Modifier(clientServerEncryptionService, serverDbEncryptionService));
                context.addBeanDeserializerModifier(new EncryptedJsonDeserializer.Modifier(clientServerEncryptionService, serverDbEncryptionService));
            }
        };

        mapper.registerModule(cryptoModule);

        return mapper;
    }

}
解决方案

要让Spring JPA(底层依赖Hibernate)复用你已配置的ObjectMapper处理实体属性的加密解密,核心是让Hibernate在持久化/加载数据时调用你的ObjectMapper逻辑,以下是两种可行方案:

方案一:自定义Hibernate AttributeConverter

通过实现Hibernate的AttributeConverter接口,在属性转换逻辑中直接注入并复用你的ObjectMapper,自动对带@Encrypted注解的属性进行加密解密:

  1. 实现转换器,注入Spring容器中的ObjectMapper:
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.ObjectMapper;

@Converter(autoApply = false)
public class EncryptedAttributeConverter implements AttributeConverter<Object, String> {

    private final ObjectMapper objectMapper;

    @Autowired
    public EncryptedAttributeConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public String convertToDatabaseColumn(Object attribute) {
        try {
            // 触发ObjectMapper中配置的加密序列化逻辑
            return objectMapper.writeValueAsString(attribute);
        } catch (Exception e) {
            throw new RuntimeException("加密属性失败", e);
        }
    }

    @Override
    public Object convertToEntityAttribute(String dbData) {
        try {
            // 触发ObjectMapper中配置的解密反序列化逻辑
            return objectMapper.readValue(dbData, Object.class);
        } catch (Exception e) {
            throw new RuntimeException("解密属性失败", e);
        }
    }
}
  1. 在实体属性上关联该转换器:
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import your.package.EncryptedAttributeConverter;

public class YourEntity {

    @Encrypted
    @Convert(converter = EncryptedAttributeConverter.class)
    @Column(columnDefinition = "TEXT")
    private String sensitiveData;

    // 其他属性与方法
}

如果想让所有带@Encrypted注解的属性自动应用转换器,可以将@Converter的autoApply设为true,再配合自定义注解的匹配逻辑,不过手动指定转换器更稳妥。

方案二:自定义Hibernate Type

通过扩展Hibernate的AbstractSingleColumnStandardBasicType,将ObjectMapper整合到Hibernate的类型系统中,灵活性更高:

  1. 自定义Hibernate类型:
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.java.StringJavaType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EncryptedJsonType extends AbstractSingleColumnStandardBasicType<Object> {

    public EncryptedJsonType(ObjectMapper objectMapper) {
        super(VarcharJdbcType.INSTANCE, new EncryptedJsonJavaType(objectMapper));
    }

    @Override
    public String getName() {
        return "encrypted_json";
    }
}

// 自定义Java类型描述器,复用ObjectMapper
class EncryptedJsonJavaType extends StringJavaType {

    private final ObjectMapper objectMapper;

    public EncryptedJsonJavaType(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Object fromString(CharSequence string) {
        try {
            return objectMapper.readValue(string.toString(), Object.class);
        } catch (Exception e) {
            throw new RuntimeException("解密值失败", e);
        }
    }

    @Override
    public String toString(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (Exception e) {
            throw new RuntimeException("加密值失败", e);
        }
    }
}
  1. 注册自定义类型到Hibernate:
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class HibernateConfig {

    private final ObjectMapper objectMapper;

    public HibernateConfig(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("your.entity.package");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        Map<String, Object> properties = new HashMap<>();
        // 注册自定义类型
        properties.put("hibernate.types.encrypted_json", your.package.EncryptedJsonType.class.getName());
        em.setJpaPropertyMap(properties);

        return em;
    }
}
  1. 在实体属性上指定自定义类型:
import org.hibernate.annotations.Type;
import your.package.EncryptedJsonType;

public class YourEntity {

    @Encrypted
    @Type(type = "encrypted_json")
    @Column(columnDefinition = "TEXT")
    private String sensitiveData;

    // 其他属性与方法
}

关键注意事项

  • 注意区分客户端-服务端和数据库场景的加密逻辑:你的ObjectMapper中同时注入了ClientServerEncryptionService和DbEncryptionService,要确保在数据库转换时使用的是serverDbEncryptionService对应的逻辑,可调整模块逻辑或在转换器中直接调用该服务的方法,避免和客户端通信逻辑冲突。
  • 数据库字段建议使用TEXT或足够长度的VARCHAR,避免加密后的字符串超出存储限制。
  • 异常处理要适配业务需求,避免泄露敏感信息。

内容的提问来源于stack exchange,提问作者Tomáš Přitrský

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 10:06:22