Рубрики
Без рубрики

Как зашифровать и расшифровать свойства JSON с помощью JPA

Узнайте, как шифровать и расшифровывать свойства JSON при использовании прослушивателей сущностей JPA и проекта с открытым исходным кодом Hibernate Types.

Автор оригинала: 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 .