9 Commits

Author SHA1 Message Date
curry772 29b8b7eaac perf: encryptField/decryptField JSON 파싱 횟수 절감
JsonPathUtil에 JsonNode 기반 저수준 API(toTree/getAt/setAt/fromTree) 추가.
기존 getValueAtPath/setValueAtPath/mergeAtRoot는 내부적으로 이를 위임.

AbstractCryptoFilter.encryptField, decryptField는 파싱된 JsonNode를
재사용하여 JSON 파싱을 기존 2~3회에서 1회로 단축.
- encryptField non-root: 파싱 2회 → 1회
- decryptField non-root: 파싱 2회 → 1회
- decryptField toPath="/": 파싱 3회 → 2회 (body 1회 + mergeJson 1회)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:55:29 +09:00
curry772 74302df5a2 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>
2026-05-14 13:34:55 +09:00
curry772 9a15b5506f fix: CryptoFilterHsmIntegrationTest - BaseCryptoFilter 잔여 참조 InCryptoFilter로 교체
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:02 +09:00
curry772 0b68b8166f 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>
2026-05-14 11:26:36 +09:00
curry772 8db66f8bef DefaultProcess - 표준전문 오류응답을 수신 응답 설정(setResMsg) 로직 변경
- AdapterErrorMessageHandler를 사용하는 경우, BWK_FAILMSG_CODE(RIBEAI999999)를
설정하도록 변경
- AdapterErrorMessageHandler 설정이 없은 경우, Exception 발생 부분 처리
2026-05-14 10:42:12 +09:00
curry772 c4a250db08 refactor: HttpAdapterFilterFactoryKjb 제거 및 HttpAdapterFilterFactory 통합
- HttpAdapterFilterFactoryKjb 삭제
- HttpAdapterFilterFactory에 HMAC_SHA256, REFLECTALLHEADER 케이스 추가
- classForName() 헬퍼로 basePackage / customBasePackage 순차 탐색 적용
- HttpAdapterServiceSupport 임포트를 HttpAdapterFilterFactory로 교체

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:09:22 +09:00
curry772 db7a728344 feat: HttpClient5 어댑터 필터 패키지 eapim-online에서 이동
- HttpClient5AdapterServiceRestAddFilter 이동
- HttpClientAdapterFilter / HttpClient5AdapterFilterFactory 등 filter 패키지 이동
  (AllHeaderFilter, AsyncReponseFilter, JBOBPFilter, KAKAOBankFilter,
   KFTCFaceFilter, KFTCP2PFilter, NAVERFinFilter, NICECreditFilter,
   SimpleFrameworkFilter, SimpleframeworkBodyFilter, TOSSBankFilter, TraceBodyFilter)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:58:07 +09:00
curry772 813bb99952 test: CryptoModuleServiceTest DYNAMIC AES/CBC 출력 로그 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:53:17 +09:00
curry772 becd8612e8 Merge branch 'feature/crypto-module'
CryptoFilter 프로퍼티 키 규격화, HSM 통합 테스트, SoftHSM2 연동 검증 완료
2026-05-13 13:29:14 +09:00
31 changed files with 2425 additions and 418 deletions
@@ -0,0 +1,142 @@
package com.eactive.eai.adapter.http.client.impl;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Node;
import org.json.simple.JSONObject;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.impl.filter.HttpClient5AdapterFilterFactory;
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
/**
* 1. 기능 : HttpClient5AdapterServiceRest 호출 전후 Filter 적용할 수 있는 기능을 제공한다.<br>
* 2. 처리 개요 : <br>
* 3. 주의사항 <br>
*
* @author :
* @version : v 1.0.0
* @see : HttpClientAdapterServiceFactory.java,
* HttpClientAdapterServiceSupport.java, HttpClient5AdapterServiceRest.java
* @since :
*
*/
public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterServiceRest
implements HttpClientAdapterServiceKey {
// 요청전 수행할 필터(쉼표(,)로 구분)
static final String PRE_FILTERS = "PRE_FILTERS";
// 요청후 수행할 필터(쉼표(,)로 구분)
static final String POST_FILTERS = "POST_FILTERS";
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
/**
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
* 3. 주의사항 <br>
*
* @param prop Http Adapter 속성 정보
* @return 반환 된 Object
* @exception Exception 수동 시스템 간 통신 중 발생
*/
public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
String adptGrpName = tempProp.getProperty(ADAPTER_GROUP_NAME);
String adptName = tempProp.getProperty(ADAPTER_NAME);
data = doPreFilters(adptGrpName, adptName, prop, data, tempProp);
Object adapterResponse = super.execute(prop, data, tempProp);
adapterResponse = doPostFilters(adptGrpName, adptName, prop, adapterResponse, tempProp);
if (adapterResponse instanceof JSONObject)
adapterResponse = ((JSONObject) adapterResponse).toJSONString();
if (adapterResponse instanceof Node)
adapterResponse = ((Node) adapterResponse).asXML();
return adapterResponse;
}
protected Object doPreFilters(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
// boolean isSetCommonFilterInAdapterProp = false;
// 1. adapter에 설정된 필터 수행
String preFiltersStr = prop.getProperty(PRE_FILTERS);
if (StringUtils.isNotEmpty(preFiltersStr)) {
String[] preFilters = StringUtils.split(preFiltersStr, ",");
for (String filterName : preFilters) {
if (StringUtils.isBlank(filterName)) {
continue;
}
logger.debug("HttpClient5AdapterServiceRestAddFilter] Processing Start [" + filterName + "]");
HttpClientAdapterFilter adapterFilter = HttpClient5AdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
// if (adapterFilter instanceof IBKOutCommonFilter)
// isSetCommonFilterInAdapterProp = true;
message = adapterFilter.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
} else {
throw new Exception("Failed get Filter Class: " + filterName);
}
}
}
// // 2. IBKOutCommonFilter 필터 수행(adapter 필터에서 수행하지 않을 때만)
// if (!isSetCommonFilterInAdapterProp) {
// HttpClientAdapterFilter ibkFilter = HttpClient5AdapterFilterFactory
// .createFilter(IBKOutCommonFilter.class.getName());
// if (ibkFilter != null) {
// message = ibkFilter.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
// }
// } else {
// logger.debug("IBKOutCommonFilter isSetCommonFilterInAdapterProp 'POST_FILTERS'. already in adapter filters");
// }
return message;
}
protected Object doPostFilters(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
// boolean isSetCommonFilterInAdapterProp = false;
// 1. adapter에 설정된 필터 수행
String postFiltersStr = prop.getProperty(POST_FILTERS);
if (StringUtils.isNotEmpty(postFiltersStr)) {
String[] postFilters = StringUtils.split(postFiltersStr, ",");
for (String filterName : postFilters) {
if (StringUtils.isBlank(filterName)) {
continue;
}
HttpClientAdapterFilter adapterFilter = HttpClient5AdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
// if (adapterFilter instanceof IBKOutCommonFilter)
// isSetCommonFilterInAdapterProp = true;
message = adapterFilter.doPostFilter(adptGrpName, adptName, prop, message, tempProp);
} else {
throw new Exception("Failed get Filter Class: " + filterName);
}
}
}
// // 2. IBKOutCommonFilter 필터 수행
// if (!isSetCommonFilterInAdapterProp) {
// HttpClientAdapterFilter ibkFilter = HttpClient5AdapterFilterFactory
// .createFilter(IBKOutCommonFilter.class.getName());
// if (ibkFilter != null) {
// message = ibkFilter.doPostFilter(adptGrpName, adptName, prop, message, tempProp);
// }
// } else {
// logger.debug("IBKOutCommonFilter isSetCommonFilterInAdapterProp 'PRE_FILTERS'. already in adapter filters");
// }
return message;
}
}
@@ -0,0 +1,121 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class AllHeaderFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
public static final String HEADER_KEY_NAMES = "AllHeaderFilter.blackList";
String[] HttpHeaderRelayBlackList = {
"Content-Length",
"Transfer-Encoding",
"Host",
"Authorization",
"Accept",
"Host",
"Cookie",
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"TE",
"Trailer",
"Transfer-Encoding",
"Upgrade",
"Content-Type",
"Content-Encoding",
"Content-Language",
"Content-Location",
"Content-MD5",
"Expect",
};
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("doPreFilter Processing Start.");
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
String[] userSettingBlackList = propValue.split(",");
if( userSettingBlackList.length > 0 ) {
HttpHeaderRelayBlackList = Stream
.concat(Arrays.stream(HttpHeaderRelayBlackList), Arrays.stream(userSettingBlackList))
.toArray(String[]::new);
}
for(String keyName : inboundHeaderMap.keySet() ) {
if( StringUtils.equalsAnyIgnoreCase( keyName, HttpHeaderRelayBlackList ) ) {
logger.debug("Skip Processing Key ["+keyName+"], value ["+inboundHeaderMap.get(keyName)+"] in HttpHeaderRelayBlackList");
continue;
}
filterHeaders.put(keyName, inboundHeaderMap.get(keyName));
logger.debug("Processing Key ["+keyName+"], value ["+inboundHeaderMap.get(keyName)+"]");
}
logger.debug("doPreFilter Processing End.");
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,118 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class AsyncReponseFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String X_TRACE_ID = "partnerTraceId";
public static final String TRACE_ID = "traceId";
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
public static final String HEADER_KEY_NAMES = "AsyncReponseFilter";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("AsyncReponseFilter] PreFilter Processing Start!!");
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
String[] headerKeyNames = propValue.split(",");
headerKeyNames = Arrays.stream(headerKeyNames)
.map(String::trim)
.toArray(String[]::new);
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_TRACE_ID)) {
filterHeaders.put(X_TRACE_ID, inboundHeaderMap.get(X_TRACE_ID));
logger.debug("AsyncReponseFilter] Processing Key ["+X_TRACE_ID+"], value ["+inboundHeaderMap.get(X_TRACE_ID)+"]");
}
if (inboundHeaderMap.containsKey(TransactionContextKeys.X_LOAN_TOKEN)) {
filterHeaders.put(TransactionContextKeys.X_LOAN_TOKEN, inboundHeaderMap.get(TransactionContextKeys.X_LOAN_TOKEN));
logger.debug("AsyncReponseFilter] Processing Key ["+TransactionContextKeys.X_LOAN_TOKEN+"], value ["+inboundHeaderMap.get(TransactionContextKeys.X_LOAN_TOKEN)+"]");
}
}
filterHeaders.setProperty(TRACE_ID, traceId);
logger.debug("AsyncReponseFilter] Processing Key ["+TRACE_ID+"], value ["+traceId+"]");
for (String keyName : inboundHeaderMap.keySet()) {
if (StringUtils.equalsAnyIgnoreCase(keyName, headerKeyNames)) {
String value = inboundHeaderMap.get(keyName);
filterHeaders.put(keyName, value);
logger.debug("AsyncReponseFilter] Processing Key [" + keyName + "], value [" + value + "]");
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,90 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.concurrent.ConcurrentHashMap;
import com.eactive.eai.common.util.Logger;
public class HttpClient5AdapterFilterFactory {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
static String customBasePackage = "com.eactive.eai.custom.adapter.http.client.filter";
static String basePackage = "com.eactive.eai.adapter.http.client.impl.filter";
private static ConcurrentHashMap<String, HttpClientAdapterFilter> h = new ConcurrentHashMap<>();
private HttpClient5AdapterFilterFactory() {
throw new IllegalStateException("Utility class");
}
public static HttpClientAdapterFilter createFilter(String type) {
if (h.containsKey(type)) {
return h.get(type);
}
HttpClientAdapterFilter filter = null;
switch (HttpClient5AdapterFilterType.getValue(type)) {
case SIMPLEFRAMEWORK:
filter = new SimpleFrameworkFilter();
break;
case SIMPLEFRAMEWORKBODY:
filter = new SimpleframeworkBodyFilter();
break;
case JBOBP:
filter = new JBOBPFilter();
break;
case KFTCFACE:
filter = new KFTCFaceFilter();
break;
case KFTCP2P:
filter = new KFTCP2PFilter();
break;
case KAKAOBANK:
filter = new KAKAOBankFilter();
break;
case NAVERFIN:
filter = new NAVERFinFilter();
break;
case NICECREDIT:
filter = new NICECreditFilter();
break;
case TRACEBODY:
filter = new TraceBodyFilter();
break;
case TOSSBANK:
filter = new TOSSBankFilter();
break;
case ASYNCRESPONSE:
filter = new AsyncReponseFilter();
break;
case ALLHEADER:
filter = new AllHeaderFilter();
break;
default:
filter = classForName(type);
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
}
if (filter == null) {
filter = classForName(basePackage + "." + type);
}
break;
}
if (filter != null) {
h.put(type, filter);
return filter;
} else {
return null;
}
}
private static HttpClientAdapterFilter classForName(String type) {
try {
Class<?> cl = Class.forName(type);
return (HttpClientAdapterFilter) cl.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.error("Cannot create a HttpClientAdapterFilter. - {}", type);
return null;
}
}
}
@@ -0,0 +1,27 @@
package com.eactive.eai.adapter.http.client.impl.filter;
public enum HttpClient5AdapterFilterType {
SIMPLEFRAMEWORK,
SIMPLEFRAMEWORKBODY,
JBOBP,
KFTCFACE,
KFTCP2P,
KAKAOBANK,
NAVERFIN,
NICECREDIT,
TOSSBANK,
TRACEBODY,
ASYNCRESPONSE,
ALLHEADER,
UNKNOWN;
public static HttpClient5AdapterFilterType getValue(String type) {
try {
return valueOf(type.toUpperCase());
} catch (NullPointerException e) {
return UNKNOWN;
} catch (Exception e) {
return UNKNOWN;
}
}
}
@@ -0,0 +1,12 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
public interface HttpClientAdapterFilter {
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception;
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception;
}
@@ -0,0 +1,35 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import lombok.Getter;
public class HttpClientAdapterFilterException extends RuntimeException {
private static final long serialVersionUID = -1208983360831093751L;
@Getter
private final String code;
@Getter
private final Properties prop;
@Getter
private final Properties tempProp;
public HttpClientAdapterFilterException(String code, String msg, Properties prop, Properties tmepProp) {
super(msg);
this.code = code;
this.prop = prop;
this.tempProp = tmepProp;
}
public HttpClientAdapterFilterException(String code, String msg, Properties prop, Properties tempProp, Throwable cause) {
super(msg, cause);
this.code = code;
this.prop = prop;
this.tempProp = tempProp;
}
@Override
public String toString() {
return "HttpClientAdapterFilterException [code=" + code + ", prop=" + prop + ", tempProp=" + tempProp + ", parent=" + super.toString() + "]";
}
}
@@ -0,0 +1,106 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class JBOBPFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String X_OBP_PARTNERCODE = "x-obp-partnercode";
public static final String X_OBP_TXID = "x-obp-txid";
public static final String X_OBP_TRUST_SYSTEM = "x-obp-trust-system";
String systemTrustKey = "APIMGW-0001-QVBJTUdXLTAwMDE=";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("JBOBPFilter] PreFilter Processing Start!!");
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) systemTrustKey = propGroupVo.getProperty(X_OBP_TRUST_SYSTEM, systemTrustKey);
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
String obpPartnerCode = "000000-00";
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_OBP_PARTNERCODE)) {
filterHeaders.put(X_OBP_PARTNERCODE, inboundHeaderMap.get(X_OBP_PARTNERCODE));
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+inboundHeaderMap.get(X_OBP_PARTNERCODE)+"]");
} else {
filterHeaders.put(X_OBP_PARTNERCODE, obpPartnerCode);
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+obpPartnerCode+"]");
}
} else {
filterHeaders.put(X_OBP_PARTNERCODE, obpPartnerCode);
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+obpPartnerCode+"]");
}
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
if (StringUtils.isNotBlank(uuid)) {
filterHeaders.setProperty(X_OBP_TXID, uuid); // JB OBP Framework용
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_TXID+"], value ["+uuid+"]");
}
if (StringUtils.isNotBlank(systemTrustKey)) {
filterHeaders.setProperty(X_OBP_TRUST_SYSTEM, systemTrustKey); // JB OBP Framework용
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_TRUST_SYSTEM+"], value ["+systemTrustKey+"]");
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,135 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class KAKAOBankFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "kakaobank";
public static final String X_KKB_PARTNER_CODE = "X-KKB-PARTNER-CODE";
public static final String X_KKB_API_NAME = "X-KKB-API-NAME";
public static final String X_KKB_API_TX_ID = "X-KKB-API-TX-ID";
public static final String X_KKB_TX_TIME = "X-KKB-TX-TIME";
public static final String X_KKB_CMPR_MGMT_NO = "X-KKB-CMPR-MGMT-NO";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("KAKAOBankFilter] PreFilter Processing Start!!");
Object returnMessage = message;
String partnerCode = "KJB";
String apiName = "status_change";
String apiTxId = "0";
String txTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String cmprMgmtNo = "0";
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
partnerCode = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_PARTNER_CODE);
apiName = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_API_NAME);
apiTxId = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_API_TX_ID);
cmprMgmtNo = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_CMPR_MGMT_NO);
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_KKB_PARTNER_CODE)) {
filterHeaders.put(X_KKB_PARTNER_CODE, inboundHeaderMap.get(X_KKB_PARTNER_CODE));
} else {
filterHeaders.put(X_KKB_PARTNER_CODE, partnerCode);
}
if (inboundHeaderMap.containsKey(X_KKB_API_NAME)) {
filterHeaders.put(X_KKB_API_NAME, inboundHeaderMap.get(X_KKB_API_NAME));
} else {
filterHeaders.put(X_KKB_API_NAME, apiName);
}
if (inboundHeaderMap.containsKey(X_KKB_API_TX_ID)) {
filterHeaders.put(X_KKB_API_TX_ID, inboundHeaderMap.get(X_KKB_API_TX_ID));
} else {
filterHeaders.put(X_KKB_API_TX_ID, apiTxId);
}
if (inboundHeaderMap.containsKey(X_KKB_TX_TIME)) {
filterHeaders.put(X_KKB_TX_TIME, inboundHeaderMap.get(X_KKB_TX_TIME));
} else {
filterHeaders.put(X_KKB_TX_TIME, txTime);
}
if (inboundHeaderMap.containsKey(X_KKB_CMPR_MGMT_NO)) {
filterHeaders.put(X_KKB_CMPR_MGMT_NO, inboundHeaderMap.get(X_KKB_CMPR_MGMT_NO));
} else {
filterHeaders.put(X_KKB_CMPR_MGMT_NO, cmprMgmtNo);
}
} else {
filterHeaders.put(X_KKB_PARTNER_CODE, partnerCode);
filterHeaders.put(X_KKB_API_NAME, apiName);
filterHeaders.put(X_KKB_API_TX_ID, apiTxId);
filterHeaders.put(X_KKB_TX_TIME, txTime);
filterHeaders.put(X_KKB_CMPR_MGMT_NO, cmprMgmtNo);
}
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_PARTNER_CODE+"], value ["+filterHeaders.getProperty(X_KKB_PARTNER_CODE)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_API_NAME+"], value ["+filterHeaders.getProperty(X_KKB_API_NAME)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_API_TX_ID+"], value ["+filterHeaders.getProperty(X_KKB_API_TX_ID)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_TX_TIME+"], value ["+filterHeaders.getProperty(X_KKB_TX_TIME)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_CMPR_MGMT_NO+"], value ["+filterHeaders.getProperty(X_KKB_CMPR_MGMT_NO)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return returnMessage;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,154 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.Logger;
public class KFTCFaceFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "kftcface";
public static final String H_CLIENT_ID = "Client-Id";
public static final String B_ORG_CODE = "org_code";
public static final String B_TRANSACTION_ID = "transaction_id";
public static final String B_REQUEST_DATETIME = "request_datetime";
public static String instId = null;
static {
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
instId = eaiServerManager.getInstId();
}
@SuppressWarnings({ "unchecked" })
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("KFTCFaceFilter] PreFilter Processing Start!!");
JSONObject returnMessage = null;
String clientId = "";
String orgCode = "034"; // 광주은행 행코드
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
if (StringUtils.isEmpty(clientId))
clientId = propGroupVo.getProperty(PROP_PREFIX + "." + H_CLIENT_ID);
orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + B_ORG_CODE);
}
String request_datetime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String reqDate = new SimpleDateFormat("yyMMdd").format(new Date());
String reqTime = new SimpleDateFormat("HHmmss").format(new Date());
// 기관코드(3) + 요청일자(6) -- 금결원 표준화 항목, 고정 9자리.
// 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 기관별 생성 거래고유번호(11자리)
// 기관코드(3) + 요청일자(6) + 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 20자리.
String transaction_id = orgCode + reqDate + reqTime + instId + RandomStringUtils.random(3, true, true).toUpperCase();
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(H_CLIENT_ID)) {
filterHeaders.put(H_CLIENT_ID, inboundHeaderMap.get(H_CLIENT_ID));
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+inboundHeaderMap.get(H_CLIENT_ID)+"]");
} else {
filterHeaders.put(H_CLIENT_ID, clientId);
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+clientId+"]");
}
} else {
filterHeaders.setProperty(H_CLIENT_ID, clientId);
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+clientId+"]");
}
} catch (Exception e) {
logger.error(e.getMessage());
}
AdapterManager adapterManager = AdapterManager.getInstance();
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
String encode = adptGrpVO.getMessageEncode();
if (message == null) {
returnMessage = new JSONObject();
} else {
if (message instanceof JSONObject) {
returnMessage = (JSONObject) message;
} else if (message instanceof String) {
returnMessage = parseJson((String) message);
} else if (message instanceof byte[]) {
returnMessage = parseJson(new String((byte[]) message, encode));
}
}
returnMessage.put(B_ORG_CODE, orgCode);
returnMessage.put(B_TRANSACTION_ID, transaction_id);
returnMessage.put(B_REQUEST_DATETIME, request_datetime);
return returnMessage.toJSONString();
}
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,122 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.RandomStringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.Logger;
public class KFTCP2PFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "kftcp2p";
public static final String H_ORG_CODE = "org_code";
public static final String H_TRX_NO = "api_trx_no";
public static final String H_TRX_DTM = "api_trx_dtm";
public static String instId = null;
static {
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
instId = eaiServerManager.getInstId();
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("KFTCP2PFilter] PreFilter Processing Start!!");
String orgCode = "D210400012"; // 광주은행 개발 기관코드(운영: K210800020)
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + H_ORG_CODE);
}
String apiTrxDtm = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
String apiTrxTm = new SimpleDateFormat("HHmmss").format(new Date());
// 기관코드(10) + 인스턴스ID(2) + 일시(6) + RandomString(2) -- 총 20자리
String apiTrxNo = orgCode + instId + apiTrxTm + RandomStringUtils.random(2, true, true).toUpperCase();
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(H_TRX_NO)) {
filterHeaders.put(H_TRX_NO, inboundHeaderMap.get(H_TRX_NO));
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+inboundHeaderMap.get(H_TRX_NO)+"]");
} else {
filterHeaders.put(H_TRX_NO, apiTrxNo);
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+apiTrxNo+"]");
}
if (inboundHeaderMap.containsKey(H_TRX_DTM)) {
filterHeaders.put(H_TRX_DTM, inboundHeaderMap.get(H_TRX_DTM));
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+inboundHeaderMap.get(H_TRX_DTM)+"]");
} else {
filterHeaders.put(H_TRX_DTM, apiTrxDtm);
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+apiTrxDtm+"]");
}
} else {
filterHeaders.put(H_TRX_NO, apiTrxNo);
filterHeaders.put(H_TRX_DTM, apiTrxDtm);
}
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+filterHeaders.getProperty(H_TRX_NO)+"]");
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+filterHeaders.getProperty(H_TRX_DTM)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,107 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class NAVERFinFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "naverfin";
public static final String X_PARTNER_ID = "X-Partner-Id";
public static final String X_FINTECH_ID = "X-Fintech-Id";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("NAVERFinFilter] PreFilter Processing Start!!");
String partnerID = "r0jtqwC9gAW5";
String fintechId = "NF";
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
partnerID = propGroupVo.getProperty(PROP_PREFIX + "." + X_PARTNER_ID);
fintechId = propGroupVo.getProperty(PROP_PREFIX + "." + X_FINTECH_ID);
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_PARTNER_ID)) {
filterHeaders.put(X_PARTNER_ID, inboundHeaderMap.get(X_PARTNER_ID));
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+inboundHeaderMap.get(X_PARTNER_ID)+"]");
} else {
filterHeaders.put(X_PARTNER_ID, partnerID);
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+partnerID+"]");
}
if (inboundHeaderMap.containsKey(X_FINTECH_ID)) {
filterHeaders.put(X_FINTECH_ID, inboundHeaderMap.get(X_FINTECH_ID));
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+inboundHeaderMap.get(X_FINTECH_ID)+"]");
} else {
filterHeaders.put(X_FINTECH_ID, fintechId);
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+fintechId+"]");
}
} else {
filterHeaders.put(X_PARTNER_ID, partnerID);
filterHeaders.put(X_FINTECH_ID, fintechId);
}
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+filterHeaders.getProperty(X_PARTNER_ID)+"]");
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+filterHeaders.getProperty(X_FINTECH_ID)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,138 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map.Entry;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class NICECreditFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "nicecredit";
public static final String H_PRODUCTID = "ProductID";
public static final String B_REQ_DTM = "req_dtm";
public static final String B_GOODS_CLS = "goods_cls";
@SuppressWarnings({ "unchecked" })
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
JSONObject returnMessage = null;
String productId = "2303102120";
String reqDtm = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String goodsCls = "KJBAPI0001";
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
productId = propGroupVo.getProperty(PROP_PREFIX + "." + H_PRODUCTID);
goodsCls = propGroupVo.getProperty(PROP_PREFIX + "." + B_GOODS_CLS);
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (inboundHeaderMap != null && inboundHeaderMap.containsKey(H_PRODUCTID)) {
for (Entry<String, String> e : inboundHeaderMap.entrySet()) {
String key = (String) e.getKey();
String value = (String) e.getValue();
if (StringUtils.equalsIgnoreCase(key, H_PRODUCTID)) {
filterHeaders.setProperty(key, value);
}
if (logger.isDebugEnabled()) {
logger.debug("Request Header :" + key + "=[" + value + "]");
}
}
} else {
filterHeaders.setProperty(H_PRODUCTID, productId);
}
} catch (Exception e) {
logger.error(e.getMessage());
}
AdapterManager adapterManager = AdapterManager.getInstance();
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
String encode = adptGrpVO.getMessageEncode();
if (message == null) {
returnMessage = new JSONObject();
} else {
if (message instanceof JSONObject) {
returnMessage = (JSONObject) message;
} else if (message instanceof String) {
returnMessage = parseJson((String) message);
} else if (message instanceof byte[]) {
returnMessage = parseJson(new String((byte[]) message, encode));
}
}
returnMessage.put(B_REQ_DTM, reqDtm);
returnMessage.put(B_GOODS_CLS, goodsCls);
return returnMessage.toJSONString();
}
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,87 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
/**
* HTTP 클라이언트 어댑터용 암복호화 필터 (송신 방향).
*
* doPreFilter : 외부 시스템으로 보내는 요청 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
* doPostFilter : 외부 시스템에서 받은 응답 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
*
* DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
*
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
* AAD 관련 추가 프로퍼티:
* CRYPTO_AAD_PROP tempProp에서 AAD 값을 조회할 키 이름.
* 미설정 또는 해당 키 없으면 aad=null → IV를 AAD 대체값으로 사용.
*/
public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientAdapterFilter {
public static final String PROP_AAD_PROP = "CRYPTO_AAD_PROP";
/**
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
* 서브클래스에서 오버라이드하여 tempProp으로부터 값을 추출할 수 있다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, Properties tempProp) {
return new HashMap<>();
}
/**
* CRYPTO_AAD_PROP 프로퍼티에 지정된 키로 tempProp에서 AAD 값을 조회한다.
* 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
*/
protected byte[] buildAad(Properties prop, Properties tempProp) {
String propKey = prop.getProperty(PROP_AAD_PROP);
if (StringUtils.isBlank(propKey) || tempProp == null) {
return null;
}
String value = tempProp.getProperty(propKey);
return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
String body = toBodyString(message);
Map<String, String> runtimeCtx = buildRuntimeContext(prop, tempProp);
byte[] aad = buildAad(prop, tempProp);
if (SCOPE_FIELD.equals(scope)) {
return encryptField(body, moduleName, runtimeCtx, aad,
getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
}
return encryptBody(body, moduleName, runtimeCtx, aad);
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
String body = toBodyString(message);
Map<String, String> runtimeCtx = buildRuntimeContext(prop, tempProp);
byte[] aad = buildAad(prop, tempProp);
if (SCOPE_FIELD.equals(scope)) {
return decryptField(body, moduleName, runtimeCtx, aad,
getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
}
return decryptBody(body, moduleName, runtimeCtx, aad);
}
}
@@ -0,0 +1,109 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.util.Logger;
public class SimpleFrameworkFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String CLIENTID = "clientId";
public static final String TRACEID = "traceId";
public static final String GUID = "guid";
public static final String RECEIVEDTIMESTAMP = "receivedTimestamp";
public static final String PARTNER_TRACE_ID = "partnerTraceId";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("SimpleFrameworkFilter] PreFilter Processing Start!!");
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
try {
/* 업체정보 */
String clientId = tempProp.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
if (StringUtils.isEmpty(clientId)) clientId = "";
filterHeaders.put(CLIENTID, clientId);
logger.debug("SimpleFrameworkFilter] Processing Key ["+CLIENTID+"], value ["+clientId+"]");
/* APIM 거래추적자 */
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
filterHeaders.put(TRACEID, uuid);
logger.debug("SimpleFrameworkFilter] Processing Key ["+TRACEID+"], value ["+uuid+"]");
/* GUID */
String guid = tempProp.getProperty(TransactionContextKeys.GUID, "");
filterHeaders.put(GUID, guid);
logger.debug("SimpleFrameworkFilter] Processing Key ["+GUID+"], value ["+guid+"]");
/* 최초요청시간 */
String receivedTimestamp = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME, "");
filterHeaders.put(RECEIVEDTIMESTAMP, receivedTimestamp);
logger.debug("SimpleFrameworkFilter] Processing Key ["+RECEIVEDTIMESTAMP+"], value ["+receivedTimestamp+"]");
String partnerTraceId = inboundHeaderMap.getOrDefault(PARTNER_TRACE_ID, "");
filterHeaders.put(PARTNER_TRACE_ID, partnerTraceId);
logger.debug("SimpleFrameworkFilter] Processing Key ["+PARTNER_TRACE_ID+"], value ["+partnerTraceId+"]");
String x_loan_token = inboundHeaderMap.getOrDefault(TransactionContextKeys.X_LOAN_TOKEN, "");
filterHeaders.put(TransactionContextKeys.X_LOAN_TOKEN, x_loan_token);
logger.debug("SimpleFrameworkFilter] Processing Key ["+TransactionContextKeys.X_LOAN_TOKEN+"], value ["+x_loan_token+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,28 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.common.TransactionContextKeys;
public class SimpleframeworkBodyFilter extends SimpleFrameworkFilter {
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
Object returnMessage = super.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
JSONObject bizJsonStr = null;
if ( returnMessage instanceof String ) {
bizJsonStr = (JSONObject) JSONValue.parse( (String)returnMessage);
if( bizJsonStr != null ) {
bizJsonStr.put("GUID", tempProp.getOrDefault(TransactionContextKeys.GUID, "") );
}
}
return bizJsonStr != null ? bizJsonStr.toJSONString() : returnMessage;
}
}
@@ -0,0 +1,97 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class TOSSBankFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "tossbank";
public static final String X_TOSSBANK_LOAN_TRACE_ID = "X-TOSSBANK-LOAN-TRACE-ID";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("TOSSBankFilter] PreFilter Processing Start!!");
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_TOSSBANK_LOAN_TRACE_ID)) {
filterHeaders.put(X_TOSSBANK_LOAN_TRACE_ID, inboundHeaderMap.get(X_TOSSBANK_LOAN_TRACE_ID));
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+inboundHeaderMap.get(X_TOSSBANK_LOAN_TRACE_ID)+"]");
} else {
filterHeaders.put(X_TOSSBANK_LOAN_TRACE_ID, traceId);
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+traceId+"]");
}
} else {
/* APIM 거래추적자 */
filterHeaders.setProperty(X_TOSSBANK_LOAN_TRACE_ID, traceId);
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+traceId+"]");
}
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+filterHeaders.getProperty(X_TOSSBANK_LOAN_TRACE_ID)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,71 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.util.Logger;
import com.nimbusds.jose.shaded.gson.JsonObject;
public class TraceBodyFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String BIZ_HEADER = "header";
public static final String GUID = "guid";
public static final String TRACEID = "traceId";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
JSONObject bizJsonStr = null;
if (message instanceof String) {
bizJsonStr = (JSONObject) JSONValue.parse((String) message);
if ( bizJsonStr == null ) return message; //json string이 아님.
if (bizJsonStr.containsKey(BIZ_HEADER)) {
JSONObject bizHeader = (JSONObject) bizJsonStr.get(BIZ_HEADER);
putBizHeaderData(bizHeader, tempProp);
}else {
JSONObject bizHeader = new JSONObject();
bizJsonStr.put(BIZ_HEADER, bizHeader);
putBizHeaderData(bizHeader, tempProp);
}
}
return bizJsonStr != null ? bizJsonStr.toJSONString() : message;
}
static private void putBizHeaderData(JSONObject bizHeader, Properties tempProp) {
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
String guid = tempProp.getProperty(TransactionContextKeys.GUID, "");
bizHeader.put(GUID, guid);
logger.debug("Processing Key [" + GUID + "], value [" + guid + "]");
bizHeader.put(TRACEID, traceId);
logger.debug("Processing Key [" + TRACEID + "], value [" + traceId + "]");
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
// TODO Auto-generated method stub
return message;
}
}
@@ -13,7 +13,7 @@ import com.eactive.eai.adapter.RequestDispatcher;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.util.Logger;
@@ -85,7 +85,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
if (!"HTC".equals(group.getType())) {
String allowIp = prop.getProperty(ALLOW_IP,"");
if (StringUtils.isNotBlank(allowIp)) {
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
if (adapterFilter != null) {
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
}
@@ -103,7 +103,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
continue;
}
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
} else {
@@ -126,7 +126,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
continue;
}
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
resultMessage = adapterFilter.doPostFilter(adptGrpName, adptName, resultMessage, prop, request,
response);
@@ -1,21 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
/**
* HTTP 어댑터 요청/응답 암복호화 필터 기반 클래스.
*
*/
public class BaseCryptoFilter extends CryptoFilter {
/**
* 빈 Runtime Context를 생성한다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
return new HashMap<>();
}
}
@@ -1,232 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.security.CryptoModuleService;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.JsonPathUtil;
/**
* HTTP 어댑터 요청/응답 암복호화 필터 기반 클래스.
*
* 서브클래스는 {@link #buildRuntimeContext}를 구현하여
* DYNAMIC 키 도출에 필요한 컨텍스트 맵을 제공한다.
* STATIC 키 모듈을 사용하는 경우 빈 Map을 반환하면 된다.
* (CryptoModuleManager가 KEY_SOURCE_TYPE=STATIC이면 runtimeContext를 무시하고 고정 키를 사용한다.)
*
* 어댑터 프로퍼티 키:
* CRYPTO_MODULE_NAME 필수. DB에 등록된 암호화 모듈명.
* CRYPTO_SCOPE BODY | FIELD (기본 FIELD)
* BODY : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
* FIELD : 특정 JSON 경로의 값만 암복호화
*
* FIELD 범위 전용:
* 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).
* 미설정 또는 헤더값 없으면 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_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 SCOPE_BODY = "BODY";
public static final String SCOPE_FIELD = "FIELD";
public static final String PATH_ROOT = "/";
public static final String PATH_ENCDATA = "/encrypted_data";
// ------------------------------------------------------------------
// 추상 메서드
// ------------------------------------------------------------------
/**
* DYNAMIC 키 도출에 필요한 runtimeContext 맵을 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
*/
protected abstract Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request);
// ------------------------------------------------------------------
// doPreFilter: 요청 복호화
// ------------------------------------------------------------------
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
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,
getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
}
return decryptBody(body, moduleName, runtimeCtx, aad);
}
// ------------------------------------------------------------------
// doPostFilter: 응답 암호화
// ------------------------------------------------------------------
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
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,
getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
}
return encryptBody(body, moduleName, runtimeCtx, aad);
}
// ------------------------------------------------------------------
// 복호화 구현
// ------------------------------------------------------------------
private String decryptBody(String body, String moduleName, Map<String, String> runtimeCtx, byte[] aad) throws Exception {
byte[] cipherBytes = Base64.getDecoder().decode(body.trim());
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
return new String(plainBytes, StandardCharsets.UTF_8);
}
/**
* fromPath의 Base64 값을 복호화하여 toPath에 반영.
* toPath = "/" → 복호화된 텍스트로 body 전체 교체.
*/
private String decryptField(String body, String moduleName, Map<String, String> runtimeCtx, byte[] aad,
String fromPath, String toPath) throws Exception {
String encBase64 = JsonPathUtil.getValueAtPath(body, fromPath);
if (StringUtils.isBlank(encBase64)) {
logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
return body;
}
byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
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.setValueAtPath(body, toPath, plainText, false);
}
// ------------------------------------------------------------------
// 암호화 구현
// ------------------------------------------------------------------
private String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx, byte[] aad) throws Exception {
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
body.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(cipherBytes);
}
/**
* fromPath 값을 암호화하여 toPath(Base64)에 반영.
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑.
*/
private String encryptField(String body, String moduleName, Map<String, String> runtimeCtx, byte[] aad,
String fromPath, String toPath) throws Exception {
String plainText;
if (PATH_ROOT.equals(fromPath)) {
plainText = body;
} else {
plainText = JsonPathUtil.getValueAtPath(body, fromPath);
if (StringUtils.isBlank(plainText)) {
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
return body;
}
}
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
plainText.getBytes(StandardCharsets.UTF_8));
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
if (PATH_ROOT.equals(fromPath)) {
String fieldName = toPath.replaceFirst("^/", "");
return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
}
return JsonPathUtil.setValueAtPath(body, toPath, encBase64, false);
}
// ------------------------------------------------------------------
// 유틸
// ------------------------------------------------------------------
/**
* 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
* <ul>
* <li>{@code String} — 그대로 반환</li>
* <li>{@code byte[]} — UTF-8 디코딩</li>
* <li>{@code JSONObject} / 기타 — {@code toString()} 호출
* (json-simple JSONObject는 toString()이 toJSONString()을 위임함)</li>
* </ul>
*/
protected String toBodyString(Object message) {
if (message instanceof String) {
return (String) message;
}
if (message instanceof byte[]) {
return new String((byte[]) message, StandardCharsets.UTF_8);
}
return message.toString();
}
/**
* crypto.aad.header 프로퍼티에 지정된 헤더값을 UTF-8 바이트로 반환한다.
* 프로퍼티 미설정 또는 헤더값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
*/
protected byte[] buildAad(Properties prop, HttpServletRequest request) {
String headerName = prop.getProperty(PROP_AAD_HEADER);
if (StringUtils.isBlank(headerName) || request == null) {
return null;
}
String headerValue = request.getHeader(headerName);
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
}
protected String required(Properties prop, String key) {
String v = prop.getProperty(key);
if (StringUtils.isBlank(v)) {
throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
}
return v;
}
private String getProp(Properties prop, String key, String defaultVal) {
String v = prop.getProperty(key);
return StringUtils.isBlank(v) ? defaultVal : v;
}
}
@@ -6,13 +6,10 @@ import com.eactive.eai.common.util.Logger;
public class HttpAdapterFilterFactory {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
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 HttpAdapterFilterFactory() {
super();
}
@SuppressWarnings("rawtypes")
public static HttpAdapterFilter createFilter(String type) {
if (h.containsKey(type)) {
return (HttpAdapterFilter) h.get(type);
@@ -32,12 +29,19 @@ public class HttpAdapterFilterFactory {
case ADAPTERALLOWIPLISTFILTER:
filter = new AdapterAllowIPListFilter();
break;
case HMAC_SHA256:
filter = new HmacSha256VerifyFilterKjb();
break;
case REFLECTALLHEADER:
filter = new ReflectAllHeaderFilter();
break;
default:
try {
Class cl = Class.forName(type);
filter = (HttpAdapterFilter) cl.newInstance();
} catch (Exception e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
filter = classForName(type);
if (filter == null) {
filter = classForName(basePackage + "." + type);
}
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
}
break;
}
@@ -49,4 +53,14 @@ public class HttpAdapterFilterFactory {
return null;
}
}
private static HttpAdapterFilter classForName(String type) {
try {
Class<?> cl = Class.forName(type);
return (HttpAdapterFilter) cl.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
return null;
}
}
}
@@ -1,71 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.concurrent.ConcurrentHashMap;
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.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() {
super();
}
@SuppressWarnings("rawtypes")
public static HttpAdapterFilter createFilter(String type) {
if (h.containsKey(type)) {
return (HttpAdapterFilter) h.get(type);
}
HttpAdapterFilter filter = null;
switch (HttpAdapterFilterType.getValue(type)) {
case APIAUTHFILTER:
filter = new ApiAuthFilter();
break;
case JWTAUTHFILTER:
filter = new JwtAuthFilter();
break;
case IPWHITELISTFILTER:
filter = new IpWhiteListFilter();
break;
case ADAPTERALLOWIPLISTFILTER:
filter = new AdapterAllowIPListFilter();
break;
case HMAC_SHA256:
filter = new HmacSha256VerifyFilterKjb();
break;
case REFLECTALLHEADER:
filter = new ReflectAllHeaderFilter();
break;
default:
filter = classForName(type);
if (filter == null) {
filter = classForName(basePackage + "." + type);
}
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
}
break;
}
if (filter != null) {
h.put(type, filter);
return filter;
} else {
return null;
}
}
private static HttpAdapterFilter classForName(String type) {
try {
Class<?> cl = Class.forName(type);
return (HttpAdapterFilter) cl.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
return null;
}
}
}
@@ -0,0 +1,85 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
/**
* HTTP 서버 어댑터용 암복호화 필터 (수신 방향).
*
* doPreFilter : 수신 요청 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
* doPostFilter : 송신 응답 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
*
* DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
*
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
*/
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {
/**
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
* 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 수 있다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
return new HashMap<>();
}
/**
* CRYPTO_AAD_HEADER 프로퍼티에 지정된 헤더값을 UTF-8 바이트로 반환한다.
* 미설정 또는 헤더값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
*/
protected byte[] buildAad(Properties prop, HttpServletRequest request) {
String headerName = prop.getProperty(PROP_AAD_HEADER);
if (StringUtils.isBlank(headerName) || request == null) {
return null;
}
String headerValue = request.getHeader(headerName);
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
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,
getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
}
return decryptBody(body, moduleName, runtimeCtx, aad);
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
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,
getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
}
return encryptBody(body, moduleName, runtimeCtx, aad);
}
}
@@ -0,0 +1,153 @@
package com.eactive.eai.adapter.http.filter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.security.CryptoModuleService;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode;
/**
* InCryptoFilter / OutCryptoFilter 공통 기반 클래스.
*
* BODY/FIELD 범위 암복호화 로직, 프로퍼티 상수, 유틸리티 메서드를 제공한다.
* HTTP 서버 어댑터용은 {@link InCryptoFilter}, 클라이언트 어댑터용은 {@link OutCryptoFilter} 참조.
*
* 프로퍼티 키:
* CRYPTO_MODULE_NAME 필수. DB에 등록된 암호화 모듈명.
* CRYPTO_SCOPE BODY | FIELD (기본값: FIELD)
* BODY : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
* FIELD : 특정 JSON 경로의 값만 암복호화
*
* FIELD 범위 전용:
* 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 요청 헤더명 (InCryptoFilter 전용).
* 미설정 또는 헤더값 없으면 aad=null → IV를 AAD 대체값으로 사용.
*/
public abstract class AbstractCryptoFilter {
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_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 SCOPE_BODY = "BODY";
public static final String SCOPE_FIELD = "FIELD";
public static final String PATH_ROOT = "/";
public static final String PATH_ENCDATA = "/encrypted_data";
// ------------------------------------------------------------------
// 복호화 구현
// ------------------------------------------------------------------
protected String decryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad) throws Exception {
byte[] cipherBytes = Base64.getDecoder().decode(body.trim());
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
return new String(plainBytes, StandardCharsets.UTF_8);
}
/**
* fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
* toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
*/
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad, String fromPath, String toPath) throws Exception {
JsonNode root = JsonPathUtil.toTree(body);
String encBase64 = JsonPathUtil.getAt(root, fromPath);
if (StringUtils.isBlank(encBase64)) {
logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
return body;
}
byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
if (PATH_ROOT.equals(toPath)) {
return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
}
JsonPathUtil.setAt(root, toPath, plainText);
return JsonPathUtil.fromTree(root);
}
// ------------------------------------------------------------------
// 암호화 구현
// ------------------------------------------------------------------
protected String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad) throws Exception {
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
body.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(cipherBytes);
}
/**
* fromPath 값을 암호화하여 toPath(Base64)에 반영. JSON 파싱 1회.
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
*/
protected String encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad, String fromPath, String toPath) throws Exception {
if (PATH_ROOT.equals(fromPath)) {
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
body.getBytes(StandardCharsets.UTF_8));
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
String fieldName = toPath.replaceFirst("^/", "");
return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
}
JsonNode root = JsonPathUtil.toTree(body);
String plainText = JsonPathUtil.getAt(root, fromPath);
if (StringUtils.isBlank(plainText)) {
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
return body;
}
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
plainText.getBytes(StandardCharsets.UTF_8));
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
JsonPathUtil.setAt(root, toPath, encBase64);
return JsonPathUtil.fromTree(root);
}
// ------------------------------------------------------------------
// 유틸
// ------------------------------------------------------------------
/**
* 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
*/
protected String toBodyString(Object message) {
if (message instanceof String) {
return (String) message;
}
if (message instanceof byte[]) {
return new String((byte[]) message, StandardCharsets.UTF_8);
}
return message.toString();
}
protected String required(Properties prop, String key) {
String v = prop.getProperty(key);
if (StringUtils.isBlank(v)) {
throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
}
return v;
}
protected String getProp(Properties prop, String key, String defaultVal) {
String v = prop.getProperty(key);
return StringUtils.isBlank(v) ? defaultVal : v;
}
}
@@ -682,11 +682,10 @@ public abstract class DefaultProcess extends Process {
AdapterVO inboundAdapterVO = AdapterManager.getInstance().getAdapterVO(inboudnAdapterGroupName, inboudnAdapterName);
String errorResponseHandlerClass = AdapterPropManager.getInstance().getProperty(inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
if(StringUtils.isBlank(errorResponseHandlerClass)) {
this.resEaiMsg.setRspErrCd("RECEAIINA001", false);
String errorCode = mapper.getErrorCode(resStandardMessage);
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("Message.Msg_Body.OUTPUT_MSG_DESC"));
this.resEaiMsg.setRspErrMsg(String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("MSG.MAIN_MSG.outp_msg_desc"));
this.resEaiMsg.setRspErr("RECEAIINA001", String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
return;
}
@@ -698,6 +697,8 @@ public abstract class DefaultProcess extends Process {
if(responseObj != null) {
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
}
this.resEaiMsg.setRspErrCd(EAIMessageKeys.BWK_FAILMSG_CODE, false);
}
}
@@ -10,34 +10,55 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
// 해당 필드에 대한 get/set 을 수행
public class JsonPathUtil {
private JsonPathUtil() {
}
private static final ObjectMapper objectMapper = new ObjectMapper();
// ---------------------------------------------------------------
// JsonNode 기반 단일 파싱 API — 연속 get/set 시 파싱 횟수 절감
// ---------------------------------------------------------------
/** JSON 문자열을 JsonNode 트리로 파싱. */
public static JsonNode toTree(String json) throws Exception {
return objectMapper.readTree(json);
}
/** 이미 파싱된 트리에서 경로의 값을 추출. */
public static String getAt(JsonNode root, String path) {
JsonNode node = getNodeAtPath(root, path);
return node != null ? node.asText() : null;
}
/** 이미 파싱된 트리의 경로에 값을 설정 (in-place). */
public static void setAt(JsonNode root, String path, String value) {
JsonNode parent = getParentNode(root, getParentPath(path));
if (parent == null) return;
String key = getFieldName(path);
if (parent.isArray()) {
((ArrayNode) parent).set(Integer.parseInt(key), objectMapper.convertValue(value, JsonNode.class));
} else if (parent.isObject()) {
((ObjectNode) parent).put(key, value);
}
}
/** JsonNode 트리를 JSON 문자열로 직렬화. */
public static String fromTree(JsonNode root) throws Exception {
return objectMapper.writeValueAsString(root);
}
// ---------------------------------------------------------------
// 문자열 기반 API (내부적으로 위 메서드에 위임)
// ---------------------------------------------------------------
// 특정 경로의 값을 설정하는 함수
public static String setValueAtPath(String jsonString, String path, String newValue, boolean isPretty) {
try {
// JSON 문자열을 JsonNode 객체로 파싱
JsonNode rootNode = objectMapper.readTree(jsonString);
// 경로에 해당하는 부모 노드를 가져옴
JsonNode parentNode = getParentNode(rootNode, getParentPath(path));
// 경로의 마지막 노드 이름 또는 배열 인덱스 추출
String fieldNameOrIndex = getFieldName(path);
// 배열 또는 객체에서 값을 설정
if (parentNode.isArray()) {
((ArrayNode) parentNode).set(Integer.parseInt(fieldNameOrIndex), objectMapper.convertValue(newValue, JsonNode.class));
} else if (parentNode.isObject()) {
((ObjectNode) parentNode).put(fieldNameOrIndex, newValue);
}
// 수정된 JSON 문자열 반환
setAt(rootNode, path, newValue);
if(isPretty)
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
else
else
return objectMapper.writeValueAsString(rootNode);
} catch (Exception e) {
e.printStackTrace();
@@ -48,13 +69,7 @@ public class JsonPathUtil {
// 특정 경로의 값을 가져오는 함수
public static String getValueAtPath(String jsonString, String path) {
try {
// JSON 문자열을 JsonNode 객체로 파싱
JsonNode rootNode = objectMapper.readTree(jsonString);
// 경로에 해당하는 노드의 값을 반환
JsonNode resultNode = getNodeAtPath(rootNode, path);
return resultNode != null ? resultNode.asText() : null;
return getAt(objectMapper.readTree(jsonString), path);
} catch (Exception e) {
e.printStackTrace();
return null;
@@ -101,6 +116,71 @@ public class JsonPathUtil {
return currentNode;
}
/**
* 이미 파싱된 bodyNode에서 removeFromPath 필드를 제거하고 mergeJson을 병합.
* body의 재파싱 없이 호출 가능하여 파싱 횟수를 1회 절감.
*
* body와 mergeJson이 모두 JSON 객체인 경우에만 병합을 수행한다.
* 어느 한쪽이 JSON 객체가 아니면 mergeJson을 그대로 반환한다 (전체 교체 fallback).
*
* @param bodyNode 이미 파싱된 원본 JsonNode
* @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
* @param mergeJson 병합할 JSON 문자열
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
*/
public static String mergeAtRoot(JsonNode bodyNode, String removeFromPath, String mergeJson) {
try {
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;
}
}
/**
* body에서 removeFromPath 필드를 제거한 뒤, mergeJson의 필드를 body에 병합하여 반환한다.
* (문자열 기반 overload — 내부적으로 body를 1회 파싱하여 위 메서드에 위임)
*
* 사용 예 (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 {
return mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson);
} 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\" } ] } }";
@@ -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;
}
}
@@ -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,14 +53,14 @@ import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
* CryptoFilter HSM 연동 통합 테스트 (SoftHSM2 / SunPKCS11)
*
* SoftHSM2에서 MASTER_KEY를 조회하여 키를 도출한 뒤
* BaseCryptoFilter / CryptoFilter 의 암복호화 전체 흐름을 검증한다.
* InCryptoFilter 의 암복호화 전체 흐름을 검증한다.
*
* 사전 조건:
* C:\SoftHSM2 에 SoftHSM2 설치 (미설치 시 자동 건너뜀)
* MASTER_KEY alias는 없으면 자동 생성됨
*
* 검증 전략:
* HSM → 키 도출 → CryptoModuleExtension → CryptoFilter doPostFilter(암호화) → doPreFilter(복호화) → 원문 일치
* HSM → 키 도출 → CryptoModuleExtension → InCryptoFilter doPostFilter(암호화) → doPreFilter(복호화) → 원문 일치
*/
@TestMethodOrder(MethodOrderer.DisplayName.class)
public class CryptoFilterHsmIntegrationTest {
@@ -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);
@@ -254,15 +256,15 @@ public class CryptoFilterHsmIntegrationTest {
@Test
@DisplayName("1-3. FIELD/GCM HsmKey + AAD 헤더 — 동일 요청 ID로 암복호화 성공")
void testHsmGcm_field_withAad_success() throws Exception {
BaseCryptoFilter filter = new BaseCryptoFilter();
InCryptoFilter filter = new InCryptoFilter();
String original = "{\"amount\":\"500000\"}";
String requestId = "REQ-20240101-001";
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);
@@ -273,7 +275,7 @@ public class CryptoFilterHsmIntegrationTest {
@Test
@DisplayName("1-4. FIELD/GCM HsmKey + AAD 헤더 — 다른 요청 ID로 복호화 시 GCM 인증 실패")
void testHsmGcm_field_withAad_wrongAad_fails() throws Exception {
BaseCryptoFilter filter = new BaseCryptoFilter();
InCryptoFilter filter = new InCryptoFilter();
String original = "{\"amount\":\"500000\"}";
// 암호화 시 요청 ID
@@ -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);
@@ -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);
@@ -168,14 +169,33 @@ class BaseCryptoFilterTest {
}
@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
@@ -195,7 +215,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 +272,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 +295,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 +310,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 +321,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;
}
}
@@ -143,11 +143,20 @@ class CryptoModuleServiceTest {
Map<String, String> runtimeCtx = new HashMap<>();
runtimeCtx.put("X-Api-Enc-Key", "clientKey0000001");
byte[] plain = "DYNAMIC 키 서비스 테스트".getBytes(StandardCharsets.UTF_8);
byte[] plain = "{\"test\": \"DYNAMIC AES/CBC 테스트\"}".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = service.encrypt("DYN_SVC", runtimeCtx, plain);
byte[] decrypted = service.decrypt("DYN_SVC", runtimeCtx, encrypted);
String encBase64 = Base64.getEncoder().encodeToString(encrypted);
String decBase64 = Base64.getEncoder().encodeToString(decrypted);
System.out.println("DYNAMIC AES/CBC");
System.out.println("plain:"+new String(plain));
System.out.println("encBase64:"+encBase64);
System.out.println("decBase64:"+decBase64);
System.out.println("plain:"+new String(decrypted));
assertArrayEquals(plain, decrypted);
}