Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы рассмотрим, как мы можем шифровать и расшифровывать свойства JSON при использовании JPA и гибернации.
Хотя шифрование всего значения столбца очень просто , когда дело доходит до столбцов JSON, нам нужно сохранить структуру объектов JSON, зашифровав только значения свойств JSON.
Модель предметной области
Давайте предположим, что наше приложение определяет сущность User
, которая инкапсулирует всю конфиденциальную информацию пользователя в объект UserDetails
:
Сущность User
сопоставляется с таблицей базы данных users
, а объект UserDetails
сохраняется в столбце JSON:
Поскольку Сведения о пользователе
содержит информацию о пользователе, мы хотим зашифровать ее перед сохранением в базе данных и расшифровать после загрузки.
Как зашифровать и расшифровать свойства JSON с помощью JPA и гибернации
Сведения о пользователе
– это простой класс POJO, который выглядит следующим образом:
public class UserDetails { private String firstName; private String lastName; private String emailAddress; //Getters and setters omitted for brevity }
Объект User
будет отображен следующим образом:
@Entity @Table(name = "users") @DynamicUpdate @TypeDef( typeClass = JsonType.class, defaultForType = UserDetails.class ) public class User { @Id private Long id; private String username; @Column(columnDefinition = "json") private UserDetails details; //Getters and setters omitted for brevity @PrePersist @PreUpdate private void encryptFields() { if (details != null) { if (details.getFirstName() != null) { details.setFirstName( CryptoUtils.encrypt(details.getFirstName()) ); } if (details.getLastName() != null) { details.setLastName( CryptoUtils.encrypt(details.getLastName()) ); } if (details.getEmailAddress() != null) { details.setEmailAddress( CryptoUtils.encrypt(details.getEmailAddress()) ); } } } @PostLoad private void decryptFields() { if (details != null) { if (details.getFirstName() != null) { details.setFirstName( CryptoUtils.decrypt(details.getFirstName()) ); } if (details.getLastName() != null) { details.setLastName( CryptoUtils.decrypt(details.getLastName()) ); } if (details.getEmailAddress() != null) { details.setEmailAddress( CryptoUtils.decrypt(details.getEmailAddress()) ); } } } }
Аннотация @DynamicUpdate
используется, потому что мы хотим, чтобы Hibernate включал только измененные столбцы при создании инструкции UPDATE. Для получения более подробной информации об аннотации @DynamicUpdate
ознакомьтесь с этой статьей .
Аннотация @TypeDef
предписывает Hibernate использовать тип Json
, предоставленный проектом Hibernate Types при сохранении и извлечении атрибутов сущности типа UserDetails
.
Метод encrypt Fields
снабжен аннотациями JPA @prePersist
и @preUpdate
, поэтому поставщик JPA вызовет этот метод до сохранения или обновления сущности. Поэтому мы собираемся использовать метод encrypt Fields
для шифрования значений атрибутов объекта UserDetails
.
Метод decryptFields
снабжен аннотацией JPA @postLoad
, поэтому поставщик JPA собирается вызвать этот метод после извлечения сущности. Поэтому мы собираемся использовать метод decrypt Fields
для расшифровки значений атрибутов объекта UserDetails
.
Класс CryptoUtils
находится в моем Высокопроизводительном репозитории Java на GitHub , и для краткости он был опущен.
Для получения более подробной информации об аннотациях @prePersist
, @preUpdate
и @postLoad
JPA также ознакомьтесь с этой статьей .
Время тестирования
При сохранении следующего Пользователя
объекта:
entityManager.persist( new User() .setId(1L) .setUsername("vladmihalcea") .setDetails( new UserDetails() .setFirstName("Vlad") .setLastName("Mihalcea") .setEmailAddress("info@vladmihalcea.com") ) );
Hibernate создает следующую инструкцию SQL INSERT:
INSERT INTO users ( details, username, id ) VALUES ( { "firstName":"3Pj42hikNEQ5Z3gQplc2AQ==", "lastName":"xTC5Ef4MFEhU4/K7a7+WHw==", "emailAddress":"6IuTqZ4e9N80vvutCztnddjNpvuNe/BGn1MrAck3sic=" }, vladmihalcea, 1 )
Обратите внимание, что зашифрованы только значения свойств JSON. Значение столбца details
по-прежнему является допустимым объектом JSON. Если бы мы зашифровали все значение столбца JSON, БД выдала бы нарушение ограничений, поскольку предоставленное зашифрованное строковое значение не было бы допустимым объектом JSON.
При загрузке Пользователя
сущности мы видим, что Сведения о пользователе
свойства правильно расшифрованы:
User user = entityManager.find(User.class,1L); UserDetails userDetails = user.getDetails(); assertEquals("Vlad", userDetails.getFirstName()); assertEquals("Mihalcea", userDetails.getLastName()); assertEquals("info@vladmihalcea.com", userDetails.getEmailAddress());
При обновлении Сведений о пользователе
свойства:
User user = entityManager.find(User.class, 1L); user.getDetails().setEmailAddress("noreply@vladmihalcea.com");
Мы видим, что инструкция UPDATE будет содержать новое значение столбца details
со значением свойства Адрес электронной почты
, содержащим новое зашифрованное значение электронной почты:
UPDATE users SET details = { "firstName":"3Pj42hikNEQ5Z3gQplc2AQ==", "lastName":"xTC5Ef4MFEhU4/K7a7+WHw==", "emailAddress":"JBBe6+rKdNjWdp47rFOy29l1X6vnY3L3R5OhCZGaF74=" } WHERE id = 1
Потрясающе, правда?
Вывод
JPA позволяет очень легко шифровать и расшифровывать свойства JSON благодаря своим методам прослушивания сущностей. И, если вы используете Hibernate, вы можете извлечь выгоду из проекта Hibernate с открытым исходным кодом для сопоставления столбцов JSON, независимо от того, используете ли вы Oracle , SQL Server , PostgreSQL или MySQL .