리플렉션 어노테이션 방식 (이전글 2.2 참조)로 
mongoDB 필드를 암호화하면, mongoTemplate.save에는 적용되지만, mongoTemplate.update에는 반영되지 않는다.

이걸 우회하는 방법으로 새로운 암호화전용 mongoTemplate을 만들 수 있는데

@Component
public class EncryptingMongoTemplate {

    private final MongoTemplate mongoTemplate;
    private final IpoCreditMasterService cryptoService;

    // 캐시: 엔티티별 암호화 필드명 목록
    private final ConcurrentMap<Class<?>, Set<String>> encryptedFieldCache = new ConcurrentHashMap<>();

    public EncryptingMongoTemplate(MongoTemplate mongoTemplate,
                                   IpoCreditMasterService cryptoService) {
        this.mongoTemplate = mongoTemplate;
        this.cryptoService = cryptoService;
    }

    // === 외부에서 이 메서드들을 써서 update === //

    public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass) {
        encryptUpdateFields(entityClass, update);
        return mongoTemplate.updateFirst(query, update, entityClass);
    }

    public UpdateResult updateMulti(Query query, Update update, Class<?> entityClass) {
        encryptUpdateFields(entityClass, update);
        return mongoTemplate.updateMulti(query, update, entityClass);
    }

    public UpdateResult upsert(Query query, Update update, Class<?> entityClass) {
        encryptUpdateFields(entityClass, update);
        return mongoTemplate.upsert(query, update, entityClass);
    }

    // 필요하면 find, save 등도 래핑해서 여기에 모아줄 수 있음

    // === 핵심: Update 안의 @EncryptedOrNotText 필드 값 암호화 === //

    private void encryptUpdateFields(Class<?> entityClass, Update update) {
        if (update == null) return;

        Set<String> encryptedFields = getEncryptedFieldNames(entityClass);
        if (encryptedFields.isEmpty()) return;

        // Spring Data Mongo의 Update는 내부에 Document를 가지고 있음
        Document root = update.getUpdateObject();

        // 여기서는 .set()만 쓴다고 하셨으니 $set만 처리
        Document setDoc = (Document) root.get("$set");
        if (setDoc == null) {
            return;
        }

        for (String fieldName : encryptedFields) {
            if (!setDoc.containsKey(fieldName)) continue;

            Object raw = setDoc.get(fieldName);
            if (raw instanceof String str) {
                setDoc.put(fieldName, cryptoService.encrypt(str));
            }
        }
    }

    private Set<String> getEncryptedFieldNames(Class<?> entityClass) {
        return encryptedFieldCache.computeIfAbsent(entityClass, this::scanEncryptedFields);
    }

    private Set<String> scanEncryptedFields(Class<?> type) {
        Set<String> result = new HashSet<>();
        Class<?> current = type;
        while (current != null && current != Object.class) {
            for (Field field : current.getDeclaredFields()) {
                if (field.isAnnotationPresent(EncryptedOrNotText.class)
                        && field.getType() == String.class) {
                    // @Field("otherName") 고려 안 한 버전
                    result.add(field.getName());
                }
            }
            current = current.getSuperclass();
        }
        return result;
    }
}

 

 

사용예제:

public class UserService {

    private final EncryptingMongoTemplate encryptMt;

    public void updateUserPhone(String userId, String newPhone) {
        Query q = Query.query(Criteria.where("_id").is(userId));
        Update u = new Update()
                .set("phone", newPhone); // phone이 @EncryptedOrNotText

        encryptMt.updateFirst(q, u, UserEntity.class);
        // → 호출 전에 EncryptingMongoTemplate이 newPhone을 encrypt해서 MongoDB에 저장
    }
}
Posted by yongary
,