feat: CryptoFilter 프로퍼티 키 규격화 및 HSM 통합 테스트 추가
- CryptoFilter prop 키를 UPPER_UNDERSCORE 형식으로 변경 (CRYPTO_MODULE_NAME 등) - CRYPTO_DEC_ENABLED / CRYPTO_ENC_ENABLED 옵션 제거 (필터 설정 시 항상 암복호화) - getProp() 빈 문자열도 기본값으로 처리 (StringUtils.isBlank 적용) - CryptoFilterHsmIntegrationTest 추가: SoftHSM2 연동 전체 흐름 검증 (7개 케이스) - attributes(*, CKO_SECRET_KEY, CKK_AES): 임포트 키도 CKA_SENSITIVE=false 적용 - HsmKeyDerivationStrategy / HsmContextSha256KeyDerivationStrategy 검증 - HsmKeyRegisterUtil 추가: SoftHSM2 마스터키 등록 유틸리티 - CryptoModuleServiceTest: GCM AAD 관련 테스트 케이스 보강 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,40 +23,37 @@ import com.eactive.eai.util.JsonPathUtil;
|
||||
* (CryptoModuleManager가 KEY_SOURCE_TYPE=STATIC이면 runtimeContext를 무시하고 고정 키를 사용한다.)
|
||||
*
|
||||
* 어댑터 프로퍼티 키:
|
||||
* crypto.module.name 필수. DB에 등록된 암호화 모듈명.
|
||||
* crypto.scope BODY | FIELD (기본 BODY)
|
||||
* CRYPTO_MODULE_NAME 필수. DB에 등록된 암호화 모듈명.
|
||||
* CRYPTO_SCOPE BODY | FIELD (기본 FIELD)
|
||||
* BODY : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
|
||||
* FIELD : 특정 JSON 경로의 값만 암복호화
|
||||
* crypto.dec.enabled Y | N (기본 Y). doPreFilter(요청) 복호화 수행 여부.
|
||||
* crypto.enc.enabled Y | N (기본 N). doPostFilter(응답) 암호화 수행 여부.
|
||||
*
|
||||
* FIELD 범위 전용:
|
||||
* crypto.dec.from.path 복호화 대상 JSON 경로 (예: /encryptedData)
|
||||
* crypto.dec.to.path 복호화 결과 반영 경로. "/" = 전체 body 교체.
|
||||
* crypto.enc.from.path 암호화 대상 JSON 경로. "/" = 전체 body 암호화.
|
||||
* crypto.enc.to.path 암호화 결과(Base64)를 넣을 JSON 경로 (예: /encryptedData)
|
||||
* CRYPTO_DEC_FROM_PATH 복호화 대상 JSON 경로 (예: /encrypted_data)
|
||||
* CRYPTO_DEC_TO_PATH 복호화 결과 반영 경로. "/" = 전체 body 교체.
|
||||
* CRYPTO_ENC_FROM_PATH 암호화 대상 JSON 경로. "/" = 전체 body 암호화.
|
||||
* CRYPTO_ENC_TO_PATH 암호화 결과(Base64)를 넣을 JSON 경로 (예: /encrypted_data)
|
||||
*
|
||||
* GCM AAD 전용 (선택):
|
||||
* crypto.aad.header AAD로 사용할 HTTP 요청 헤더명 (예: X-Api-Request-Id).
|
||||
* CRYPTO_AAD_HEADER AAD로 사용할 HTTP 요청 헤더명 (예: X-Api-Request-Id).
|
||||
* 미설정 또는 헤더값 없으면 aad=null → IV를 AAD 대체값으로 사용.
|
||||
*/
|
||||
public abstract class CryptoFilter implements HttpAdapterFilter {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String PROP_MODULE_NAME = "crypto.module.name";
|
||||
public static final String PROP_SCOPE = "crypto.scope";
|
||||
public static final String PROP_DEC_ENABLED = "crypto.dec.enabled";
|
||||
public static final String PROP_ENC_ENABLED = "crypto.enc.enabled";
|
||||
public static final String PROP_DEC_FROM_PATH = "crypto.dec.from.path";
|
||||
public static final String PROP_DEC_TO_PATH = "crypto.dec.to.path";
|
||||
public static final String PROP_ENC_FROM_PATH = "crypto.enc.from.path";
|
||||
public static final String PROP_ENC_TO_PATH = "crypto.enc.to.path";
|
||||
public static final String PROP_AAD_HEADER = "crypto.aad.header";
|
||||
public static final String PROP_MODULE_NAME = "CRYPTO_MODULE_NAME";
|
||||
public static final String PROP_SCOPE = "CRYPTO_SCOPE";
|
||||
public static final String PROP_DEC_FROM_PATH = "CRYPTO_DEC_FROM_PATH";
|
||||
public static final String PROP_DEC_TO_PATH = "CRYPTO_DEC_TO_PATH";
|
||||
public static final String PROP_ENC_FROM_PATH = "CRYPTO_ENC_FROM_PATH";
|
||||
public static final String PROP_ENC_TO_PATH = "CRYPTO_ENC_TO_PATH";
|
||||
public static final String PROP_AAD_HEADER = "CRYPTO_AAD_HEADER";
|
||||
|
||||
protected static final String SCOPE_BODY = "BODY";
|
||||
protected static final String SCOPE_FIELD = "FIELD";
|
||||
protected static final String PATH_ROOT = "/";
|
||||
public static final String SCOPE_BODY = "BODY";
|
||||
public static final String SCOPE_FIELD = "FIELD";
|
||||
public static final String PATH_ROOT = "/";
|
||||
public static final String PATH_ENCDATA = "/encrypted_data";
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 추상 메서드
|
||||
@@ -76,20 +73,16 @@ public abstract class CryptoFilter implements HttpAdapterFilter {
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
if (!isEnabled(prop, PROP_DEC_ENABLED, "Y")) {
|
||||
return message;
|
||||
}
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = prop.getProperty(PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String body = toBodyString(message);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
|
||||
byte[] aad = buildAad(prop, request);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return decryptField(body, moduleName, runtimeCtx, aad,
|
||||
required(prop, PROP_DEC_FROM_PATH),
|
||||
prop.getProperty(PROP_DEC_TO_PATH, PATH_ROOT));
|
||||
getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
|
||||
getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
|
||||
}
|
||||
return decryptBody(body, moduleName, runtimeCtx, aad);
|
||||
}
|
||||
@@ -102,20 +95,16 @@ public abstract class CryptoFilter implements HttpAdapterFilter {
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
if (!isEnabled(prop, PROP_ENC_ENABLED, "N")) {
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = prop.getProperty(PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String body = toBodyString(resultMessage);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
|
||||
byte[] aad = buildAad(prop, request);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return encryptField(body, moduleName, runtimeCtx, aad,
|
||||
prop.getProperty(PROP_ENC_FROM_PATH, PATH_ROOT),
|
||||
required(prop, PROP_ENC_TO_PATH));
|
||||
getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
|
||||
getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
|
||||
}
|
||||
return encryptBody(body, moduleName, runtimeCtx, aad);
|
||||
}
|
||||
@@ -228,10 +217,6 @@ public abstract class CryptoFilter implements HttpAdapterFilter {
|
||||
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private boolean isEnabled(Properties prop, String key, String defaultVal) {
|
||||
return !"N".equalsIgnoreCase(prop.getProperty(key, defaultVal));
|
||||
}
|
||||
|
||||
protected String required(Properties prop, String key) {
|
||||
String v = prop.getProperty(key);
|
||||
if (StringUtils.isBlank(v)) {
|
||||
@@ -239,4 +224,9 @@ public abstract class CryptoFilter implements HttpAdapterFilter {
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private String getProp(Properties prop, String key, String defaultVal) {
|
||||
String v = prop.getProperty(key);
|
||||
return StringUtils.isBlank(v) ? defaultVal : v;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -6,7 +6,8 @@ import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class HttpAdapterFilterFactoryKjb extends HttpAdapterFilterFactory {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
static String basePackage = "com.eactive.eai.custom.adapter.http.dynamic.filter";
|
||||
static String basePackage = "com.eactive.eai.adapter.http.dynamic.filter";
|
||||
static String customBasePackage = "com.eactive.eai.custom.adapter.http.dynamic.filter";
|
||||
static private ConcurrentHashMap<String, HttpAdapterFilter> h = new ConcurrentHashMap<String, HttpAdapterFilter>();
|
||||
|
||||
public HttpAdapterFilterFactoryKjb() {
|
||||
@@ -44,6 +45,9 @@ public class HttpAdapterFilterFactoryKjb extends HttpAdapterFilterFactory {
|
||||
if (filter == null) {
|
||||
filter = classForName(basePackage + "." + type);
|
||||
}
|
||||
if (filter == null) {
|
||||
filter = classForName(customBasePackage + "." + type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user