简体   繁体   English

使用 Spring Data JPA 和 JPA EntityListener 进行字段级加密

[英]Field Level Encryption with Spring Data JPA and JPA EntityListener

I'm trying to encrypt a handful of fields on domain entities before insert/update and decrypt them upon select to display in the UI.我试图在插入/更新之前加密域实体上的一些字段,并在选择显示在 UI 时解密它们。

I'm using Spring Data JPA repositories with Hibernate and an EntityListener which decrypts during @PostLoad lifecycle event and encrypts during @PrePersist and @PreUpdate.我将 Spring Data JPA 存储库与 Hibernate 和一个 EntityListener 一起使用,它在 @PostLoad 生命周期事件期间解密并在 @PrePersist 和 @PreUpdate 期间加密。 The problem I have is that once the record is loaded from the DB into the PersistenceContext, the listener decrypts the data which makes the EntityManager think the entity has been altered which in turn triggers an Update and hence @PreUpdate encryption again.我遇到的问题是,一旦将记录从数据库加载到 PersistenceContext 中,侦听器就会解密数据,这使 EntityManager 认为实体已被更改,进而触发更新,因此再次触发 @PreUpdate 加密。 Any advice on how to handle this?有关如何处理此问题的任何建议?

  • Spring 4.0.4.RELEASE春季 4.0.4.RELEASE
  • Spring Data JPA 1.5.2.RELEASE Spring Data JPA 1.5.2.RELEASE
  • Hibernate 4.2.14.Final休眠 4.2.14.Final

Is there an easy way to return detached entities from the JPA Repository?有没有一种简单的方法可以从 JPA 存储库中返回分离的实体?

Entity Class实体类

@Entity
@Table(name="cases")
@EntityListeners(EncryptionListener.class)
public class MyCase implements Serializable, EncryptionEntity {

private static final Logger logger = LoggerFactory.getLogger(MyCase.class);

private static final long serialVersionUID = 1L;

private String caseNumber;
private byte[] secretProperty;
private byte[] iv;

@Id
@Column(name="case_number")
public String getCaseNumber() {
    return caseNumber;
}
public void setCaseNumber(String caseNumber) {
    this.caseNumber = caseNumber;
}

@Column(name="secret_property")
public byte[] getSecretProperty() {
    return secretProperty;
}
public void setSecretProperty(byte[] secretProperty) {
    this.secretProperty = secretProperty;
}

@Column
public byte[] getIv() {
    return iv;
}
public void setIv(byte[] iv) {
    this.iv = iv;
}

@Override
@Transient
public byte[] getInitializationVector() {
    return this.iv;
}

@Override
public void setInitializationVector(byte[] iv) {
    this.setIv(iv);
}

}

EncryptionEntity Interface加密实体接口

public interface EncryptionEntity {

    public byte[] getInitializationVector();
    public void setInitializationVector(byte[] iv);
}

Spring Data JPA Repository Spring 数据 JPA 存储库

public interface MyCaseRepository extends JpaRepository<MyCase, String> {

}

MyCaseService Interface MyCaseService 接口

public interface MyCaseService {

    public MyCase findOne(String caseNumber);

    public MyCase save(MyCase case);

}

MyCaseService Implementation MyCaseService 实现

public class MyCaseServiceImpl implements MyCaseService {

    private static final Logger logger = LoggerFactory.getLogger(MyCaseServiceImpl.class);

    @Autowired
    private MyCaseRepository repos;


    @Override
    public MyCase findOne(String caseNumber) {
        return repos.findOne(caseNumber);
    }

    @Transactional(readOnly=false)
    public MyCase save(MyCase case) {

        return repos.save(case);
    }

}

Encryption JPA Listener Class加密 JPA 侦听器类

@Component
public class EncryptionListener {

    private static final Logger logger = LoggerFactory.getLogger(EncryptionListener.class);

    private static EncryptionUtils encryptionUtils;
    private static SecureRandom secureRandom;

    private static Map<Class<? extends EncryptionEntity>, 
        List<EncryptionEntityProperty>> propertiesToEncrypt;

    @Autowired
    public void setCrypto(EncryptionUtils encryptionUtils){
        EncryptionListener.encryptionUtils = encryptionUtils;
    }

    @Autowired
    public void setSecureRandom(SecureRandom secureRandom){
        EncryptionListener.secureRandom = secureRandom;
    }

    public EncryptionListener(){

        if (propertiesToEncrypt == null){

            propertiesToEncrypt = new HashMap<Class<? extends EncryptionEntity>, List<EncryptionEntityProperty>>();

            //MY CASE
            List<EncryptionEntityProperty> propertyList = new ArrayList<EncryptionEntityProperty>();
            propertyList.add(new EncryptionEntityProperty(MyCase.class, "secretProperty", byte[].class));
            propertiesToEncrypt.put(MyCase.class, propertyList);

        }

    }

    @PrePersist
    public void prePersistEncryption(EncryptionEntity entity){
        logger.debug("PRE-PERSIST");
        encryptFields(entity);
    }

    @PreUpdate
    public void preUpdateEncryption(EncryptionEntity entity){
        logger.debug("PRE-UPDATE");
        encryptFields(entity); 
    }

    public void encryptFields(EncryptionEntity entity){
        byte[] iv = new byte[16];
        secureRandom.nextBytes(iv);
        encryptionUtils.setIv(iv);
        entity.setInitializationVector(iv);

        logger.debug("Encrypting " + entity);

        Class<? extends EncryptionEntity> entityClass = entity.getClass();

        List<EncryptionEntityProperty> properties = propertiesToEncrypt.get(entityClass);

        for (EncryptionEntityProperty property : properties){

            logger.debug("Encrypting '{}' field of {}", property.getName(), entityClass.getSimpleName());
            if (property.isEncryptedWithIv() == false){
                logger.debug("Encrypting '{}' without IV.", property.getName());
            }

            try {
                byte[] bytesToEncrypt = (byte[]) property.getGetter().invoke(entity, (Object[]) null);

                if (bytesToEncrypt == null || bytesToEncrypt.length == 0){
                    continue;
                }

                byte[] encrypted = encryptionUtils.encrypt(bytesToEncrypt, property.isEncryptedWithIv());

                property.getSetter().invoke(entity, new Object[]{encrypted});


            } catch (Exception e){
                logger.error("Error while encrypting '{}' property of {}: " + e.getMessage(), property.getName(), entityClass.toString());
                e.printStackTrace();
            }

        }

    }

    @PostLoad
    public void decryptFields(EncryptionEntity entity){

        logger.debug("POST-LOAD");

        logger.debug("Decrypting " + entity);

        Class<? extends EncryptionEntity> entityClass = entity.getClass();
        byte[] iv = entity.getInitializationVector();

        List<EncryptionEntityProperty> properties = propertiesToEncrypt.get(entityClass);

        for (EncryptionEntityProperty property : properties){

            try {
                byte[] value = (byte[]) property.getGetter().invoke(entity, (Object[]) null);

                if (value == null || value.length == 0){
                    logger.debug("Ignoring blank field {} of {}", property.getName(), entityClass.getSimpleName());
                    continue;
                } 

                logger.debug("Decrypting '{}' field of {}", property.getName(), entityClass.getSimpleName());
                if (property.isEncryptedWithIv() == false){
                    logger.debug("Decrypting '{}' without IV.", property.getName());
                }

                byte[] decrypted = encryptionUtils.decrypt(value, iv, property.isEncryptedWithIv());

                property.getSetter().invoke(entity, new Object[]{decrypted});

            } catch (Exception e){
                logger.error("Error while decrypting '{}' property of {}", property.getName(), entityClass.toString());
                e.printStackTrace();
            }
        }

    }



}

I know the question asked is pretty old.我知道问的问题已经很老了。 but i was facing the same issue and to overcome, you can use PreLoadEventListener.但是我遇到了同样的问题并且要克服,您可以使用 PreLoadEventListener。 In your decryptFields instead of using @PostLoad use @PreLoad在您的decryptFields而不是使用@PostLoad使用@PreLoad

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM