diff --git a/src/main/java/com/woozuda/backend/note/entity/NoteContent.java b/src/main/java/com/woozuda/backend/note/entity/NoteContent.java index 7abb69d..d5d6ce8 100644 --- a/src/main/java/com/woozuda/backend/note/entity/NoteContent.java +++ b/src/main/java/com/woozuda/backend/note/entity/NoteContent.java @@ -1,7 +1,9 @@ package com.woozuda.backend.note.entity; import com.woozuda.backend.global.entity.BaseTimeEntity; +import com.woozuda.backend.note.entity.converter.NoteContentConverter; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -34,6 +36,7 @@ public class NoteContent extends BaseTimeEntity { private Integer noteOrder; @Column(length = 2000, nullable = false) + @Convert(converter = NoteContentConverter.class) private String content; // 회고 부분 내용 private NoteContent(Integer noteOrder, String content) { diff --git a/src/main/java/com/woozuda/backend/note/entity/converter/AesEncryptor.java b/src/main/java/com/woozuda/backend/note/entity/converter/AesEncryptor.java new file mode 100644 index 0000000..299757f --- /dev/null +++ b/src/main/java/com/woozuda/backend/note/entity/converter/AesEncryptor.java @@ -0,0 +1,50 @@ +package com.woozuda.backend.note.entity.converter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.encrypt.Encryptors; +import org.springframework.security.crypto.encrypt.TextEncryptor; +import org.springframework.security.crypto.keygen.KeyGenerators; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AesEncryptor { + + public static final String SALT_DATA_DELIMITER = "::"; + + private final String password; + + public AesEncryptor(@Value("${aes.password}") String password) { + this.password = password; + } + + public String encrypt(String plainText) { + //무작위 salt 생성 + String salt = KeyGenerators.string().generateKey(); + TextEncryptor encryptor = Encryptors.delux(password, salt); + + // salt를 암호화된 데이터와 함께 저장 + return salt + SALT_DATA_DELIMITER + encryptor.encrypt(plainText); + } + + public String decrypt(String encryptedText) { + // 암호화된 데이터에서 salt 분리 + String[] parts = encryptedText.split(SALT_DATA_DELIMITER, 2); + if (parts.length > 2) { + throw new IllegalArgumentException("Invalid encrypted text format"); + } + if (parts.length == 1) { + return encryptedText; + } + + String salt = parts[0]; + String cipherText = parts[1]; + + TextEncryptor encryptor = Encryptors.delux(password, salt); + + return encryptor.decrypt(cipherText); + } + + +} diff --git a/src/main/java/com/woozuda/backend/note/entity/converter/NoteContentConverter.java b/src/main/java/com/woozuda/backend/note/entity/converter/NoteContentConverter.java new file mode 100644 index 0000000..170a2cb --- /dev/null +++ b/src/main/java/com/woozuda/backend/note/entity/converter/NoteContentConverter.java @@ -0,0 +1,38 @@ +package com.woozuda.backend.note.entity.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import lombok.RequiredArgsConstructor; + +@Converter +@RequiredArgsConstructor +public class NoteContentConverter implements AttributeConverter { + + private final AesEncryptor aesEncryptor; + + @Override + public String convertToDatabaseColumn(String attribute) { + if (attribute == null) { + return null; + } + + try { + return aesEncryptor.encrypt(attribute); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String convertToEntityAttribute(String dbData) { + if (dbData == null) { + return null; + } + + try { + return aesEncryptor.decrypt(dbData); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 94f9269..09451b7 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -92,3 +92,6 @@ management: endpoint: prometheus: enabled: true #default + +aes: + password: "${aes-password}" diff --git a/src/main/resources/application-release.yml b/src/main/resources/application-release.yml index c74e1d3..8f8585f 100644 --- a/src/main/resources/application-release.yml +++ b/src/main/resources/application-release.yml @@ -90,4 +90,7 @@ management: enabled: true #default logging: - config: "classpath:./logback-release.xml" \ No newline at end of file + config: "classpath:./logback-release.xml" + +aes: + password: "${aes_password}" \ No newline at end of file diff --git a/src/test/java/com/woozuda/backend/diary/repository/DiaryRepositoryTest.java b/src/test/java/com/woozuda/backend/diary/repository/DiaryRepositoryTest.java index 1a0275a..2e94f9e 100644 --- a/src/test/java/com/woozuda/backend/diary/repository/DiaryRepositoryTest.java +++ b/src/test/java/com/woozuda/backend/diary/repository/DiaryRepositoryTest.java @@ -3,12 +3,14 @@ import com.woozuda.backend.account.entity.UserEntity; import com.woozuda.backend.diary.dto.response.SingleDiaryResponseDto; import com.woozuda.backend.diary.entity.Diary; +import com.woozuda.backend.note.entity.converter.AesEncryptor; import com.woozuda.backend.tag.entity.Tag; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -18,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest +@MockBean(AesEncryptor.class) @Transactional class DiaryRepositoryTest { diff --git a/src/test/java/com/woozuda/backend/note/repository/NoteRepositoryTest.java b/src/test/java/com/woozuda/backend/note/repository/NoteRepositoryTest.java index 59a2baf..172a094 100644 --- a/src/test/java/com/woozuda/backend/note/repository/NoteRepositoryTest.java +++ b/src/test/java/com/woozuda/backend/note/repository/NoteRepositoryTest.java @@ -4,7 +4,12 @@ import com.woozuda.backend.diary.entity.Diary; import com.woozuda.backend.note.dto.request.NoteCondRequestDto; import com.woozuda.backend.note.dto.response.NoteResponseDto; -import com.woozuda.backend.note.entity.*; +import com.woozuda.backend.note.entity.CommonNote; +import com.woozuda.backend.note.entity.NoteContent; +import com.woozuda.backend.note.entity.QuestionNote; +import com.woozuda.backend.note.entity.RetrospectiveNote; +import com.woozuda.backend.note.entity.converter.AesEncryptor; +import com.woozuda.backend.note.entity.converter.NoteContentConverter; import com.woozuda.backend.question.entity.Question; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -12,19 +17,35 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; import static com.woozuda.backend.account.entity.AiType.PICTURE_NOVEL; -import static com.woozuda.backend.note.entity.type.Feeling.*; -import static com.woozuda.backend.note.entity.type.Framework.*; +import static com.woozuda.backend.note.entity.type.Feeling.ANGER; +import static com.woozuda.backend.note.entity.type.Feeling.CONTENT; +import static com.woozuda.backend.note.entity.type.Feeling.DISSATISFACTION; +import static com.woozuda.backend.note.entity.type.Feeling.JOY; +import static com.woozuda.backend.note.entity.type.Feeling.NEUTRAL; +import static com.woozuda.backend.note.entity.type.Feeling.SADNESS; +import static com.woozuda.backend.note.entity.type.Feeling.TIREDNESS; +import static com.woozuda.backend.note.entity.type.Framework.FOUR_F_S; +import static com.woozuda.backend.note.entity.type.Framework.KPT; +import static com.woozuda.backend.note.entity.type.Framework.PMI; +import static com.woozuda.backend.note.entity.type.Framework.SCS; import static com.woozuda.backend.note.entity.type.Season.FALL; import static com.woozuda.backend.note.entity.type.Season.WINTER; import static com.woozuda.backend.note.entity.type.Visibility.PRIVATE; import static com.woozuda.backend.note.entity.type.Visibility.PUBLIC; -import static com.woozuda.backend.note.entity.type.Weather.*; +import static com.woozuda.backend.note.entity.type.Weather.CLEAR; +import static com.woozuda.backend.note.entity.type.Weather.CLOUDY; +import static com.woozuda.backend.note.entity.type.Weather.RAIN; +import static com.woozuda.backend.note.entity.type.Weather.SNOW; +import static com.woozuda.backend.note.entity.type.Weather.SUNNY; +import static com.woozuda.backend.note.entity.type.Weather.THUNDERSTORM; import static java.time.Month.DECEMBER; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +53,19 @@ @Transactional class NoteRepositoryTest { + @TestConfiguration + static class NoteContentTestConfig { + @Bean + public AesEncryptor aesEncryptor() { + return new AesEncryptor("test-password"); + } + + @Bean + public NoteContentConverter noteContentConverter(AesEncryptor aesEncryptor) { + return new NoteContentConverter(aesEncryptor); + } + } + LocalDate date1 = LocalDate.of(2024, DECEMBER, 5); LocalDate date2 = LocalDate.of(2024, DECEMBER, 6); diff --git a/src/test/java/com/woozuda/backend/note/repository/QuestionRepositoryTest.java b/src/test/java/com/woozuda/backend/note/repository/QuestionRepositoryTest.java index bffee8a..5cb78be 100644 --- a/src/test/java/com/woozuda/backend/note/repository/QuestionRepositoryTest.java +++ b/src/test/java/com/woozuda/backend/note/repository/QuestionRepositoryTest.java @@ -1,5 +1,6 @@ package com.woozuda.backend.note.repository; +import com.woozuda.backend.note.entity.converter.AesEncryptor; import com.woozuda.backend.question.entity.Question; import com.woozuda.backend.question.repository.QuestionRepository; import org.junit.jupiter.api.DisplayName; @@ -7,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -14,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest +@MockBean(AesEncryptor.class) @Transactional class QuestionRepositoryTest { diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 4115cba..ac90476 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -71,4 +71,7 @@ openai: chat: options: model: "gpt-3.5-turbo" # 사용할 AI 모델 (예: gpt-3.5-turbo) - temperature: 0.6 # 생성되는 텍스트의 다양성 정도를 조절하는 파라미터 (0.0 ~ 1.0) \ No newline at end of file + temperature: 0.6 # 생성되는 텍스트의 다양성 정도를 조절하는 파라미터 (0.0 ~ 1.0) + +aes: + password: "aes_password" \ No newline at end of file