1. Postgres + JPA: AttributeConverter 예시 (OK)

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String attribute) {
        if (attribute == null) return null;
        return IpoCreditMasterService.encrypt(attribute);
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        if (dbData == null) return null;
        try {
            return IpoCreditMasterService.decrypt(dbData);
        } catch (Exception e) {
            // 섞인 데이터 방어
            return dbData;
        }
    }
}

 

 

@Entity
public class EncTest {

    @Id
    private String 계약계정;

    private int yymm;

    @Convert(converter = EncryptedStringConverter.class)
    private String 고객이름;
}

 

 

2. mongoDB 에는 위 방식은 적용안되고, 유사방식 추천: Mongo용 Converter 두 개

(이것도 필요없음: 아래 2-2 방식이 더 좋음)

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;

@WritingConverter
public class EncryptedStringWritingConverter implements Converter<String, String> {
    @Override
    public String convert(String source) {
        if (source == null) return null;
        return CryptoUtil.encrypt(source);
    }
}

@ReadingConverter
public class EncryptedStringReadingConverter implements Converter<String, String> {
    @Override
    public String convert(String source) {
        if (source == null) return null;
        try {
            return CryptoUtil.decrypt(source);
        } catch (Exception e) {
            // 섞인 데이터 방어
            return source;
        }
    }
}


2-1 mongoDB 설정 변경

(이건 전체 설정용이므로, 필드 설정에선 필요없음)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;

import java.util.List;

//@Configuration
//public class MongoConfig {

//    @Bean
//    public MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions(List.of(
                new EncryptedStringWritingConverter(),
                new EncryptedStringReadingConverter()
        ));
    }
//}

 

2-2 위는 모든 필드 적용이므로, mongoDB 특정필드에만 적용 방식
  (A: 어노테이션 하나 추가)

package com.vwsai.project2.entity.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)          // 필드에만 붙이기
@Retention(RetentionPolicy.RUNTIME) // 런타임에 리플렉션으로 읽어야 하니까 RUNTIME
public @interface EncryptedOrNotText {
}


  (B: 리스터 구현)

import com.vwsai.project2.entity.annotation.EncryptedOrNotText;
// import com.vwsai.project2.entity.annotation.EncryptedText;  // 이건 더 안 쓸 거면 삭제

@Component
@Slf4j
public class EncryptionMongoEventListener extends AbstractMongoEventListener<Object> {

    private final IpoCreditMasterService cryptoService;

    public EncryptionMongoEventListener(IpoCreditMasterService cryptoService) {
        this.cryptoService = cryptoService;
    }

    @Override
    public void onBeforeConvert(BeforeConvertEvent<Object> event) {
        processFields(event.getSource(), true);
    }

    @Override
    public void onAfterConvert(AfterConvertEvent<Object> event) {
        processFields(event.getSource(), false);
    }

    private void processFields(Object entity, boolean encrypt) {
        if (entity == null) return;

        Class<?> current = entity.getClass();
        while (current != null && current != Object.class) {
            for (Field field : current.getDeclaredFields()) {

                // 🔽 여기 어노테이션 타입만 바꿔주면 됨
                if (!field.isAnnotationPresent(EncryptedOrNotText.class)) {
                    continue;
                }
                if (field.getType() != String.class) {
                    continue;
                }

                field.setAccessible(true);
                try {
                    String value = (String) field.get(entity);
                    if (value == null || value.isEmpty()) {
                        continue;
                    }

                    String processed;
                    if (encrypt) {
                        processed = cryptoService.encrypt(value);
                    } else {
                        try {
                            processed = cryptoService.decrypt(value);
                        } catch (Exception e) {
                            log.error("복호화 실패: field={}, value={}", field.getName(), value);
                            processed = value; // 섞인 데이터 방어
                        }
                    }
                    field.set(entity, processed);
                } catch (Exception e) {
                    log.error("암/복호화 처리 실패: field={}", field.getName(), e);
                }
            }
            current = current.getSuperclass();
        }
    }
}

 

Posted by yongary
,