DJBErpNsmapiAccessTokenService 토큰 만료시간 및 HTTP 클라이언트 재사용 수정
- 하드코딩된 30초 만료를 제거하고 oAuthCredentialVo.getIntervalSec() 기준으로 복원 (미설정 시 24시간 폴백), 스케줄러 주기와 불일치하던 문제 해결 - execute() 호출마다 PoolingHttpClientConnectionManager/CloseableHttpClient를 새로 만들고 즉시 닫던 것을, mTLS 여부+clientId 조합별로 1회만 생성해 재사용하도록 변경. 이 서비스는 HttpClientAccessTokenServiceFactoryByDB에 싱글턴으로 캐시되어 재사용되므로 인스턴스 필드로 안전하게 캐시 가능
This commit is contained in:
+107
-79
@@ -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);
|
||||||
@@ -266,10 +205,13 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
JsonNode data = responseJSON.path("data");
|
JsonNode data = responseJSON.path("data");
|
||||||
if(data.has("access_token")) {
|
if(data.has("access_token")) {
|
||||||
String token = data.path("access_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.info("oauthToken =" + accessToken.toString());
|
logger.debug("oauthToken =" + accessToken.toString());
|
||||||
return accessToken;
|
return accessToken;
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("oauth token return null");
|
throw new Exception("oauth token return null");
|
||||||
@@ -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에 경로를 추가합니다. 중복되는 슬래시를 방지합니다.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user