9 Commits

Author SHA1 Message Date
curry772 1b37fc5e1b GUID 생성 시 node 번호를 -Dinst.Name값을 이용하도록 변경 2026-07-24 17:02:36 +09:00
curry772 9a730dc0b7 Test Filter 수신 메시지 JsonNode 지원 2026-07-24 17:01:54 +09:00
curry772 0ff2259f78 서브모듈 인덱스 갱신 (elink-online-common, elink-online-core,
elink-online-transformer)
2026-07-24 09:01:12 +09:00
curry772 8231318f14 version info 생성 기능 추가 2026-07-23 11:44:35 +09:00
curry772 c44b331a32 DJBErpNsmapiAccessTokenService 토큰 만료시간 및 HTTP 클라이언트 재사용 수정
- 하드코딩된 30초 만료를 제거하고 oAuthCredentialVo.getIntervalSec() 기준으로
  복원 (미설정 시 24시간 폴백), 스케줄러 주기와 불일치하던 문제 해결
- execute() 호출마다 PoolingHttpClientConnectionManager/CloseableHttpClient를
  새로 만들고 즉시 닫던 것을, mTLS 여부+clientId 조합별로 1회만 생성해 재사용하도록
  변경. 이 서비스는 HttpClientAccessTokenServiceFactoryByDB에 싱글턴으로 캐시되어
  재사용되므로 인스턴스 필드로 안전하게 캐시 가능
2026-07-22 09:11:20 +09:00
curry772 88f774e6b5 더존 NSM API 토큰 발급 응답 데이터 필드 이름 수정 token -> access_token 2026-07-21 15:32:00 +09:00
curry772 a77f4d4ef0 message 타입 ObjectNode 대응 2026-07-21 10:49:39 +09:00
curry772 73a50e5db4 미확인 오류 로그 출력 수정 및 favicon.ico 처리하지않도록 변경 2026-07-20 11:13:27 +09:00
curry772 ebf3cfa98d 어댑터별 apikey 헤더명 동적 적용 2026-07-20 11:12:28 +09:00
12 changed files with 190 additions and 134 deletions
+1
View File
@@ -5,6 +5,7 @@ gradle.properties
src/main/generated src/main/generated
WebContent/generated WebContent/generated
src/main/resources/version.info
# Eclipse # # Eclipse #
.metadata .metadata
+48
View File
@@ -172,6 +172,54 @@ task initDirs() {
file(generatedJavaDir).mkdirs() file(generatedJavaDir).mkdirs()
} }
// version.info 를 classpath 리소스로 생성해 WAR의 WEB-INF/classes에 포함시킨다.
// elink-online-common 등 공유 라이브러리 모듈이 아니라, 실제 배포 아티팩트(eapim-online.war)를
// 만드는 이 루트 프로젝트에서 생성해야 "지금 배포된 게이트웨이가 정확히 어느 커밋인지"를
// eapim-online 소스 변경 여부와 무관하게 항상 최신으로 반영한다.
// ToolsController(/manage/tools/version, elink-online-common 모듈)는 이 파일을
// Class.getResourceAsStream("/version.info")로 읽는데, WAR는 WEB-INF/classes와
// WEB-INF/lib 전체가 하나의 클래스로더를 공유하므로 어느 모듈의 클래스에서 조회하든 찾을 수 있다.
//
// elink-online-common/elink-online-core/... 는 별도 저장소를 참조하는 git submodule이라
// 루트(eapim-online)의 describe/dirty 만으로는 "어느 서브모듈이 바뀌었는지"를 알 수 없다
// (서브모듈 포인터가 커밋되고 나면 루트는 다시 clean 해져서 -dirty 흔적도 사라진다).
// 그래서 서브모듈 각각에 대해서도 describe를 실행해 module.<이름>=<결과> 형식으로 함께 기록한다.
def describeGit(File dir) {
try {
def proc = "git describe --tags --always --dirty".execute(null, dir)
// proc.text는 JVM/OS 기본 charset(Windows 환경에선 CP949 등)으로 stdout을 디코딩한다.
// git 태그명은 UTF-8 바이트이므로 기본 charset이 UTF-8이 아니면 여기서 한글이 깨진다.
// stream을 명시적으로 UTF-8로 읽어서 이 문제를 피한다.
def output = proc.inputStream.getText("UTF-8")
proc.waitFor()
return (proc.exitValue() == 0) ? output.trim() : "unknown"
} catch (Exception e) {
logger.warn("${dir} git describe 실행 불가, unknown 으로 대체: ${e.message}")
return "unknown"
}
}
task generateVersionInfo {
doLast {
def gitVersion = describeGit(projectDir)
def sb = new StringBuilder()
sb.append("version=${gitVersion}\n")
sb.append("buildTime=${new Date().format("yyyy-MM-dd HH:mm:ss")}\n")
subprojects.sort { it.name }.each { sub ->
if (file("${sub.projectDir}/.git").exists()) {
sb.append("module.${sub.name}=${describeGit(sub.projectDir)}\n")
}
}
def versionInfoFile = file("$projectDir/src/main/resources/version.info")
versionInfoFile.parentFile.mkdirs()
versionInfoFile.write(sb.toString(), "UTF-8")
}
}
processResources.dependsOn generateVersionInfo
eclipse { eclipse {
wtp { wtp {
component { component {
@@ -67,7 +67,7 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
"/tsb/{path:^(?!oauth).*$}", "/tsb/{path:^(?!oauth).*$}/**", "/tsb/{path:^(?!oauth).*$}", "/tsb/{path:^(?!oauth).*$}/**",
"/shb/{path:^(?!oauth).*$}", "/shb/{path:^(?!oauth).*$}/**", "/shb/{path:^(?!oauth).*$}", "/shb/{path:^(?!oauth).*$}/**",
"/tst/{path:^(?!oauth).*$}", "/tst/{path:^(?!oauth).*$}/**", "/tst/{path:^(?!oauth).*$}", "/tst/{path:^(?!oauth).*$}/**",
"/**/{path:^(?!oauth).*$}", "/**/{path:^(?!oauth).*$}/**", "/**/{path:^(?!oauth)(?!favicon\\.ico$).*$}", "/**/{path:^(?!oauth)(?!favicon\\.ico$).*$}/**",
}) })
public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse) public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws Exception { throws Exception {
@@ -83,17 +83,25 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI()); logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI());
} }
if (adptUri == null) {
logError(servletRequest);
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
"can not find Adapter Uri");
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
}
// Received time , jwhong // Received time , jwhong
long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함 long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함
String receivedTimeStr = String.valueOf(receivedTimeMillis); String receivedTimeStr = String.valueOf(receivedTimeMillis);
ResponseEntity<String> responseEntity = null;
Properties transactionProp = new Properties();
String uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
if (adptUri == null) {
//logError(servletRequest);
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
"can not find Adapter Uri");
logError(servletRequest, "HTTP_IN_NO_URI", MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
errorMsg, transactionProp, null);
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
}
String adapterGroupName = adptUri.getAdptGrpName(); String adapterGroupName = adptUri.getAdptGrpName();
String adapterName = adptUri.getAdptName(); String adapterName = adptUri.getAdptName();
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
@@ -101,6 +109,8 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
if (adapterVO == null) { if (adapterVO == null) {
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR,
"Adapter not found error"); "Adapter not found error");
logError(servletRequest, adapterGroupName, MessageUtil.ERROR_CODE_AP_ERROR,
errorMsg, transactionProp, null);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(errorMsg); .body(errorMsg);
} }
@@ -112,16 +122,10 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode); MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode);
String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT); String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);
ResponseEntity<String> responseEntity = null;
Properties transactionProp = new Properties();
String uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
TxSiftContext.begin(uuid); TxSiftContext.begin(uuid);
// jwhong, put api received time, eaiSvcCode // jwhong, put api received time, eaiSvcCode
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
String responseData = ""; String responseData = "";
try { try {
responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO, responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO,
@@ -154,6 +154,8 @@ public class DJBApiAdapterService extends HttpAdapterServiceSupport {
transactionProp.put(HEADER_GROUP, headerGroupName); transactionProp.put(HEADER_GROUP, headerGroupName);
transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys); transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys);
transactionProp.put(ADAPTER_TOKEN_HEADER_NAME, httpProp.getProperty(ADAPTER_TOKEN_HEADER_NAME, "")); // OAuth 인증 토큰 헤더 이름
transactionProp.put(ADAPTER_APIKEY_HEADER_NAME, httpProp.getProperty(ADAPTER_APIKEY_HEADER_NAME, "")); // API-KEY 인증 헤더 이름
// SEED 컬럼암호하 시 Key로 사용함 // SEED 컬럼암호하 시 Key로 사용함
String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString(); String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString();
ElinkTransactionContext.setSeedKey(seedkey); ElinkTransactionContext.setSeedKey(seedkey);
@@ -6,7 +6,6 @@ import java.util.Properties;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException; import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
@@ -16,6 +15,7 @@ import com.eactive.eai.common.security.ARIACryptoModuleExtension;
import com.eactive.eai.common.security.CryptoModuleExtension; import com.eactive.eai.common.security.CryptoModuleExtension;
import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter; import com.eactive.eai.util.HexaConverter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -30,17 +30,8 @@ public class DecrytTestFilter implements HttpAdapterFilter {
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 {
try { try {
ObjectNode rootNode = null; ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
JsonNode alg = rootNode.get("alg"); JsonNode alg = rootNode.get("alg");
JsonNode mode = rootNode.get("mode"); JsonNode mode = rootNode.get("mode");
JsonNode padding = rootNode.get("padding"); JsonNode padding = rootNode.get("padding");
@@ -8,7 +8,6 @@ import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException; import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
@@ -19,6 +18,7 @@ import com.eactive.eai.common.security.ARIACryptoModuleExtension;
import com.eactive.eai.common.security.CryptoModuleExtension; import com.eactive.eai.common.security.CryptoModuleExtension;
import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.HexaConverter; import com.eactive.eai.util.HexaConverter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -33,17 +33,8 @@ public class EncrytTestFilter implements HttpAdapterFilter {
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 {
try { try {
ObjectNode rootNode = null; ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
// 1. HSM에서 키 가져오기 // 1. HSM에서 키 가져오기
byte[] hsmKeyBytes = null; byte[] hsmKeyBytes = null;
JsonNode hsmKeyAlias = rootNode.get("hsmKeyAlias"); JsonNode hsmKeyAlias = rootNode.get("hsmKeyAlias");
@@ -8,7 +8,6 @@ import java.util.Properties;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.filter.FilterException; import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
@@ -18,6 +17,7 @@ import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy; import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy;
import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy; import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy;
import com.eactive.eai.util.HexaConverter; import com.eactive.eai.util.HexaConverter;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -32,17 +32,8 @@ public class GenKeyFilter implements HttpAdapterFilter {
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 {
try { try {
ObjectNode rootNode = null; ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
String jsonStr = null;
if (message instanceof String) {
jsonStr = (String) message;
} else if (message instanceof JSONObject) {
jsonStr = ((JSONObject) message).toJSONString();
} else {
return message;
}
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
JsonNode hsmKey = rootNode.get("hsmKey"); JsonNode hsmKey = rootNode.get("hsmKey");
JsonNode hsmIv = rootNode.get("hsmIv"); JsonNode hsmIv = rootNode.get("hsmIv");
JsonNode contextKey = rootNode.get("contextKey"); JsonNode contextKey = rootNode.get("contextKey");
@@ -10,6 +10,7 @@ import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.Date; import java.util.Date;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@@ -62,6 +63,13 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
private static boolean testMode = TestModeChecker.isTestMode(); private static boolean testMode = TestModeChecker.isTestMode();
/**
* mTLS 여부/clientId 조합별로 CloseableHttpClient(및 내부 PoolingHttpClientConnectionManager)를
* 1회만 생성해 재사용한다. 이 서비스 인스턴스는 HttpClientAccessTokenServiceFactoryByDB에
* className 기준으로 캐시되어 재사용되므로, 이 필드도 인스턴스 생명주기 동안 안전하게 재사용된다.
*/
private final ConcurrentHashMap<String, CloseableHttpClient> httpClientCache = new ConcurrentHashMap<>();
/** /**
* 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항 * 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항
* - JSON 방식 * - JSON 방식
@@ -121,80 +129,11 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
logger.info("adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId); logger.info("adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId);
} }
int maxTotalConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_TOTAL_CONNECTIONS; // mTLS 여부(및 clientId)별로 CloseableHttpClient를 재사용한다. 매 호출마다 새로 만들면
int maxHostConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_CONNECTION_PER_HOST; // PoolingHttpClientConnectionManager를 쓰는 의미가 없어지고 SSL 핸드셰이크 비용만 반복된다.
String httpClientCacheKey = useMtls ? "mtls:" + clientId : "default";
HttpOutTlsInfoVO mtlsInfo = null; CloseableHttpClient httpClient = httpClientCache.computeIfAbsent(httpClientCacheKey,
SSLContext sslContext = null; key -> buildHttpClient(useMtls, clientId, name));
PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
PoolingHttpClientConnectionManager connectionManager = null;
try {
if (useMtls) {
HttpOutTlsInfoManager tlsManager = HttpOutTlsInfoManager.getInstance();
if (StringUtils.isNotEmpty(clientId)) {
mtlsInfo = tlsManager.getHttpOutTlsInfo(clientId);
}
}
if (useMtls && mtlsInfo != null) {
String storeType = mtlsInfo.getStoreType();
String keyStoreInfo = mtlsInfo.getKeystoreInfo();
String keyStorePassword = mtlsInfo.getKeystorePassword();
String trustStoreInfo = mtlsInfo.getTruststoreInfo();
String trustStorePassword = mtlsInfo.getTruststorePassword();
String[] tlsVersions = null;
String[] cipherSuites = null;
if (StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword)) {
throw new Exception("mTLS keyStore config error");
}
boolean skipTrust = false;
if (StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword)) {
if (logger.isWarn())
logger.warn("Skip trustStore validation adapterGroupName : " + name);
skipTrust = true;
}
sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(storeType, keyStoreInfo,
keyStorePassword, trustStoreInfo, trustStorePassword, skipTrust, tlsVersions, cipherSuites);
SSLConnectionSocketFactory sslSocketFactory = null;
if (testMode) {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
} else {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
} else {
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
SSLConnectionSocketFactory sslSocketFactory = null;
if(testMode) {
// Hostname verifier 비활성화 (테스트용)
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
, NoopHostnameVerifier.INSTANCE
);
}
else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
}
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException
| IOException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
connectionManager = cmBuilder.build();
connectionManager.setMaxTotal(maxTotalConnections);
connectionManager.setDefaultMaxPerRoute(maxHostConnections);
//json body setting //json body setting
// Map<String,Object> jsonMap = new HashMap<>(); // Map<String,Object> jsonMap = new HashMap<>();
@@ -203,7 +142,7 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
// ObjectMapper objMapper = new ObjectMapper(); // ObjectMapper objMapper = new ObjectMapper();
// String authJsonBody = objMapper.writeValueAsString(jsonMap); // String authJsonBody = objMapper.writeValueAsString(jsonMap);
try (CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();) { {
HttpPost httpPost = new HttpPost(uri); HttpPost httpPost = new HttpPost(uri);
// httpPost.setEntity(new StringEntity(authJsonBody)); // httpPost.setEntity(new StringEntity(authJsonBody));
httpPost.setHeader("Content-Type", contentType+" charset=" + encode); httpPost.setHeader("Content-Type", contentType+" charset=" + encode);
@@ -264,11 +203,14 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
if (responseJSON.has("data")) { if (responseJSON.has("data")) {
JsonNode data = responseJSON.path("data"); JsonNode data = responseJSON.path("data");
if(data.has("token")) { if(data.has("access_token")) {
String token = data.path("token").asText(); String token = data.path("access_token").asText();
long intervalSec = oAuthCredentialVo.getIntervalSec() > 0
? oAuthCredentialVo.getIntervalSec()
: 24 * 60 * 60;
accessToken = new OAuth2AccessTokenVO(); accessToken = new OAuth2AccessTokenVO();
accessToken.setAccessToken(token); accessToken.setAccessToken(token);
accessToken.setExpiration(new Date(currentTime + 30_000L)); accessToken.setExpiration(new Date(currentTime + intervalSec * 1000L));
logger.debug("oauthToken =" + accessToken.toString()); logger.debug("oauthToken =" + accessToken.toString());
return accessToken; return accessToken;
} else { } else {
@@ -277,8 +219,6 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
} else { } else {
throw new Exception("oauth token return null"); throw new Exception("oauth token return null");
} }
// accessToken.setExpiration(new Date(currentTime + oAuthCredentialVo.getIntervalSec() * 1000L));
} else { } else {
throw new Exception("oauth token return null"); throw new Exception("oauth token return null");
} }
@@ -289,6 +229,94 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
} }
} }
/**
* mTLS 여부/clientId 조합에 맞는 SSLContext로 PoolingHttpClientConnectionManager와
* CloseableHttpClient를 생성한다. httpClientCache에 의해 조합당 1회만 호출되며, 반환된
* CloseableHttpClient는 재사용을 위해 닫지 않는다(닫으면 커넥션 풀이 함께 종료된다).
*/
private CloseableHttpClient buildHttpClient(boolean useMtls, String clientId, String adapterGroupName) {
int maxTotalConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_TOTAL_CONNECTIONS;
int maxHostConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_CONNECTION_PER_HOST;
HttpOutTlsInfoVO mtlsInfo = null;
SSLContext sslContext = null;
PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
try {
if (useMtls) {
HttpOutTlsInfoManager tlsManager = HttpOutTlsInfoManager.getInstance();
if (StringUtils.isNotEmpty(clientId)) {
mtlsInfo = tlsManager.getHttpOutTlsInfo(clientId);
}
}
if (useMtls && mtlsInfo != null) {
String storeType = mtlsInfo.getStoreType();
String keyStoreInfo = mtlsInfo.getKeystoreInfo();
String keyStorePassword = mtlsInfo.getKeystorePassword();
String trustStoreInfo = mtlsInfo.getTruststoreInfo();
String trustStorePassword = mtlsInfo.getTruststorePassword();
String[] tlsVersions = null;
String[] cipherSuites = null;
if (StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword)) {
throw new Exception("mTLS keyStore config error");
}
boolean skipTrust = false;
if (StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword)) {
if (logger.isWarn())
logger.warn("Skip trustStore validation adapterGroupName : " + adapterGroupName);
skipTrust = true;
}
sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(storeType, keyStoreInfo,
keyStorePassword, trustStoreInfo, trustStorePassword, skipTrust, tlsVersions, cipherSuites);
SSLConnectionSocketFactory sslSocketFactory = null;
if (testMode) {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
} else {
sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
} else {
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
SSLConnectionSocketFactory sslSocketFactory = null;
if(testMode) {
// Hostname verifier 비활성화 (테스트용)
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
, NoopHostnameVerifier.INSTANCE
);
}
else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
}
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException
| IOException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
PoolingHttpClientConnectionManager connectionManager = cmBuilder.build();
connectionManager.setMaxTotal(maxTotalConnections);
connectionManager.setDefaultMaxPerRoute(maxHostConnections);
if (logger.isInfo()) {
logger.info("DJBErpNsmapiAccessTokenService] HttpClient(재사용) 생성. adapterGroupName={}, useMtls={}, clientId={}",
adapterGroupName, useMtls, clientId);
}
return HttpClients.custom().setConnectionManager(connectionManager).build();
}
/** /**
* URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다. * URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다.
* *
@@ -28,7 +28,7 @@ public final class GUIDGeneratorDJB {
static { static {
// 노드번호(2): 서버명 마지막 2자리, 없으면 "00" // 노드번호(2): 서버명 마지막 2자리, 없으면 "00"
String serverName = System.getProperty("server.key", ""); String serverName = System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY, "");
if (serverName.length() >= 2) { if (serverName.length() >= 2) {
NODE_NO = serverName.substring(serverName.length() - 2); NODE_NO = serverName.substring(serverName.length() - 2);
} else { } else {