7

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

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

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

有人可以帮我弄这个吗 ?

4

6 回答 6

6

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

实体类:

@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>
于 2016-11-18T10:38:48.827 回答
1

休眠配置本质上是静态的。它不打算在运行时进行修改。但如果你仔细做,它是可以做到的。

基本上,构建 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 !
  }
于 2013-10-23T12:55:22.223 回答
1

因为@ColumnTransformer 是在Runtime 之前完成加载的,而你想用@Value 从application.properties 中获取key 必须满足Runtime,所以你会发现在@ColumnTransformer 中嵌入key 时会警告'Attribute value must be constant'。但是,我们可以改变另一种方式,以下是我的解决方案:

1.编写一个AES算法工具。

2.覆盖您的实体类获取/设置方法。

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上的演示。

于 2020-12-18T02:36:04.547 回答
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);
}
}
于 2019-08-09T11:21:27.870 回答
0

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

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

于 2021-04-23T19:32:29.883 回答
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);
    }
}
于 2016-11-23T11:29:23.517 回答