Java hibernate applique conditionnellement une annotation à un champ?
Scénario: un objet de données qui persiste dans la table DB. Il y a quelques anciennes entrées dans le tableau. Maintenant, je dois appliquer le cryptage à de nouvelles entrées supplémentaires dans le tableau. J'ajoute donc une nouvelle colonne dont le champ crypté est défini sur False par défaut pour vérifier si les valeurs sont cryptées.
Problème: Je souhaite écrire une annotation pour crypter les champs dans le modèle de données (POJO) avant de persister et décrypter lors des appels getter () uniquement s'il est crypté.
Le contexte:
Le modèle utilisateur.
public class UserData {
@Id
@Column(name = "ID", length = 36)
private String id;
@Column(name = "IS_ENCRYPTED")
private boolean isEncrypted;
@Column(name = "NAME")
@Convert(converter = EncryptionConverter.class)
private String name;
// more fields ....
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
// more similar getter and setters
}
La classe de cryptage que j'ai écrite.
@Converter
public class EncryptionConverter implements AttributeConverter<String, String>{
private final String secretKey= "someSecret";
UserData Data = new UserData();
@Override
public String convertToDatabaseColumn(String str) {
if(!isNullOrBlank(str))
return AesEncrypt.encrypt(str, secretKey);
return str;
}
@Override
public String convertToEntityAttribute(String encrypedStr) {
if(!isNullOrBlank(encrypedStr) && Data.isEncrypted)
return AesEncrypt.decrypt(encrypedStr, secretKey);
return encrypedStr;
}
}
Cette classe est à l'intérieur de la classe modèle. (peut se déplacer à l'extérieur, mais comment passer un drapeau chiffré à l'annotation)
Comment puis-je faire cela, mon approche est-elle correcte?
Edit: il y a plusieurs champs qui doivent être chiffrés / déchiffrés pas seulement le nom.
Réponses
Vous pouvez créer le comportement de chiffrement dans une autre classe de configuration, par exemple EncryptedPropertyConfig, dans ce cas vous pouvez créer un bean, EncryptablePropertyResolver à partir de jasypt-spring-boot
@EnableAutoConfiguration
public class EncryptedPropertyConfig {
public EncryptedPropertyConfig() {
}
@Bean
public EncryptablePropertyResolver encryptablePropertyResolver() {
EncryptablePropertyResolver r = new MyPropertyPlaceholderConfigurer();
return r;
}
}
public final class MyPropertyPlaceholderConfigurer implements EncryptablePropertyResolver {
private StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
private EnvironmentStringPBEConfig envConfig = new EnvironmentStringPBEConfig();
public MyPropertyPlaceholderConfigurer() {
// set the encryption key and config
}
public String resolvePropertyValue(String passedValue) {
if (!PropertyValueEncryptionUtils.isEncryptedValue(passedValue)) {
return passedValue;
} else {
String returnValue = "";
try {
returnValue = PropertyValueEncryptionUtils.decrypt(passedValue, this.encryptor);
return returnValue;
} catch (Exception var4) {
throw new RuntimeException("Error in decryption of property value:" + passedValue, var4);
}
}
}
}
Je suggère une solution alternative en utilisant Entity Listeners
import javax.persistence.PostLoad;
import javax.persistence.PreUpdate;
public class UserData {
private final String secretKey= "someSecret";
// ...
@PreUpdate
private void onUpdate() {
// triggered before saving entity to DB (both create & update)
if(!isNullOrBlank(name)) {
name = AesEncrypt.encrypt(name, secretKey);
}
}
@PostLoad
private void onLoad() {
// triggered after entity is fetched from Entity Provider
if (!isNullOrBlank(name) && isEncrypted) {
name = AesEncrypt.decrypt(name, secretKey);
}
}
}
Au lieu d'utiliser JPA AttributeConverter, vous pouvez implémenter le type d'utilisateur de mise en veille prolongée de cette manière:
import java.util.Objects;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StringType;
import org.hibernate.usertype.UserType;
public class CustomNameType implements UserType
{
private String secretKey = "someSecret";
public CustomNameType()
{
}
@Override
public Object deepCopy(Object value) throws HibernateException
{
if (null == value) return null;
return ((CustomName) value).clone();
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
@Override
public Serializable disassemble(Object value) throws HibernateException
{
return (Serializable) value;
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
@Override
public boolean equals(Object one, Object two) throws HibernateException
{
return Objects.equals(one, two);
}
@Override
public int hashCode(Object obj) throws HibernateException
{
return Objects.hashCode(obj);
}
@Override
public boolean isMutable()
{
return true;
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException
{
boolean isEncrypted = rs.getBoolean(0); // IS_ENCRYPTED
String name = rs.getString(1); // NAME
if (isEncrypted) {
name = AesEncrypt.decrypt(name, secretKey);
}
return new CustomName(isEncrypted, name);
}
@Override
public void nullSafeSet(PreparedStatement statement, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException
{
CustomName customName = (CustomName) value;
String name = customName.getName();
if (customName.isEncrypted()) {
name = AesEncrypt.encrypt(name, secretKey);
}
statement.setBoolean(0, customName.isEncrypted());
statement.setString(1, name);
}
@Override
public Class<?> returnedClass()
{
return CustomName.class;
}
@Override
public int[] sqlTypes()
{
// I do not know the types of your IS_ENCRYPTED and NAME fields
// So, this place maybe require correction
int[] types = {BooleanType.INSTANCE.sqlType(), StringType.INSTANCE.sqlType()};
return types;
}
}
où CustomNameest:
public class CustomName implements Serializable, Cloneable
{
private boolean isEncrypted;
private String name;
public CustomName(boolean isEncrypted, String name)
{
this.isEncrypted = isEncrypted;
this.name = name;
}
// getters , equals, hashCode ...
@Override
public CustomName clone()
{
return new CustomName(isEncrypted, name);
}
}
puis utilisez-le:
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Columns;
@Entity
public class UserData {
@Type(type = "com.your.CustomNameType")
@Columns(columns = {
@Column(name = "IS_ENCRYPTED"),
@Column(name = "NAME")
})
private CustomName name;
}