Compare commits

..

13 Commits

Author SHA1 Message Date
curry772 d78e422368 PreFilter에서 헤더복사하도록 수정 2026-06-25 09:56:23 +09:00
curry772 3cc66fcaa8 JacksonUtil 개발 2026-06-25 09:56:01 +09:00
curry772 73ed49372a ERP용 token 발급 개발 2026-06-25 09:55:27 +09:00
curry772 3574ad9e0c damo 테스트 추가 2026-06-23 09:55:50 +09:00
curry772 49eb8fbe85 API에 scope이 적용되어 있지 않은 경우, Client에 API 적용여부를 확인하도록 수정 2026-06-23 09:55:20 +09:00
curry772 253716f4b2 암복호화 적용 2026-06-23 09:54:07 +09:00
curry772 02e282c7ba Bypass REST Outbound Adapter 이동 2026-06-23 09:46:54 +09:00
curry772 180ea8cac1 거래차단 거래 503 Service unavailable throw 하도록 수정 2026-06-23 09:33:14 +09:00
curry772 2c21c691df UnkownMessageLogUtils.logUnkownMessage 로그 위치 변경 2026-06-23 09:23:17 +09:00
curry772 0da31a8de2 OAuthServer 프러퍼티 그룹 암호화 적용 2026-06-23 08:54:00 +09:00
curry772 46d88d77f2 필터 에러코드 추가 2026-06-19 18:00:31 +09:00
curry772 51967284ba api 찾지 못할 경우 FilterException throw로 변경 2026-06-19 18:00:13 +09:00
curry772 a218291748 rest outbound adapter에서 통신오류가 아닌 경우에만, adaptererrorhandler를 호출하도록 수정 2026-06-19 11:21:22 +09:00
17 changed files with 750 additions and 149 deletions
+1
View File
@@ -142,6 +142,7 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.15' testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.15'
testImplementation 'junit:junit:4.4' testImplementation 'junit:junit:4.4'
testImplementation files('libs/damo-manager.jar', 'libs/kjb-safedb.jar')
} }
test { test {
@@ -1,4 +1,4 @@
package com.eactive.eai.adapter.http.client.impl.kjb; package com.eactive.eai.adapter.http.client.impl;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
@@ -43,7 +43,6 @@ import com.eactive.eai.adapter.http.HttpMethodType;
import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport; import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.HttpClientAdapterVO; import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
import com.eactive.eai.adapter.http.client.impl.HttpClientAdapterServiceRest;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.impl.HttpAdapterServiceBypass; import com.eactive.eai.adapter.http.dynamic.impl.HttpAdapterServiceBypass;
import com.eactive.eai.common.TransactionContextKeys; import com.eactive.eai.common.TransactionContextKeys;
@@ -146,6 +146,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
String headerGroupName = prop.getProperty(HEADER_GROUP); String headerGroupName = prop.getProperty(HEADER_GROUP);
String relayResponseHeaderKeys = prop.getProperty(HEADER_KEYS, ""); String relayResponseHeaderKeys = prop.getProperty(HEADER_KEYS, "");
useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y"); useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y");
String authorizationHeaderName = prop.getProperty("ADAPTER_TOKEN_HEADER_NAME", "Authorization");
boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y"); boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y");
String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL"); String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL");
@@ -332,8 +333,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
// OAuth2AccessToken check 부분을 뒤에서 여기로 위치를 옮김, 나이스지키미 암호화 KEY를 얻기 위해서. JWHONG // OAuth2AccessToken check 부분을 뒤에서 여기로 위치를 옮김, 나이스지키미 암호화 KEY를 얻기 위해서. JWHONG
OAuth2AccessTokenVO accessToken = null; OAuth2AccessTokenVO accessToken = null;
String auth ="";
Date currentDate = new Date();
if (useAdapterToken) { if (useAdapterToken) {
AccessTokenManagerByDB tokenManager = AccessTokenManagerByDB.getInstance(); AccessTokenManagerByDB tokenManager = AccessTokenManagerByDB.getInstance();
accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName()); accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName());
@@ -351,29 +350,11 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
+ accessToken + "]"); + accessToken + "]");
} }
Properties properties = AdapterPropManager.getInstance().getProperties(vo.getAdapterName()); // jwhong setAuthHeaders(method, accessToken, authorizationHeaderName);
String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong
if (useAdapterToken && "AES256-NICEON".equals(encryptAlgorithm)) {
String niceToken = accessToken.getAccessToken();
if ( niceToken == null || niceToken == "") {
throw new Exception("NiceOn Token is empty, TOKEN failed to be issued, TOKEN = [" + niceToken + "]");
}
String clientId = accessToken.getClientId(); // HttpClientAccessTokenServiceWithBase64NiceOn 서 넣어줌
long currentTime = currentDate.getTime() / 1000;
auth = niceToken + ":" + currentTime + ":" + clientId ;
logger.debug("HttpClientAdapterServiceRest] NiceOn auth = [" + auth + "]");
auth = Base64.getEncoder().encodeToString(auth.getBytes("UTF-8"));
logger.debug("HttpClientAdapterServiceRest] NiceOn Base64 encoded auth = [" + auth + "]");
setNiceOnAuthHeaders(method, auth);
} else {
setAuthHeaders(method, accessToken); // 원래 있던 logic
}
if (logger.isDebug()) { if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName()
+ ") RequestHeader [" + method.getHeader("Authorization").getName() + ": " + method.getHeader("Authorization").getValue() + "]"); + ") RequestHeader [" + method.getHeader(authorizationHeaderName).getName() + ": " + method.getHeader(authorizationHeaderName).getValue() + "]");
} }
} }
@@ -390,7 +371,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken); logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken);
if(!StringUtils.isBlank(authToken)) { if(!StringUtils.isBlank(authToken)) {
setAuthHeaders(method, authToken); setAuthHeaders(method, authToken, authorizationHeaderName);
} }
// 여기 까지 // 여기 까지
@@ -618,9 +599,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
String oldToken = accessToken == null ? null : accessToken.getAccessToken(); String oldToken = accessToken == null ? null : accessToken.getAccessToken();
OAuth2AccessTokenVO newaccessToken = (OAuth2AccessTokenVO) tokenManager OAuth2AccessTokenVO newaccessToken = (OAuth2AccessTokenVO) tokenManager
.retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken); .retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken);
method.setHeader("Authorization", newaccessToken.getAuthorization());
setAuthHeaders(method, newaccessToken); setAuthHeaders(method, accessToken, authorizationHeaderName);
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName()
@@ -878,11 +858,21 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
String.format("%s", authorization)); String.format("%s", authorization));
} }
protected void setAuthHeaders(HttpUriRequestBase method, String authorization, String authorizationHeaderName) throws Exception {
method.setHeader(authorizationHeaderName,
String.format("%s", authorization));
}
protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception { protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
method.setHeader("Authorization", method.setHeader("Authorization",
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken())); String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
} }
protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken, String authorizationHeaderName) throws Exception {
method.setHeader(authorizationHeaderName,
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
}
/* nice on 나이스 지키미 용으로 생성함 /* nice on 나이스 지키미 용으로 생성함
* jwhong 2015-11-04 * jwhong 2015-11-04
*/ */
@@ -70,13 +70,13 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
throw e; throw e;
} catch (JwtAuthException e) { // filter error } catch (JwtAuthException e) { // filter error
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e); logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response, // UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
"RECEAIIRP202", e); // "RECEAIIRP202", e);
throw e; throw e;
} catch (Exception e) { // filter error } catch (Exception e) { // filter error
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e); logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response, // UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
"RECEAIIRP201", e); // "RECEAIIRP201", e);
throw e; throw e;
} }
@@ -24,6 +24,8 @@ import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.util.IOUtils; import com.nimbusds.jose.util.IOUtils;
import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.SignedJWT;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@@ -142,23 +144,29 @@ public class ApiAuthFilter implements HttpAdapterFilter {
OAuth2Manager manager = OAuth2Manager.getInstance(); OAuth2Manager manager = OAuth2Manager.getInstance();
HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId); HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE); // API에 scope 설정이 안되어 있는 경우, scope 체크를 pass하고 나중에 client API 맵을 체크한다.
if (scopeArr == null || scopeArr.length == 0) { if(CollectionUtils.isEmpty(scopeSet))
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL, isPassScope = true;
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
for (String scope : scopeArr) { if(!isPassScope) {
if (StringUtils.equals(scope, "oob") || StringUtils.equals(scope, "public")) { String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE);
isPassScope = true; if (scopeArr == null || scopeArr.length == 0) {
}
}
if (!isPassScope) {
if (!verifyScope(scopeSet, signedJWT)) {
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL, throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL,
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString())); String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
} }
for (String scope : scopeArr) {
if (StringUtils.equals(scope, "oob") || StringUtils.equals(scope, "public")) {
isPassScope = true;
}
}
if (!isPassScope) {
if (!verifyScope(scopeSet, signedJWT)) {
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL,
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
}
} }
// header 값으로 전송된 clientId와 토큰 소유 clientId check // header 값으로 전송된 clientId와 토큰 소유 clientId check
@@ -6,6 +6,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter; import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
@@ -48,7 +49,7 @@ public class ApiKeyExtractFilter implements HttpAdapterFilter {
// PathVariable 지원 추가 // PathVariable 지원 추가
String ruledPath = getMatchedKey(requestPath); String ruledPath = getMatchedKey(requestPath);
if(ruledPath == null) if(ruledPath == null)
throw new IllegalAccessException("path not found - " + requestPath); throw new FilterException("path not found - " + requestPath, ERROR_PRE_FAIL, HttpStatus.FORBIDDEN.value());
prop.setProperty(FINAL_STD_MESSAGE_KEY, ruledPath);// StandardMessageUtil.getMatchedKey(), url prop.setProperty(FINAL_STD_MESSAGE_KEY, ruledPath);// StandardMessageUtil.getMatchedKey(), url
// pathvariable도 처리 // pathvariable도 처리
@@ -7,6 +7,9 @@ import javax.servlet.http.HttpServletResponse;
public interface HttpAdapterFilter { public interface HttpAdapterFilter {
public static final String ERROR_PRE_FAIL = "E.PREFILTER_FAIL";
public static final String ERROR_POST_FAIL = "E.POSTFILTER_FAIL";
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop, public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception; HttpServletRequest request, HttpServletResponse response) throws Exception;
@@ -51,13 +51,6 @@ public class ReflectAllHeaderFilter implements HttpAdapterFilter {
@Override @Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop, public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest request, HttpServletResponse response) throws Exception {
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.debug("doPreFilter ReflectAllHeaderFilter Start."); logger.debug("doPreFilter ReflectAllHeaderFilter Start.");
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim(); String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
@@ -90,6 +83,45 @@ public class ReflectAllHeaderFilter implements HttpAdapterFilter {
logger.debug("doPreFilter ReflectAllHeaderFilter End."); logger.debug("doPreFilter ReflectAllHeaderFilter End.");
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.debug("doPostFilter ReflectAllHeaderFilter Start.");
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);
}
try {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
if( StringUtils.equalsAnyIgnoreCase( headerName, HttpHeaderRelayBlackList ) ) {
logger.debug("Skip Processing Key ["+headerName+"], value ["+headerValue+"] in HttpHeaderRelayBlackList");
continue;
}
response.setHeader(headerName, headerValue);
}
} catch (Exception e) {
logger.error(e.getMessage());
}
logger.debug("doPostFilter ReflectAllHeaderFilter End.");
return resultMessage; return resultMessage;
} }
} }
@@ -1,5 +1,27 @@
package com.eactive.eai.adapter.http.dynamic.impl; package com.eactive.eai.adapter.http.dynamic.impl;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.mina.common.ByteBuffer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.AdapterGroupVO; import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager; import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager; import com.eactive.eai.adapter.AdapterPropManager;
@@ -8,8 +30,8 @@ import com.eactive.eai.adapter.Keys;
import com.eactive.eai.adapter.http.HttpMemoryLogger; import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.HttpStatusException; import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport; import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.UnkownMessageLogUtils;
import com.eactive.eai.common.TransactionContextKeys; import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.exception.ExceptionUtil; import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.util.CommonLib; import com.eactive.eai.common.util.CommonLib;
@@ -19,30 +41,6 @@ import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.env.ElinkConfig; import com.eactive.eai.env.ElinkConfig;
import com.eactive.eai.inbound.processor.Processor; import com.eactive.eai.inbound.processor.Processor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.core5.http.ContentType;
import org.apache.mina.common.ByteBuffer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.springframework.jms.support.converter.MessageType;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
{ {
public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod"; public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";
@@ -66,7 +64,8 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
AdapterGroupVO adapterGroupVo = null; AdapterGroupVO adapterGroupVo = null;
Properties prop = null; Properties prop = null;
byte[] requestBytes = null;
String encode = "MS949";
try { try {
AdapterManager adapterManager = AdapterManager.getInstance(); AdapterManager adapterManager = AdapterManager.getInstance();
AdapterVO adptVO = adapterManager.getAdapterVO(adptGrpName,adptName); AdapterVO adptVO = adapterManager.getAdapterVO(adptGrpName,adptName);
@@ -78,9 +77,9 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
} }
Properties httpProp = manager.getProperties(adptVO.getPropGroupName()); Properties httpProp = manager.getProperties(adptVO.getPropGroupName());
encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(), "MS949");
String responseType = httpProp.getProperty(RESPONSE_TYPE,"SYNC"); String responseType = httpProp.getProperty(RESPONSE_TYPE,"SYNC");
String urlDecodeYn = httpProp.getProperty(URL_DECODE_YN,"N"); String urlDecodeYn = httpProp.getProperty(URL_DECODE_YN,"N");
String encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(), "MS949");
String messageType = adapterManager.getAdapterGroupVO(adptGrpName).getMessageType(); String messageType = adapterManager.getAdapterGroupVO(adptGrpName).getMessageType();
String requestBodyYn = httpProp.getProperty(REQUEST_BODY_YN,"N"); String requestBodyYn = httpProp.getProperty(REQUEST_BODY_YN,"N");
String parameterName = httpProp.getProperty(PARAMETER_NAME,PARAMETER_MESSAGE); String parameterName = httpProp.getProperty(PARAMETER_NAME,PARAMETER_MESSAGE);
@@ -107,7 +106,6 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
logger.info("시작 >> encode = [" + encode + "]"); logger.info("시작 >> encode = [" + encode + "]");
byte[] requestBytes = null;
if ("Y".equals(requestBodyYn)){ if ("Y".equals(requestBodyYn)){
ServletInputStream sis = request.getInputStream(); ServletInputStream sis = request.getInputStream();
@@ -191,19 +189,19 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
prop.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod()); prop.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod());
prop.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, "")); prop.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, ""));
String body = new String ( requestBytes,encode); // String body = new String ( requestBytes,encode);
//
try { // try {
Object root = JSONValue.parse(body); // Object root = JSONValue.parse(body);
if( root instanceof JSONArray ) { // if( root instanceof JSONArray ) {
JSONObject wrappedObject = new JSONObject(); // JSONObject wrappedObject = new JSONObject();
wrappedObject.put("DJB_ROOTLESS_ARRAY", root); // wrappedObject.put("DJB_ROOTLESS_ARRAY", root);
requestBytes = wrappedObject.toJSONString().getBytes(encode); // requestBytes = wrappedObject.toJSONString().getBytes(encode);
} // }
//
}catch (Exception e) { // }catch (Exception e) {
// ignore json이 아니기에 해줄것이 없음. // // ignore json이 아니기에 해줄것이 없음.
} // }
// 로컬 서비스 호출 // 로컬 서비스 호출
Object result = null; Object result = null;
@@ -338,7 +336,14 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
} }
} catch (Exception e) { } catch (Exception e) {
if (traceLevel >= 3){ try {
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, requestBytes != null ? new String(requestBytes, encode) : null, prop, request, response,
"RECEAIIRP202", e);
} catch (UnsupportedEncodingException e1) {
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, requestBytes != null ? new String(requestBytes) : null, prop, request, response,
"RECEAIIRP202", e);
}
if (traceLevel >= 3){
HttpMemoryLogger.error(adptGrpName+adptName, e.toString(),e); HttpMemoryLogger.error(adptGrpName+adptName, e.toString(),e);
} }
logger.error("HttpAdapter] "+adptGrpName+"-"+adptName, e); logger.error("HttpAdapter] "+adptGrpName+"-"+adptName, e);
@@ -12,8 +12,8 @@ import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.Keys; import com.eactive.eai.common.server.Keys;
import com.eactive.eai.common.util.ApplicationContextProvider; import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.Logger;
import com.eactive.ext.djb.DamoManager;
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper; import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
import com.eactive.ext.kjb.safedb.Utils;
@Component @Component
public class EncryptionManager implements Lifecycle { public class EncryptionManager implements Lifecycle {
@@ -161,14 +161,19 @@ public class EncryptionManager implements Lifecycle {
public String decryptDBData(String dbData) { public String decryptDBData(String dbData) {
if (StringUtils.isNotEmpty(dbData)) { if (StringUtils.isNotEmpty(dbData)) {
if (this.isEncrypted(dbData)) { if (this.isEncrypted(dbData)) {
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance(); if ("DAMO".equals(dbEncryptSolutionName)) {
try { com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
dbData = safeDBWrapper.decryptNotRnno(dbData); return damoManager.decrypt(dbData);
} catch (Exception e) { } else if ("SAFEDB".equals(dbEncryptSolutionName)) {
// 복호화 실패시 원본 반환 KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "..."; try {
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length()); dbData = safeDBWrapper.decryptNotRnno(dbData);
return dbData; } catch (Exception e) {
// 복호화 실패시 원본 반환
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
return dbData;
}
} }
} else { } else {
logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData); logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
@@ -197,7 +202,7 @@ public class EncryptionManager implements Lifecycle {
data = data.trim(); data = data.trim();
// Base64 형식 체크 // Base64 형식 체크
if (!Utils.isBase64(data)) { if (!isBase64(data)) {
return false; return false;
} }
@@ -208,4 +213,13 @@ public class EncryptionManager implements Lifecycle {
return true; return true;
} }
public boolean isBase64(String str) {
if (str == null || str.isEmpty()) {
return false;
}
String base64Pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$";
return str.matches(base64Pattern);
}
} }
@@ -327,11 +327,11 @@ public class AccessTokenManagerByDB implements Lifecycle {
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName); logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
} }
}catch (Exception e) { } catch (Exception e) {
logger.error("Task execution failed for adapter group: {}", adapterGroupName, e); logger.error("Task execution failed for adapter group: {}", adapterGroupName, e);
} }
return accessToken; return accessToken;
} }
@@ -474,18 +474,18 @@ public class AccessTokenManagerByDB implements Lifecycle {
if (accessToken == null || accessToken.isExpired() if (accessToken == null || accessToken.isExpired()
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) { || StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
if (isOAuthCredentialRegistered(adapterGroupName) == false) { if (isOAuthCredentialRegistered(adapterGroupName) == false) {
throw new Exception( throw new Exception(
"There is no OAuthCredentialRegistered information, or whether to use it is 'N'."); "There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
} }
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE"); String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type); HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName); OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
if (service != null) { if (service != null) {
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties,
outboundOAuthCredentialVo); outboundOAuthCredentialVo);
} }
} }
return accessToken; return accessToken;
@@ -16,6 +16,7 @@ import javax.crypto.SecretKey;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.eactive.eai.agent.encryption.EncryptionManager;
import com.eactive.eai.common.exception.ExceptionUtil; import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle; import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException; import com.eactive.eai.common.lifecycle.LifecycleException;
@@ -81,8 +82,10 @@ public class HsmManager implements Lifecycle {
} }
private void init() throws Exception { private void init() throws Exception {
String configContent = PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG); EncryptionManager encManager = EncryptionManager.getInstance();
String pin = PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN);
String configContent = encManager.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG));
String pin = encManager.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN));
if (configContent == null || configContent.trim().isEmpty()) { if (configContent == null || configContent.trim().isEmpty()) {
logger.warn("HsmManager] PKCS11_CONFIG 가 설정되지 않았습니다. HSM 초기화를 건너뜁니다."); logger.warn("HsmManager] PKCS11_CONFIG 가 설정되지 않았습니다. HSM 초기화를 건너뜁니다.");
@@ -0,0 +1,261 @@
package com.eactive.eai.common.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.json.simple.JSONObject;
/**
* Jackson JsonNode 범용 접근 유틸리티
*
* "." 구분자로 필드 단계를 이동하고, "[n]" 으로 배열 인덱스에 접근하는
* path 표현식을 지원한다.
*
* 사용 예:
* JacksonUtil.getText(root, "term_agreements[0].is_agreed")
* JacksonUtil.getBoolean(root, "term_agreements[0].is_agreed", false)
* JacksonUtil.getNode(root, "data.list[2].child[0].name")
*
* 경로 중간에 필드가 없거나, 배열 인덱스가 범위를 벗어나거나,
* 배열이 아닌 노드에 인덱스 접근을 시도하는 경우 모두
* 예외를 던지지 않고 null / 기본값을 반환한다.
*/
public final class JacksonUtil {
/** "fieldName" 또는 "fieldName[0]" 형태의 토큰을 분해하는 패턴 */
private static final Pattern TOKEN_PATTERN = Pattern.compile("([^\\[\\]]*)((?:\\[\\d+\\])*)");
private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]");
private JacksonUtil() {
// 인스턴스화 방지
}
// ------------------------------------------------------------------
// 기본 트리 탐색
// ------------------------------------------------------------------
/**
* Object(JSONObject/String/JsonNode 등)를 JsonNode로 변환한다.
* KakaopayFilter.readTree()를 일반화한 버전.
*/
public static JsonNode readTree(Object jsonData, ObjectMapper objectMapper)
throws JsonMappingException, JsonProcessingException {
if (jsonData == null) {
return null;
}
if (jsonData instanceof JsonNode) {
return (JsonNode) jsonData;
}
String jsonStr = null;
if (jsonData instanceof JSONObject) {
jsonStr = ((JSONObject) jsonData).toJSONString();
} else if (jsonData instanceof String) {
jsonStr = (String) jsonData;
} else {
// 그 외 POJO 등은 writeValueAsString을 통해 변환
jsonStr = objectMapper.writeValueAsString(jsonData);
}
if (jsonStr == null) {
return null;
}
return objectMapper.readTree(jsonStr);
}
/**
* path 표현식으로 JsonNode를 탐색한다.
* 경로가 존재하지 않으면 null을 반환한다 (MissingNode가 아닌 진짜 null).
*
* @param root 탐색을 시작할 JsonNode
* @param path 예: "term_agreements[0].is_agreed", "data.list[2].name"
*/
public static JsonNode getNode(JsonNode root, String path) {
if (root == null || path == null || path.isEmpty()) {
return null;
}
JsonNode current = root;
for (String rawToken : path.split("\\.")) {
if (current == null || current.isMissingNode() || current.isNull()) {
return null;
}
String fieldName = extractFieldName(rawToken);
List<Integer> indices = extractIndices(rawToken);
// 필드명이 있으면 먼저 필드로 이동 (빈 문자열이면 현재 노드 유지 - 최상위 배열 접근용)
if (!fieldName.isEmpty()) {
if (!current.has(fieldName)) {
return null;
}
current = current.get(fieldName);
}
// 이어지는 [n][m]... 인덱스를 순서대로 적용
for (Integer idx : indices) {
if (current == null || !current.isArray() || idx < 0 || idx >= current.size()) {
return null;
}
current = current.get(idx);
}
}
return current;
}
// ------------------------------------------------------------------
// 타입별 getter (전부 null-safe, 기본값 지원)
// ------------------------------------------------------------------
public static String getText(JsonNode root, String path) {
return getText(root, path, null);
}
public static String getText(JsonNode root, String path, String defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asText(defaultValue);
}
public static boolean getBoolean(JsonNode root, String path, boolean defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asBoolean(defaultValue);
}
public static int getInt(JsonNode root, String path, int defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asInt(defaultValue);
}
public static long getLong(JsonNode root, String path, long defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asLong(defaultValue);
}
public static double getDouble(JsonNode root, String path, double defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asDouble(defaultValue);
}
/** path가 가리키는 노드가 실제로 존재하는지 (null/missing이 아닌지) */
public static boolean exists(JsonNode root, String path) {
JsonNode node = getNode(root, path);
return node != null && !node.isMissingNode() && !node.isNull();
}
/** path가 가리키는 노드가 배열일 때 그 크기를 반환, 배열이 아니거나 없으면 -1 */
public static int size(JsonNode root, String path) {
JsonNode node = getNode(root, path);
return (node != null && node.isArray()) ? node.size() : -1;
}
/** path가 가리키는 ArrayNode를 List<JsonNode>로 반환, 없으면 빈 리스트 */
public static List<JsonNode> getList(JsonNode root, String path) {
List<JsonNode> result = new ArrayList<>();
JsonNode node = getNode(root, path);
if (node != null && node.isArray()) {
for (JsonNode item : node) {
result.add(item);
}
}
return result;
}
// ------------------------------------------------------------------
// 값 설정 (필요 시 사용 - 존재하는 경로에 대해서만 동작)
// ------------------------------------------------------------------
/**
* path가 가리키는 위치의 텍스트 값을 변경한다.
* 부모 컨테이너(ObjectNode/ArrayNode)가 존재해야 하며, 중간 경로가 없으면 false를 반환한다.
* (자동으로 중간 경로를 생성하지는 않음)
*/
public static boolean setText(JsonNode root, String path, String value) {
return setValue(root, path, value);
}
private static boolean setValue(JsonNode root, String path, String value) {
int lastDot = path.lastIndexOf('.');
String parentPath = (lastDot == -1) ? "" : path.substring(0, lastDot);
String lastToken = (lastDot == -1) ? path : path.substring(lastDot + 1);
JsonNode parent = parentPath.isEmpty() ? root : getNode(root, parentPath);
if (parent == null) {
return false;
}
String fieldName = extractFieldName(lastToken);
List<Integer> indices = extractIndices(lastToken);
JsonNode target = parent;
if (!fieldName.isEmpty()) {
if (!target.has(fieldName)) {
return false;
}
target = target.get(fieldName);
}
// 마지막 인덱스 전까지 이동
for (int i = 0; i < indices.size() - 1; i++) {
int idx = indices.get(i);
if (target == null || !target.isArray() || idx < 0 || idx >= target.size()) {
return false;
}
target = target.get(idx);
}
if (!indices.isEmpty()) {
// 배열의 특정 인덱스 값 교체
int lastIdx = indices.get(indices.size() - 1);
if (target == null || !target.isArray() || lastIdx < 0 || lastIdx >= target.size()) {
return false;
}
((ArrayNode) target).set(lastIdx, value);
return true;
} else {
// 객체 필드 값 교체 (target은 fieldName으로 이미 이동된 상태이므로, parent 기준 재설정 필요)
if (parent.isObject() && fieldName != null && !fieldName.isEmpty()) {
((ObjectNode) parent).put(fieldName, value);
return true;
}
return false;
}
}
// ------------------------------------------------------------------
// 내부 파싱 헬퍼
// ------------------------------------------------------------------
/** "term_agreements[0]" -> "term_agreements" / "[0]" -> "" */
private static String extractFieldName(String token) {
Matcher m = TOKEN_PATTERN.matcher(token);
if (m.matches()) {
return m.group(1) == null ? "" : m.group(1);
}
return token;
}
/** "term_agreements[0][1]" -> [0, 1] / "term_agreements" -> [] */
private static List<Integer> extractIndices(String token) {
List<Integer> indices = new ArrayList<>();
Matcher m = INDEX_PATTERN.matcher(token);
while (m.find()) {
indices.add(Integer.parseInt(m.group(1)));
}
return indices;
}
}
@@ -1007,7 +1007,14 @@ public class RequestProcessor extends RequestProcessorSupport {
if (logger.isError()) logger.error(guidLogPrefix + " : 거래통제 Log 실패 - " + vo.getRspErrorMsg()); if (logger.isError()) logger.error(guidLogPrefix + " : 거래통제 Log 실패 - " + vo.getRspErrorMsg());
} }
return restrictResponse; AdapterGroupVO adptGrpVO = AdapterManager.getInstance().getAdapterGroupVO(vo.getAdapterGroupName());
if (!Keys.IF_STANDARD.equals(vo.getStdMsgTypeCode()) && StringUtils.equalsAny(adptGrpVO.getType(),
Keys.TYPE_REST, Keys.TYPE_HTTP, Keys.TYPE_HTTP_CUSTOM)) {
throw new HttpStatusException(eaiMsg.getRspErrMsg(), eaiMsg.getRspErrCd(),
HttpStatus.SERVICE_UNAVAILABLE.value());
} else {
return restrictResponse;
}
} }
else { else {
// ExceptionHandler에 의해 에러 응답송신 // ExceptionHandler에 의해 에러 응답송신
@@ -694,35 +694,37 @@ public class RESTProcess extends HTTPProcess {
public void setOutboundErrorMessage() throws Exception { public void setOutboundErrorMessage() throws Exception {
this.logPssSno = TranLogUtil.getResLogSeq(this.svcPssTp, this.svcPssSeq, this.isComp); this.logPssSno = TranLogUtil.getResLogSeq(this.svcPssTp, this.svcPssSeq, this.isComp);
boolean setResObject = false;
// 에러에 대한 응답메시지 // 에러에 대한 응답메시지
if(this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP) instanceof Map) { if(this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP) instanceof Map) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP); Map<String, Object> map = (Map<String, Object>) this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP);
this.callProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map); this.callProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
}
boolean setResObject = false; // OUTBOUND_PROPERTY_MAP 이 있는 경우, 즉 타겟서버와 통신은 정상적으로 이루진 경우 처리한다.
AdapterGroupVO outboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); AdapterGroupVO outboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
if (outboundAdapterGroupVO != null) { if (outboundAdapterGroupVO != null) {
AdapterVO outboundAdapterVo = null; AdapterVO outboundAdapterVo = null;
if(adapterName != null) if(adapterName != null)
outboundAdapterVo = outboundAdapterGroupVO.getAdapterVO(adapterName); outboundAdapterVo = outboundAdapterGroupVO.getAdapterVO(adapterName);
else else
outboundAdapterVo = outboundAdapterGroupVO.nextAdapterVO(); outboundAdapterVo = outboundAdapterGroupVO.nextAdapterVO();
if (outboundAdapterVo != null) { if (outboundAdapterVo != null) {
String errorHandlerClass = AdapterPropManager.getInstance().getProperty( String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
outboundAdapterVo.getPropGroupName(), "ERR_MSG_HANDLER"); outboundAdapterVo.getPropGroupName(), "ERR_MSG_HANDLER");
if (StringUtils.isNotBlank(errorHandlerClass)) { if (StringUtils.isNotBlank(errorHandlerClass)) {
if (logger.isInfo()) if (logger.isInfo())
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass); logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass); AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
this.resObject = handler.generateOutboundErrorResponseMessage(adapterGroupName, adapterGroupName, this.resObject = handler.generateOutboundErrorResponseMessage(adapterGroupName, adapterGroupName,
callProp, this.reqObject, this.resEaiMsg); callProp, this.reqObject, this.resEaiMsg);
setResObject = true; setResObject = true;
}
} }
} }
} }
if (!setResObject) { if (!setResObject) {
this.resObject = ""; this.resObject = "";
} }
@@ -0,0 +1,187 @@
package com.eactive.eai.agent.encryption;
import static org.junit.jupiter.api.Assertions.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* EncryptionManager 단위 테스트 — DAMO 솔루션 모드.
*
* damo-manager.jar 가 DAMO 라이브러리(com.penta.scpdb.ScpDbAgent) 없이 로드되면
* 자동으로 FAKE MODE 로 동작한다:
* encrypt(plain) → Base64 인코딩
* decrypt(b64) → Base64 디코딩
* 따라서 encrypt → decrypt 라운드트립이 완전히 검증 가능하다.
*
* EncryptionManager 생성자가 private 이므로 리플렉션으로 인스턴스를 생성하고
* 필드를 직접 주입한다.
*/
class EncryptionManagerTest {
private EncryptionManager manager;
@BeforeEach
void setUp() throws Exception {
Constructor<EncryptionManager> ctor = EncryptionManager.class.getDeclaredConstructor();
ctor.setAccessible(true);
manager = ctor.newInstance();
}
// -------------------------------------------------------------------------
// 헬퍼
// -------------------------------------------------------------------------
private void setField(String name, Object value) throws Exception {
Field f = EncryptionManager.class.getDeclaredField(name);
f.setAccessible(true);
f.set(manager, value);
}
private void enableDamo() throws Exception {
setField("encryptYN", "Y");
setField("dbEncryptSolutionName", "DAMO");
}
private void disableEncrypt() throws Exception {
setField("encryptYN", "N");
setField("dbEncryptSolutionName", "DAMO");
}
// =========================================================================
// 1. DAMO 모드 — encrypt + decrypt 라운드트립
// =========================================================================
@Test
@DisplayName("1-1. DAMO 암호화 후 복호화 시 원문 복원 — 한글")
void testRoundTrip_korean() throws Exception {
enableDamo();
String plain = "홍길동";
String encrypted = manager.encryptDBData(plain);
assertNotEquals(plain, encrypted, "암호화 결과가 원문과 달라야 한다");
assertEquals(plain, manager.decryptDBData(encrypted));
}
@Test
@DisplayName("1-2. DAMO 암호화 후 복호화 시 원문 복원 — 영문/숫자 혼합")
void testRoundTrip_alphanumeric() throws Exception {
enableDamo();
String plain = "test@example.com";
String encrypted = manager.encryptDBData(plain);
assertNotEquals(plain, encrypted);
assertEquals(plain, manager.decryptDBData(encrypted));
}
@Test
@DisplayName("1-3. DAMO 암호화 후 복호화 시 원문 복원 — 주민등록번호 형식")
void testRoundTrip_idNumber() throws Exception {
enableDamo();
String plain = "900101-1234567";
String encrypted = manager.encryptDBData(plain);
// 숫자와 '-' 만으로 이루어진 원문은 암호화되어 Base64 형식으로 변환된다
assertNotEquals(plain, encrypted);
assertEquals(plain, manager.decryptDBData(encrypted));
}
@Test
@DisplayName("1-4. DAMO 암호화 후 복호화 시 원문 복원 — 다양한 입력 일괄 검증")
void testRoundTrip_multipleValues() throws Exception {
enableDamo();
String[] inputs = {
"홍길동",
"test@example.com",
"900101-1234567",
"ABC테스트123",
"special chars: !@#$%",
"긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열"
};
for (String input : inputs) {
String encrypted = manager.encryptDBData(input);
String decrypted = manager.decryptDBData(encrypted);
assertEquals(input, decrypted, "라운드트립 불일치: [" + input + "]");
}
}
// =========================================================================
// 2. 암호화 비활성(encryptYN=N)
// =========================================================================
@Test
@DisplayName("2-1. encryptYN=N 이면 encryptDBData 가 원문 그대로 반환")
void testEncrypt_disabled_returnsPlain() throws Exception {
disableEncrypt();
String plain = "홍길동";
assertEquals(plain, manager.encryptDBData(plain));
}
@Test
@DisplayName("2-2. encryptYN=N 상태의 원문은 isEncrypted=false 이므로 decryptDBData 도 원문 반환")
void testDecrypt_plainTextNotBase64_returnsOriginal() throws Exception {
disableEncrypt();
String plain = "홍길동";
assertEquals(plain, manager.decryptDBData(plain));
}
// =========================================================================
// 3. 특수 입력 처리
// =========================================================================
@Test
@DisplayName("3-1. decryptDBData — null 입력 시 null 반환")
void testDecrypt_null() throws Exception {
enableDamo();
assertNull(manager.decryptDBData(null));
}
@Test
@DisplayName("3-2. decryptDBData — 빈 문자열 입력 시 빈 문자열 반환")
void testDecrypt_empty() throws Exception {
enableDamo();
assertEquals("", manager.decryptDBData(""));
}
@Test
@DisplayName("3-3. decryptDBData — 숫자로만 구성된 값은 복호화하지 않고 원문 반환 (연락처)")
void testDecrypt_numericOnly_returnsOriginal() throws Exception {
enableDamo();
assertEquals("01012345678", manager.decryptDBData("01012345678"));
assertEquals("0101234-5678", manager.decryptDBData("0101234-5678"));
}
@Test
@DisplayName("3-4. decryptDBData — Base64 형식이 아닌 문자열은 원문 반환")
void testDecrypt_nonBase64_returnsOriginal() throws Exception {
enableDamo();
String plain = "이름: 홍길동"; // 공백 포함, Base64 문자셋 아님
assertEquals(plain, manager.decryptDBData(plain));
}
// =========================================================================
// 4. isEncrypt() / getEncryptYN() 상태 확인
// =========================================================================
@Test
@DisplayName("4-1. encryptYN=Y 이면 isEncrypt()=true")
void testIsEncrypt_true() throws Exception {
setField("encryptYN", "Y");
assertTrue(manager.isEncrypt());
}
@Test
@DisplayName("4-2. encryptYN=N 이면 isEncrypt()=false")
void testIsEncrypt_false() throws Exception {
setField("encryptYN", "N");
assertFalse(manager.isEncrypt());
}
@Test
@DisplayName("4-3. getDBEncryptSolutionName() 반환값 확인")
void testGetDBEncryptSolutionName() throws Exception {
setField("dbEncryptSolutionName", "DAMO");
assertEquals("DAMO", manager.getDBEncryptSolutionName());
}
}
@@ -0,0 +1,88 @@
package com.eactive.eai.common.hsm;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.Base64;
import javax.crypto.SecretKey;
public class HsmManagerSingleTest {
/**
* JDK 버전에 따라 SunPKCS11 Provider 를 생성한다.
*
* JDK 8 : SunPKCS11(InputStream) 생성자를 리플렉션으로 호출
* JDK 9+: Provider.configure(configFilePath) 를 리플렉션으로 호출
*
* 두 경로 모두 리플렉션을 사용하므로 컴파일 타임에 JDK 버전 의존성이 없다.
*/
static Provider createProvider(String cfgContent) throws Exception {
String javaVersion = System.getProperty("java.version");
if (javaVersion.startsWith("1.")) {
// JDK 8: new SunPKCS11(InputStream)
InputStream is = new ByteArrayInputStream(cfgContent.getBytes("UTF-8"));
return (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
.getConstructor(InputStream.class)
.newInstance(is);
} else {
// JDK 9+: Security.getProvider("SunPKCS11").configure(configFilePath)
// configure() 는 JDK 9 에서 추가된 메서드이므로 리플렉션으로 호출
File tmp = File.createTempFile("pkcs11-hsm-", ".cfg");
tmp.deleteOnExit();
Files.write(tmp.toPath(), cfgContent.getBytes("UTF-8"));
Provider base = Security.getProvider("SunPKCS11");
Method configure = Provider.class.getMethod("configure", String.class);
return (Provider) configure.invoke(base, tmp.getAbsolutePath());
}
}
public static void main(String[] args) throws Exception {
// String config = "name = ProtectServer\r\n"
// + "library = /usr/safenet/protecttoolkit5/ptk/lib/libcryptoki.so\r\n"
// + "slot = 0";
String config = "name = SoftHSM\r\nlibrary = C:/SoftHSM2/lib/softhsm2-x64.dll\r\nslotListIndex = 0\r\n";
KeyStore keyStore;
// Provider pkcs11Provider = HsmManagerSingleTest.createProvider(config);
InputStream is = new ByteArrayInputStream(config.getBytes("UTF-8"));
Provider pkcs11Provider = (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
.getConstructor(InputStream.class)
.newInstance(is);
Provider existing = Security.getProvider(pkcs11Provider.getName());
Security.addProvider(pkcs11Provider);
String pin = "1234";
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
char[] pinChars = (pin != null) ? pin.toCharArray() : null;
keyStore.load(null, pinChars);
System.out.println("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());
java.util.Enumeration<String> aliases = keyStore.aliases();
StringBuilder aliasList = new StringBuilder();
while (aliases.hasMoreElements()) {
if (aliasList.length() > 0) aliasList.append(", ");
String alias = aliases.nextElement();
aliasList.append(alias);
java.security.Key key = keyStore.getKey(alias, null);
if (key instanceof SecretKey) {
SecretKey secretKey = (SecretKey) key;
if(secretKey.getEncoded() != null) {
String encBase64 = Base64.getEncoder().encodeToString(secretKey.getEncoded());
System.out.println("HsmManager] HSM - alias:" + alias +", base64:"+ encBase64);
} else {
System.out.println("HsmManager] HSM - secretKey null - alias:" + alias + ", secretKey:" + secretKey);
}
}
}
System.out.println("HsmManager] HSM 키 목록: [" + aliasList + "]");
}
}