From 74302df5a21f059245a0f1f6430766a4027ab941 Mon Sep 17 00:00:00 2001 From: curry772 Date: Thu, 14 May 2026 13:34:55 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20decryptField=20toPath=3D'/'=20=EC=8B=9C?= =?UTF-8?q?=20=EA=B8=B0=EC=A1=B4=20=ED=95=84=EB=93=9C=20=EB=B3=B4=EC=A1=B4?= =?UTF-8?q?=20-=20mergeAtRoot=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit toPath='/'일 때 복호화된 텍스트로 body 전체를 교체하던 동작을 JsonPathUtil.mergeAtRoot 를 통해 fromPath 필드만 제거하고 복호화된 JSON을 body에 병합하는 방식으로 수정. - AbstractCryptoFilter.decryptField: return plainText → mergeAtRoot 호출 - JsonPathUtil.mergeAtRoot: 신규 메서드 추가 - InCryptoFilterTest: 4-4 병합 동작 검증 테스트 추가, 4-2 설명 수정 Co-Authored-By: Claude Sonnet 4.6 --- .../http/filter/AbstractCryptoFilter.java | 4 +- .../com/eactive/eai/util/JsonPathUtil.java | 48 +++++++++++++++++++ .../dynamic/filter/InCryptoFilterTest.java | 25 ++++++++-- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/eactive/eai/adapter/http/filter/AbstractCryptoFilter.java b/src/main/java/com/eactive/eai/adapter/http/filter/AbstractCryptoFilter.java index dd36072..59fba34 100644 --- a/src/main/java/com/eactive/eai/adapter/http/filter/AbstractCryptoFilter.java +++ b/src/main/java/com/eactive/eai/adapter/http/filter/AbstractCryptoFilter.java @@ -63,7 +63,7 @@ public abstract class AbstractCryptoFilter { /** * fromPath의 Base64 값을 복호화하여 toPath에 반영. - * toPath = "/" → 복호화된 텍스트로 body 전체 교체. + * toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합. */ protected String decryptField(String body, String moduleName, Map runtimeCtx, byte[] aad, String fromPath, String toPath) throws Exception { @@ -76,7 +76,7 @@ public abstract class AbstractCryptoFilter { byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes); String plainText = new String(plainBytes, StandardCharsets.UTF_8); if (PATH_ROOT.equals(toPath)) { - return plainText; + return JsonPathUtil.mergeAtRoot(body, fromPath, plainText); } return JsonPathUtil.setValueAtPath(body, toPath, plainText, false); } diff --git a/src/main/java/com/eactive/eai/util/JsonPathUtil.java b/src/main/java/com/eactive/eai/util/JsonPathUtil.java index 845d7af..d7fc69d 100644 --- a/src/main/java/com/eactive/eai/util/JsonPathUtil.java +++ b/src/main/java/com/eactive/eai/util/JsonPathUtil.java @@ -101,6 +101,54 @@ public class JsonPathUtil { return currentNode; } + /** + * body에서 removeFromPath 필드를 제거한 뒤, mergeJson의 필드를 body에 병합하여 반환한다. + * + * body와 mergeJson이 모두 JSON 객체인 경우에만 병합을 수행한다. + * 어느 한쪽이 JSON 객체가 아니면 mergeJson을 그대로 반환한다 (전체 교체 fallback). + * + * 사용 예 (toPath="/" 복호화): + * body = {"encryptedData":"...","requestId":"REQ001"} + * removeFrom = /encryptedData + * mergeJson = {"userId":"U001","name":"test"} + * 결과 = {"requestId":"REQ001","userId":"U001","name":"test"} + * + * @param body 원본 JSON 문자열 + * @param removeFromPath 제거할 필드의 경로 (예: /encryptedData) + * @param mergeJson 병합할 JSON 문자열 + * @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환 + */ + public static String mergeAtRoot(String body, String removeFromPath, String mergeJson) { + try { + JsonNode bodyNode = objectMapper.readTree(body); + JsonNode mergeNode = objectMapper.readTree(mergeJson); + + if (!bodyNode.isObject() || !mergeNode.isObject()) { + return mergeJson; + } + + ObjectNode result = (ObjectNode) bodyNode.deepCopy(); + + // removeFromPath 필드 제거 + int lastSlash = removeFromPath.lastIndexOf('/'); + String fieldName = removeFromPath.substring(lastSlash + 1); + if (lastSlash == 0) { + result.remove(fieldName); + } else { + JsonNode parent = getNodeAtPath(result, removeFromPath.substring(0, lastSlash)); + if (parent instanceof ObjectNode) { + ((ObjectNode) parent).remove(fieldName); + } + } + + // mergeJson 필드를 body에 병합 (기존 필드 덮어쓰기) + result.setAll((ObjectNode) mergeNode); + return objectMapper.writeValueAsString(result); + } catch (Exception e) { + return mergeJson; + } + } + public static void main(String[] args) { String jsonString = "{ \"person\": { \"name\": \"John\", \"age\": 30, \"groups\": [ { \"id\": 1, \"name\": \"Group A\" }, { \"id\": 2, \"name\": \"Group B\" }, { \"id\": 3, \"name\": \"Group C\" } ] } }"; diff --git a/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java b/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java index 334278f..66ba5de 100644 --- a/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java +++ b/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java @@ -169,14 +169,33 @@ class InCryptoFilterTest { } @Test - @DisplayName("4-2. FIELD scope, toPath='/' — 복호화된 텍스트로 body 전체 교체") - void testPreFilter_field_decryptToRoot() throws Exception { + @DisplayName("4-2. FIELD scope, toPath='/' — 복호화 결과가 비-JSON이면 텍스트 그대로 반환 (fallback)") + void testPreFilter_field_decryptToRoot_nonJsonFallback() throws Exception { String body = "{\"enc\":\"" + CIPHER_B64 + "\"}"; Properties prop = fieldDecryptProps("/enc", "/"); Object result = filter.doPreFilter("g", "a", body, prop, mockRequest, mockResponse); - assertEquals(PLAIN_STR, result, "toPath='/'이면 body 전체가 복호화된 텍스트로 교체되어야 한다"); + assertEquals(PLAIN_STR, result, "복호화 결과가 JSON 객체가 아니면 텍스트 그대로 반환"); + } + + @Test + @DisplayName("4-4. FIELD scope, toPath='/' — 복호화된 JSON을 body에 병합 (fromPath 필드 제거)") + void testPreFilter_field_decryptToRoot_mergesJson() throws Exception { + String decryptedJson = "{\"userId\":\"U001\",\"name\":\"test\"}"; + when(mockService.decrypt(anyString(), anyMap(), any(), any(byte[].class))) + .thenReturn(decryptedJson.getBytes(StandardCharsets.UTF_8)); + + String body = "{\"encryptedData\":\"" + CIPHER_B64 + "\",\"requestId\":\"REQ001\"}"; + Properties prop = fieldDecryptProps("/encryptedData", "/"); + + Object result = filter.doPreFilter("g", "a", body, prop, mockRequest, mockResponse); + + String r = result.toString(); + assertFalse(r.contains("encryptedData"), "암호화 필드(encryptedData)는 제거되어야 한다"); + assertTrue(r.contains("\"requestId\":\"REQ001\""), "원본 body의 다른 필드는 유지되어야 한다"); + assertTrue(r.contains("\"userId\":\"U001\""), "복호화된 JSON 필드가 병합되어야 한다"); + assertTrue(r.contains("\"name\":\"test\""), "복호화된 JSON 필드가 병합되어야 한다"); } @Test