我想知道是否有一种方法可以用Hibernate来执行ColumnTransformer的编程配置?

bgibtngc  于 2023-06-23  发布在  其他
关注(0)|答案(6)|浏览(104)

我现在有一个注解如下的字段:

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

这在Mysql数据库中可以正常工作,但我需要这个配置是可选的,因为我们的应用程序可以根据start参数使用另一个数据库(HsqlDB)。因此,我需要的是一种方法,只有在使用特定的start参数时才使用ColumnTransformer(HsqlDB没有ColumnTransformer,它不能使用“AES_ENCRYPT”)
有人能帮我吗?

mm9b1k5b

mm9b1k5b1#

我有同样的问题,我希望键是可配置的。我找到的唯一解决方案是在运行时更新注解值。是的,我知道这听起来很可怕,但据我所知,没有其他办法。
实体类:

@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之前运行。在我的例子中,我使用了depends-on(用于xml配置,或者@DependsOn用于java配置)。

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

jk9hmnmh2#

休眠配置基本上是静态的。不打算在运行时修改它。但是如果你仔细做的话,是可以做到的。
基本上,构建SessionFactory的常用方法是这样做:

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

大多数情况下,这些代码是框架的一部分(例如,对于Spring,您必须查看SessionFactoryBean才能找到它)。因此,要做的第一件事是识别这部分代码并覆盖框架组件,以便在使用buildSessionFactory()之前访问conf对象。
然后你必须修改AnnotationConfiguration来删除/添加与 optional annotations相关的数据:

{
      ...
      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 !
  }
koaltpgm

koaltpgm3#

因为@ColumnTransformer是在Runtime之前完成加载的,而你要使用@Value从www.example.com获取keyapplication.properties必须满足Runtime,所以你会发现当你在@ColumnTransformer中嵌入key时会警告'Attribute value must be constant'。但是,我们可以改变另一种方式,以下是我的解决方案:
1.编写AES算法utils。
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

eufgjt7s

eufgjt7s4#

根据用户3035947的回答:

@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);
    }
}
vu8f3i0k

vu8f3i0k5#

我还尝试覆盖@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);
}
}
uqcuzwp8

uqcuzwp86#

同事们提到的这种做法很有意思。我只想补充一点,使用application.properties或作为环境变量似乎不是最好的方法,一旦它是一种敏感数据,并且可能会因您的解决方案所使用的每个客户而异。
使用客户特定的密钥加密数据会很有趣,因此您需要从该类外部访问setKey()方法,以根据您的客户进行重新定义。

相关问题