프로젝트/단기 프로젝트

[BetterDay] #2. 일기 내용을 암호화하기 (Converter, @DataJpaTest)

Chipmunks 2024. 6. 23. 16:19
728x90

최근에 올린 강의 글을 프로젝트에 적용한 결과다.

 

[JPA] 민감한 데이터 암호화 / 마스킹하기 (Attribute Converter, @Converter)

어느 여름 낮.시원한 에어컨 아래, 냉커피를 마시며,막바지 API 개발을 하고 있는 다람쥐 사원. 그러던 중 메신저 알림이 울립니다. PM님 : 람쥐님 안녕하세요~ 😊오전에 전달주신 기능들 테스트

itchipmunk.tistory.com

 

민감한 정보가 들어갈 수 있는

일기 내용을 데이터베이스에 암호화했다.

그리고 데이터베이스에서 가져올 때 복호화를 진행해 응답한다.

 

Attribute Converter 와 AES 암호화 모듈을 사용했다.

@DataJpaTest 으로 JPA 관련 설정을 불러와 이를 테스트했다.

 

모듈 구조

 

모듈 구조는 위와 같습니다.

adapter-postgres 에서 JPA 기술로 엔티티를 구현합니다.

ContentConverter 라는 이름으로 일기 내용을 암호화 / 복호화합니다.

이는 외부 PostgreSQL 데이터베이스와 통신합니다.

 

adapter-core 모듈에 PrivacyEncryptor 인터페이스를 정의했습니다.

같은 adapter 모듈 안에선 PrivacyEncryptor 인터페이스를 주입받습니다.

주입받는 객체는 런타임 시에 외부 보안 모듈에 정의된 객체가 됩니다.

 

// adapter/core/src/main/java/com/mashup/feelring/PrivacyEncryptor.java

package com.mashup.feelring;

public interface PrivacyEncryptor {
    String encrypt(String raw) throws Exception;
    String decrypt(String encrypted) throws Exception;
}

 

위 구조는 의존성 역전의 이점을 살렸습니다.

계층(레이어) 레벨은 아니지만, 모듈 레벨에서 의존성을 분리하고

의존성을 역전시켜 확장이 용이한 구조로 개선했습니다.

 

일기 내용을 암호화 / 복호화하는 ContentConverter

ContentConverter 코드는 아래와 같습니다.

 

package com.mashup.feelring.diary.converter;

import com.mashup.feelring.PrivacyEncryptor;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Converter
public class ContentConverter implements AttributeConverter<String, String> {

    private final PrivacyEncryptor privacyEncryptor;

    @Override
    public String convertToDatabaseColumn(String raw) {
        try {
            return privacyEncryptor.encrypt(raw);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String convertToEntityAttribute(String encrypted) {
        try {
            return privacyEncryptor.decrypt(encrypted);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

 

이전 강의글과 크게 다르지 않는 코드입니다.

이를 아래 엔티티 필드에 적용합니다.

 

package com.mashup.feelring.diary;

import com.mashup.feelring.diary.converter.ContentConverter;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity(name = "diary")
public class DiaryEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Convert(converter = ContentConverter.class)
    private String content;
    ....
}

 

테스트 코드 또한 강의 글과 크게 다르지 않습니다.

 

package com.mashup.feelring.diary.converter;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

import com.mashup.feelring.PrivacyEncryptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ContentConverterTest {

    @InjectMocks
    private ContentConverter contentConverter;

    @Mock
    private PrivacyEncryptor privacyEncryptor;

    @Test
    void convertToDatabaseColumn() throws Exception {
        final String privacyData = "사적이고 민감한 데이터입니다.";
        final String expected = "암호화한 데이터";

        when(privacyEncryptor.encrypt(privacyData)).thenReturn(expected);

        final String encrypted = this.contentConverter.convertToDatabaseColumn(privacyData);

        assertEquals(expected, encrypted);
    }

    @Test
    void convertToEntityAttribute() throws Exception {
        final String encrypted = "암호화한 데이터";
        final String expected = "사적이고 민감한 데이터입니다.";

        when(privacyEncryptor.decrypt(encrypted)).thenReturn(expected);

        final String actual = this.contentConverter.convertToEntityAttribute(encrypted);

        assertEquals(expected, actual);
    }
}

 

마무리

모듈 단위에서 의존성 역전을 적용해 확장이 용이한 구조로 만들었습니다.

멀티 모듈로 만들다보니 어떻게 해야 확장이 용이할까? 를 고민하게 되네요.

암호화 모듈도 AES 뿐 아니라 다른 암호화 모듈도 추가될 가능성이 높다고 가정했습니다.

 

데이터베이스 엔티티 모듈도 PostgreSQL 뿐 아니라 MySQL, MongoDB, Redis 등도 될 수 있고요.

기존 코드를 변경하지 않고 모듈을 추가해 확장하는 식으로 설계 정책을 정하니,

코드 작성에 집중하기가 간편해졌네요.

또한 원하는 모듈을 가져와 사용하는 재미도 있습니다.

 

다음 글은 다른 내용으로 찾아뵙겠습니다.