我目前有一个这样的字段注释:
ColumnTransformer(
read="AES_DECRYPT(C_first_name, 'yourkey')",
write="AES_ENCRYPT(?, 'yourkey')")
public String getFirstName() {
return firstName;
}
这适用于Mysql数据库,但我需要这个配置是可选的,因为我们的应用程序可以根据启动参数使用另一个数据库(HsqlDB)。所以我需要的是只有在使用特定的启动参数时才使用ColumnTransformer(并且没有用于HsqlDB的ColumnTransformer,它不能使用“AES_ENCRYPT”)
有人可以帮我这个吗?
答案 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() 方法以根据您的客户重新定义。