fix: decryptField toPath='/' 시 기존 필드 보존 - mergeAtRoot 연동
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 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ public abstract class AbstractCryptoFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* fromPath의 Base64 값을 복호화하여 toPath에 반영.
|
* fromPath의 Base64 값을 복호화하여 toPath에 반영.
|
||||||
* toPath = "/" → 복호화된 텍스트로 body 전체 교체.
|
* toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
|
||||||
*/
|
*/
|
||||||
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||||
byte[] aad, String fromPath, String toPath) throws Exception {
|
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);
|
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
|
||||||
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
|
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
|
||||||
if (PATH_ROOT.equals(toPath)) {
|
if (PATH_ROOT.equals(toPath)) {
|
||||||
return plainText;
|
return JsonPathUtil.mergeAtRoot(body, fromPath, plainText);
|
||||||
}
|
}
|
||||||
return JsonPathUtil.setValueAtPath(body, toPath, plainText, false);
|
return JsonPathUtil.setValueAtPath(body, toPath, plainText, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,54 @@ public class JsonPathUtil {
|
|||||||
return currentNode;
|
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) {
|
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\" } ] } }";
|
String jsonString = "{ \"person\": { \"name\": \"John\", \"age\": 30, \"groups\": [ { \"id\": 1, \"name\": \"Group A\" }, { \"id\": 2, \"name\": \"Group B\" }, { \"id\": 3, \"name\": \"Group C\" } ] } }";
|
||||||
|
|
||||||
|
|||||||
@@ -169,14 +169,33 @@ class InCryptoFilterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("4-2. FIELD scope, toPath='/' — 복호화된 텍스트로 body 전체 교체")
|
@DisplayName("4-2. FIELD scope, toPath='/' — 복호화 결과가 비-JSON이면 텍스트 그대로 반환 (fallback)")
|
||||||
void testPreFilter_field_decryptToRoot() throws Exception {
|
void testPreFilter_field_decryptToRoot_nonJsonFallback() throws Exception {
|
||||||
String body = "{\"enc\":\"" + CIPHER_B64 + "\"}";
|
String body = "{\"enc\":\"" + CIPHER_B64 + "\"}";
|
||||||
Properties prop = fieldDecryptProps("/enc", "/");
|
Properties prop = fieldDecryptProps("/enc", "/");
|
||||||
|
|
||||||
Object result = filter.doPreFilter("g", "a", body, prop, mockRequest, mockResponse);
|
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
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user