Compare commits

..

10 Commits

13 changed files with 415 additions and 97 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;
@@ -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;
@@ -141,26 +143,32 @@ 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);
if (scopeArr == null || scopeArr.length == 0) {
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL,
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
for (String scope : scopeArr) { // API에 scope 설정이 안되어 있는 경우, scope 체크를 pass하고 나중에 client API 맵을 체크한다.
if (StringUtils.equals(scope, "oob") || StringUtils.equals(scope, "public")) { if(CollectionUtils.isEmpty(scopeSet))
isPassScope = true; isPassScope = true;
}
}
if (!isPassScope) { if(!isPassScope) {
if (!verifyScope(scopeSet, signedJWT)) { String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE);
if (scopeArr == null || scopeArr.length == 0) {
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
if (!verifyClientId(prop, signedJWT)) { if (!verifyClientId(prop, signedJWT)) {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "client_id not matched(header/token)"); throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "client_id not matched(header/token)");
@@ -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;
@@ -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 {
@@ -130,7 +130,7 @@ public class EncryptionManager implements Lifecycle {
if (StringUtils.isNotEmpty(plainText)) { if (StringUtils.isNotEmpty(plainText)) {
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance(); KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
plainText = wrapper.encryptNotRnnoString(plainText); plainText = wrapper.encryptNotRnnoString(plainText);
if (logger.isInfo()) { if (logger.isInfo()) {
// originalAttribute 홀수 글자 마스킹 // originalAttribute 홀수 글자 마스킹
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -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);
}
} }
@@ -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 초기화를 건너뜁니다.");
@@ -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;
AdapterGroupVO outboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
if (outboundAdapterGroupVO != null) {
AdapterVO outboundAdapterVo = null;
if(adapterName != null)
outboundAdapterVo = outboundAdapterGroupVO.getAdapterVO(adapterName);
else
outboundAdapterVo = outboundAdapterGroupVO.nextAdapterVO();
if (outboundAdapterVo != null) { // OUTBOUND_PROPERTY_MAP 이 있는 경우, 즉 타겟서버와 통신은 정상적으로 이루진 경우 처리한다.
String errorHandlerClass = AdapterPropManager.getInstance().getProperty( AdapterGroupVO outboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
outboundAdapterVo.getPropGroupName(), "ERR_MSG_HANDLER"); if (outboundAdapterGroupVO != null) {
if (StringUtils.isNotBlank(errorHandlerClass)) { AdapterVO outboundAdapterVo = null;
if (logger.isInfo()) if(adapterName != null)
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass); outboundAdapterVo = outboundAdapterGroupVO.getAdapterVO(adapterName);
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass); else
this.resObject = handler.generateOutboundErrorResponseMessage(adapterGroupName, adapterGroupName, outboundAdapterVo = outboundAdapterGroupVO.nextAdapterVO();
callProp, this.reqObject, this.resEaiMsg);
setResObject = true; if (outboundAdapterVo != null) {
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
outboundAdapterVo.getPropGroupName(), "ERR_MSG_HANDLER");
if (StringUtils.isNotBlank(errorHandlerClass)) {
if (logger.isInfo())
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
this.resObject = handler.generateOutboundErrorResponseMessage(adapterGroupName, adapterGroupName,
callProp, this.reqObject, this.resEaiMsg);
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 + "]");
}
}