Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a943e3399 | |||
| 3090f745ba | |||
| af6b84f57d | |||
| 90633b3d26 | |||
| 822072147e | |||
| 3527884e31 | |||
| 8df8a73175 | |||
| 5d9676b504 | |||
| 40f496cfba | |||
| efc06e6023 | |||
| a923ff3f9f | |||
| ab9b86e7cf | |||
| d78e422368 | |||
| 3cc66fcaa8 | |||
| 73ed49372a | |||
| 3574ad9e0c | |||
| 49eb8fbe85 | |||
| 253716f4b2 | |||
| 02e282c7ba | |||
| 180ea8cac1 | |||
| 2c21c691df | |||
| 0da31a8de2 | |||
| 46d88d77f2 | |||
| 51967284ba | |||
| a218291748 |
@@ -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 {
|
||||||
|
|||||||
Binary file not shown.
+1
-2
@@ -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;
|
||||||
+130
-645
@@ -7,13 +7,10 @@ import java.net.ConnectException;
|
|||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -21,10 +18,6 @@ import java.util.Properties;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
// encrypt test by jwhong
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.time.StopWatch;
|
import org.apache.commons.lang3.time.StopWatch;
|
||||||
@@ -57,42 +50,33 @@ import org.json.simple.JSONObject;
|
|||||||
import org.json.simple.JSONValue;
|
import org.json.simple.JSONValue;
|
||||||
import org.json.simple.parser.JSONParser;
|
import org.json.simple.parser.JSONParser;
|
||||||
import org.json.simple.parser.ParseException;
|
import org.json.simple.parser.ParseException;
|
||||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
|
||||||
import org.springframework.web.util.UriComponents;
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import com.eactive.eai.adapter.AdapterPropManager;
|
|
||||||
import com.eactive.eai.adapter.http.HttpMemoryLogger;
|
import com.eactive.eai.adapter.http.HttpMemoryLogger;
|
||||||
import com.eactive.eai.adapter.http.HttpMethodType;
|
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.dynamic.HttpAdapterServiceKey;
|
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||||
import com.eactive.eai.authserver.service.OAuth2Manager;
|
|
||||||
import com.eactive.eai.common.TransactionContextKeys;
|
import com.eactive.eai.common.TransactionContextKeys;
|
||||||
import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB;
|
import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB;
|
||||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||||
import com.eactive.eai.common.message.MessageType;
|
import com.eactive.eai.common.message.MessageType;
|
||||||
import com.eactive.eai.common.property.PropManager;
|
import com.eactive.eai.common.property.PropManager;
|
||||||
import com.eactive.eai.common.util.CommonLib;
|
import com.eactive.eai.common.util.CommonLib;
|
||||||
import com.eactive.eai.common.util.JSONUtils;
|
import com.eactive.eai.common.util.JacksonUtil;
|
||||||
import com.eactive.eai.common.util.TxFileLogger;
|
import com.eactive.eai.common.util.TxFileLogger;
|
||||||
import com.eactive.eai.common.util.XMLUtils;
|
import com.eactive.eai.common.util.XMLUtils;
|
||||||
import com.eactive.eai.util.JsonPathUtil;
|
import com.eactive.eai.util.JsonPathUtil;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.jayway.jsonpath.DocumentContext;
|
import com.jayway.jsonpath.DocumentContext;
|
||||||
import com.jayway.jsonpath.JsonPath;
|
import com.jayway.jsonpath.JsonPath;
|
||||||
import com.kjbank.encrypt.exchange.crypto.AES256Cipher;
|
|
||||||
import com.kjbank.encrypt.exchange.crypto.AES256GCMCipher;
|
|
||||||
import com.kjbank.encrypt.exchange.crypto.AESCipher;
|
|
||||||
import com.kjbank.encrypt.exchange.crypto.ECDHESA256Cipher;
|
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
|
||||||
import com.openbanking.eai.common.token.AccessTokenManager;
|
import com.openbanking.eai.common.token.AccessTokenManager;
|
||||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
|
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
|
||||||
|
|
||||||
import edu.emory.mathcs.backport.java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : EAI HTTP OutBound 용 어댑터로 수동 시스템의 HTTP 웹 컴포넌트를 GET/POST 방식으로 호출할 수 있는
|
* 1. 기능 : EAI HTTP OutBound 용 어댑터로 수동 시스템의 HTTP 웹 컴포넌트를 GET/POST 방식으로 호출할 수 있는
|
||||||
* 기능을 제공한다.<br>
|
* 기능을 제공한다.<br>
|
||||||
@@ -124,8 +108,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
|
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
|
||||||
|
|
||||||
private boolean useAdapterToken;
|
private boolean useAdapterToken;
|
||||||
private boolean encryptResponseApply;
|
|
||||||
private String httpHeaderSettingByAdapter = "{}";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : REST API 통신에 사용 <br>
|
* 1. 기능 : REST API 통신에 사용 <br>
|
||||||
@@ -146,7 +128,8 @@ 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");
|
||||||
|
|
||||||
@@ -160,9 +143,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST");
|
String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST");
|
||||||
//String inboundToken = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_TOKEN, "");
|
//String inboundToken = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_TOKEN, "");
|
||||||
|
|
||||||
//Outbound 요청에 대한 응답 메시지의 복호화 여부 결정위해 jwhong
|
|
||||||
encryptResponseApply = StringUtils.equalsIgnoreCase(prop.getProperty("ENCRYPT_RESPONSE_APPLY", "N"), "Y");
|
|
||||||
|
|
||||||
//업체 300응답시 특정 http status code 경우 에러 아닌걸로 처리. 광주은행
|
//업체 300응답시 특정 http status code 경우 에러 아닌걸로 처리. 광주은행
|
||||||
String errIgnore = prop.getProperty("ERROR_IGNORE_CODE", "");
|
String errIgnore = prop.getProperty("ERROR_IGNORE_CODE", "");
|
||||||
|
|
||||||
@@ -218,6 +198,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
sendData = new String((byte[]) data, vo.getEncode());
|
sendData = new String((byte[]) data, vo.getEncode());
|
||||||
} else if (data instanceof JSONObject) {
|
} else if (data instanceof JSONObject) {
|
||||||
sendData = data;
|
sendData = data;
|
||||||
|
} else if (data instanceof JsonNode) {
|
||||||
|
sendData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
////mclient.getParams().setContentCharset(vo.getEncode());
|
////mclient.getParams().setContentCharset(vo.getEncode());
|
||||||
@@ -227,13 +209,12 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
//Object dataContent = null;
|
//Object dataContent = null;
|
||||||
|
|
||||||
if (MessageType.JSON.equals(messageType)) {
|
if (MessageType.JSON.equals(messageType)) {
|
||||||
Object parsed = JSONUtils.parseJsonGeneric(sendData);
|
if (sendData instanceof ObjectNode) {
|
||||||
if(parsed instanceof JSONObject) {
|
dataObject = sendData;
|
||||||
JSONObject jsonObject = (JSONObject) parsed;
|
} else if(sendData instanceof JSONObject) {
|
||||||
dataObject = jsonObject;
|
dataObject = sendData;
|
||||||
|
} else {
|
||||||
} else if(parsed instanceof JSONArray) {
|
dataObject = JacksonUtil.readTree(sendData);
|
||||||
// not support
|
|
||||||
}
|
}
|
||||||
} else if (MessageType.XML.equals(messageType)) {
|
} else if (MessageType.XML.equals(messageType)) {
|
||||||
Document doc = XMLUtils.convertXmlDocument(sendData);
|
Document doc = XMLUtils.convertXmlDocument(sendData);
|
||||||
@@ -264,7 +245,12 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dataObject instanceof JSONObject) {
|
if(dataObject instanceof ObjectNode) {
|
||||||
|
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
|
||||||
|
httpHeader = ((ObjectNode)dataObject).get(headerGroupName);
|
||||||
|
((ObjectNode)dataObject).remove(headerGroupName);
|
||||||
|
}
|
||||||
|
} else if(dataObject instanceof JSONObject) {
|
||||||
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
|
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
|
||||||
httpHeader = ((JSONObject)dataObject).get(headerGroupName);
|
httpHeader = ((JSONObject)dataObject).get(headerGroupName);
|
||||||
((JSONObject)dataObject).remove(headerGroupName);
|
((JSONObject)dataObject).remove(headerGroupName);
|
||||||
@@ -332,8 +318,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 +335,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 +356,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 +584,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, accessToken, authorizationHeaderName);
|
||||||
setAuthHeaders(method, newaccessToken);
|
|
||||||
|
|
||||||
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()
|
||||||
@@ -785,7 +750,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
* @return
|
* @return
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Object assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName,
|
private Object assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName,
|
||||||
String encode, String responseContentType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception {
|
String encode, String responseContentType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception {
|
||||||
String messageType = MessageType.JSON;
|
String messageType = MessageType.JSON;
|
||||||
@@ -812,43 +776,52 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(StringUtils.equalsAny(messageType, MessageType.JSON)) {
|
if(StringUtils.equalsAny(messageType, MessageType.JSON)) {
|
||||||
JSONObject headerJson = new JSONObject();
|
ObjectNode headerJson = JacksonUtil.createObjectNode();
|
||||||
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
|
|
||||||
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
|
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
|
||||||
String key = (String)e.nextElement();
|
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
|
||||||
headerJson.put(StringUtils.lowerCase(key), responseHeaderProp.getProperty(key));
|
String key = (String) e.nextElement();
|
||||||
}
|
headerJson.put(StringUtils.lowerCase(key), responseHeaderProp.getProperty(key));
|
||||||
} else {
|
}
|
||||||
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
|
} else {
|
||||||
for (String key : relayKeyArr) {
|
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
|
||||||
String value = responseHeaderProp.getProperty(key);
|
for (String key : relayKeyArr) {
|
||||||
if (StringUtils.isNotBlank(value)) {
|
String value = responseHeaderProp.getProperty(key);
|
||||||
headerJson.put(StringUtils.lowerCase(key), value);
|
if (StringUtils.isNotBlank(value)) {
|
||||||
}
|
headerJson.put(StringUtils.lowerCase(key), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
headerJson.put(HTTP_STATUS, String.valueOf(status));
|
|
||||||
|
headerJson.put(HTTP_STATUS, String.valueOf(status));
|
||||||
if (headerJson.size() <= 0) {
|
|
||||||
return new String(responseMessage, charset);
|
if (headerJson.isEmpty()) {
|
||||||
}
|
return new String(responseMessage, charset);
|
||||||
|
}
|
||||||
JSONObject message = null;
|
|
||||||
if (responseMessage == null || responseMessage.length == 0) {
|
ObjectNode message;
|
||||||
message = new JSONObject();
|
if (responseMessage == null || responseMessage.length == 0) {
|
||||||
} else {
|
message = JacksonUtil.createObjectNode();
|
||||||
message = parseJson(new String(responseMessage, charset));
|
} else {
|
||||||
if (message == null) {
|
JsonNode parsed = null;
|
||||||
message = new JSONObject();
|
try {
|
||||||
message.put("Malformed_Response_Message", new String(responseMessage, charset));
|
parsed = JacksonUtil.readTree(new String(responseMessage, charset));
|
||||||
}
|
} catch (Exception e) {
|
||||||
}
|
logger.warn("response message parse error", e);
|
||||||
|
}
|
||||||
if (StringUtils.isNotBlank(headerGroupName) && message != null)
|
if (parsed == null || !parsed.isObject()) {
|
||||||
message.put(headerGroupName, headerJson);
|
message = JacksonUtil.createObjectNode();
|
||||||
|
message.put("Malformed_Response_Message", new String(responseMessage, charset));
|
||||||
return message;
|
} else {
|
||||||
|
message = (ObjectNode) parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(headerGroupName) && message != null) {
|
||||||
|
message.set(headerGroupName, headerJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
} else if (StringUtils.equalsAny(messageType, MessageType.XML)) {
|
} else if (StringUtils.equalsAny(messageType, MessageType.XML)) {
|
||||||
Document doc = XMLUtils.convertXmlDocument(new String(responseMessage, charset));
|
Document doc = XMLUtils.convertXmlDocument(new String(responseMessage, charset));
|
||||||
Element root = doc.getRootElement();
|
Element root = doc.getRootElement();
|
||||||
@@ -878,43 +851,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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* nice on 나이스 지키미 용으로 생성함
|
protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken, String authorizationHeaderName) throws Exception {
|
||||||
* jwhong 2015-11-04
|
method.setHeader(authorizationHeaderName,
|
||||||
*/
|
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
|
||||||
private void setNiceOnAuthHeaders(HttpUriRequestBase method, String auth) throws Exception {
|
|
||||||
method.setHeader("Authorization",
|
|
||||||
String.format("%s %s", "bearer", auth)); // 나이스지키미 format임, bearer을 소문자로 보내야함
|
|
||||||
String productId = PropManager.getInstance().getProperty("NiceOnProperty","productId.value");
|
|
||||||
method.setHeader("ProductID", productId);
|
|
||||||
//method.setHeader("Authorization",
|
|
||||||
// String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* nice clientid 추출을 위하여 추가한 내용임. jwhong
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private String JwtClientIdExtractor(String inboundToken) {
|
|
||||||
|
|
||||||
String tokenClientId ="";
|
|
||||||
try {
|
|
||||||
SignedJWT signedJWT = SignedJWT.parse(inboundToken);
|
|
||||||
tokenClientId = (String) signedJWT.getJWTClaimsSet().getClaim(PAYLOAD_PARAM_NAME_CLIENT_ID);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e.getMessage());
|
|
||||||
tokenClientId = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("[ NICE Token Client ID ]" + tokenClientId);
|
|
||||||
return tokenClientId;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues,
|
private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues,
|
||||||
String encode) {
|
String encode) {
|
||||||
if (responseMessage == null) {
|
if (responseMessage == null) {
|
||||||
@@ -947,9 +898,20 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
|
|
||||||
protected void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
|
protected void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
|
||||||
if (httpHeader != null) {
|
if (httpHeader != null) {
|
||||||
if (httpHeader instanceof JSONObject) {
|
if ( httpHeader instanceof ObjectNode) {
|
||||||
|
ObjectNode objectNode = (ObjectNode) httpHeader;
|
||||||
|
objectNode.fields().forEachRemaining(e -> {
|
||||||
|
if ("Content-Length".equalsIgnoreCase(e.getKey()))
|
||||||
|
return;
|
||||||
|
JsonNode value = e.getValue();
|
||||||
|
method.setHeader((String) e.getKey(), value.isNull() ? null : value.asText());
|
||||||
|
});
|
||||||
|
} else if (httpHeader instanceof JSONObject) {
|
||||||
JSONObject jsonObject = (JSONObject) httpHeader;
|
JSONObject jsonObject = (JSONObject) httpHeader;
|
||||||
for (Object key : jsonObject.keySet()) {
|
for (Object key : jsonObject.keySet()) {
|
||||||
|
if ("Content-Length".equalsIgnoreCase((String) key))
|
||||||
|
continue;
|
||||||
|
|
||||||
Object obj = jsonObject.get(key);
|
Object obj = jsonObject.get(key);
|
||||||
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
|
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -960,7 +922,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]");
|
logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -977,24 +938,31 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject httpValues = (JSONObject) JSONValue.parse(httpSettingJsonStr);
|
JsonNode httpValues = JacksonUtil.readTree(httpSettingJsonStr);
|
||||||
|
|
||||||
for (Object key : httpValues.keySet()) {
|
|
||||||
Object obj = httpValues.get(key);
|
|
||||||
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
method.setHeader((String) key, obj);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (httpValues == null || !httpValues.isObject()) {
|
||||||
|
logger.error("HTTP_HEADER_SETTING value is not json Type, skip.");
|
||||||
|
} else {
|
||||||
|
Iterator<String> fieldNames = httpValues.fieldNames();
|
||||||
|
while (fieldNames.hasNext()) {
|
||||||
|
String key = fieldNames.next();
|
||||||
|
JsonNode obj = httpValues.get(key);
|
||||||
|
|
||||||
|
if (obj == null || obj.isObject() || obj.isArray()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String headerValue = obj.isNull() ? "" : obj.asText();
|
||||||
|
method.setHeader(key, headerValue);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Request Header :" + key + "=[" + headerValue + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("HTTP_HEADER_SETTING value is not JSONObject Type, skip.", e);
|
logger.error("HTTP_HEADER_SETTING value is not json Type, skip.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1018,79 +986,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* KJBank Target System으로 요청 보낼때 header에 clientId(업체정보), APIM거래추적자(UUID), 최초 요청시간, guid 추가해서 보낸다. jwhong
|
|
||||||
* token 만 return 하는것으로 수정함
|
|
||||||
*/
|
|
||||||
private String assignRequestKJHeaders(HttpUriRequestBase method, Properties prop, Properties tempProp){
|
|
||||||
|
|
||||||
//weblogic 에서는 tempProp.get 이 property로 return 해어 아래처럼 수정함. ClassCastException 발생함.
|
|
||||||
//Properties inboundHeaders = (Properties) tempProp.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
|
||||||
Properties inboundHeaders = null;
|
|
||||||
String authorization ="";
|
|
||||||
try {
|
|
||||||
Object headerObj = tempProp.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
|
||||||
if (headerObj instanceof Properties) { //tomcat
|
|
||||||
inboundHeaders = (Properties) headerObj;
|
|
||||||
for ( String k : inboundHeaders.stringPropertyNames()) {
|
|
||||||
if(k.equalsIgnoreCase("authorization")) {
|
|
||||||
authorization = inboundHeaders.getProperty(k);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (headerObj instanceof String) { //weblogic
|
|
||||||
String str = (String) headerObj;
|
|
||||||
str = str.substring(1, str.length() - 1);
|
|
||||||
// 2. key=value 쌍 분리
|
|
||||||
String[] entries = str.split(",");
|
|
||||||
// map에 담기
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
for (String entry : entries) {
|
|
||||||
String[] kv = entry.split("=", 2);
|
|
||||||
if (kv.length == 2) {
|
|
||||||
map.put(kv[0].trim(), kv[1].trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String k : map.keySet()) {
|
|
||||||
if (k.equalsIgnoreCase("authorization")) {
|
|
||||||
authorization = map.get(k);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
String jwtToken = "";
|
|
||||||
|
|
||||||
if (authorization != null && authorization.startsWith("Bearer ")) {
|
|
||||||
jwtToken = authorization.substring(7); // "Bearer " 이후의 JWT만 추출
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwtToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, String> parseInboundHeader(String str) {
|
|
||||||
java.util.HashMap<String, String> map = new java.util.HashMap<>();
|
|
||||||
if (str == null || str.isEmpty()) return map;
|
|
||||||
|
|
||||||
// 중괄호 제거
|
|
||||||
str = str.trim();
|
|
||||||
if (str.startsWith("{") && str.endsWith("}")) {
|
|
||||||
str = str.substring(1, str.length() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// key=value 쌍 분리
|
|
||||||
String[] pairs = str.split(",");
|
|
||||||
for (String pair : pairs) {
|
|
||||||
String[] kv = pair.split("=", 2);
|
|
||||||
if (kv.length == 2) {
|
|
||||||
map.put(kv[0].trim(), kv[1].trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String assignPostBody(Object eaiBody, String mimeType, String charset, HttpUriRequestBase method,
|
protected String assignPostBody(Object eaiBody, String mimeType, String charset, HttpUriRequestBase method,
|
||||||
boolean chunked) throws Exception {
|
boolean chunked) throws Exception {
|
||||||
@@ -1112,6 +1008,12 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
for (Object key : jsonObject.keySet()) {
|
for (Object key : jsonObject.keySet()) {
|
||||||
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
|
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
|
||||||
}
|
}
|
||||||
|
} else if (eaiBody instanceof ObjectNode) {
|
||||||
|
ObjectNode objectNode = (ObjectNode) eaiBody;
|
||||||
|
objectNode.fields().forEachRemaining(e -> {
|
||||||
|
JsonNode value = e.getValue();
|
||||||
|
params.add(new BasicNameValuePair(e.getKey(), value.isNull() ? null : value.asText()));
|
||||||
|
});
|
||||||
} else if (eaiBody instanceof Document) {
|
} else if (eaiBody instanceof Document) {
|
||||||
Document bodyDoc = (Document) eaiBody;
|
Document bodyDoc = (Document) eaiBody;
|
||||||
Element root = bodyDoc.getRootElement();
|
Element root = bodyDoc.getRootElement();
|
||||||
@@ -1133,6 +1035,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
body = ((Document)eaiBody).asXML();
|
body = ((Document)eaiBody).asXML();
|
||||||
} else if(eaiBody instanceof JSONObject) {
|
} else if(eaiBody instanceof JSONObject) {
|
||||||
body = ((JSONObject)eaiBody).toJSONString();
|
body = ((JSONObject)eaiBody).toJSONString();
|
||||||
|
} else if(eaiBody instanceof ObjectNode) {
|
||||||
|
body = JacksonUtil.writeAsString((ObjectNode) eaiBody);
|
||||||
} else {
|
} else {
|
||||||
body = eaiBody.toString();
|
body = eaiBody.toString();
|
||||||
}
|
}
|
||||||
@@ -1153,429 +1057,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from here by jwhong
|
|
||||||
private String doOutboundPreFilter(Object eaiBody, String adapterName, String niceAuth, String inboundToken, Date currentDate) throws Exception {
|
|
||||||
|
|
||||||
Properties properties = AdapterPropManager.getInstance().getProperties(adapterName); // jwhong
|
|
||||||
String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM",""); // jwhong
|
|
||||||
String encryptBaseKeyType = properties.getProperty("ENCRYPT_BASE_TYPE");
|
|
||||||
String encryptClientId = properties.getProperty("ENCRYPT_CLIENT_ID");
|
|
||||||
String encryptAES256Iv = properties.getProperty("ENCRYPT_AES256_IV");
|
|
||||||
String encryptAES256Key = properties.getProperty("ENCRYPT_AES256_KEY");
|
|
||||||
String certPath = properties.getProperty("ENCRYPT_CERT_PATH");
|
|
||||||
|
|
||||||
// outbound encrypt 는 SecretKey 또는 NICE-auth 가 암호화 key로 사용된다.
|
|
||||||
// outbound 알림결과는 SecretKey, NICE-auth, Token을 사용한다.
|
|
||||||
// 참고로 inbound encrypt 에는 Token 또는 SecretKey 가 암호화 key로 사용된다.
|
|
||||||
// 여기에 NICE-auth logic 추가함 .
|
|
||||||
String clientSecret = "";
|
|
||||||
if ("TOKEN".equalsIgnoreCase(encryptBaseKeyType)) {
|
|
||||||
clientSecret = inboundToken;
|
|
||||||
} else if ( "SECRETKEY".equalsIgnoreCase(encryptBaseKeyType)) {
|
|
||||||
OAuth2Manager manager = OAuth2Manager.getInstance();
|
|
||||||
ClientDetails clientDetail = manager.getClientDeatilsStore().get(encryptClientId);
|
|
||||||
if ( clientDetail != null ) { // not null 이라는 것은 APIM Oauth Server에 등록된 client ID 이다.
|
|
||||||
// 업체가 우리 OAuth 를 사용하는 경우임
|
|
||||||
clientSecret = clientDetail.getClientSecret();
|
|
||||||
} else { // 이경우는 우리 OAuth를 사용안하는 경우임, 업체에서 사용하는 secretkey를 사용한다.
|
|
||||||
clientSecret = encryptClientId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nice-auth , token 값을 substring 해서 사용한다. 나이스 지키미는 adapter token에서 추출된 token을 사용한다.
|
|
||||||
if (useAdapterToken && "AES256-NICEON".equals(encryptAlgorithm)) {
|
|
||||||
clientSecret = niceAuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 여기서 SECRETY인지 nice-auth 인지 check 하여 clientSecret 값을 정하는 logic 추가하여 넘겨야한다.
|
|
||||||
|
|
||||||
String responseMessage = doOutboundPreEncrypt(eaiBody, encryptAlgorithm, clientSecret, encryptAES256Iv, encryptAES256Key, certPath, currentDate);
|
|
||||||
logger.info("outbound pre encrypte value is :"+responseMessage);
|
|
||||||
return responseMessage;
|
|
||||||
|
|
||||||
}
|
|
||||||
private String doOutboundPreEncrypt(Object eaiBody, String encryptAlgorithm, String clientSecretKey, String encryptAES256Iv, String encryptAES256Key, String certPath, Date currentDate) throws Exception {
|
|
||||||
|
|
||||||
String encryptedMessage = "";
|
|
||||||
String encryptedJsonKey = "";
|
|
||||||
|
|
||||||
encryptedJsonKey = getJsonKey(encryptAlgorithm);
|
|
||||||
|
|
||||||
switch (encryptAlgorithm) {
|
|
||||||
case "AES":
|
|
||||||
encryptedMessage = encrypt(eaiBody.toString()); // encrypt by jwhong
|
|
||||||
break;
|
|
||||||
case "AES128":
|
|
||||||
case "AES128-TOSS":
|
|
||||||
try {
|
|
||||||
String key = clientSecretKey.substring(22,38);
|
|
||||||
encryptedMessage = AESCipher.encrypt(eaiBody.toString(), key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES128 Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES128 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES128-TOGETHER":
|
|
||||||
try {
|
|
||||||
String key = clientSecretKey.substring(44,60);
|
|
||||||
encryptedMessage = AESCipher.encrypt(eaiBody.toString(), key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES128 Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES128 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256":
|
|
||||||
try {
|
|
||||||
String key = clientSecretKey.substring(10,42);
|
|
||||||
encryptedMessage = AES256Cipher.encrypt(eaiBody.toString(), key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES256 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256-KAKAO":
|
|
||||||
try {
|
|
||||||
encryptedMessage = AES256Cipher.encrypt(eaiBody.toString(), encryptAES256Iv, encryptAES256Key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES256 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256-TOSS":
|
|
||||||
try {
|
|
||||||
encryptedMessage = AES256Cipher.encrypt(eaiBody.toString(), encryptAES256Iv, encryptAES256Key);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES256 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256-NICEON":
|
|
||||||
try {
|
|
||||||
String key = clientSecretKey.substring(10,42);
|
|
||||||
logger.debug("HttpClientAdapterServiceRest] NICE ON AES256 Encryption key is :+="+key);
|
|
||||||
encryptedMessage = AES256Cipher.encrypt(eaiBody.toString(), key);
|
|
||||||
//encryptedMessage = ExchangeCrypto.encodedData(eaiStringBody, clientSecretKey);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES256 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256GCM":
|
|
||||||
try {
|
|
||||||
byte[] key = AES256GCMCipher.generateKey();
|
|
||||||
AES256GCMCipher cipher = new AES256GCMCipher(key);
|
|
||||||
byte[] aad = "metadata".getBytes();
|
|
||||||
|
|
||||||
encryptedMessage = cipher.encrypt(eaiBody.toString(), aad);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256GCM Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, AES256GCM Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "ECDHESA256":
|
|
||||||
try {
|
|
||||||
ECDHESA256Cipher cipher = new ECDHESA256Cipher();
|
|
||||||
|
|
||||||
encryptedMessage = cipher.encrypt(eaiBody.toString());
|
|
||||||
// System.out.println("Encrypted JWE: " + encryptedMessage);
|
|
||||||
|
|
||||||
String decrypted = cipher.decrypt(encryptedMessage);
|
|
||||||
// System.out.println("Decrypted Payload: " + decrypted);
|
|
||||||
|
|
||||||
//아래 인증서로 테스트
|
|
||||||
ECDHESA256Cipher cipherCert = new ECDHESA256Cipher(certPath);
|
|
||||||
encryptedMessage = cipherCert.encrypt(eaiBody.toString());
|
|
||||||
|
|
||||||
decrypted = cipherCert.decrypt(encryptedMessage);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256GCM Encryption error=" + e.getMessage());
|
|
||||||
throw new Exception("[HttpClient5AdapterServiceRest] Pre Encrypt Error : Encrypted Error, ECDHESA256 Key Invalid");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
encryptedMessage = eaiBody.toString();
|
|
||||||
return encryptedMessage;
|
|
||||||
//break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encryptAlgorithm.equals("AES256-NICEON")) {
|
|
||||||
String goodsCls = PropManager.getInstance().getProperty("NiceOnProperty","goods.cls.value");
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
SimpleDateFormat sdfDateTime =new SimpleDateFormat("yyyyMMddHHmmss");
|
|
||||||
map.put("req_dtm", sdfDateTime.format(currentDate));
|
|
||||||
map.put("goods_cls", goodsCls);
|
|
||||||
map.put(encryptedJsonKey, encryptedMessage);
|
|
||||||
JSONObject dataBody = new JSONObject();
|
|
||||||
dataBody.putAll(map);
|
|
||||||
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
json.put("dataBody", dataBody);
|
|
||||||
return json.toJSONString();
|
|
||||||
} else {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put(encryptedJsonKey, encryptedMessage);
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
json.putAll(map);
|
|
||||||
return json.toJSONString();
|
|
||||||
}
|
|
||||||
|
|
||||||
//return encryptedMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private byte[] doOutboundPostFilter(byte[] eaiBody, String decryptAlgorithm, String adapterName, OAuth2AccessTokenVO niceAccessToken ) {
|
|
||||||
|
|
||||||
if ( !encryptResponseApply) {
|
|
||||||
return eaiBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
Properties properties = AdapterPropManager.getInstance().getProperties(adapterName); // jwhong
|
|
||||||
//String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong
|
|
||||||
//String encryptBaseKeyType = properties.getProperty("ENCRYPT_BASE_TYPE");
|
|
||||||
String encryptClientId = properties.getProperty("ENCRYPT_CLIENT_ID");
|
|
||||||
String encryptAES256Iv = properties.getProperty("ENCRYPT_AES256_IV");
|
|
||||||
String encryptAES256Key = properties.getProperty("ENCRYPT_AES256_KEY");
|
|
||||||
|
|
||||||
// outbound encrypt 는 SecretKey 또는 NICE-auth 가 암호화 key로 사용된다.
|
|
||||||
OAuth2Manager manager = OAuth2Manager.getInstance();
|
|
||||||
ClientDetails clientDetail = manager.getClientDeatilsStore().get(encryptClientId);
|
|
||||||
String clientSecret = "";
|
|
||||||
if ( clientDetail != null ) {
|
|
||||||
clientSecret = clientDetail.getClientSecret();
|
|
||||||
} else {
|
|
||||||
clientSecret = encryptClientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nice-auth , token 값을 substring 해서 사용한다. 나이스 지키미는 adapter token에서 추출된 token을 사용한다.
|
|
||||||
if (useAdapterToken && "AES256-NICEON".equals(decryptAlgorithm)) {
|
|
||||||
clientSecret = niceAccessToken.getAccessToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] resPostMessage = doOutboundPostDecrypt(eaiBody, decryptAlgorithm, clientSecret, encryptAES256Iv, encryptAES256Key);
|
|
||||||
return resPostMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] doOutboundPostDecrypt(byte[] eaiBody, String decryptAlgorithm, String clientSecretKey, String encryptAES256Iv, String encryptAES256Key ) {
|
|
||||||
|
|
||||||
byte[] decryptedMessage = null;
|
|
||||||
String eaiStringBody = new String(eaiBody, StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
try {
|
|
||||||
eaiStringBody = extractBodyMsg(decryptAlgorithm, eaiStringBody);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
logger.debug("[JSON 파싱 오류 발생 : ]" +e.getMessage());
|
|
||||||
return eaiBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
//test source
|
|
||||||
/*
|
|
||||||
logger.debug("eaiBody 바이트 배열 길이: " + eaiBody.length);
|
|
||||||
logger.debug("Base64 문자열: " + eaiStringBody);
|
|
||||||
logger.debug("Base64 문자열 길이: " + eaiStringBody.length());
|
|
||||||
logger.debug("Base64 문자열 끝: " + eaiStringBody.substring(eaiStringBody.length() - 10));
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Base64 디코딩 테스트
|
|
||||||
try {
|
|
||||||
byte[] decoded = Base64.getDecoder().decode(eaiStringBody);
|
|
||||||
logger.debug("[디코딩 성공, 길이: ]" + decoded.length);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
logger.debug("[Base64 디코딩 실패: ]" + e.getMessage());
|
|
||||||
}
|
|
||||||
// end test source
|
|
||||||
|
|
||||||
switch (decryptAlgorithm) {
|
|
||||||
case "AES":
|
|
||||||
decryptedMessage = decrypt(eaiBody); // encrypt by jwhong
|
|
||||||
break;
|
|
||||||
case "AES128":
|
|
||||||
case "AES128-TOSS":
|
|
||||||
try {
|
|
||||||
String key128 = clientSecretKey.substring(22,38);
|
|
||||||
String decryptedStrMessage = AESCipher.decryptExistException(eaiStringBody, key128);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES128 Decryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES128-TOGETHER":
|
|
||||||
try {
|
|
||||||
String key128 = clientSecretKey.substring(44,60);
|
|
||||||
String decryptedStrMessage = AESCipher.decryptExistException(eaiStringBody, key128);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES128 Decryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256":
|
|
||||||
try {
|
|
||||||
String key = clientSecretKey.substring(10,42);
|
|
||||||
String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, key);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256-KAKAO":
|
|
||||||
try {
|
|
||||||
String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, encryptAES256Iv, encryptAES256Key);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256-TOSS":
|
|
||||||
try {
|
|
||||||
String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, encryptAES256Iv, encryptAES256Key);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256-NICEON":
|
|
||||||
try {
|
|
||||||
String key = clientSecretKey.substring(10,42);
|
|
||||||
String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, key);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "AES256GCM":
|
|
||||||
try {
|
|
||||||
byte[] key = AES256GCMCipher.generateKey();
|
|
||||||
AES256GCMCipher cipher = new AES256GCMCipher(key);
|
|
||||||
byte[] aad = "metadata".getBytes();
|
|
||||||
String decryptedStrMessage = cipher.decrypt(eaiStringBody, aad);
|
|
||||||
decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
try {
|
|
||||||
decryptedMessage = Arrays.copyOf(eaiBody, eaiBody.length);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClientAdapterServiceRest] doOutboundPostFilter Array copy error=" + e.getMessage());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return decryptedMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String extractBodyMsg(String decryptAlgorithm, String eaiStringBody) throws ParseException {
|
|
||||||
|
|
||||||
String decryptedJsonKey = "";
|
|
||||||
String decryptedBodyMessage = "";
|
|
||||||
|
|
||||||
decryptedJsonKey = getJsonKey(decryptAlgorithm);
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONParser parser = new JSONParser();
|
|
||||||
JSONObject jsonObject = (JSONObject) parser.parse(eaiStringBody);
|
|
||||||
decryptedBodyMessage = (String) jsonObject.get(decryptedJsonKey);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
logger.error("HttpClient5AdapterServiceRest] extractBodyMsg error : " + e.getMessage());
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return decryptedBodyMessage;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getJsonKey(String Algorithm) {
|
|
||||||
|
|
||||||
String jsonKey = "";
|
|
||||||
|
|
||||||
switch (Algorithm) {
|
|
||||||
case "AES128":
|
|
||||||
case "AES128-TOSS":
|
|
||||||
jsonKey = "preScreeningRequest";
|
|
||||||
break;
|
|
||||||
case "AES128-TOGETHER":
|
|
||||||
jsonKey = "obpTxData";
|
|
||||||
break;
|
|
||||||
case "AES256":
|
|
||||||
jsonKey = "preScreeningRequest";
|
|
||||||
break;
|
|
||||||
case "AES256-KAKAO":
|
|
||||||
jsonKey = "encrypted_data";
|
|
||||||
break;
|
|
||||||
case "AES256-TOSS":
|
|
||||||
jsonKey = "encryptedData";
|
|
||||||
break;
|
|
||||||
case "AES256-NICEON":
|
|
||||||
jsonKey = "enc_data";
|
|
||||||
break;
|
|
||||||
case "AES256GCM":
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return jsonKey;
|
|
||||||
}
|
|
||||||
// by jwhong
|
|
||||||
private String encrypt(String plainText) {
|
|
||||||
try {
|
|
||||||
SecretKeySpec key = new SecretKeySpec("1234567890123456".getBytes(), "AES");
|
|
||||||
Cipher cipher = Cipher.getInstance("AES");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
|
||||||
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
|
||||||
return Base64.getEncoder().encodeToString(encrypted);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 예외 로그 출력 또는 사용자 정의 처리
|
|
||||||
e.printStackTrace();
|
|
||||||
return null; // 또는 에러 메시지 반환
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static byte[] decrypt(byte[] encryptedBytes) {
|
|
||||||
try {
|
|
||||||
// byte[] --> Base64 문자열로 변환
|
|
||||||
String encryptedText = new String(encryptedBytes, StandardCharsets.UTF_8).trim();
|
|
||||||
// 암호화에 사용한 키와 동일한 키 사용
|
|
||||||
SecretKeySpec key = new SecretKeySpec("1234567890123456".getBytes(), "AES");
|
|
||||||
|
|
||||||
// 암호화와 동일한 알고리즘 설정
|
|
||||||
Cipher cipher = Cipher.getInstance("AES");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
|
||||||
|
|
||||||
// Base64로 인코딩된 문자열을 디코딩
|
|
||||||
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
|
|
||||||
|
|
||||||
// 복호화 수행
|
|
||||||
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
|
|
||||||
|
|
||||||
// UTF-8 문자열로 변환하여 반환
|
|
||||||
//return new String(decryptedBytes, StandardCharsets.UTF_8);
|
|
||||||
//복호화된 바이트 배열 반환
|
|
||||||
return decryptedBytes;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private HashMap<String, String> getParameters(Object message) throws Exception {
|
private HashMap<String, String> getParameters(Object message) throws Exception {
|
||||||
@@ -1583,7 +1064,13 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
HashMap<String, String> result = new HashMap<String, String>();
|
HashMap<String, String> result = new HashMap<String, String>();
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
if (message instanceof JSONObject) {
|
if (message instanceof JsonNode) {
|
||||||
|
ObjectNode objectNode = (ObjectNode) message;
|
||||||
|
objectNode.fields().forEachRemaining(e -> {
|
||||||
|
JsonNode value = e.getValue();
|
||||||
|
result.put(e.getKey(), value.isNull() ? null : value.asText());
|
||||||
|
});
|
||||||
|
} else if (message instanceof JSONObject) {
|
||||||
JSONObject jsonObject = (JSONObject) message;
|
JSONObject jsonObject = (JSONObject) message;
|
||||||
for (Object key : jsonObject.keySet()) {
|
for (Object key : jsonObject.keySet()) {
|
||||||
Object obj = jsonObject.get(key);
|
Object obj = jsonObject.get(key);
|
||||||
@@ -1628,7 +1115,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
if (StringUtils.isBlank(jsonData)) {
|
if (StringUtils.isBlank(jsonData)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONParser parser = new JSONParser();
|
JSONParser parser = new JSONParser();
|
||||||
Object parsedObj = parser.parse(jsonData);
|
Object parsedObj = parser.parse(jsonData);
|
||||||
@@ -1654,7 +1140,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private String changeUrl(String messageType, String url, String restOption, Object sendData, Map<String, String> inboundPathVariables) {
|
private String changeUrl(String messageType, String url, String restOption, Object sendData, Map<String, String> inboundPathVariables) {
|
||||||
if ((MessageType.JSON.equals(messageType) || MessageType.XML.equals(messageType))) {
|
if ((MessageType.JSON.equals(messageType) || MessageType.XML.equals(messageType))) {
|
||||||
try {
|
try {
|
||||||
@@ -1679,7 +1164,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
uriVariables.add(fieldName);
|
uriVariables.add(fieldName);
|
||||||
}
|
}
|
||||||
if (MessageType.JSON.equals(messageType)) {
|
if (MessageType.JSON.equals(messageType)) {
|
||||||
JsonNode jsonNode = JsonPathUtil.toTree(sendData);
|
JsonNode jsonNode = JacksonUtil.readTree(sendData);
|
||||||
// JSONObject jsonObject;
|
// JSONObject jsonObject;
|
||||||
// if (sendData instanceof JSONObject) {
|
// if (sendData instanceof JSONObject) {
|
||||||
// jsonObject = (JSONObject) sendData;
|
// jsonObject = (JSONObject) sendData;
|
||||||
|
|||||||
+8
-2
@@ -9,6 +9,9 @@ import org.json.simple.JSONObject;
|
|||||||
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
|
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.HttpClient5AdapterFilterFactory;
|
||||||
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
|
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : HttpClient5AdapterServiceRest 호출 전후 Filter 적용할 수 있는 기능을 제공한다.<br>
|
* 1. 기능 : HttpClient5AdapterServiceRest 호출 전후 Filter 적용할 수 있는 기능을 제공한다.<br>
|
||||||
@@ -34,6 +37,8 @@ public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterSe
|
|||||||
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
|
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
|
||||||
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
|
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
|
||||||
|
|
||||||
|
private ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
|
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
|
||||||
* 3. 주의사항 <br>
|
* 3. 주의사항 <br>
|
||||||
@@ -54,8 +59,9 @@ public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterSe
|
|||||||
adapterResponse = doPostFilters(adptGrpName, adptName, prop, adapterResponse, tempProp);
|
adapterResponse = doPostFilters(adptGrpName, adptName, prop, adapterResponse, tempProp);
|
||||||
if (adapterResponse instanceof JSONObject)
|
if (adapterResponse instanceof JSONObject)
|
||||||
adapterResponse = ((JSONObject) adapterResponse).toJSONString();
|
adapterResponse = ((JSONObject) adapterResponse).toJSONString();
|
||||||
|
else if (adapterResponse instanceof ObjectNode)
|
||||||
if (adapterResponse instanceof Node)
|
adapterResponse = mapper.writeValueAsString((ObjectNode) adapterResponse);
|
||||||
|
else if (adapterResponse instanceof Node)
|
||||||
adapterResponse = ((Node) adapterResponse).asXML();
|
adapterResponse = ((Node) adapterResponse).asXML();
|
||||||
|
|
||||||
return adapterResponse;
|
return adapterResponse;
|
||||||
|
|||||||
@@ -31,7 +31,15 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA
|
|||||||
* 서브클래스에서 오버라이드하여 tempProp으로부터 값을 추출할 수 있다.
|
* 서브클래스에서 오버라이드하여 tempProp으로부터 값을 추출할 수 있다.
|
||||||
*/
|
*/
|
||||||
protected Map<String, String> buildRuntimeContext(Properties prop, Properties tempProp) {
|
protected Map<String, String> buildRuntimeContext(Properties prop, Properties tempProp) {
|
||||||
return new HashMap<>();
|
Map<String, String> context = new HashMap<>();
|
||||||
|
for (String key : prop.stringPropertyNames()) {
|
||||||
|
context.put(key, prop.getProperty(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String key : tempProp.stringPropertyNames()) {
|
||||||
|
context.put(key, tempProp.getProperty(key));
|
||||||
|
}
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,11 +47,10 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA
|
|||||||
* 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
|
* 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
|
||||||
*/
|
*/
|
||||||
protected byte[] buildAad(Properties prop, Properties tempProp) {
|
protected byte[] buildAad(Properties prop, Properties tempProp) {
|
||||||
String propKey = prop.getProperty(PROP_AAD_HEADER);
|
if (tempProp == null) {
|
||||||
if (StringUtils.isBlank(propKey) || tempProp == null) {
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
String value = tempProp.getProperty(PROP_AAD_HEADER);
|
||||||
String value = tempProp.getProperty(propKey);
|
|
||||||
return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
|
return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +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.slf4j.MDC;
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||||
import com.eactive.eai.adapter.AdapterManager;
|
import com.eactive.eai.adapter.AdapterManager;
|
||||||
@@ -21,6 +21,8 @@ import com.eactive.eai.common.TransactionContextKeys;
|
|||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
import com.eactive.eai.common.util.TxSiftContext;
|
import com.eactive.eai.common.util.TxSiftContext;
|
||||||
import com.eactive.eai.common.util.UUIDGenerator;
|
import com.eactive.eai.common.util.UUIDGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
public abstract class HttpAdapterServiceSupport implements HttpAdapterService, HttpAdapterServiceKey {
|
public abstract class HttpAdapterServiceSupport implements HttpAdapterService, HttpAdapterServiceKey {
|
||||||
private static final String LOG_PREFIX = "HttpAdapterServiceSupport] ";
|
private static final String LOG_PREFIX = "HttpAdapterServiceSupport] ";
|
||||||
@@ -28,7 +30,8 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
|||||||
public static final String HEADER_NAME_CLIENT_ID = "x-elink-client-id";
|
public static final String HEADER_NAME_CLIENT_ID = "x-elink-client-id";
|
||||||
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
|
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
|
||||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||||
|
private ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
|
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
|
||||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||||
@@ -63,6 +66,13 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
|||||||
Object obj = null;
|
Object obj = null;
|
||||||
try {
|
try {
|
||||||
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
|
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
|
||||||
|
|
||||||
|
if (message instanceof JSONObject) {
|
||||||
|
message = ((JSONObject) message).toJSONString();
|
||||||
|
} else if (message instanceof JsonNode) {
|
||||||
|
message = mapper.writeValueAsString((JsonNode) message);
|
||||||
|
}
|
||||||
|
|
||||||
obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
|
obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
|
||||||
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
|
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
|
||||||
} catch (HttpStatusException e) { // inbound error
|
} catch (HttpStatusException e) { // inbound error
|
||||||
@@ -70,13 +80,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +96,10 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
|||||||
return (byte[]) obj;
|
return (byte[]) obj;
|
||||||
} else if (obj instanceof String) {
|
} else if (obj instanceof String) {
|
||||||
return (String) obj;
|
return (String) obj;
|
||||||
|
} else if (obj instanceof JSONObject) {
|
||||||
|
return ((JSONObject) obj).toJSONString();
|
||||||
|
} else if (obj instanceof JsonNode) {
|
||||||
|
return (JsonNode) obj;
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("RECEAIAHA001");
|
throw new Exception("RECEAIAHA001");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,19 @@ import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
|
|||||||
*/
|
*/
|
||||||
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {
|
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {
|
||||||
|
|
||||||
/**
|
public static final String ERROR_DEC_FAIL = "E.DECRYPT_FAIL";
|
||||||
|
public static final String ERROR_ENC_FAIL = "E.ENCRYPT_FAIL";
|
||||||
|
/**
|
||||||
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
|
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
|
||||||
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
|
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
|
||||||
* 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 수 있다.
|
* 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 수 있다.
|
||||||
*/
|
*/
|
||||||
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
|
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
|
||||||
return new HashMap<>();
|
Map<String, String> context = new HashMap<>();
|
||||||
|
for (String key : prop.stringPropertyNames()) {
|
||||||
|
context.put(key, prop.getProperty(key));
|
||||||
|
}
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +49,9 @@ public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterF
|
|||||||
if (StringUtils.isBlank(headerName) || request == null) {
|
if (StringUtils.isBlank(headerName) || request == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String headerValue = request.getHeader(headerName);
|
Properties inboundHeaderProp = (Properties)prop.get("INBOUND_HEADER");
|
||||||
|
String headerValue = inboundHeaderProp.getProperty(headerName);
|
||||||
|
// String headerValue = request.getHeader(headerName);
|
||||||
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
|
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+39
-7
@@ -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();
|
||||||
@@ -89,6 +82,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;
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-44
@@ -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;
|
||||||
@@ -256,10 +254,10 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
|
|||||||
responseData = new String((byte[]) result, encode);
|
responseData = new String((byte[]) result, encode);
|
||||||
responseBytes = (byte[]) result;
|
responseBytes = (byte[]) result;
|
||||||
} else if (result instanceof String) {
|
} else if (result instanceof String) {
|
||||||
responseBytes = ((String)result).getBytes();
|
responseBytes = ((String)result).getBytes(encode);
|
||||||
if ( StringUtils.isBlank((String)result ) ) {
|
if ( StringUtils.isBlank((String)result ) ) {
|
||||||
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
|
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
|
||||||
responseBytes = responseData.getBytes();
|
responseBytes = responseData.getBytes(encode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public abstract class AbstractCryptoFilter {
|
|||||||
* fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
|
* fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
|
||||||
* toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
|
* toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
|
||||||
*/
|
*/
|
||||||
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
protected Object decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||||
byte[] aad, String fromPath, String toPath) throws Exception {
|
byte[] aad, String fromPath, String toPath) throws Exception {
|
||||||
JsonNode root = JsonPathUtil.toTree(body);
|
JsonNode root = JsonPathUtil.toTree(body);
|
||||||
String encBase64 = JsonPathUtil.getAt(root, fromPath);
|
String encBase64 = JsonPathUtil.getAt(root, fromPath);
|
||||||
@@ -81,7 +81,7 @@ public abstract class AbstractCryptoFilter {
|
|||||||
return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
|
return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
|
||||||
}
|
}
|
||||||
JsonPathUtil.setAt(root, toPath, plainText);
|
JsonPathUtil.setAt(root, toPath, plainText);
|
||||||
return JsonPathUtil.fromTree(root);
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
@@ -99,7 +99,7 @@ public abstract class AbstractCryptoFilter {
|
|||||||
* fromPath 값을 암호화하여 toPath(Base64)에 반영. JSON 파싱 1회.
|
* fromPath 값을 암호화하여 toPath(Base64)에 반영. JSON 파싱 1회.
|
||||||
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
|
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
|
||||||
*/
|
*/
|
||||||
protected String encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
protected Object encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||||
byte[] aad, String fromPath, String toPath) throws Exception {
|
byte[] aad, String fromPath, String toPath) throws Exception {
|
||||||
if (PATH_ROOT.equals(fromPath)) {
|
if (PATH_ROOT.equals(fromPath)) {
|
||||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
||||||
@@ -112,13 +112,13 @@ public abstract class AbstractCryptoFilter {
|
|||||||
String plainText = JsonPathUtil.getAt(root, fromPath);
|
String plainText = JsonPathUtil.getAt(root, fromPath);
|
||||||
if (StringUtils.isBlank(plainText)) {
|
if (StringUtils.isBlank(plainText)) {
|
||||||
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
|
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
|
||||||
return body;
|
return root;
|
||||||
}
|
}
|
||||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
||||||
plainText.getBytes(StandardCharsets.UTF_8));
|
plainText.getBytes(StandardCharsets.UTF_8));
|
||||||
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
|
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
|
||||||
JsonPathUtil.setAt(root, toPath, encBase64);
|
JsonPathUtil.setAt(root, toPath, encBase64);
|
||||||
return JsonPathUtil.fromTree(root);
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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 static 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,12 +327,12 @@ 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopToken(String adapterGroupName) {
|
public void stopToken(String adapterGroupName) {
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import javax.annotation.PostConstruct;
|
|||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -44,9 +45,26 @@ public class HsmCryptoService implements PropertyChangeListener {
|
|||||||
private static final String PROP_GROUP = "HSM";
|
private static final String PROP_GROUP = "HSM";
|
||||||
private static final String PROP_CACHE_RELOAD_YN = "CACHE_RELOAD_YN";
|
private static final String PROP_CACHE_RELOAD_YN = "CACHE_RELOAD_YN";
|
||||||
|
|
||||||
|
private static final String PROP_CACHE_TTL_SEC = "CACHE_TTL_SEC";
|
||||||
|
private static long CACHE_TTL_MS = 10 * 60 * 1000; // 10분, 필요시 PropManager로 외부화
|
||||||
|
|
||||||
|
private static class CachedKey<T> {
|
||||||
|
final T key;
|
||||||
|
final long cachedAt;
|
||||||
|
|
||||||
|
CachedKey(T key) {
|
||||||
|
this.key = key;
|
||||||
|
this.cachedAt = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isExpired() {
|
||||||
|
return System.currentTimeMillis() - cachedAt > CACHE_TTL_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HSM 통신 최소화: 최초 1회 조회 후 JVM 메모리에 캐싱
|
// HSM 통신 최소화: 최초 1회 조회 후 JVM 메모리에 캐싱
|
||||||
private final ConcurrentHashMap<String, SecretKey> secretKeyCache = new ConcurrentHashMap<String, SecretKey>();
|
private final ConcurrentHashMap<String, CachedKey<SecretKey>> secretKeyCache = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<String, PublicKey> publicKeyCache = new ConcurrentHashMap<String, PublicKey>();
|
private final ConcurrentHashMap<String, CachedKey<PublicKey>> publicKeyCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PropManager propManager;
|
private PropManager propManager;
|
||||||
@@ -70,6 +88,9 @@ public class HsmCryptoService implements PropertyChangeListener {
|
|||||||
if ("Y".equalsIgnoreCase(reloadYn)) {
|
if ("Y".equalsIgnoreCase(reloadYn)) {
|
||||||
clearKeyCache();
|
clearKeyCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String propCacheTtlSec = propManager.getProperty(PROP_GROUP, PROP_CACHE_TTL_SEC, "600");
|
||||||
|
CACHE_TTL_MS = (Integer.parseInt(propCacheTtlSec.trim())) * 1000; // 10분, 필요시 PropManager로 외부화
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -78,54 +99,97 @@ public class HsmCryptoService implements PropertyChangeListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* HSM KeyStore 에서 공개키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
|
* HSM KeyStore 에서 공개키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
|
||||||
|
* HSM 장애 시 만료된 캐시가 있으면 그것을 반환하여 서비스 연속성을 유지합니다.
|
||||||
*/
|
*/
|
||||||
public PublicKey getPublicKey(String keyAlias) throws HsmException {
|
public PublicKey getPublicKey(String keyAlias) throws HsmException {
|
||||||
checkReady();
|
// 1. 유효한 캐시 즉시 반환 (HSM 상태 무관)
|
||||||
PublicKey cached = publicKeyCache.get(keyAlias);
|
CachedKey<PublicKey> cached = publicKeyCache.get(keyAlias);
|
||||||
if (cached != null) {
|
if (cached != null && !cached.isExpired()) {
|
||||||
return cached;
|
return cached.key;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
Certificate cert = HsmManager.getInstance().getKeyStore().getCertificate(keyAlias);
|
// 2. 캐시 미스 또는 만료 → HSM 갱신 시도
|
||||||
if (cert == null) {
|
if (HsmManager.getInstance().isReady()) {
|
||||||
throw new HsmException("인증서를 찾을 수 없습니다. alias=" + keyAlias);
|
try {
|
||||||
|
Certificate cert = HsmManager.getInstance().getKeyStore().getCertificate(keyAlias);
|
||||||
|
if (cert == null) {
|
||||||
|
throw new HsmException("인증서를 찾을 수 없습니다. alias=" + keyAlias);
|
||||||
|
}
|
||||||
|
PublicKey key = cert.getPublicKey();
|
||||||
|
publicKeyCache.put(keyAlias, new CachedKey<>(key));
|
||||||
|
logger.warn("HsmCryptoService] 공개키 캐시 등록: alias=" + keyAlias);
|
||||||
|
return key;
|
||||||
|
} catch (HsmException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("HsmCryptoService] 공개키 HSM 조회 실패: " + e.getMessage());
|
||||||
}
|
}
|
||||||
PublicKey key = cert.getPublicKey();
|
|
||||||
publicKeyCache.put(keyAlias, key);
|
|
||||||
logger.warn("HsmCryptoService] 공개키 캐시 등록: alias=" + keyAlias);
|
|
||||||
return key;
|
|
||||||
} catch (HsmException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new HsmException("공개키 조회 실패: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. HSM 조회 불가 → 만료 캐시 fallback
|
||||||
|
if (cached != null) {
|
||||||
|
logger.warn("HsmCryptoService] HSM 장애, 만료 공개키 캐시 fallback: alias=" + keyAlias);
|
||||||
|
return cached.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HsmException("공개키 조회 불가: HSM 장애이며 캐시도 없습니다. alias=" + keyAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HSM KeyStore 에서 AES 대칭키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
|
* HSM KeyStore 에서 AES 대칭키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
|
||||||
* HSM 키 생성 시 CKA_EXTRACTABLE=true (ctkmu -x 플래그) 로 생성된 키만 반환됩니다.
|
* HSM 키 생성 시 CKA_EXTRACTABLE=true (ctkmu -x 플래그) 로 생성된 키만 반환됩니다.
|
||||||
|
*
|
||||||
|
* [캐싱 전략]
|
||||||
|
* HSM 에서 가져온 P11SecretKey(PKCS11 핸들 래퍼)를 그대로 캐싱하면, HSM Provider 가
|
||||||
|
* 재초기화될 때 세션 무효화로 인해 캐시된 키를 사용한 Cipher 연산이 실패한다.
|
||||||
|
* 따라서 getEncoded() 로 키 바이트를 추출하여 SecretKeySpec(JVM 메모리 키) 으로 변환 후
|
||||||
|
* 캐싱한다. encryptAes/decryptAes 는 이미 JVM 소프트웨어 Cipher 를 사용하므로,
|
||||||
|
* HSM Provider 상태와 완전히 독립적으로 동작한다.
|
||||||
|
*
|
||||||
|
* HSM 장애 시 만료된 캐시가 있으면 그것을 반환하여 서비스 연속성을 유지합니다.
|
||||||
*/
|
*/
|
||||||
public SecretKey getSecretKey(String keyAlias) throws HsmException {
|
public SecretKey getSecretKey(String keyAlias) throws HsmException {
|
||||||
checkReady();
|
// 1. 유효한 캐시 즉시 반환 (HSM 상태 무관)
|
||||||
SecretKey cached = secretKeyCache.get(keyAlias);
|
CachedKey<SecretKey> cached = secretKeyCache.get(keyAlias);
|
||||||
if (cached != null) {
|
if (cached != null && !cached.isExpired()) {
|
||||||
return cached;
|
return cached.key;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
KeyStore keyStore = HsmManager.getInstance().getKeyStore();
|
// 2. 캐시 미스 또는 만료 → HSM 갱신 시도
|
||||||
java.security.Key key = keyStore.getKey(keyAlias, null);
|
if (HsmManager.getInstance().isReady()) {
|
||||||
if (!(key instanceof SecretKey)) {
|
try {
|
||||||
throw new HsmException("AES 키를 찾을 수 없습니다 (CKA_EXTRACTABLE=true 확인 필요). alias=" + keyAlias);
|
KeyStore keyStore = HsmManager.getInstance().getKeyStore();
|
||||||
|
java.security.Key key = keyStore.getKey(keyAlias, null);
|
||||||
|
if (key == null) {
|
||||||
|
throw new HsmException("키를 찾을 수 없습니다. alias=" + keyAlias);
|
||||||
|
}
|
||||||
|
SecretKey secretKey = (SecretKey) key;
|
||||||
|
|
||||||
|
// P11SecretKey → SecretKeySpec 변환: HSM Provider 의존성 제거
|
||||||
|
// getEncoded() 가 null 이면 non-extractable 키이므로 원본 유지
|
||||||
|
byte[] keyBytes = secretKey.getEncoded();
|
||||||
|
if (keyBytes != null) {
|
||||||
|
secretKey = new SecretKeySpec(keyBytes, secretKey.getAlgorithm());
|
||||||
|
}
|
||||||
|
|
||||||
|
secretKeyCache.put(keyAlias, new CachedKey<>(secretKey));
|
||||||
|
logger.warn("HsmCryptoService] 대칭키 캐시 등록(갱신): alias=" + keyAlias);
|
||||||
|
return secretKey;
|
||||||
|
|
||||||
|
} catch (HsmException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("HsmCryptoService] 대칭키 HSM 조회 실패: " + e.getMessage());
|
||||||
}
|
}
|
||||||
SecretKey secretKey = (SecretKey) key;
|
|
||||||
secretKeyCache.put(keyAlias, secretKey);
|
|
||||||
logger.warn("HsmCryptoService] 대칭키 캐시 등록: alias=" + keyAlias);
|
|
||||||
return secretKey;
|
|
||||||
} catch (HsmException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new HsmException("AES 키 조회 실패: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. HSM 조회 불가 → 만료 캐시 fallback
|
||||||
|
if (cached != null) {
|
||||||
|
logger.warn("HsmCryptoService] HSM 장애, 만료 대칭키 캐시 fallback: alias=" + keyAlias);
|
||||||
|
return cached.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HsmException("키 조회 불가: HSM 장애이며 캐시도 없습니다. alias=" + keyAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 캐시를 비웁니다. HSM 키 교체 후 재로드가 필요할 때 호출합니다. */
|
/** 캐시를 비웁니다. HSM 키 교체 후 재로드가 필요할 때 호출합니다. */
|
||||||
@@ -254,13 +318,4 @@ public class HsmCryptoService implements PropertyChangeListener {
|
|||||||
return decryptAes(secretKey, iv, ciphertext, null);
|
return decryptAes(secretKey, iv, ciphertext, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Private helpers
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private void checkReady() throws HsmException {
|
|
||||||
if (!HsmManager.getInstance().isReady()) {
|
|
||||||
throw new HsmException("HsmManager 가 초기화되지 않았습니다.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,23 @@ import java.io.File;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.security.AuthProvider;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.cert.Certificate;
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.util.Base64;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
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;
|
||||||
@@ -40,6 +46,18 @@ import com.eactive.eai.common.util.Logger;
|
|||||||
* name = SoftHSM
|
* name = SoftHSM
|
||||||
* library = C:/SoftHSM2/lib/softhsm2-x64.dll
|
* library = C:/SoftHSM2/lib/softhsm2-x64.dll
|
||||||
* slotListIndex = 0
|
* slotListIndex = 0
|
||||||
|
*
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
|
* [세션 누적 방지 / 회로차단 로직 추가]
|
||||||
|
* 기존 구현은 재로드마다 KeyStore.getInstance(...).load(null, pin) 을 새로
|
||||||
|
* 호출하여 PKCS11 세션(C_OpenSession)이 계속 누적되고, 결국 HSM 파티션의
|
||||||
|
* 최대 세션 수를 초과하면서 reload 가 영구적으로 실패하는 문제가 있었다.
|
||||||
|
* 이를 방지하기 위해:
|
||||||
|
* 1) 정상 상황에서는 "기존 keyStore 인스턴스"에 다시 load() 하여 세션 재사용
|
||||||
|
* 2) 실제 키 조회(probe)로 세션이 살아있는지 검증
|
||||||
|
* 3) 연속 실패가 임계치를 넘으면 Provider 자체를 logout 후 완전히 재생성
|
||||||
|
* 4) 재생성마저 실패하면 마지막으로 성공한 keyStore 를 유지 (서비스 연속성 우선)
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class HsmManager implements Lifecycle {
|
public class HsmManager implements Lifecycle {
|
||||||
@@ -49,13 +67,21 @@ public class HsmManager implements Lifecycle {
|
|||||||
private static final String GROUP_NAME = "HSM";
|
private static final String GROUP_NAME = "HSM";
|
||||||
private static final String PROP_CONFIG = "PKCS11_CONFIG";
|
private static final String PROP_CONFIG = "PKCS11_CONFIG";
|
||||||
private static final String PROP_PIN = "PIN";
|
private static final String PROP_PIN = "PIN";
|
||||||
|
private static final String PROP_RELOAD_INTERVAL_MINUTES = "RELOAD_INTERVAL_MINUTES";
|
||||||
|
|
||||||
private Provider pkcs11Provider;
|
private Provider pkcs11Provider;
|
||||||
private KeyStore keyStore;
|
private volatile KeyStore keyStore;
|
||||||
private boolean started;
|
private boolean started;
|
||||||
|
|
||||||
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
|
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
|
||||||
|
|
||||||
|
private volatile char[] pin;
|
||||||
|
|
||||||
|
// 재로드 주기 (분 단위). 필요시 PropManager로 외부화 가능.
|
||||||
|
private static long RELOAD_INTERVAL_MINUTES = 1;
|
||||||
|
private ScheduledExecutorService scheduler;
|
||||||
|
private ScheduledFuture<?> reloadFuture;
|
||||||
|
|
||||||
private HsmManager() {
|
private HsmManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +98,7 @@ public class HsmManager implements Lifecycle {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
init();
|
init();
|
||||||
|
startReloadScheduler();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIHSM002"));
|
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIHSM002"));
|
||||||
}
|
}
|
||||||
@@ -81,8 +108,13 @@ public class HsmManager implements Lifecycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void init() throws Exception {
|
private void init() throws Exception {
|
||||||
String configContent = PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG);
|
|
||||||
String pin = PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN);
|
String reloadIntervalStr = PropManager.getInstance().getProperty(GROUP_NAME, PROP_RELOAD_INTERVAL_MINUTES, "10");
|
||||||
|
RELOAD_INTERVAL_MINUTES = Long.parseLong(reloadIntervalStr);
|
||||||
|
EncryptionManager encManager = EncryptionManager.getInstance();
|
||||||
|
|
||||||
|
String configContent = encManager.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG));
|
||||||
|
String pinStr = 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 초기화를 건너뜁니다.");
|
||||||
@@ -99,31 +131,142 @@ public class HsmManager implements Lifecycle {
|
|||||||
Security.addProvider(pkcs11Provider);
|
Security.addProvider(pkcs11Provider);
|
||||||
|
|
||||||
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
|
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
|
||||||
char[] pinChars = (pin != null) ? pin.toCharArray() : null;
|
this.pin = (pinStr != null) ? pinStr.toCharArray() : null;
|
||||||
keyStore.load(null, pinChars);
|
keyStore.load(null, pin);
|
||||||
|
|
||||||
logger.warn("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());
|
logger.warn("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());
|
||||||
|
|
||||||
java.util.Enumeration<String> aliases = keyStore.aliases();
|
logKeyStore(this.keyStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logKeyStore(KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
java.util.Enumeration<String> aliases = keyStore.aliases();
|
||||||
StringBuilder aliasList = new StringBuilder();
|
StringBuilder aliasList = new StringBuilder();
|
||||||
while (aliases.hasMoreElements()) {
|
while (aliases.hasMoreElements()) {
|
||||||
if (aliasList.length() > 0) aliasList.append(", ");
|
if (aliasList.length() > 0) aliasList.append(", ");
|
||||||
|
|
||||||
String alias = aliases.nextElement();
|
String alias = aliases.nextElement();
|
||||||
aliasList.append(alias);
|
aliasList.append(alias);
|
||||||
|
|
||||||
java.security.Key key = keyStore.getKey(alias, null);
|
java.security.Key key = keyStore.getKey(alias, null);
|
||||||
if (key instanceof SecretKey) {
|
if (key instanceof SecretKey) {
|
||||||
SecretKey secretKey = (SecretKey) key;
|
SecretKey secretKey = (SecretKey) key;
|
||||||
if(secretKey.getEncoded() != null) {
|
if (secretKey.getEncoded() == null) {
|
||||||
String encBase64 = Base64.getEncoder().encodeToString(secretKey.getEncoded());
|
logger.warn("HsmManager] HSM - secretKey null {} : [{}]", alias, secretKey);
|
||||||
logger.debug("HsmManager] HSM - {} : [{}]", alias, encBase64);
|
}
|
||||||
} else {
|
|
||||||
logger.debug("HsmManager] HSM - secretKey null {} : [{}]", alias, secretKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.warn("HsmManager] HSM 키 목록: [" + aliasList + "]");
|
logger.warn("HsmManager] HSM 키 목록: [" + aliasList + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 별도 스레드에서 주기적으로 KeyStore 를 다시 로드하여
|
||||||
|
* HSM 에 새로 생성/추가된 키를 인식하도록 한다.
|
||||||
|
*/
|
||||||
|
private void startReloadScheduler() {
|
||||||
|
scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "hsm-keystore-reloader");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
reloadFuture = scheduler.scheduleWithFixedDelay(
|
||||||
|
this::reloadKeyStoreSafely,
|
||||||
|
RELOAD_INTERVAL_MINUTES,
|
||||||
|
RELOAD_INTERVAL_MINUTES,
|
||||||
|
TimeUnit.MINUTES
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.warn("HsmManager] KeyStore 주기적 재로드 스케줄러 시작. interval=" + RELOAD_INTERVAL_MINUTES + "분");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 스케줄러에서 호출되는 래퍼. 예외가 스케줄러 스레드를 죽이지 않도록 반드시 catch 한다.
|
||||||
|
* (ScheduledExecutorService 는 task 에서 예외가 던져지면 이후 스케줄을 자동으로 중단시킨다)
|
||||||
|
*/
|
||||||
|
private void reloadKeyStoreSafely() {
|
||||||
|
try {
|
||||||
|
reloadKeyStoreIfNeeded();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn("HsmManager] KeyStore 주기적 재로드 실패: " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KeyStore 를 다시 로드한다. 외부(getSecretKey 등)에서 키 미스 발생 시
|
||||||
|
* 즉시 재시도용으로 직접 호출할 수도 있다.
|
||||||
|
*
|
||||||
|
* 세션 누적 방지를 위해 새 KeyStore 인스턴스를 만들지 않고,
|
||||||
|
* 기존 keyStore 객체에 다시 load() 하여 기존 PKCS11 세션을 재사용한다.
|
||||||
|
* 연속 실패가 임계치를 넘으면 Provider 자체를 재생성한다.
|
||||||
|
*/
|
||||||
|
public synchronized void reloadKeyStoreIfNeeded() throws Exception {
|
||||||
|
if (pkcs11Provider == null) {
|
||||||
|
return; // HSM 비활성화 상태
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (keyStore != null) {
|
||||||
|
keyStore.load(null, pin);
|
||||||
|
logger.warn("HsmManager] KeyStore 재로드 완료 (기존 세션 재사용).");
|
||||||
|
} else {
|
||||||
|
KeyStore ks = KeyStore.getInstance("PKCS11", pkcs11Provider);
|
||||||
|
ks.load(null, pin);
|
||||||
|
this.keyStore = ks;
|
||||||
|
logger.warn("HsmManager] KeyStore 신규 생성 완료.");
|
||||||
|
}
|
||||||
|
|
||||||
|
logKeyStore(keyStore);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("HsmManager] KeyStore 재로드 실패:" + e.getMessage(), e);
|
||||||
|
logger.warn("HsmManager] Provider 전체 재초기화를 시도합니다.");
|
||||||
|
fullReinitialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 세션 누적, 네트워크 단절 등으로 일반 재로드가 더 이상 복구되지 않을 때
|
||||||
|
* 기존 세션을 정리하고 Provider 를 완전히 새로 생성한다.
|
||||||
|
*
|
||||||
|
* 신규 Provider/KeyStore 준비가 완전히 성공한 후에만 기존 Provider 를 제거하고 교체한다.
|
||||||
|
* 재초기화 중 예외가 발생하면 기존 Provider 와 keyStore 를 그대로 유지한다
|
||||||
|
* (서비스 중단보다 마지막 정상 상태 보존을 우선).
|
||||||
|
*/
|
||||||
|
private void fullReinitialize() throws Exception {
|
||||||
|
Provider oldProvider = this.pkcs11Provider;
|
||||||
|
try {
|
||||||
|
// 1. 신규 Provider 인스턴스 생성 (Security 미등록 상태)
|
||||||
|
String configContent = EncryptionManager.getInstance()
|
||||||
|
.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG));
|
||||||
|
Provider newProvider = createProvider(configContent.trim().replace("\\n", "\n"));
|
||||||
|
|
||||||
|
// 2. 신규 Provider 로 KeyStore 로드 테스트
|
||||||
|
// KeyStore.getInstance(type, providerInstance) 는 Security 등록 없이도 동작하므로
|
||||||
|
// 여기서 실패해도 oldProvider/keyStore 는 변경되지 않은 상태를 유지함
|
||||||
|
KeyStore ks = KeyStore.getInstance("PKCS11", newProvider);
|
||||||
|
ks.load(null, pin);
|
||||||
|
|
||||||
|
// 3. 신규 연결 성공 → 기존 Provider 정리 후 교체
|
||||||
|
if (oldProvider instanceof AuthProvider) {
|
||||||
|
try {
|
||||||
|
((AuthProvider) oldProvider).logout();
|
||||||
|
} catch (Exception logoutEx) {
|
||||||
|
logger.warn("HsmManager] 기존 세션 logout 실패(무시하고 진행): " + logoutEx.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Security.removeProvider(oldProvider.getName());
|
||||||
|
Security.addProvider(newProvider);
|
||||||
|
|
||||||
|
this.pkcs11Provider = newProvider;
|
||||||
|
this.keyStore = ks;
|
||||||
|
|
||||||
|
logger.warn("HsmManager] Provider 전체 재초기화 성공.");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("HsmManager] Provider 전체 재초기화 실패. 이전 keyStore 를 그대로 유지합니다: " + e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +304,21 @@ public class HsmManager implements Lifecycle {
|
|||||||
}
|
}
|
||||||
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
||||||
|
|
||||||
|
if (reloadFuture != null) {
|
||||||
|
reloadFuture.cancel(false);
|
||||||
|
}
|
||||||
|
if (scheduler != null) {
|
||||||
|
scheduler.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
if (pkcs11Provider != null) {
|
if (pkcs11Provider != null) {
|
||||||
|
if (pkcs11Provider instanceof AuthProvider) {
|
||||||
|
try {
|
||||||
|
((AuthProvider) pkcs11Provider).logout();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("HsmManager] 종료 시 logout 실패(무시): " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
Security.removeProvider(pkcs11Provider.getName());
|
Security.removeProvider(pkcs11Provider.getName());
|
||||||
}
|
}
|
||||||
pkcs11Provider = null;
|
pkcs11Provider = null;
|
||||||
@@ -202,4 +359,20 @@ public class HsmManager implements Lifecycle {
|
|||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
return started && pkcs11Provider != null && keyStore != null;
|
return started && pkcs11Provider != null && keyStore != null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* isReady() 와 달리 실제 HSM 호출로 세션 생존 여부까지 확인하는 헬스체크.
|
||||||
|
* 모니터링/헬스체크 엔드포인트에서 사용을 권장한다.
|
||||||
|
*/
|
||||||
|
public boolean isHealthy() {
|
||||||
|
if (!isReady()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logKeyStore(keyStore);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -177,7 +177,7 @@ public class CryptoModuleManager implements Lifecycle {
|
|||||||
private KeyDerivationStrategy resolveStrategy(String fqcn) {
|
private KeyDerivationStrategy resolveStrategy(String fqcn) {
|
||||||
return strategyCache.computeIfAbsent(fqcn, key -> {
|
return strategyCache.computeIfAbsent(fqcn, key -> {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = Class.forName(key);
|
Class<?> clazz = Class.forName(key.trim());
|
||||||
return (KeyDerivationStrategy) clazz.getDeclaredConstructor().newInstance();
|
return (KeyDerivationStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException("전략 클래스 로드 실패: " + key, e);
|
throw new IllegalArgumentException("전략 클래스 로드 실패: " + key, e);
|
||||||
|
|||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
package com.eactive.eai.common.security.keyderiv.strategy;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
|
|
||||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
|
||||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
|
||||||
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
|
|
||||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HSM 마스터키 + 런타임 컨텍스트값을 연결(concatenate)한 뒤 SHA-256 해싱으로 키를 도출하는 전략.
|
|
||||||
* SHA-256 출력은 항상 32바이트(AES-256)이므로 별도 keyLength 파라미터가 불필요하다.
|
|
||||||
*
|
|
||||||
* key_deriv_params (JSON) 예시:
|
|
||||||
* {
|
|
||||||
* "hsmKeyAlias" : "MASTER_KEY_AES",
|
|
||||||
* "contextKey" : "X-Api-Group-Seq" -- runtimeContext에서 꺼낼 키 이름
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
|
||||||
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
|
|
||||||
String contextKey = required(params, PARAM_CONTEXT_KEY);
|
|
||||||
|
|
||||||
SecretKey masterKey = hsmCryptoService().getSecretKey(hsmKeyAlias);
|
|
||||||
byte[] masterKeyBytes = masterKey.getEncoded();
|
|
||||||
byte[] contextBytes = runtimeContext.getOrDefault(contextKey, "")
|
|
||||||
.getBytes(StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
byte[] combined = new byte[masterKeyBytes.length + contextBytes.length];
|
|
||||||
System.arraycopy(masterKeyBytes, 0, combined, 0, masterKeyBytes.length);
|
|
||||||
System.arraycopy(contextBytes, 0, combined, masterKeyBytes.length, contextBytes.length);
|
|
||||||
|
|
||||||
byte[] derived = MessageDigest.getInstance("SHA-256").digest(combined);
|
|
||||||
return new DerivedKey(derived, derived);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
|
||||||
String contextKey = params.getOrDefault(PARAM_CONTEXT_KEY, "");
|
|
||||||
String contextValue = runtimeContext.getOrDefault(contextKey, "");
|
|
||||||
return cryptoName + ":" + contextValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String required(Map<String, String> params, String key) {
|
|
||||||
String value = params.get(key);
|
|
||||||
if (value == null || value.trim().isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("key_deriv_params 필수값 누락: " + key);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HsmCryptoService hsmCryptoService() {
|
|
||||||
return ApplicationContextProvider.getContext().getBean(HsmCryptoService.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
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 static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonNode readTree(Object jsonData) throws JsonMappingException, JsonProcessingException {
|
||||||
|
return readTree(jsonData, OBJECT_MAPPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String writeAsString(JsonNode node) throws JsonProcessingException {
|
||||||
|
return OBJECT_MAPPER.writeValueAsString(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObjectNode createObjectNode() {
|
||||||
|
return OBJECT_MAPPER.createObjectNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ import java.lang.reflect.Method;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.rmi.RemoteException;
|
import java.rmi.RemoteException;
|
||||||
|
import java.rmi.registry.LocateRegistry;
|
||||||
|
import java.rmi.registry.Registry;
|
||||||
|
import java.rmi.server.ExportException;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ import com.eactive.eai.common.dao.Keys;
|
|||||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||||
import com.eactive.eai.common.lifecycle.LifecycleManager;
|
import com.eactive.eai.common.lifecycle.LifecycleManager;
|
||||||
|
import com.eactive.eai.common.logger.async.AsyncHttpLoggingPoolManager;
|
||||||
import com.eactive.eai.common.logger.async.AsyncLoggingPoolManager;
|
import com.eactive.eai.common.logger.async.AsyncLoggingPoolManager;
|
||||||
import com.eactive.eai.common.property.PropManager;
|
import com.eactive.eai.common.property.PropManager;
|
||||||
import com.eactive.eai.common.routing.rmi.RemoteProxy;
|
import com.eactive.eai.common.routing.rmi.RemoteProxy;
|
||||||
@@ -36,7 +40,6 @@ import com.eactive.eai.common.util.ServiceLocator;
|
|||||||
import com.eactive.eai.common.util.ServiceLocatorException;
|
import com.eactive.eai.common.util.ServiceLocatorException;
|
||||||
import com.eactive.eai.env.ConfigKeys;
|
import com.eactive.eai.env.ConfigKeys;
|
||||||
import com.eactive.eai.env.ElinkConfig;
|
import com.eactive.eai.env.ElinkConfig;
|
||||||
import com.eactive.eai.common.logger.async.AsyncHttpLoggingPoolManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* eLink FrameWork이 초기화(Deploy)될 때 실행되어야 할 작업을 정의
|
* eLink FrameWork이 초기화(Deploy)될 때 실행되어야 할 작업을 정의
|
||||||
@@ -180,14 +183,25 @@ public class AppInitializer implements InitializingBean, DisposableBean {
|
|||||||
this.shutdownWaitIntervalMs = shutdownWaitIntervalMs;
|
this.shutdownWaitIntervalMs = shutdownWaitIntervalMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Registry rmiRegistry;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void initRmiServer(int registryPort, int servicePort) throws RemoteException {
|
private void initRmiServer(int registryPort, int servicePort) throws RemoteException {
|
||||||
|
try {
|
||||||
|
rmiRegistry = LocateRegistry.createRegistry(registryPort);
|
||||||
|
} catch (ExportException e) {
|
||||||
|
// 혹시 이전 정리 실패로 이미 떠있다면 재사용 시도
|
||||||
|
Logger.getLogger(Logger.LOGGER_DEFAULT)
|
||||||
|
.warn("Registry already exists on port " + registryPort + ", reusing", e);
|
||||||
|
rmiRegistry = LocateRegistry.getRegistry(registryPort);
|
||||||
|
}
|
||||||
|
|
||||||
rmiServiceExporter.setServiceName("RemoteProxy");
|
rmiServiceExporter.setServiceName("RemoteProxy");
|
||||||
rmiServiceExporter.setService(remoteProxy);
|
rmiServiceExporter.setService(remoteProxy);
|
||||||
rmiServiceExporter.setServiceInterface(serviceInterface);
|
rmiServiceExporter.setServiceInterface(serviceInterface);
|
||||||
rmiServiceExporter.setRegistryPort(registryPort);
|
rmiServiceExporter.setRegistryPort(registryPort);
|
||||||
rmiServiceExporter.setServicePort(servicePort);
|
rmiServiceExporter.setServicePort(servicePort);
|
||||||
rmiServiceExporter.setAlwaysCreateRegistry(true);
|
rmiServiceExporter.setAlwaysCreateRegistry(false);
|
||||||
rmiServiceExporter.afterPropertiesSet();
|
rmiServiceExporter.afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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에 의해 에러 응답송신
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.springframework.http.HttpStatus;
|
|||||||
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.http.HttpStatusException;
|
import com.eactive.eai.adapter.http.HttpStatusException;
|
||||||
|
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||||
import com.eactive.eai.common.exception.ExceptionHandler;
|
import com.eactive.eai.common.exception.ExceptionHandler;
|
||||||
import com.eactive.eai.common.message.EAIMessage;
|
import com.eactive.eai.common.message.EAIMessage;
|
||||||
@@ -111,7 +112,13 @@ public abstract class RequestProcessorSupport implements Processor
|
|||||||
**/
|
**/
|
||||||
public Object execute(Object message, Properties prop) throws HttpStatusException
|
public Object execute(Object message, Properties prop) throws HttpStatusException
|
||||||
{
|
{
|
||||||
long msgRcvTm = System.currentTimeMillis();
|
String receivedTimestamp = prop.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME);
|
||||||
|
long msgRcvTm = 0L;
|
||||||
|
if(receivedTimestamp == null)
|
||||||
|
msgRcvTm = System.currentTimeMillis();
|
||||||
|
else
|
||||||
|
msgRcvTm = Long.parseLong(receivedTimestamp);
|
||||||
|
|
||||||
increaseRcvCount();
|
increaseRcvCount();
|
||||||
local.set(new Long(msgRcvTm));
|
local.set(new Long(msgRcvTm));
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
|
|||||||
|
|
||||||
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
||||||
readerMap.clear();
|
readerMap.clear();
|
||||||
readerMap = null;
|
// readerMap = null;
|
||||||
started = false;
|
started = false;
|
||||||
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
|
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
|
||||||
}
|
}
|
||||||
@@ -208,6 +208,10 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
|
|||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private void initReaderFactory(Properties config) {
|
private void initReaderFactory(Properties config) {
|
||||||
|
if (readerMap == null) {
|
||||||
|
readerMap = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
Class cl = null;
|
Class cl = null;
|
||||||
StandardReader reader = null;
|
StandardReader reader = null;
|
||||||
|
|
||||||
|
|||||||
@@ -684,11 +684,17 @@ public abstract class DefaultProcess extends Process {
|
|||||||
AdapterVO inboundAdapterVO = AdapterManager.getInstance().getAdapterVO(inboudnAdapterGroupName, inboudnAdapterName);
|
AdapterVO inboundAdapterVO = AdapterManager.getInstance().getAdapterVO(inboudnAdapterGroupName, inboudnAdapterName);
|
||||||
String errorResponseHandlerClass = AdapterPropManager.getInstance().getProperty(inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
|
String errorResponseHandlerClass = AdapterPropManager.getInstance().getProperty(inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
|
||||||
if(StringUtils.isBlank(errorResponseHandlerClass)) {
|
if(StringUtils.isBlank(errorResponseHandlerClass)) {
|
||||||
String errorCode = mapper.getErrorCode(resStandardMessage);
|
if ((com.eactive.eai.adapter.Keys.IF_STANDARD.equals(this.adptrMsgPtrnCd)
|
||||||
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
|
||com.eactive.eai.adapter.Keys.IF_SUBSTANDARD.equals(this.adptrMsgPtrnCd))) {
|
||||||
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("MSG.MAIN_MSG.outp_msg_desc"));
|
String errorCode = mapper.getErrorCode(resStandardMessage);
|
||||||
this.resEaiMsg.setRspErr("RECEAIINA001", String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
|
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
|
||||||
return;
|
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;
|
||||||
|
} else {
|
||||||
|
this.resEaiMsg.setRspErr("RECEAIINA001", "비표준 오류 응답 수신");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(inboudnAdapterName+", Handle Error Message Class-"+errorResponseHandlerClass);
|
logger.info(inboudnAdapterName+", Handle Error Message Class-"+errorResponseHandlerClass);
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,33 @@ public class HexaConverter {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String binToHex(byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder(bytes.length * 2);
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hexToBin(String hexStr) {
|
||||||
|
String lookup = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
int len = hexStr.length();
|
||||||
|
byte[] bArray = new byte[len / 2];
|
||||||
|
|
||||||
|
for (int i = 0; i < bArray.length; i++) {
|
||||||
|
char highChar = Character.toUpperCase(hexStr.charAt(i * 2));
|
||||||
|
char lowChar = Character.toUpperCase(hexStr.charAt(i * 2 + 1));
|
||||||
|
|
||||||
|
int high = lookup.indexOf(highChar);
|
||||||
|
int low = lookup.indexOf(lowChar);
|
||||||
|
|
||||||
|
bArray[i] = (byte) ((high << 4) | low);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bArray;
|
||||||
|
}
|
||||||
|
|
||||||
// public static void main(String[] argv) {
|
// public static void main(String[] argv) {
|
||||||
// String str = "TEST\nÇѱÛ";
|
// String str = "TEST\nÇѱÛ";
|
||||||
// String hexStr = HexaConverter.bytesToHexa(str.getBytes());
|
// String hexStr = HexaConverter.bytesToHexa(str.getBytes());
|
||||||
|
|||||||
@@ -145,12 +145,12 @@ public class JsonPathUtil {
|
|||||||
* @param mergeJson 병합할 JSON 문자열
|
* @param mergeJson 병합할 JSON 문자열
|
||||||
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
|
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
|
||||||
*/
|
*/
|
||||||
public static String mergeAtRoot(JsonNode bodyNode, String removeFromPath, String mergeJson) {
|
public static JsonNode mergeAtRoot(JsonNode bodyNode, String removeFromPath, String mergeJson) {
|
||||||
try {
|
try {
|
||||||
JsonNode mergeNode = objectMapper.readTree(mergeJson);
|
JsonNode mergeNode = objectMapper.readTree(mergeJson);
|
||||||
|
|
||||||
if (!bodyNode.isObject() || !mergeNode.isObject()) {
|
if (!bodyNode.isObject() || !mergeNode.isObject()) {
|
||||||
return mergeJson;
|
return bodyNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectNode result = (ObjectNode) bodyNode.deepCopy();
|
ObjectNode result = (ObjectNode) bodyNode.deepCopy();
|
||||||
@@ -169,9 +169,10 @@ public class JsonPathUtil {
|
|||||||
|
|
||||||
// mergeJson 필드를 body에 병합 (기존 필드 덮어쓰기)
|
// mergeJson 필드를 body에 병합 (기존 필드 덮어쓰기)
|
||||||
result.setAll((ObjectNode) mergeNode);
|
result.setAll((ObjectNode) mergeNode);
|
||||||
return objectMapper.writeValueAsString(result);
|
// return objectMapper.writeValueAsString(result);
|
||||||
|
return result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return mergeJson;
|
return bodyNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@ public class JsonPathUtil {
|
|||||||
*/
|
*/
|
||||||
public static String mergeAtRoot(String body, String removeFromPath, String mergeJson) {
|
public static String mergeAtRoot(String body, String removeFromPath, String mergeJson) {
|
||||||
try {
|
try {
|
||||||
return mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson);
|
return objectMapper.writeValueAsString(mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return mergeJson;
|
return mergeJson;
|
||||||
}
|
}
|
||||||
|
|||||||
-466
@@ -1,466 +0,0 @@
|
|||||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
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.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;
|
|
||||||
import com.eactive.eai.common.security.CryptoModuleConfigVO;
|
|
||||||
import com.eactive.eai.common.security.CryptoModuleManager;
|
|
||||||
import com.eactive.eai.common.security.CryptoModuleService;
|
|
||||||
import com.eactive.eai.common.security.keyderiv.strategy.HsmContextSha256KeyDerivationStrategy;
|
|
||||||
import com.eactive.eai.common.security.keyderiv.strategy.HsmKeyDerivationStrategy;
|
|
||||||
import com.eactive.eai.common.security.loader.CryptoModuleConfigLoader;
|
|
||||||
import com.eactive.eai.common.security.mapper.CryptoModuleConfigMapper;
|
|
||||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
|
||||||
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CryptoFilter HSM 연동 통합 테스트 (SoftHSM2 / SunPKCS11)
|
|
||||||
*
|
|
||||||
* SoftHSM2에서 MASTER_KEY를 조회하여 키를 도출한 뒤
|
|
||||||
* InCryptoFilter 의 암복호화 전체 흐름을 검증한다.
|
|
||||||
*
|
|
||||||
* 사전 조건:
|
|
||||||
* C:\SoftHSM2 에 SoftHSM2 설치 (미설치 시 자동 건너뜀)
|
|
||||||
* MASTER_KEY alias는 없으면 자동 생성됨
|
|
||||||
*
|
|
||||||
* 검증 전략:
|
|
||||||
* HSM → 키 도출 → CryptoModuleExtension → InCryptoFilter doPostFilter(암호화) → doPreFilter(복호화) → 원문 일치
|
|
||||||
*/
|
|
||||||
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
|
||||||
public class CryptoFilterHsmIntegrationTest {
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 상수
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private static final String SOFTHSM2_DLL = "C:/SoftHSM2/lib/softhsm2-x64.dll";
|
|
||||||
private static final String SOFTHSM2_UTIL = "C:/SoftHSM2/bin/softhsm2-util.exe";
|
|
||||||
private static final String TOKEN_LABEL = "eapim-test";
|
|
||||||
private static final String PIN = "1234";
|
|
||||||
private static final String MASTER_KEY_ALIAS = "MASTER_KEY";
|
|
||||||
|
|
||||||
/** HsmKeyDerivationStrategy: HSM 마스터키를 파생 없이 직접 사용 */
|
|
||||||
private static final String MOD_HSM_GCM = "HSM_GCM";
|
|
||||||
/** HsmContextSha256KeyDerivationStrategy: HSM 마스터키 + 컨텍스트값 SHA-256 파생 */
|
|
||||||
private static final String MOD_CTX_SHA_GCM = "CTX_SHA256_GCM";
|
|
||||||
|
|
||||||
private static final String CTX_KEY = "X-Api-Group-Seq";
|
|
||||||
private static final String CTX_VAL_A = "GROUP-A";
|
|
||||||
private static final String CTX_VAL_B = "GROUP-B";
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 정적 필드
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private static GenericApplicationContext springCtx;
|
|
||||||
private static HsmManager hsmManager;
|
|
||||||
private static File tempCfgFile;
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 내부 클래스: runtimeContext를 고정값으로 반환하는 필터
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static class ContextAwareCryptoFilter extends InCryptoFilter {
|
|
||||||
private final String key;
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
ContextAwareCryptoFilter(String key, String value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
|
|
||||||
Map<String, String> ctx = new HashMap<>();
|
|
||||||
ctx.put(key, value);
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 인스턴스 필드
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
private HttpServletRequest mockReq;
|
|
||||||
private HttpServletResponse mockRes;
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 전체 설정 / 해제
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
static void setUpAll() throws Exception {
|
|
||||||
assumeTrue(new File(SOFTHSM2_DLL).exists(),
|
|
||||||
"SoftHSM2 미설치 (C:/SoftHSM2) - 테스트 건너뜀");
|
|
||||||
System.setProperty("SOFTHSM2_CONF", "C:\\SoftHSM2\\etc\\softhsm2.conf");
|
|
||||||
|
|
||||||
// 1. 토큰 초기화
|
|
||||||
ensureTokenInitialized();
|
|
||||||
|
|
||||||
// 2. PKCS11 설정 문자열
|
|
||||||
// attributes(*,...): generate/import 모두 CKA_SENSITIVE=false → getEncoded() 사용 가능
|
|
||||||
String cfgContent = "name = SoftHSM\n"
|
|
||||||
+ "library = " + SOFTHSM2_DLL + "\n"
|
|
||||||
+ "slotListIndex = 0\n"
|
|
||||||
+ "attributes(*, CKO_SECRET_KEY, CKK_AES) = {\n"
|
|
||||||
+ " CKA_SENSITIVE = false\n"
|
|
||||||
+ " CKA_EXTRACTABLE = true\n"
|
|
||||||
+ "}\n";
|
|
||||||
tempCfgFile = File.createTempFile("pkcs11-filter-it-", ".cfg");
|
|
||||||
Files.write(tempCfgFile.toPath(), cfgContent.getBytes("UTF-8"));
|
|
||||||
|
|
||||||
// 3. Mock PropManager (DB 없이 HSM 설정값 제공)
|
|
||||||
PropManager mockPropManager = mock(PropManager.class);
|
|
||||||
when(mockPropManager.getProperty("HSM", "PKCS11_CONFIG")).thenReturn(cfgContent);
|
|
||||||
when(mockPropManager.getProperty("HSM", "PIN")).thenReturn(PIN);
|
|
||||||
when(mockPropManager.getProperty(eq("HSM"), eq("CACHE_RELOAD_YN"), anyString())).thenReturn("N");
|
|
||||||
|
|
||||||
// 4. HsmManager (private 생성자 → 리플렉션)
|
|
||||||
Constructor<HsmManager> hsmCtor = HsmManager.class.getDeclaredConstructor();
|
|
||||||
hsmCtor.setAccessible(true);
|
|
||||||
hsmManager = hsmCtor.newInstance();
|
|
||||||
|
|
||||||
// 5. CryptoModuleManager (private 생성자 → 리플렉션)
|
|
||||||
CryptoModuleConfigLoader mockLoader = mock(CryptoModuleConfigLoader.class);
|
|
||||||
when(mockLoader.findAll()).thenReturn(buildModuleConfigs());
|
|
||||||
|
|
||||||
Constructor<CryptoModuleManager> managerCtor = CryptoModuleManager.class.getDeclaredConstructor();
|
|
||||||
managerCtor.setAccessible(true);
|
|
||||||
CryptoModuleManager manager = managerCtor.newInstance();
|
|
||||||
|
|
||||||
CryptoModuleService cryptoService = new CryptoModuleService();
|
|
||||||
HsmCryptoService hsmCryptoService = new HsmCryptoService();
|
|
||||||
|
|
||||||
// 6. Spring ApplicationContext 구성
|
|
||||||
springCtx = new GenericApplicationContext();
|
|
||||||
springCtx.getBeanFactory().registerSingleton("propManager", mockPropManager);
|
|
||||||
springCtx.getBeanFactory().registerSingleton("hsmManager", hsmManager);
|
|
||||||
springCtx.getBeanFactory().registerSingleton("hsmCryptoService", hsmCryptoService);
|
|
||||||
springCtx.getBeanFactory().registerSingleton("cryptoModuleConfigLoader", mockLoader);
|
|
||||||
springCtx.getBeanFactory().registerSingleton("cryptoModuleManager", manager);
|
|
||||||
springCtx.getBeanFactory().registerSingleton("cryptoModuleService", cryptoService);
|
|
||||||
springCtx.getBeanFactory().registerSingleton("cryptoModuleConfigMapper", testMapper());
|
|
||||||
springCtx.registerBeanDefinition("applicationContextProvider",
|
|
||||||
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
|
||||||
.getBeanDefinition());
|
|
||||||
springCtx.refresh();
|
|
||||||
|
|
||||||
// 7. HsmManager 시작 (SunPKCS11 Provider 초기화 + KeyStore 오픈)
|
|
||||||
hsmManager.start();
|
|
||||||
assertTrue(hsmManager.isReady(), "HsmManager 초기화 실패");
|
|
||||||
System.out.println("[HSM] Provider: " + hsmManager.getPkcs11Provider().getName());
|
|
||||||
|
|
||||||
// 8. MASTER_KEY 등록 (없으면 자동 생성)
|
|
||||||
ensureMasterKey();
|
|
||||||
|
|
||||||
// 9. CryptoModuleManager 시작 (모듈 설정 로드)
|
|
||||||
manager.start();
|
|
||||||
System.out.println("[Crypto] 모듈 로드 완료");
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
static void tearDownAll() throws Exception {
|
|
||||||
if (hsmManager != null && hsmManager.isStarted()) hsmManager.stop();
|
|
||||||
if (springCtx != null) springCtx.close();
|
|
||||||
if (tempCfgFile != null) tempCfgFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
mockReq = mock(HttpServletRequest.class);
|
|
||||||
mockRes = mock(HttpServletResponse.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// 1. HsmKeyDerivationStrategy — HSM 마스터키 직접 사용 (InCryptoFilter)
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("1-1. FIELD/GCM HsmKey — doPostFilter 암호화 → doPreFilter 복호화 라운드트립")
|
|
||||||
void testHsmGcm_field_roundTrip() throws Exception {
|
|
||||||
InCryptoFilter filter = new InCryptoFilter();
|
|
||||||
String original = "{\"acno\":\"1234567890\",\"amount\":\"100000\"}";
|
|
||||||
|
|
||||||
Properties prop = new Properties();
|
|
||||||
prop.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_HSM_GCM);
|
|
||||||
// SCOPE 기본값 FIELD, 경로 기본값 사용
|
|
||||||
|
|
||||||
// 암호화: body 전체 → /encrypted_data 필드
|
|
||||||
String encrypted = (String) filter.doPostFilter("G", "A", original, prop, mockReq, mockRes);
|
|
||||||
System.out.println("[1-1] encrypted: " + encrypted);
|
|
||||||
assertTrue(encrypted.contains("encrypted_data"), "/encrypted_data 필드가 있어야 한다");
|
|
||||||
|
|
||||||
// 복호화: /encrypted_data 필드 → body 전체 교체
|
|
||||||
String decrypted = (String) filter.doPreFilter("G", "A", encrypted, prop, mockReq, mockRes);
|
|
||||||
System.out.println("[1-1] decrypted: " + decrypted);
|
|
||||||
assertEquals(original, decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("1-2. BODY/GCM HsmKey — doPostFilter 암호화 → doPreFilter 복호화 라운드트립")
|
|
||||||
void testHsmGcm_body_roundTrip() throws Exception {
|
|
||||||
InCryptoFilter filter = new InCryptoFilter();
|
|
||||||
String original = "{\"acno\":\"1234567890\",\"amount\":\"100000\"}";
|
|
||||||
|
|
||||||
Properties prop = new Properties();
|
|
||||||
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);
|
|
||||||
System.out.println("[1-2] encrypted(Base64): " + encrypted);
|
|
||||||
assertNotEquals(original, encrypted, "암호문은 평문과 달라야 한다");
|
|
||||||
|
|
||||||
// 복호화: Base64 암호문 → 원문
|
|
||||||
String decrypted = (String) filter.doPreFilter("G", "A", encrypted, prop, mockReq, mockRes);
|
|
||||||
System.out.println("[1-2] decrypted: " + decrypted);
|
|
||||||
assertEquals(original, decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("1-3. FIELD/GCM HsmKey + AAD 헤더 — 동일 요청 ID로 암복호화 성공")
|
|
||||||
void testHsmGcm_field_withAad_success() throws Exception {
|
|
||||||
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(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);
|
|
||||||
System.out.println("[1-3] decrypted: " + decrypted);
|
|
||||||
assertEquals(original, decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("1-4. FIELD/GCM HsmKey + AAD 헤더 — 다른 요청 ID로 복호화 시 GCM 인증 실패")
|
|
||||||
void testHsmGcm_field_withAad_wrongAad_fails() throws Exception {
|
|
||||||
InCryptoFilter filter = new InCryptoFilter();
|
|
||||||
String original = "{\"amount\":\"500000\"}";
|
|
||||||
|
|
||||||
// 암호화 시 요청 ID
|
|
||||||
HttpServletRequest encReq = mock(HttpServletRequest.class);
|
|
||||||
when(encReq.getHeader("X-Request-Id")).thenReturn("REQ-CORRECT");
|
|
||||||
|
|
||||||
// 복호화 시 다른 요청 ID
|
|
||||||
HttpServletRequest decReq = mock(HttpServletRequest.class);
|
|
||||||
when(decReq.getHeader("X-Request-Id")).thenReturn("REQ-WRONG");
|
|
||||||
|
|
||||||
Properties prop = new Properties();
|
|
||||||
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);
|
|
||||||
|
|
||||||
assertThrows(Exception.class,
|
|
||||||
() -> filter.doPreFilter("G", "A", encrypted, prop, decReq, mockRes),
|
|
||||||
"잘못된 AAD로 GCM 복호화 시 예외가 발생해야 한다");
|
|
||||||
System.out.println("[1-4] 다른 AAD 복호화 예외 확인 완료");
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// 2. HsmContextSha256KeyDerivationStrategy — 컨텍스트 기반 SHA-256 파생 키
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("2-1. FIELD/GCM ContextSha256 — 동일 컨텍스트로 암복호화 라운드트립")
|
|
||||||
void testCtxSha256Gcm_field_roundTrip() throws Exception {
|
|
||||||
InCryptoFilter filter = new ContextAwareCryptoFilter(CTX_KEY, CTX_VAL_A);
|
|
||||||
String original = "{\"accNo\":\"0011234567890\",\"amount\":\"50000\"}";
|
|
||||||
|
|
||||||
Properties prop = new Properties();
|
|
||||||
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);
|
|
||||||
assertTrue(encrypted.contains("encrypted_data"));
|
|
||||||
|
|
||||||
String decrypted = (String) filter.doPreFilter("G", "A", encrypted, prop, mockReq, mockRes);
|
|
||||||
System.out.println("[2-1] decrypted: " + decrypted);
|
|
||||||
assertEquals(original, decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("2-2. FIELD/GCM ContextSha256 — 암호화와 다른 컨텍스트로 복호화 시 GCM 인증 실패")
|
|
||||||
void testCtxSha256Gcm_differentContext_throwsException() throws Exception {
|
|
||||||
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(AbstractCryptoFilter.PROP_MODULE_NAME, MOD_CTX_SHA_GCM);
|
|
||||||
|
|
||||||
String encrypted = (String) encFilter.doPostFilter("G", "A", original, prop, mockReq, mockRes);
|
|
||||||
|
|
||||||
assertThrows(Exception.class,
|
|
||||||
() -> decFilter.doPreFilter("G", "A", encrypted, prop, mockReq, mockRes),
|
|
||||||
"다른 컨텍스트 키로 파생된 키는 복호화에 실패해야 한다");
|
|
||||||
System.out.println("[2-2] 다른 컨텍스트 복호화 예외 확인 완료");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("2-3. FIELD/GCM ContextSha256 — 컨텍스트별 독립 암복호화 (GROUP-A, GROUP-B 각각 성공)")
|
|
||||||
void testCtxSha256Gcm_perGroupRoundTrip() throws Exception {
|
|
||||||
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(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);
|
|
||||||
|
|
||||||
assertEquals(plainA, (String) filterA.doPreFilter("G", "A", encA, prop, mockReq, mockRes));
|
|
||||||
assertEquals(plainB, (String) filterB.doPreFilter("G", "B", encB, prop, mockReq, mockRes));
|
|
||||||
System.out.println("[2-3] GROUP-A, GROUP-B 각각 독립 암복호화 성공");
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// 헬퍼
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
private static List<CryptoModuleConfig> buildModuleConfigs() {
|
|
||||||
return Arrays.asList(
|
|
||||||
buildDynamic(MOD_HSM_GCM, "AES", "GCM", "NoPadding",
|
|
||||||
HsmKeyDerivationStrategy.class.getName(),
|
|
||||||
"{\"hsmKeyAlias\":\"" + MASTER_KEY_ALIAS + "\"}"),
|
|
||||||
buildDynamic(MOD_CTX_SHA_GCM, "AES", "GCM", "NoPadding",
|
|
||||||
HsmContextSha256KeyDerivationStrategy.class.getName(),
|
|
||||||
"{\"hsmKeyAlias\":\"" + MASTER_KEY_ALIAS + "\",\"contextKey\":\"" + CTX_KEY + "\"}")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CryptoModuleConfig buildDynamic(String name, String alg, String mode, String padding,
|
|
||||||
String strategy, String params) {
|
|
||||||
CryptoModuleConfig c = new CryptoModuleConfig();
|
|
||||||
c.setCryptoId(UUID.randomUUID().toString());
|
|
||||||
c.setCryptoName(name);
|
|
||||||
c.setAlgType(alg);
|
|
||||||
c.setCipherMode(mode);
|
|
||||||
c.setPadding(padding);
|
|
||||||
c.setKeySourceType("DYNAMIC");
|
|
||||||
c.setKeyDerivStrategy(strategy);
|
|
||||||
c.setKeyDerivParams(params);
|
|
||||||
c.setCacheYn("Y");
|
|
||||||
c.setCacheTtlSec(60);
|
|
||||||
c.setUseYn("Y");
|
|
||||||
// ivHex = null: GCM에서 IV를 AAD 대체값으로 사용하지 않도록 의도적으로 미설정
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureMasterKey() throws Exception {
|
|
||||||
java.security.KeyStore ks = hsmManager.getKeyStore();
|
|
||||||
|
|
||||||
// 이전 실행 키 삭제 (sensitive 여부 무관하게 항상 고정키로 재등록)
|
|
||||||
if (ks.containsAlias(MASTER_KEY_ALIAS)) {
|
|
||||||
ks.deleteEntry(MASTER_KEY_ALIAS);
|
|
||||||
System.out.println("[HSM] 기존 MASTER_KEY 삭제");
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoModuleServiceTest.KEY_128 과 동일한 고정 키 → 두 테스트 간 암호문 비교 가능
|
|
||||||
// attributes(*, CKO_SECRET_KEY, CKK_AES) 지시어로 임포트 시 CKA_SENSITIVE=false 적용
|
|
||||||
byte[] keyBytes = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
|
|
||||||
SecretKey fixedKey = new SecretKeySpec(keyBytes, "AES");
|
|
||||||
ks.setKeyEntry(MASTER_KEY_ALIAS, fixedKey, null, null);
|
|
||||||
|
|
||||||
// 임포트 후 getEncoded() 검증
|
|
||||||
SecretKey stored = (SecretKey) ks.getKey(MASTER_KEY_ALIAS, null);
|
|
||||||
byte[] encoded = stored.getEncoded();
|
|
||||||
System.out.println("[HSM] MASTER_KEY 등록 완료: alias=" + MASTER_KEY_ALIAS
|
|
||||||
+ " / getEncoded()=" + (encoded != null ? encoded.length + "bytes, " + new String(encoded) : "null (추출 불가!)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureTokenInitialized() throws Exception {
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(
|
|
||||||
SOFTHSM2_UTIL, "--init-token", "--free",
|
|
||||||
"--label", TOKEN_LABEL, "--pin", PIN, "--so-pin", PIN);
|
|
||||||
pb.redirectErrorStream(true);
|
|
||||||
Process p = pb.start();
|
|
||||||
String out = readOutput(p);
|
|
||||||
p.waitFor(10, TimeUnit.SECONDS);
|
|
||||||
System.out.println("[SoftHSM2] init-token: " + out.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CryptoModuleConfigMapper testMapper() {
|
|
||||||
return new CryptoModuleConfigMapper() {
|
|
||||||
@Override
|
|
||||||
public CryptoModuleConfigVO toVo(CryptoModuleConfig e) {
|
|
||||||
CryptoModuleConfigVO vo = new CryptoModuleConfigVO();
|
|
||||||
vo.setCryptoId(e.getCryptoId());
|
|
||||||
vo.setCryptoName(e.getCryptoName());
|
|
||||||
vo.setCryptoDesc(e.getCryptoDesc());
|
|
||||||
vo.setAlgType(e.getAlgType());
|
|
||||||
vo.setCipherMode(e.getCipherMode());
|
|
||||||
vo.setPadding(e.getPadding());
|
|
||||||
vo.setIvHex(e.getIvHex());
|
|
||||||
vo.setKeySourceType(e.getKeySourceType());
|
|
||||||
vo.setEncKeyHex(e.getEncKeyHex());
|
|
||||||
vo.setDecKeyHex(e.getDecKeyHex());
|
|
||||||
vo.setKeyDerivStrategy(e.getKeyDerivStrategy());
|
|
||||||
vo.setKeyDerivParams(e.getKeyDerivParams());
|
|
||||||
vo.setCacheYn(e.getCacheYn());
|
|
||||||
vo.setCacheTtlSec(e.getCacheTtlSec());
|
|
||||||
vo.setUseYn(e.getUseYn());
|
|
||||||
return vo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CryptoModuleConfig toEntity(CryptoModuleConfigVO vo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String readOutput(Process p) throws Exception {
|
|
||||||
InputStream is = p.getInputStream();
|
|
||||||
byte[] buf = new byte[4096];
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int len;
|
|
||||||
while ((len = is.read(buf)) != -1) {
|
|
||||||
sb.append(new String(buf, 0, len, "UTF-8"));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,196 @@
|
|||||||
|
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.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
public class HsmAddKeyTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
String config = "name = SoftHSM\r\n"
|
||||||
|
+ "library = C:/SoftHSM2/lib/softhsm2-x64.dll\r\n"
|
||||||
|
+ "slotListIndex = 0\r\n"
|
||||||
|
+ "attributes(*, CKO_SECRET_KEY, CKK_AES) = {\r\n"
|
||||||
|
+ " CKA_TOKEN = true\r\n"
|
||||||
|
+ " CKA_SENSITIVE = false\r\n"
|
||||||
|
+ " CKA_EXTRACTABLE = true\r\n"
|
||||||
|
+ " CKA_ENCRYPT = true\r\n"
|
||||||
|
+ " CKA_DECRYPT = true\r\n"
|
||||||
|
+ "}\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);
|
||||||
|
Security.addProvider(pkcs11Provider);
|
||||||
|
String pin = "1234";
|
||||||
|
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
|
||||||
|
|
||||||
|
char[] pinChars = (pin != null) ? pin.toCharArray() : null;
|
||||||
|
keyStore.load(null, pinChars);
|
||||||
|
|
||||||
|
// addKey("20250916_openapi_erpbank_t", "55187afc5d36a2e3b71f3bf992947389964ac5ea0bc8ebe2297265134fa34cb9",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
// addKey("20240401_openapi_kakaobank_t", "f009328cb94f5c866ff730735ce8031b43ca84f9266ced641b4046c6a5c21038",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
// addKey("20250620_openapi_coocon_t", "3e7a7673392c424e58265c7b6f2e596f7a59542b277472704c3d5d6c70226673",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
// addKey("20211221_openapi_kakaopay_t", "23667b665037785c3c312a414b6c566d2f3832476b254646727c4d703d484534",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
//
|
||||||
|
// addKey("20250916_openapi_erpbank_t_0", "55187afc5d36a2e3b71f3bf992947389964ac5ea0bc8ebe2297265134fa34cb9",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
// addKey("20240401_openapi_kakaobank_t_0", "f009328cb94f5c866ff730735ce8031b43ca84f9266ced641b4046c6a5c21038",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
// addKey("20250620_openapi_coocon_t_0", "3e7a7673392c424e58265c7b6f2e596f7a59542b277472704c3d5d6c70226673",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
// addKey("20211221_openapi_kakaopay_t_0", "23667b665037785c3c312a414b6c566d2f3832476b254646727c4d703d484534",
|
||||||
|
// keyStore, pkcs11Provider);
|
||||||
|
|
||||||
|
addKey("20260518_openapi_npayloan_t", "7f7f6608a0c2f28d9a7e53f81b265f12b46f893ffcecc253bdc0561338e10208",
|
||||||
|
keyStore, pkcs11Provider);
|
||||||
|
addKey("20260518_openapi_npayloan_iv_t", "b797008af20f0e1eda6c71d14bec5bb7",
|
||||||
|
keyStore, pkcs11Provider);
|
||||||
|
addKey("20241022_openapi_toss_t", "3051557a6c786475536c4f532f4c6b6b436a4c312f42434947764d336b66306e",
|
||||||
|
keyStore, pkcs11Provider);
|
||||||
|
addKey("20250307_openapi_tossbank_t", "48b8465313d6fc529a9f16c3347cde55a663efaf9ade41429c1cb484398bcf93",
|
||||||
|
keyStore, pkcs11Provider);
|
||||||
|
addKey("20250307_openapi_tossbank_iv_t", "2bdf93b1f872962d2aa2f890e179e3e0",
|
||||||
|
keyStore, pkcs11Provider);
|
||||||
|
addKey("20240524_openapi_banksalad_t", "466166733957513862616a646e55686a353759554c784e4a6463783279767035",
|
||||||
|
keyStore, pkcs11Provider);
|
||||||
|
|
||||||
|
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 + ", secretKey:" + secretKey);
|
||||||
|
} else {
|
||||||
|
System.out
|
||||||
|
.println("HsmManager] HSM - secretKey null - alias:" + alias + ", secretKey:" + secretKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("HsmManager] HSM 키 목록: [" + aliasList + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addKey(String keyAlias, String keyValue, KeyStore keyStore, Provider pkcs11Provider)
|
||||||
|
throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
byte[] keyBytes = hexToBytes(keyValue);
|
||||||
|
|
||||||
|
SecretKeySpec spec = new SecretKeySpec(keyBytes, "AES");
|
||||||
|
SecretKeyFactory factory = SecretKeyFactory.getInstance("AES", pkcs11Provider);
|
||||||
|
SecretKey fixedKey = (SecretKey) factory.generateSecret(spec);
|
||||||
|
|
||||||
|
// SecretKey fixedKey = new SecretKeySpec(keyBytes, "AES");
|
||||||
|
|
||||||
|
keyStore.setKeyEntry(keyAlias, fixedKey, null, null);
|
||||||
|
|
||||||
|
// // 2. wrapping key 생성 (최초 1회만 — 이후엔 재사용)
|
||||||
|
// KeyGenerator wrapKeyGen = KeyGenerator.getInstance("AES", pkcs11Provider);
|
||||||
|
// wrapKeyGen.init(256);
|
||||||
|
// SecretKey wrappingKey = wrapKeyGen.generateKey();
|
||||||
|
//
|
||||||
|
// // 3. 테스트 환경 key 값 지정
|
||||||
|
// byte[] testKeyBytes = hexToBytes(keyValue);
|
||||||
|
// SecretKey targetKeySpec = new SecretKeySpec(testKeyBytes, "AES");
|
||||||
|
//
|
||||||
|
// // 4. wrap
|
||||||
|
// Cipher wrapCipher = Cipher.getInstance("AESWRAP", pkcs11Provider);
|
||||||
|
// wrapCipher.init(Cipher.WRAP_MODE, wrappingKey);
|
||||||
|
// byte[] wrapped = wrapCipher.wrap(targetKeySpec);
|
||||||
|
//
|
||||||
|
// // 5. unwrap → SoftHSM2 내부에 실제 등록
|
||||||
|
// Cipher unwrapCipher = Cipher.getInstance("AESWRAP", pkcs11Provider);
|
||||||
|
// unwrapCipher.init(Cipher.UNWRAP_MODE, wrappingKey);
|
||||||
|
// SecretKey hsmKey = (SecretKey) unwrapCipher.unwrap(wrapped, "AES", Cipher.SECRET_KEY
|
||||||
|
//
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// // 6. 평문 byte 배열 즉시 제거
|
||||||
|
// Arrays.fill(testKeyBytes, (byte) 0);
|
||||||
|
//
|
||||||
|
// System.out.println("Key 등록 완료: " + hsmKey.getAlgorithm() + " / " + hsmKey.getFormat());
|
||||||
|
//
|
||||||
|
// // 7. 등록 확인 — alias 지정해서 KeyStore에 올리고 싶으면:
|
||||||
|
// keyStore.setKeyEntry(keyAlias, hsmKey, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] hexToBytes(String hex) {
|
||||||
|
int len = hex.length();
|
||||||
|
byte[] data = new byte[len / 2];
|
||||||
|
for (int i = 0; i < len; i += 2) {
|
||||||
|
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String bytesToHex(byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
+179
-179
@@ -1,179 +1,179 @@
|
|||||||
package com.eactive.eai.common.security.keyderiv;
|
//package com.eactive.eai.common.security.keyderiv;
|
||||||
|
//
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
//import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
//import static org.mockito.Mockito.*;
|
||||||
|
//
|
||||||
import java.nio.charset.StandardCharsets;
|
//import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
//import java.security.MessageDigest;
|
||||||
import java.util.HashMap;
|
//import java.util.HashMap;
|
||||||
import java.util.Map;
|
//import java.util.Map;
|
||||||
|
//
|
||||||
import javax.crypto.SecretKey;
|
//import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
//import javax.crypto.spec.SecretKeySpec;
|
||||||
|
//
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
//import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
//import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
//import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
//import org.junit.jupiter.api.MethodOrderer;
|
||||||
import org.junit.jupiter.api.Test;
|
//import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
//import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
//import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
import org.springframework.context.support.GenericApplicationContext;
|
//import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
//
|
||||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
//import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
//import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||||
import com.eactive.eai.common.security.keyderiv.strategy.HsmContextSha256KeyDerivationStrategy;
|
//import com.eactive.eai.common.security.keyderiv.strategy.HsmContextSha256KeyDerivationStrategy;
|
||||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
//import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
* HsmContextSha256KeyDerivationStrategy 단위 테스트
|
// * HsmContextSha256KeyDerivationStrategy 단위 테스트
|
||||||
*/
|
// */
|
||||||
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
//@TestMethodOrder(MethodOrderer.DisplayName.class)
|
||||||
class HsmContextSha256KeyDerivationStrategyTest {
|
//class HsmContextSha256KeyDerivationStrategyTest {
|
||||||
|
//
|
||||||
private static final String HSM_KEY_ALIAS = "MASTER_KEY_AES";
|
// private static final String HSM_KEY_ALIAS = "MASTER_KEY_AES";
|
||||||
private static final byte[] MASTER_KEY = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
|
// private static final byte[] MASTER_KEY = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
|
||||||
|
//
|
||||||
private static GenericApplicationContext ctx;
|
// private static GenericApplicationContext ctx;
|
||||||
private static HsmCryptoService mockHsmCryptoService;
|
// private static HsmCryptoService mockHsmCryptoService;
|
||||||
private static HsmContextSha256KeyDerivationStrategy strategy;
|
// private static HsmContextSha256KeyDerivationStrategy strategy;
|
||||||
|
//
|
||||||
private Map<String, String> params;
|
// private Map<String, String> params;
|
||||||
private Map<String, String> runtimeContext;
|
// private Map<String, String> runtimeContext;
|
||||||
|
//
|
||||||
@BeforeAll
|
// @BeforeAll
|
||||||
static void setUpClass() throws Exception {
|
// static void setUpClass() throws Exception {
|
||||||
mockHsmCryptoService = mock(HsmCryptoService.class);
|
// mockHsmCryptoService = mock(HsmCryptoService.class);
|
||||||
SecretKey mockSecretKey = new SecretKeySpec(MASTER_KEY, "AES");
|
// SecretKey mockSecretKey = new SecretKeySpec(MASTER_KEY, "AES");
|
||||||
when(mockHsmCryptoService.getSecretKey(HSM_KEY_ALIAS)).thenReturn(mockSecretKey);
|
// when(mockHsmCryptoService.getSecretKey(HSM_KEY_ALIAS)).thenReturn(mockSecretKey);
|
||||||
|
//
|
||||||
ctx = new GenericApplicationContext();
|
// ctx = new GenericApplicationContext();
|
||||||
ctx.getBeanFactory().registerSingleton("hsmCryptoService", mockHsmCryptoService);
|
// ctx.getBeanFactory().registerSingleton("hsmCryptoService", mockHsmCryptoService);
|
||||||
ctx.registerBeanDefinition("applicationContextProvider",
|
// ctx.registerBeanDefinition("applicationContextProvider",
|
||||||
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
// BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
||||||
.getBeanDefinition());
|
// .getBeanDefinition());
|
||||||
ctx.refresh();
|
// ctx.refresh();
|
||||||
|
//
|
||||||
strategy = new HsmContextSha256KeyDerivationStrategy();
|
// strategy = new HsmContextSha256KeyDerivationStrategy();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@BeforeEach
|
// @BeforeEach
|
||||||
void setUpParams() {
|
// void setUpParams() {
|
||||||
params = new HashMap<>();
|
// params = new HashMap<>();
|
||||||
params.put("hsmKeyAlias", HSM_KEY_ALIAS);
|
// params.put("hsmKeyAlias", HSM_KEY_ALIAS);
|
||||||
params.put("contextKey", "X-Api-Group-Seq");
|
// params.put("contextKey", "X-Api-Group-Seq");
|
||||||
|
//
|
||||||
runtimeContext = new HashMap<>();
|
// runtimeContext = new HashMap<>();
|
||||||
runtimeContext.put("X-Api-Group-Seq", "GROUP_001");
|
// runtimeContext.put("X-Api-Group-Seq", "GROUP_001");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// =========================================================================
|
// // =========================================================================
|
||||||
// 1. deriveKey
|
// // 1. deriveKey
|
||||||
// =========================================================================
|
// // =========================================================================
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-1. 정상 파라미터 — DerivedKey 반환, 길이 32바이트(SHA-256 고정 출력)")
|
// @DisplayName("1-1. 정상 파라미터 — DerivedKey 반환, 길이 32바이트(SHA-256 고정 출력)")
|
||||||
void testDeriveKey_validParams_returns32ByteKey() throws Exception {
|
// void testDeriveKey_validParams_returns32ByteKey() throws Exception {
|
||||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
// DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||||
|
//
|
||||||
assertNotNull(dk);
|
// assertNotNull(dk);
|
||||||
assertNotNull(dk.getEncKey());
|
// assertNotNull(dk.getEncKey());
|
||||||
assertNotNull(dk.getDecKey());
|
// assertNotNull(dk.getDecKey());
|
||||||
assertEquals(32, dk.getEncKey().length, "SHA-256 출력은 항상 32바이트여야 한다");
|
// assertEquals(32, dk.getEncKey().length, "SHA-256 출력은 항상 32바이트여야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-2. SHA-256(masterKey || contextValue) 계산 결과 검증")
|
// @DisplayName("1-2. SHA-256(masterKey || contextValue) 계산 결과 검증")
|
||||||
void testDeriveKey_sha256ResultIsCorrect() throws Exception {
|
// void testDeriveKey_sha256ResultIsCorrect() throws Exception {
|
||||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
// DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||||
|
//
|
||||||
byte[] contextBytes = "GROUP_001".getBytes(StandardCharsets.UTF_8);
|
// byte[] contextBytes = "GROUP_001".getBytes(StandardCharsets.UTF_8);
|
||||||
byte[] combined = new byte[MASTER_KEY.length + contextBytes.length];
|
// byte[] combined = new byte[MASTER_KEY.length + contextBytes.length];
|
||||||
System.arraycopy(MASTER_KEY, 0, combined, 0, MASTER_KEY.length);
|
// System.arraycopy(MASTER_KEY, 0, combined, 0, MASTER_KEY.length);
|
||||||
System.arraycopy(contextBytes, 0, combined, MASTER_KEY.length, contextBytes.length);
|
// System.arraycopy(contextBytes, 0, combined, MASTER_KEY.length, contextBytes.length);
|
||||||
byte[] expected = MessageDigest.getInstance("SHA-256").digest(combined);
|
// byte[] expected = MessageDigest.getInstance("SHA-256").digest(combined);
|
||||||
|
//
|
||||||
assertArrayEquals(expected, dk.getEncKey(), "SHA-256 해싱 결과가 일치해야 한다");
|
// assertArrayEquals(expected, dk.getEncKey(), "SHA-256 해싱 결과가 일치해야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-3. 대칭 알고리즘 — encKey == decKey")
|
// @DisplayName("1-3. 대칭 알고리즘 — encKey == decKey")
|
||||||
void testDeriveKey_encKeyEqualsDecKey() throws Exception {
|
// void testDeriveKey_encKeyEqualsDecKey() throws Exception {
|
||||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
// DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||||
|
//
|
||||||
assertArrayEquals(dk.getEncKey(), dk.getDecKey(), "대칭 방식이므로 encKey와 decKey가 동일해야 한다");
|
// assertArrayEquals(dk.getEncKey(), dk.getDecKey(), "대칭 방식이므로 encKey와 decKey가 동일해야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-4. runtimeContext 값 없음 — 빈 문자열로 처리")
|
// @DisplayName("1-4. runtimeContext 값 없음 — 빈 문자열로 처리")
|
||||||
void testDeriveKey_missingContextValue_emptyStringUsed() throws Exception {
|
// void testDeriveKey_missingContextValue_emptyStringUsed() throws Exception {
|
||||||
runtimeContext.remove("X-Api-Group-Seq");
|
// runtimeContext.remove("X-Api-Group-Seq");
|
||||||
DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
// DerivedKey dk = strategy.deriveKey(params, runtimeContext);
|
||||||
|
//
|
||||||
byte[] expected = MessageDigest.getInstance("SHA-256").digest(MASTER_KEY);
|
// byte[] expected = MessageDigest.getInstance("SHA-256").digest(MASTER_KEY);
|
||||||
|
//
|
||||||
assertArrayEquals(expected, dk.getEncKey(), "컨텍스트 값 없으면 masterKey만 해싱해야 한다");
|
// assertArrayEquals(expected, dk.getEncKey(), "컨텍스트 값 없으면 masterKey만 해싱해야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-5. 다른 runtimeContext 값 — 다른 DerivedKey 생성")
|
// @DisplayName("1-5. 다른 runtimeContext 값 — 다른 DerivedKey 생성")
|
||||||
void testDeriveKey_differentContextValue_differentKey() throws Exception {
|
// void testDeriveKey_differentContextValue_differentKey() throws Exception {
|
||||||
DerivedKey dk1 = strategy.deriveKey(params, runtimeContext);
|
// DerivedKey dk1 = strategy.deriveKey(params, runtimeContext);
|
||||||
|
//
|
||||||
runtimeContext.put("X-Api-Group-Seq", "GROUP_002");
|
// runtimeContext.put("X-Api-Group-Seq", "GROUP_002");
|
||||||
DerivedKey dk2 = strategy.deriveKey(params, runtimeContext);
|
// DerivedKey dk2 = strategy.deriveKey(params, runtimeContext);
|
||||||
|
//
|
||||||
assertFalse(java.util.Arrays.equals(dk1.getEncKey(), dk2.getEncKey()),
|
// assertFalse(java.util.Arrays.equals(dk1.getEncKey(), dk2.getEncKey()),
|
||||||
"컨텍스트 값이 다르면 도출된 키도 달라야 한다");
|
// "컨텍스트 값이 다르면 도출된 키도 달라야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-6. 필수 파라미터 누락(hsmKeyAlias) — IllegalArgumentException")
|
// @DisplayName("1-6. 필수 파라미터 누락(hsmKeyAlias) — IllegalArgumentException")
|
||||||
void testDeriveKey_missingHsmKeyAlias_throwsException() {
|
// void testDeriveKey_missingHsmKeyAlias_throwsException() {
|
||||||
params.remove("hsmKeyAlias");
|
// params.remove("hsmKeyAlias");
|
||||||
|
//
|
||||||
assertThrows(IllegalArgumentException.class,
|
// assertThrows(IllegalArgumentException.class,
|
||||||
() -> strategy.deriveKey(params, runtimeContext),
|
// () -> strategy.deriveKey(params, runtimeContext),
|
||||||
"hsmKeyAlias 누락 시 IllegalArgumentException이 발생해야 한다");
|
// "hsmKeyAlias 누락 시 IllegalArgumentException이 발생해야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("1-7. 필수 파라미터 누락(contextKey) — IllegalArgumentException")
|
// @DisplayName("1-7. 필수 파라미터 누락(contextKey) — IllegalArgumentException")
|
||||||
void testDeriveKey_missingContextKey_throwsException() {
|
// void testDeriveKey_missingContextKey_throwsException() {
|
||||||
params.remove("contextKey");
|
// params.remove("contextKey");
|
||||||
|
//
|
||||||
assertThrows(IllegalArgumentException.class,
|
// assertThrows(IllegalArgumentException.class,
|
||||||
() -> strategy.deriveKey(params, runtimeContext),
|
// () -> strategy.deriveKey(params, runtimeContext),
|
||||||
"contextKey 누락 시 IllegalArgumentException이 발생해야 한다");
|
// "contextKey 누락 시 IllegalArgumentException이 발생해야 한다");
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// =========================================================================
|
// // =========================================================================
|
||||||
// 2. buildCacheKey
|
// // 2. buildCacheKey
|
||||||
// =========================================================================
|
// // =========================================================================
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("2-1. buildCacheKey — cryptoName:contextValue 형식")
|
// @DisplayName("2-1. buildCacheKey — cryptoName:contextValue 형식")
|
||||||
void testBuildCacheKey_format() {
|
// void testBuildCacheKey_format() {
|
||||||
String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
// String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
||||||
|
//
|
||||||
assertEquals("CRYPTO_A:GROUP_001", cacheKey);
|
// assertEquals("CRYPTO_A:GROUP_001", cacheKey);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("2-2. buildCacheKey — contextKey 값 없으면 빈 문자열")
|
// @DisplayName("2-2. buildCacheKey — contextKey 값 없으면 빈 문자열")
|
||||||
void testBuildCacheKey_missingContextValue_emptyString() {
|
// void testBuildCacheKey_missingContextValue_emptyString() {
|
||||||
runtimeContext.remove("X-Api-Group-Seq");
|
// runtimeContext.remove("X-Api-Group-Seq");
|
||||||
String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
// String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
||||||
|
//
|
||||||
assertEquals("CRYPTO_A:", cacheKey);
|
// assertEquals("CRYPTO_A:", cacheKey);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@DisplayName("2-3. buildCacheKey — 다른 cryptoName은 다른 캐시 키")
|
// @DisplayName("2-3. buildCacheKey — 다른 cryptoName은 다른 캐시 키")
|
||||||
void testBuildCacheKey_differentCryptoName_differentKey() {
|
// void testBuildCacheKey_differentCryptoName_differentKey() {
|
||||||
String key1 = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
// String key1 = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
|
||||||
String key2 = strategy.buildCacheKey("CRYPTO_B", params, runtimeContext);
|
// String key2 = strategy.buildCacheKey("CRYPTO_B", params, runtimeContext);
|
||||||
|
//
|
||||||
assertNotEquals(key1, key2);
|
// assertNotEquals(key1, key2);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|||||||
Reference in New Issue
Block a user