[英]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?
有关如何处理此问题的任何建议?
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.