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

时间:2014-10-10 17:46:13

标签: java spring hibernate jpa encryption

我试图在插入/更新之前加密域实体上的少数字段,并在选择时解密它们以在UI中显示。

我使用带有Hibernate的Spring Data JPA存储库和一个EntityListener,它在@PostLoad生命周期事件期间解密,并在@PrePersist和@PreUpdate期间加密。我遇到的问题是,一旦将记录从DB加载到PersistenceContext中,侦听器就会解密数据,这会使EntityManager认为实体已被更改,从而触发更新,从而再次触发@PreUpdate加密。关于如何处理这个问题的任何建议?

  • Spring 4.0.4.RELEASE
  • Spring Data JPA 1.5.2.RELEASE
  • Hibernate 4.2.14.Final

是否有一种简单的方法可以从JPA存储库返回分离的实体?

实体类

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

}

加密实体接口

public interface EncryptionEntity {

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

Spring Data JPA存储库

public interface MyCaseRepository extends JpaRepository<MyCase, String> {

}

MyCaseService接口

public interface MyCaseService {

    public MyCase findOne(String caseNumber);

    public MyCase save(MyCase case);

}

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

}

加密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();
            }
        }

    }



}

1 个答案:

答案 0 :(得分:0)

我知道所问的问题已经很老了。但是我也面临着同样的问题,要克服这个问题,您可以使用PreLoadEventListener。在您的decryptFields中,不要使用@PostLoad