feat: InCryptoFilter / OutCryptoFilter 추가 및 암호화 필터 계층 재구성
- AbstractCryptoFilter (신규, http.filter 패키지): 공통 암복호화 로직 및 상수 분리 - InCryptoFilter (신규, dynamic.filter): HttpAdapterFilter 구현 - 수신 요청 복호화, 응답 암호화 - OutCryptoFilter (신규, client.impl.filter): HttpClientAdapterFilter 구현 - 송신 요청 암호화, 응답 복호화 - BaseCryptoFilter, CryptoFilter 제거 → InCryptoFilter로 통합 - CryptoFilterHsmIntegrationTest: 새 클래스명으로 업데이트 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+268
@@ -0,0 +1,268 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
|
||||
import com.eactive.eai.common.security.CryptoModuleService;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
|
||||
/**
|
||||
* OutCryptoFilter 단위 테스트.
|
||||
*
|
||||
* doPreFilter = 외부로 보내는 요청 암호화 (InCryptoFilter와 반대 방향)
|
||||
* doPostFilter = 외부에서 받은 응답 복호화
|
||||
*/
|
||||
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
||||
class OutCryptoFilterTest {
|
||||
|
||||
private static final String MODULE = "TEST_MODULE";
|
||||
private static final String PLAIN_STR = "plain-request-body";
|
||||
private static final byte[] PLAIN = PLAIN_STR.getBytes(StandardCharsets.UTF_8);
|
||||
private static final byte[] CIPHER = new byte[]{0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
|
||||
private static final String CIPHER_B64 = Base64.getEncoder().encodeToString(CIPHER);
|
||||
|
||||
private static GenericApplicationContext ctx;
|
||||
private static CryptoModuleService mockService;
|
||||
private static OutCryptoFilter filter;
|
||||
|
||||
private Properties tempProp;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpClass() throws Exception {
|
||||
mockService = mock(CryptoModuleService.class);
|
||||
|
||||
ctx = new GenericApplicationContext();
|
||||
ctx.getBeanFactory().registerSingleton("cryptoModuleService", mockService);
|
||||
ctx.registerBeanDefinition("applicationContextProvider",
|
||||
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
||||
.getBeanDefinition());
|
||||
ctx.refresh();
|
||||
|
||||
filter = new OutCryptoFilter();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownClass() {
|
||||
if (ctx != null) ctx.close();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
reset(mockService);
|
||||
tempProp = new Properties();
|
||||
|
||||
when(mockService.decrypt(anyString(), anyMap(), any(), any(byte[].class))).thenReturn(PLAIN);
|
||||
when(mockService.encrypt(anyString(), anyMap(), any(), any(byte[].class))).thenReturn(CIPHER);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 1. buildAad
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("1-1. buildAad — CRYPTO_AAD_PROP 미설정 → null")
|
||||
void testBuildAad_noProp_returnsNull() {
|
||||
assertNull(filter.buildAad(new Properties(), tempProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-2. buildAad — CRYPTO_AAD_PROP 설정, tempProp에 값 없음 → null")
|
||||
void testBuildAad_propSet_noTempValue_returnsNull() {
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(OutCryptoFilter.PROP_AAD_PROP, "aadKey");
|
||||
|
||||
assertNull(filter.buildAad(prop, tempProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("1-3. buildAad — CRYPTO_AAD_PROP 설정, tempProp에 값 있음 → UTF-8 바이트 반환")
|
||||
void testBuildAad_propSet_tempValuePresent_returnsByte() {
|
||||
String aadVal = "aad-value-xyz";
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(OutCryptoFilter.PROP_AAD_PROP, "aadKey");
|
||||
tempProp.setProperty("aadKey", aadVal);
|
||||
|
||||
assertArrayEquals(aadVal.getBytes(StandardCharsets.UTF_8), filter.buildAad(prop, tempProp));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 2. doPreFilter — 암호화 (요청을 외부로 보내기 전)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("2-1. BODY scope — 평문 암호화 → Base64 반환")
|
||||
void testPreFilter_body_encrypt() throws Exception {
|
||||
Object result = filter.doPreFilter("g", "a", bodyEncryptProps(), PLAIN_STR, tempProp);
|
||||
|
||||
assertEquals(CIPHER_B64, result);
|
||||
verify(mockService).encrypt(eq(MODULE), anyMap(), isNull(), eq(PLAIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-2. FIELD scope, fromPath='/' — body 전체 암호화 → toPath 필드명으로 래핑")
|
||||
void testPreFilter_field_encryptFromRoot() throws Exception {
|
||||
String body = "{\"userId\":\"U001\"}";
|
||||
Properties prop = fieldEncryptProps("/", "/encryptedData");
|
||||
|
||||
Object result = filter.doPreFilter("g", "a", prop, body, tempProp);
|
||||
|
||||
assertEquals("{\"encryptedData\":\"" + CIPHER_B64 + "\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-3. FIELD scope — fromPath 평문 암호화 → toPath Base64 반영")
|
||||
void testPreFilter_field_encryptSpecificPath() throws Exception {
|
||||
String body = "{\"data\":\"secret\",\"enc\":\"\"}";
|
||||
Properties prop = fieldEncryptProps("/data", "/enc");
|
||||
|
||||
Object result = filter.doPreFilter("g", "a", prop, body, tempProp);
|
||||
|
||||
assertTrue(result.toString().contains("\"enc\":\"" + CIPHER_B64 + "\""),
|
||||
"toPath에 Base64 암호문이 반영되어야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-4. FIELD scope — fromPath 값 없음 → body 그대로 반환")
|
||||
void testPreFilter_field_missingFromPath_passThrough() throws Exception {
|
||||
String body = "{\"other\":\"value\"}";
|
||||
Properties prop = fieldEncryptProps("/data", "/enc");
|
||||
|
||||
Object result = filter.doPreFilter("g", "a", prop, body, tempProp);
|
||||
|
||||
assertEquals(body, result);
|
||||
verifyNoInteractions(mockService);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("2-5. CRYPTO_MODULE_NAME 누락 — IllegalArgumentException")
|
||||
void testPreFilter_missingModuleName_throwsException() {
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_SCOPE, "BODY");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> filter.doPreFilter("g", "a", prop, PLAIN_STR, tempProp));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 3. doPostFilter — 복호화 (외부로부터 응답 받은 후)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("3-1. BODY scope — Base64 암호문 복호화 → 평문 반환")
|
||||
void testPostFilter_body_decrypt() throws Exception {
|
||||
Object result = filter.doPostFilter("g", "a", bodyDecryptProps(), CIPHER_B64, tempProp);
|
||||
|
||||
assertEquals(PLAIN_STR, result);
|
||||
verify(mockService).decrypt(eq(MODULE), anyMap(), isNull(), eq(CIPHER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-2. FIELD scope — fromPath 복호화 → toPath 반영")
|
||||
void testPostFilter_field_decryptToPath() throws Exception {
|
||||
String body = "{\"enc\":\"" + CIPHER_B64 + "\",\"result\":\"\"}";
|
||||
Properties prop = fieldDecryptProps("/enc", "/result");
|
||||
|
||||
Object result = filter.doPostFilter("g", "a", prop, body, tempProp);
|
||||
|
||||
assertTrue(result.toString().contains("\"result\":\"" + PLAIN_STR + "\""),
|
||||
"toPath에 복호화된 값이 반영되어야 한다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-3. FIELD scope, toPath='/' — 복호화된 텍스트로 body 전체 교체")
|
||||
void testPostFilter_field_decryptToRoot() throws Exception {
|
||||
String body = "{\"enc\":\"" + CIPHER_B64 + "\"}";
|
||||
Properties prop = fieldDecryptProps("/enc", "/");
|
||||
|
||||
Object result = filter.doPostFilter("g", "a", prop, body, tempProp);
|
||||
|
||||
assertEquals(PLAIN_STR, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-4. FIELD scope — fromPath 값 없음 → body 그대로 반환")
|
||||
void testPostFilter_field_missingFromPath_passThrough() throws Exception {
|
||||
String body = "{\"other\":\"value\"}";
|
||||
Properties prop = fieldDecryptProps("/enc", "/result");
|
||||
|
||||
Object result = filter.doPostFilter("g", "a", prop, body, tempProp);
|
||||
|
||||
assertEquals(body, result);
|
||||
verifyNoInteractions(mockService);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 4. AAD (tempProp 경유)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("4-1. doPreFilter BODY — CRYPTO_AAD_PROP 설정 시 tempProp 값을 AAD로 전달")
|
||||
void testPreFilter_body_aadFromTempProp() throws Exception {
|
||||
String aadVal = "out-aad-value";
|
||||
Properties prop = bodyEncryptProps();
|
||||
prop.setProperty(OutCryptoFilter.PROP_AAD_PROP, "myAadKey");
|
||||
tempProp.setProperty("myAadKey", aadVal);
|
||||
|
||||
filter.doPreFilter("g", "a", prop, PLAIN_STR, tempProp);
|
||||
|
||||
verify(mockService).encrypt(eq(MODULE), anyMap(),
|
||||
eq(aadVal.getBytes(StandardCharsets.UTF_8)), any(byte[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("4-2. doPostFilter BODY — CRYPTO_AAD_PROP 미설정 시 aad=null로 전달")
|
||||
void testPostFilter_body_noAadProp_nullAad() throws Exception {
|
||||
filter.doPostFilter("g", "a", bodyDecryptProps(), CIPHER_B64, tempProp);
|
||||
|
||||
verify(mockService).decrypt(eq(MODULE), anyMap(), isNull(), any(byte[].class));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼
|
||||
// =========================================================================
|
||||
|
||||
private Properties bodyEncryptProps() {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "BODY");
|
||||
return p;
|
||||
}
|
||||
|
||||
private Properties bodyDecryptProps() {
|
||||
return bodyEncryptProps();
|
||||
}
|
||||
|
||||
private Properties fieldEncryptProps(String fromPath, String toPath) {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "FIELD");
|
||||
p.setProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH, fromPath);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH, toPath);
|
||||
return p;
|
||||
}
|
||||
|
||||
private Properties fieldDecryptProps(String fromPath, String toPath) {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "FIELD");
|
||||
p.setProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH, fromPath);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH, toPath);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
+22
-20
@@ -34,6 +34,8 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.InCryptoFilter;
|
||||
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
|
||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||
import com.eactive.eai.common.hsm.HsmManager;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
@@ -51,7 +53,7 @@ import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
|
||||
* CryptoFilter HSM 연동 통합 테스트 (SoftHSM2 / SunPKCS11)
|
||||
*
|
||||
* SoftHSM2에서 MASTER_KEY를 조회하여 키를 도출한 뒤
|
||||
* BaseCryptoFilter / CryptoFilter 의 암복호화 전체 흐름을 검증한다.
|
||||
* InCryptoFilter 의 암복호화 전체 흐름을 검증한다.
|
||||
*
|
||||
* 사전 조건:
|
||||
* C:\SoftHSM2 에 SoftHSM2 설치 (미설치 시 자동 건너뜀)
|
||||
@@ -94,7 +96,7 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
// 내부 클래스: runtimeContext를 고정값으로 반환하는 필터
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class ContextAwareCryptoFilter extends CryptoFilter {
|
||||
static class ContextAwareCryptoFilter extends InCryptoFilter {
|
||||
private final String key;
|
||||
private final String value;
|
||||
|
||||
@@ -206,17 +208,17 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 1. HsmKeyDerivationStrategy — HSM 마스터키 직접 사용 (BaseCryptoFilter)
|
||||
// 1. HsmKeyDerivationStrategy — HSM 마스터키 직접 사용 (InCryptoFilter)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("1-1. FIELD/GCM HsmKey — doPostFilter 암호화 → doPreFilter 복호화 라운드트립")
|
||||
void testHsmGcm_field_roundTrip() throws Exception {
|
||||
BaseCryptoFilter filter = new BaseCryptoFilter();
|
||||
InCryptoFilter filter = new InCryptoFilter();
|
||||
String original = "{\"acno\":\"1234567890\",\"amount\":\"100000\"}";
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
// SCOPE 기본값 FIELD, 경로 기본값 사용
|
||||
|
||||
// 암호화: body 전체 → /encrypted_data 필드
|
||||
@@ -233,12 +235,12 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
@Test
|
||||
@DisplayName("1-2. BODY/GCM HsmKey — doPostFilter 암호화 → doPreFilter 복호화 라운드트립")
|
||||
void testHsmGcm_body_roundTrip() throws Exception {
|
||||
BaseCryptoFilter filter = new BaseCryptoFilter();
|
||||
InCryptoFilter filter = new InCryptoFilter();
|
||||
String original = "{\"acno\":\"1234567890\",\"amount\":\"100000\"}";
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(CryptoFilter.PROP_SCOPE, "BODY");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_SCOPE, "BODY");
|
||||
|
||||
// 암호화: body 전체 → Base64 암호문
|
||||
String encrypted = (String) filter.doPostFilter("G", "A", original, prop, mockReq, mockRes);
|
||||
@@ -261,8 +263,8 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
when(mockReq.getHeader("X-Request-Id")).thenReturn(requestId);
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(CryptoFilter.PROP_AAD_HEADER, "X-Request-Id");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "X-Request-Id");
|
||||
|
||||
String encrypted = (String) filter.doPostFilter("G", "A", original, prop, mockReq, mockRes);
|
||||
String decrypted = (String) filter.doPreFilter( "G", "A", encrypted, prop, mockReq, mockRes);
|
||||
@@ -285,8 +287,8 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
when(decReq.getHeader("X-Request-Id")).thenReturn("REQ-WRONG");
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(CryptoFilter.PROP_AAD_HEADER, "X-Request-Id");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "X-Request-Id");
|
||||
|
||||
String encrypted = (String) filter.doPostFilter("G", "A", original, prop, encReq, mockRes);
|
||||
|
||||
@@ -303,11 +305,11 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
@Test
|
||||
@DisplayName("2-1. FIELD/GCM ContextSha256 — 동일 컨텍스트로 암복호화 라운드트립")
|
||||
void testCtxSha256Gcm_field_roundTrip() throws Exception {
|
||||
CryptoFilter filter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
||||
InCryptoFilter filter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
||||
String original = "{\"accNo\":\"0011234567890\",\"amount\":\"50000\"}";
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
||||
|
||||
String encrypted = (String) filter.doPostFilter("G", "A", original, prop, mockReq, mockRes);
|
||||
System.out.println("[2-1] encrypted: " + encrypted);
|
||||
@@ -321,12 +323,12 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
@Test
|
||||
@DisplayName("2-2. FIELD/GCM ContextSha256 — 암호화와 다른 컨텍스트로 복호화 시 GCM 인증 실패")
|
||||
void testCtxSha256Gcm_differentContext_throwsException() throws Exception {
|
||||
CryptoFilter encFilter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
||||
CryptoFilter decFilter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_B);
|
||||
InCryptoFilter encFilter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
||||
InCryptoFilter decFilter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_B);
|
||||
String original = "{\"data\":\"sensitive\"}";
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
||||
|
||||
String encrypted = (String) encFilter.doPostFilter("G", "A", original, prop, mockReq, mockRes);
|
||||
|
||||
@@ -339,13 +341,13 @@ public class CryptoFilterHsmIntegrationTest {
|
||||
@Test
|
||||
@DisplayName("2-3. FIELD/GCM ContextSha256 — 컨텍스트별 독립 암복호화 (GROUP-A, GROUP-B 각각 성공)")
|
||||
void testCtxSha256Gcm_perGroupRoundTrip() throws Exception {
|
||||
CryptoFilter filterA = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
||||
CryptoFilter filterB = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_B);
|
||||
InCryptoFilter filterA = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
||||
InCryptoFilter filterB = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_B);
|
||||
String plainA = "{\"group\":\"A\",\"amount\":\"1000\"}";
|
||||
String plainB = "{\"group\":\"B\",\"amount\":\"2000\"}";
|
||||
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
||||
|
||||
String encA = (String) filterA.doPostFilter("G", "A", plainA, prop, mockReq, mockRes);
|
||||
String encB = (String) filterB.doPostFilter("G", "B", plainB, prop, mockReq, mockRes);
|
||||
|
||||
+24
-23
@@ -22,15 +22,16 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
|
||||
import com.eactive.eai.common.security.CryptoModuleService;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
|
||||
/**
|
||||
* BaseCryptoFilter / CryptoFilter 단위 테스트.
|
||||
* InCryptoFilter / AbstractCryptoFilter 단위 테스트.
|
||||
* CryptoModuleService는 Mock으로 대체하여 필터 로직(Base64, JSON 경로, 프로퍼티 파싱)만 검증한다.
|
||||
*/
|
||||
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
||||
class BaseCryptoFilterTest {
|
||||
class InCryptoFilterTest {
|
||||
|
||||
private static final String MODULE = "TEST_MODULE";
|
||||
private static final String PLAIN_STR = "decrypted-plain-text";
|
||||
@@ -40,7 +41,7 @@ class BaseCryptoFilterTest {
|
||||
|
||||
private static GenericApplicationContext ctx;
|
||||
private static CryptoModuleService mockService;
|
||||
private static BaseCryptoFilter filter;
|
||||
private static InCryptoFilter filter;
|
||||
|
||||
private HttpServletRequest mockRequest;
|
||||
private HttpServletResponse mockResponse;
|
||||
@@ -56,7 +57,7 @@ class BaseCryptoFilterTest {
|
||||
.getBeanDefinition());
|
||||
ctx.refresh();
|
||||
|
||||
filter = new BaseCryptoFilter();
|
||||
filter = new InCryptoFilter();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@@ -81,7 +82,7 @@ class BaseCryptoFilterTest {
|
||||
@Test
|
||||
@DisplayName("1-1. buildRuntimeContext — 항상 빈 Map 반환")
|
||||
void testBuildRuntimeContext_returnsEmptyMap() {
|
||||
Map<String, String> result = filter.buildRuntimeContext(new Properties(), mockRequest);
|
||||
Map<String, String> result = filter.buildRuntimeContext(new Properties(), mockRequest);
|
||||
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
@@ -101,7 +102,7 @@ class BaseCryptoFilterTest {
|
||||
@DisplayName("2-2. buildAad — 헤더명 설정, 헤더값 없음 → null")
|
||||
void testBuildAad_headerNameSet_noValue_returnsNull() {
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_AAD_HEADER, "X-Api-Request-Id");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "X-Api-Request-Id");
|
||||
when(mockRequest.getHeader("X-Api-Request-Id")).thenReturn(null);
|
||||
|
||||
assertNull(filter.buildAad(prop, mockRequest));
|
||||
@@ -112,7 +113,7 @@ class BaseCryptoFilterTest {
|
||||
void testBuildAad_headerNameSet_valuePresent_returnsByte() {
|
||||
String headerVal = "req-id-abc123";
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_AAD_HEADER, "X-Api-Request-Id");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "X-Api-Request-Id");
|
||||
when(mockRequest.getHeader("X-Api-Request-Id")).thenReturn(headerVal);
|
||||
|
||||
assertArrayEquals(headerVal.getBytes(StandardCharsets.UTF_8), filter.buildAad(prop, mockRequest));
|
||||
@@ -132,17 +133,17 @@ class BaseCryptoFilterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-3. CRYPTO_MODULE_NAME 누락 — IllegalArgumentException")
|
||||
@DisplayName("3-2. CRYPTO_MODULE_NAME 누락 — IllegalArgumentException")
|
||||
void testPreFilter_missingModuleName_throwsException() {
|
||||
Properties prop = new Properties();
|
||||
prop.setProperty(CryptoFilter.PROP_SCOPE, "BODY");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_SCOPE, "BODY");
|
||||
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> filter.doPreFilter("g", "a", CIPHER_B64, prop, mockRequest, mockResponse));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("3-4. message가 byte[] — UTF-8 디코딩 후 처리")
|
||||
@DisplayName("3-3. message가 byte[] — UTF-8 디코딩 후 처리")
|
||||
void testPreFilter_body_byteArrayMessage() throws Exception {
|
||||
Object result = filter.doPreFilter("g", "a", CIPHER_B64.getBytes(StandardCharsets.UTF_8),
|
||||
bodyDecryptProps(), mockRequest, mockResponse);
|
||||
@@ -195,7 +196,7 @@ class BaseCryptoFilterTest {
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("5-1. BODY scope, CRYPTO_ENC_ENABLED=Y — 평문 암호화 → Base64 반환")
|
||||
@DisplayName("5-1. BODY scope — 평문 암호화 → Base64 반환")
|
||||
void testPostFilter_body_encrypt() throws Exception {
|
||||
String body = "plain-response-body";
|
||||
Object result = filter.doPostFilter("g", "a", body, bodyEncryptProps(), mockRequest, mockResponse);
|
||||
@@ -252,7 +253,7 @@ class BaseCryptoFilterTest {
|
||||
void testPreFilter_body_aadFromHeader_passedToService() throws Exception {
|
||||
String aadVal = "request-id-xyz";
|
||||
Properties prop = bodyDecryptProps();
|
||||
prop.setProperty(CryptoFilter.PROP_AAD_HEADER, "X-Api-Request-Id");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "X-Api-Request-Id");
|
||||
when(mockRequest.getHeader("X-Api-Request-Id")).thenReturn(aadVal);
|
||||
|
||||
filter.doPreFilter("g", "a", CIPHER_B64, prop, mockRequest, mockResponse);
|
||||
@@ -275,7 +276,7 @@ class BaseCryptoFilterTest {
|
||||
String aadVal = "field-aad-value";
|
||||
String body = "{\"enc\":\"" + CIPHER_B64 + "\",\"result\":\"\"}";
|
||||
Properties prop = fieldDecryptProps("/enc", "/result");
|
||||
prop.setProperty(CryptoFilter.PROP_AAD_HEADER, "X-Api-Aad");
|
||||
prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "X-Api-Aad");
|
||||
when(mockRequest.getHeader("X-Api-Aad")).thenReturn(aadVal);
|
||||
|
||||
filter.doPreFilter("g", "a", body, prop, mockRequest, mockResponse);
|
||||
@@ -290,8 +291,8 @@ class BaseCryptoFilterTest {
|
||||
|
||||
private Properties bodyDecryptProps() {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(CryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(CryptoFilter.PROP_SCOPE, "BODY");
|
||||
p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "BODY");
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -301,19 +302,19 @@ class BaseCryptoFilterTest {
|
||||
|
||||
private Properties fieldDecryptProps(String fromPath, String toPath) {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(CryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(CryptoFilter.PROP_SCOPE, "FIELD");
|
||||
p.setProperty(CryptoFilter.PROP_DEC_FROM_PATH, fromPath);
|
||||
p.setProperty(CryptoFilter.PROP_DEC_TO_PATH, toPath);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "FIELD");
|
||||
p.setProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH, fromPath);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH, toPath);
|
||||
return p;
|
||||
}
|
||||
|
||||
private Properties fieldEncryptProps(String fromPath, String toPath) {
|
||||
Properties p = new Properties();
|
||||
p.setProperty(CryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(CryptoFilter.PROP_SCOPE, "FIELD");
|
||||
p.setProperty(CryptoFilter.PROP_ENC_FROM_PATH, fromPath);
|
||||
p.setProperty(CryptoFilter.PROP_ENC_TO_PATH, toPath);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "FIELD");
|
||||
p.setProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH, fromPath);
|
||||
p.setProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH, toPath);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user