spring 保存操作数据与休眠

ie3xauqp  于 5个月前  发布在  Spring
关注(0)|答案(2)|浏览(57)

我试图保存用户的详细信息到用户表,但与一些列有加密的数据,但我仍然想使用他们解密后的代码。例如:用户的详细信息有用户名,我加密并存储在表中。但我想使用用户名解密后。
为了实现这一点,我在bean的setter中添加了加密用户名的代码,并在getter中添加了解密值的代码,如下所示

@Table
@Entity
class User {

private String username;
private int age;

@Column
public String getUsername(){
 return Commons.decrypt(this.username);
}

@Column
public int getAge(){
 return this.age;
}

public void setUsername(String username){
 this.username = Commons.encrypt(username);
}

public void setAge(int age){}
 this.age = age;
}

字符串
在保存User表中的值时,我使用javax.persistence.EntityManager类的persist方法,代码如下所示

UserDetails user = new UserDetails();
user.setUsername("User 1");
user.setAge(31);
entityManager.persist();


我期望代码执行如下
1.当通过setter设置username时,它应该加密值
1.当调用persist方法时,加密值必须保存在数据库中
1.从数据库中获取数据时,应该具有加密的用户名值
1.在调用getter方法时,它应该解密并返回解密的用户名
但问题是,只要我调用persist方法,hibernate就会在内部调用gettersetter方法n次,最终导致在数据库中保存解密值,即User 1而不是eytsnkcjsdvnfsnvkscd=,因此,当我试图从DB中获取数据时,会返回解密值,并且getter中的Commons.decrypt(this.username)失败。
如何通过一个通用的代码策略来实现上述期望,我在getter和setter中尝试,因为大多数代码已经编写并正在使用它。

yr9zkbsy

yr9zkbsy1#

对于Spring Data,通常使用Repository Interface并使用AttributeConverter进行加密。
下面是一个AttributeConverterexample by sultanov.dev

@Component
public class AttributeEncryptor implements AttributeConverter<String, String> {

    private static final String AES = "AES";
    private static final String SECRET = "secret-key-12345";

    private final Key key;
    private final Cipher cipher;

    public AttributeEncryptor() throws Exception {
        key = new SecretKeySpec(SECRET.getBytes(), AES);
        cipher = Cipher.getInstance(AES);
    }

    @Override
    public String convertToDatabaseColumn(String attribute) {
        try {
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return Base64.getEncoder().encodeToString(cipher.doFinal(attribute.getBytes()));
        } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        try {
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(cipher.doFinal(Base64.getDecoder().decode(dbData)));
        } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
            throw new IllegalStateException(e);
        }
    }
}

字符串
然后,实体User将只包含基本的数据字段和getter/setter方法(简称为Lombok)以及用户名的@Convert注解。

@Entity
@Getter
@Setter
public class User {
    @Id
    private Long id;

    @Convert(converter = AttributeEncryptor.class)
    private String username;

    private int age;
}


@Column注解用于当实体中的属性从辅助表中提取时,其中table属性标识相应的表。我不确定您试图将getter方法标记为列来实现什么。
对存储对象的访问通常使用Sping Data的Repository接口之一来实现,例如JpaRepository

public interface UserRepository extends JpaRepository<User, Long> {
    
}


在您的服务层中,您应该能够使用来自存储库的数据,并自动处理加密。

@Service
public class UserService {
    @Autowired
    private UserRepository repository;

    @Transactional(readOnly = true)
    public User loadUser(Long id) {
        return repository.findById(id).orElse(null);;   
    }

    @Transactional  
    public User save(User user) {
        return repository.save(user);
    }
}

7vux5j2d

7vux5j2d2#

你面临的问题是由于Hibernate在持久化和检索过程中与实体getter和setter交互的方式。Hibernate使用getter和setter访问实体属性,在你的情况下,这会导致无意中的解密和重新加密。
要解决此问题,您可以执行以下步骤:
1.数据库和域模型分离:您可以引入一个单独的域模型类,而不是直接在User实体的getter和setter中进行加密和解密,这个类将代表您的数据的解密版本,用于应用逻辑中。
1.使用@ PrePersists和@PostLoad Annotations:使用这些Annotations在getter和setter之外处理加密和解密。这可以确保数据在持久化之前加密,从数据库加载后解密,而不会干扰标准的getter/setter行为。

*重构实体类

  • username作为普通字符串保存在User实体中。
  • 在持久化之前使用@PrePersist加密用户名。
  • 在从数据库加载用户名之后,立即使用@PostLoad解密用户名。

以下是如何重构User实体:

import javax.persistence.*;

@Entity
@Table
class User {

    @Column
    private String username;
    private int age;

    // Standard getters and setters
    public String getUsername(){
        return this.username;
    }

    public void setUsername(String username){
        this.username = username;
    }

    public int getAge(){
        return this.age;
    }

    public void setAge(int age){
        this.age = age;
    }

    @PrePersist
    private void encryptFields() {
        this.username = Commons.encrypt(this.username);
    }

    @PostLoad
    private void decryptFields() {
        this.username = Commons.decrypt(this.username);
    }
}

字符串
在这一办法中:

  • 在实体管理器持久化实体之前,调用@PrePersist方法encryptFields,确保username在数据库中加密。
  • 从数据库加载实体后,将调用@PostLoad方法decryptFields,解密username以供应用程序使用。

这样,标准的getter和setter不受影响,加密/解密在持久化和检索阶段被透明地处理。这种策略应该可以满足您对存储数据加密的需求,同时仍然可以在代码中使用解密的数据。

相关问题