Java-Ruhezustand bedingt Annotation auf ein Feld anwenden?

Aug 31 2020

Szenario: Ein Datenobjekt, das in der DB-Tabelle verbleibt. Es gibt einige alte Einträge in der Tabelle. Jetzt muss ich neue weitere Einträge in der Tabelle verschlüsseln. Daher füge ich eine neue Spalte hinzu, in der das Feld verschlüsselt standardmäßig auf Falsch gesetzt ist, um zu überprüfen, ob die Werte verschlüsselt sind.

Problem: Ich möchte eine Anmerkung schreiben, um die Felder im Datenmodell (POJO) zu verschlüsseln, bevor sie bei getter () -Aufrufen beibehalten und entschlüsselt werden, wenn sie verschlüsselt sind.

Kontext:

Das Benutzermodell.

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
}

Die Verschlüsselungsklasse, die ich geschrieben habe.

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

Diese Klasse befindet sich innerhalb der Modellklasse. (Kann sich nach draußen bewegen, aber wie wird das verschlüsselte Flag an die Annotation übergeben?)

Wie kann ich das machen, ist mein Ansatz korrekt?

Bearbeiten: Es gibt mehrere Felder, die verschlüsselt / entschlüsselt werden sollen, nicht nur den Namen.

Antworten

1 Soni Aug 31 2020 at 17:02

Sie können das Verschlüsselungsverhalten in einer anderen Konfigurationsklasse erstellen, z. B. EncryptedPropertyConfig. In dieser können Sie eine Bean, EncryptablePropertyResolver, aus jasypt-spring-boot erstellen

@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);
            }
        }
    }
}
1 NikolaiShevchenko Aug 31 2020 at 17:02

Ich schlage eine alternative Lösung mit Entity Listeners vor

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);
        }
    }
}
1 SternK Aug 31 2020 at 21:57

Anstatt JPA AttributeConverter zu verwenden, können Sie den Benutzertyp im Ruhezustand folgendermaßen implementieren :

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;
   }
   
}

wo CustomNameist:

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

und dann benutze es:

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;
}