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();
}
}
}



