我想知道Hibernate是否有办法执行ColumnTransformer的编程配置?

时间:2013-10-23 12:08:02

标签: hibernate

我目前有一个这样的字段注释:

ColumnTransformer(
          read="AES_DECRYPT(C_first_name, 'yourkey')",
          write="AES_ENCRYPT(?, 'yourkey')")
public String getFirstName() {
   return firstName;
}

这适用于Mysql数据库,但我需要这个配置是可选的,因为我们的应用程序可以根据启动参数使用另一个数据库(HsqlDB)。所以我需要的是只有在使用特定的启动参数时才使用ColumnTransformer(并且没有用于HsqlDB的ColumnTransformer,它不能使用“AES_ENCRYPT”)

有人可以帮我这个吗?

6 个答案:

答案 0 :(得分:5)

我有同样的问题,我希望密钥可配置。我在这个项目中找到的唯一解决方案是在运行时更新注释值。是的,我知道这听起来很糟糕,但据我所知,别无他法。

实体类:

@Entity
@Table(name = "user")
public class User implements Serializable {
    @Column(name = "password")
    @ColumnTransformer(read = "AES_DECRYPT(password, '${encryption.key}')", write = "AES_ENCRYPT(?, '${encryption.key}')")
    private String password;
}

我实现了将$ {encryption.key}替换为其他值的类(在我的情况下从Spring应用程序上下文加载)

import org.hibernate.annotations.ColumnTransformer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Map;

import javax.annotation.PostConstruct;

@Component(value = "transformerColumnKeyLoader")
public class TransformerColumnKeyLoader {

    public static final String KEY_ANNOTATION_PROPERTY = "${encryption.key}"; 

    @Value(value = "${secret.key}")
    private String key;

    @PostConstruct
    public void postConstruct() {
        setKey(User.class, "password");
    }

    private void setKey(Class<?> clazz, String columnName) {
        try {
            Field field = clazz.getDeclaredField(columnName);

            ColumnTransformer columnTransformer = field.getDeclaredAnnotation(ColumnTransformer.class);
            updateAnnotationValue(columnTransformer, "read");
            updateAnnotationValue(columnTransformer, "write");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new RuntimeException(
                    String.format("Encryption key cannot be loaded into %s,%s", clazz.getName(), columnName));
        }
    }

    @SuppressWarnings("unchecked")
    private void updateAnnotationValue(Annotation annotation, String annotationProperty) {
        Object handler = Proxy.getInvocationHandler(annotation);
        Field merberValuesField;
        try {
            merberValuesField = handler.getClass().getDeclaredField("memberValues");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }
        merberValuesField.setAccessible(true);
        Map<String, Object> memberValues;
        try {
            memberValues = (Map<String, Object>) merberValuesField.get(handler);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
        Object oldValue = memberValues.get(annotationProperty);
        if (oldValue == null || oldValue.getClass() != String.class) {
            throw new IllegalArgumentException(String.format(
                    "Annotation value should be String. Current value is of type: %s", oldValue.getClass().getName()));
        }

        String oldValueString = oldValue.toString();
        if (!oldValueString.contains(TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY)) {
            throw new IllegalArgumentException(
                    String.format("Annotation value should be contain %s. Current value is : %s",
                            TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY, oldValueString));
        }
        String newValueString = oldValueString.replace(TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY, key);

        memberValues.put(annotationProperty, newValueString);
    }
}

此代码应在 创建EntityManager之前运行。在我的情况下,我使用依赖(对于xml配置或@DependsOn用于java配置)。

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" depends-on="transformerColumnKeyLoader"> ... </bean>

答案 1 :(得分:1)

hibernate配置基本上是静态的。它不打算在运行时进行修改。但如果你仔细考虑,就可以做到。

基本上,构建SessionFactory的常用方法是执行以下操作:

  AnnotationConfiguration conf = new AnnotationConfiguration().configure();
  sessionFactory = conf.buildSessionFactory();

大多数情况下,此代码是框架的一部分(例如,使用Spring,您必须查看SessionFactoryBean才能找到它)。所以要做的第一件事就是识别这部分代码并覆盖框架组件,以便在conf对象被buildSessionFactory()使用之前访问它。

然后您必须修改AnnotationConfiguration以删除/添加与可选注释相关的数据:

  {
      ...
      AnnotationConfiguration conf = new AnnotationConfiguration().configure();
      if(FLAG_INDICATING_TO_REMOVE_SOME_ANNOTATION){
          manipulateHibernateConfig(conf);
      }
      sessionFactory = conf.buildSessionFactory();
      ...
  }

  private void manipulateHibernateConfig(AnnotationConfiguration conf){
      ...
     //this is the tricky part because lot of fields and setters are either
     //final or private so it requires reflection etc...

     //you must also be sure that those manipulation won't break the config !
  }

答案 2 :(得分:0)

根据user3035947的回答:

@Component
public class RemoveAesFunction {



    @PostConstruct
    public void postConstruct() {
        setKey(MyEntity.class);
    }

    private void setKey(Class<?> clazz) {
        try {
            Field field = clazz.getDeclaredField("firstName");

            ColumnTransformer columnTransformer = field.getDeclaredAnnotation(ColumnTransformer.class);
            updateAnnotationValue(columnTransformer, "read","");
            updateAnnotationValue(columnTransformer, "write","?");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new RuntimeException();
        }
    }

    @SuppressWarnings("unchecked")
    private void updateAnnotationValue(Annotation annotation, String annotationProperty,String value) {
        Object handler = Proxy.getInvocationHandler(annotation);
        Field merberValuesField;
        try {
            merberValuesField = handler.getClass().getDeclaredField("memberValues");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }
        merberValuesField.setAccessible(true);
        Map<String, Object> memberValues;
        try {
            memberValues = (Map<String, Object>) merberValuesField.get(handler);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }

        memberValues.put(annotationProperty, value);
    }
}

答案 3 :(得分:0)

我也在尝试覆盖@ColumnTransformer注释。 该组件在实体管理器的注入之前启动,但是当我执行查询时,该字段将返回null,就像未传递正确的键一样。 如果将键直接插入注释中,则一切正常。 在执行查询之前,该值已正确打印

实体字段

@Column(name = "NAME")
@ColumnTransformer(read = "AES_DECRYPT(NAME, '${encryption.key}')")
private String name;

查询管理器

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<FooBar> query = builder.createQuery(FooBar.class);
Root<FooBar> root = query.from(FooBar.class);
query.select(root);
query.where(builder.and(builder.equal(root.get(FooBar_.id), id),
                builder.equal(root.get(FooBar_.STATUS), 0),
                builder.equal(root.get(FooBar_.REQUEST), true)));
Field field = FooBar.class.getDeclaredField("name");
ColumnTransformer oldAnnotation = field.getDeclaredAnnotation(ColumnTransformer.class);
LOGGER.debug("oldAnnotation = " + oldAnnotation.read());
entity = em.createQuery(query).getSingleResult();

TransformerColumnKeyLoader

@Component(value = "transformerColumnKeyLoader")
public class TransformerColumnKeyLoader {

public static final String KEY_ANNOTATION_PROPERTY = "${encryption.key}";

@Value(value = "${secret.key}")
private String key;

@PostConstruct
public void postConstruct() {
    setKey(FooBar.class, "name");
}

private void setKey(Class<?> clazz, String columnName) {
    try {
        Field field = clazz.getDeclaredField(columnName);

        ColumnTransformer columnTransformer = field.getDeclaredAnnotation(ColumnTransformer.class);
        updateAnnotationValue(columnTransformer, "read");
    } catch (NoSuchFieldException | SecurityException e) {
        throw new RuntimeException(
                String.format("Encryption key cannot be loaded into %s,%s", clazz.getName(), columnName));
    }
}

@SuppressWarnings("unchecked")
private void updateAnnotationValue(Annotation annotation, String annotationProperty) {
    Object handler = Proxy.getInvocationHandler(annotation);
    Field merberValuesField;
    try {
        merberValuesField = handler.getClass().getDeclaredField("memberValues");
    } catch (NoSuchFieldException | SecurityException e) {
        throw new IllegalStateException(e);
    }
    merberValuesField.setAccessible(true);
    Map<String, Object> memberValues;
    try {
        memberValues = (Map<String, Object>) merberValuesField.get(handler);
    } catch (IllegalArgumentException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
    Object oldValue = memberValues.get(annotationProperty);
    if (oldValue == null || oldValue.getClass() != String.class) {
        throw new IllegalArgumentException(String.format(
                "Annotation value should be String. Current value is of type: %s", oldValue.getClass().getName()));
    }

    String oldValueString = oldValue.toString();
    if (!oldValueString.contains(TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY)) {
        throw new IllegalArgumentException(
                String.format("Annotation value should be contain %s. Current value is : %s",
                        TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY, oldValueString));
    }
    String newValueString = oldValueString.replace(TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY, key);

    memberValues.put(annotationProperty, newValueString);
    System.out.println(memberValues);
}
}

答案 4 :(得分:0)

因为@ColumnTransformer在运行时之前已完全加载,并且您要使用@Value从application.properties获取键,所以属性必须符合运行时,因此当您将键嵌入@ColumnTransformer中时,您会发现警告“属性值必须为不变'。 但是,我们可以更改另一种方式,以下是我的解决方案:

1。编写AES算法实用程序。

2。重写实体类的get / set方法。

import static com.mysql.demo.utils.AesEncodeUtil.encrypt2Str;
import static com.mysql.demo.utils.AesEncodeUtil.decrypt2Str;
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
public void setName(String name) throws Exception {
    this.name = encrypt2Str(name, PRIVATE_KEY);
  }

public String getName() throws Exception {
    return decrypt2Str(name, PRIVATE_KEY);
  }
}

这是Github中的my demo

答案 5 :(得分:0)

同事提到的这个做法很有意思。我只想补充一点,使用 application.properties 或作为环境变量似乎不是最好的方法,一旦它是一种敏感数据,并且可能因使用您的解决方案的每个客户而异。

使用客户特定的密钥加密数据会很有趣,因此您需要从此类外部访问 setKey() 方法以根据您的客户重新定义。