spring 保存操作数据与休眠

ie3xauqp  于 2024-01-05  发布在  Spring
关注(0)|答案(2)|浏览(156)

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

  1. @Table
  2. @Entity
  3. class User {
  4. private String username;
  5. private int age;
  6. @Column
  7. public String getUsername(){
  8. return Commons.decrypt(this.username);
  9. }
  10. @Column
  11. public int getAge(){
  12. return this.age;
  13. }
  14. public void setUsername(String username){
  15. this.username = Commons.encrypt(username);
  16. }
  17. public void setAge(int age){}
  18. this.age = age;
  19. }

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

  1. UserDetails user = new UserDetails();
  2. user.setUsername("User 1");
  3. user.setAge(31);
  4. 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

  1. @Component
  2. public class AttributeEncryptor implements AttributeConverter<String, String> {
  3. private static final String AES = "AES";
  4. private static final String SECRET = "secret-key-12345";
  5. private final Key key;
  6. private final Cipher cipher;
  7. public AttributeEncryptor() throws Exception {
  8. key = new SecretKeySpec(SECRET.getBytes(), AES);
  9. cipher = Cipher.getInstance(AES);
  10. }
  11. @Override
  12. public String convertToDatabaseColumn(String attribute) {
  13. try {
  14. cipher.init(Cipher.ENCRYPT_MODE, key);
  15. return Base64.getEncoder().encodeToString(cipher.doFinal(attribute.getBytes()));
  16. } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) {
  17. throw new IllegalStateException(e);
  18. }
  19. }
  20. @Override
  21. public String convertToEntityAttribute(String dbData) {
  22. try {
  23. cipher.init(Cipher.DECRYPT_MODE, key);
  24. return new String(cipher.doFinal(Base64.getDecoder().decode(dbData)));
  25. } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
  26. throw new IllegalStateException(e);
  27. }
  28. }
  29. }

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

  1. @Entity
  2. @Getter
  3. @Setter
  4. public class User {
  5. @Id
  6. private Long id;
  7. @Convert(converter = AttributeEncryptor.class)
  8. private String username;
  9. private int age;
  10. }


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

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


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

  1. @Service
  2. public class UserService {
  3. @Autowired
  4. private UserRepository repository;
  5. @Transactional(readOnly = true)
  6. public User loadUser(Long id) {
  7. return repository.findById(id).orElse(null);;
  8. }
  9. @Transactional
  10. public User save(User user) {
  11. return repository.save(user);
  12. }
  13. }

展开查看全部
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实体:

  1. import javax.persistence.*;
  2. @Entity
  3. @Table
  4. class User {
  5. @Column
  6. private String username;
  7. private int age;
  8. // Standard getters and setters
  9. public String getUsername(){
  10. return this.username;
  11. }
  12. public void setUsername(String username){
  13. this.username = username;
  14. }
  15. public int getAge(){
  16. return this.age;
  17. }
  18. public void setAge(int age){
  19. this.age = age;
  20. }
  21. @PrePersist
  22. private void encryptFields() {
  23. this.username = Commons.encrypt(this.username);
  24. }
  25. @PostLoad
  26. private void decryptFields() {
  27. this.username = Commons.decrypt(this.username);
  28. }
  29. }

字符串
在这一办法中:

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

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

展开查看全部

相关问题