From 9890f889a41f7a967bad67efb4091b4393141aed Mon Sep 17 00:00:00 2001 From: jaewohong Date: Tue, 16 Dec 2025 17:55:42 +0900 Subject: [PATCH] =?UTF-8?q?NiceOn=20token=20=EA=B4=80=EB=A0=A8=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/HttpClient5AdapterServiceRest.java | 2 +- ...entAccessTokenServiceWithBase64Header.java | 3 +- ...entAccessTokenServiceWithBase64NiceOn.java | 298 ++++++++++++++++++ 3 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64NiceOn.java diff --git a/src/main/java/com/eactive/eai/adapter/http/client/impl/HttpClient5AdapterServiceRest.java b/src/main/java/com/eactive/eai/adapter/http/client/impl/HttpClient5AdapterServiceRest.java index fe84003..2491d51 100644 --- a/src/main/java/com/eactive/eai/adapter/http/client/impl/HttpClient5AdapterServiceRest.java +++ b/src/main/java/com/eactive/eai/adapter/http/client/impl/HttpClient5AdapterServiceRest.java @@ -323,7 +323,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp if (useAdapterToken && "AES256-NICEON".equals(encryptAlgorithm)) { String niceToken = accessToken.getAccessToken(); if ( niceToken == null || niceToken == "") { - throw new Exception("NiceOn Token is empty, toke failed to be issued, TOKEN = [" + niceToken + "]"); + throw new Exception("NiceOn Token is empty, TOKEN failed to be issued, TOKEN = [" + niceToken + "]"); } String clientId = JwtClientIdExtractor(niceToken); long currentTime = System.currentTimeMillis(); diff --git a/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64Header.java b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64Header.java index 07f7864..30ef683 100644 --- a/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64Header.java +++ b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64Header.java @@ -96,8 +96,7 @@ public class HttpClientAccessTokenServiceWithBase64Header implements HttpClientA if (!UrlUtils.isAbsoluteUrl(uri)) { uri = appendPath(adapterUrl, uri); } - //String contentType = "application/json;"; // comment by jwhong - String contentType = "application/x-www-form-urlencoded"; // 광주은행 나이스지키미 + String contentType = "application/json;"; Charset charset; if (StringUtils.isNotBlank(encode)) { diff --git a/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64NiceOn.java b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64NiceOn.java new file mode 100644 index 0000000..51d0382 --- /dev/null +++ b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64NiceOn.java @@ -0,0 +1,298 @@ + +package com.eactive.eai.authoutbound.client.impl; + +import com.eactive.eai.adapter.AdapterGroupVO; +import com.eactive.eai.adapter.AdapterManager; +import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; +import com.eactive.eai.adapter.http.secure.HttpClient5SSLContextFactory; +import com.eactive.eai.authoutbound.OutboundOAuthCredentialVo; +import com.eactive.eai.authoutbound.client.HttpClientAccessTokenServiceByDB; +import com.eactive.eai.common.util.Logger; +import com.eactive.eai.httpouttlsinfo.HttpOutTlsInfoManager; +import com.eactive.eai.httpouttlsinfo.HttpOutTlsInfoVO; +import com.eactive.eai.util.TestModeChecker; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.openbanking.eai.common.token.AccessTokenVO; +import com.openbanking.eai.common.token.OAuth2AccessTokenVO; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.util.Timeout; +import org.springframework.security.web.util.UrlUtils; +import tcseed.base64; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.*; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; + +/** + * 1. 기능 : HTTP 웹 컴포넌트를 POST 방식으로 호출할 수 있는 기능을 제공한다. 2. 처리 개요 : * - 2009.11.24 + * retry 로직 제거 : 요청 3. 주의사항 + * + * @author : + * @version : v 1.0.0 + * @see : WLIAdapterFactory.java, WLIAdapter.java, WLIDefaultAdapter.java + * @since : + * + */ +public class HttpClientAccessTokenServiceWithBase64NiceOn implements HttpClientAccessTokenServiceByDB { + + public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + + private static boolean testMode = TestModeChecker.isTestMode(); + + /** + * 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항 + * - JSON 방식 + * - ( Header Authorization Basic ) 으로 구성 + * @param adapterProp Http Adapter 속성 정보 + * @return 반환 된 AccessTokenVO + * @exception Exception 수동 시스템 간 통신 중 발생 + **/ + public AccessTokenVO execute(String name, Properties adapterProp, OutboundOAuthCredentialVo oAuthCredentialVo) + throws Exception { + AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(name); + String adapterUrl = adapterProp.getProperty("URL"); + String encode = gvo.getMessageEncode(); + String timeoutTemp = adapterProp.getProperty("HTTP_TIME_OUT"); + if (StringUtils.isBlank(timeoutTemp)) { + timeoutTemp = "30000"; + } + String connectionTimeoutTemp = adapterProp.getProperty("CONNECTION_TIMEOUT"); + if (StringUtils.isBlank(connectionTimeoutTemp)) { + connectionTimeoutTemp = "30000"; + } + + int timeout = Integer.parseInt(timeoutTemp); + int connectionTimeout = Integer.parseInt(connectionTimeoutTemp); + long currentTime = System.currentTimeMillis(); + + String uri = oAuthCredentialVo.getUrl(); + + if (!UrlUtils.isAbsoluteUrl(uri)) { + uri = appendPath(adapterUrl, uri); + } + String contentType = "application/x-www-form-urlencoded"; // 광주은행 나이스지키미 + + Charset charset; + if (StringUtils.isNotBlank(encode)) { + charset = Charset.forName(encode); + } else { + charset = Charset.defaultCharset(); + encode = charset.toString(); + } + + boolean useForwardProxy = StringUtils.equalsIgnoreCase(adapterProp.getProperty("FORWARD_PROXY_USE_YN"), "Y"); + String forwardProxyUrl = adapterProp.getProperty("FORWARD_PROXY_URL"); + + logger.debug( + "uri:{}, charset:{}, transactionTimeout:{}, connectionTimeout:{}, useForwardProxy:{}, forwardProxyUrl:{}", + uri, encode, timeout, connectionTimeout, useForwardProxy, forwardProxyUrl); + + List formParams = new ArrayList<>(); + formParams.add(new BasicNameValuePair("client_id", oAuthCredentialVo.getClientId())); + formParams.add(new BasicNameValuePair("client_secret", oAuthCredentialVo.getClientSecret())); + formParams.add(new BasicNameValuePair("scope", oAuthCredentialVo.getScope())); + formParams.add(new BasicNameValuePair("grant_type", oAuthCredentialVo.getGrantType())); + + // mTLS config with default connection parameters + boolean useMtls = StringUtils.equalsIgnoreCase(adapterProp.getProperty(HttpClientAdapterServiceKey.USE_MTLS), + "Y"); + AdapterGroupVO adapterGroup = AdapterManager.getInstance().getAdapterGroup(name); + String clientId = adapterGroup.getClientId(); + + if (logger.isInfo()) { + logger.info("MTLS adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId); + } + + 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(); + 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 csf = new SSLConnectionSocketFactory(sslContext); + cmBuilder.setSSLSocketFactory(csf); + } + } 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); + + try (CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();) { + HttpPost httpPost = new HttpPost(uri); + httpPost.setEntity(new UrlEncodedFormEntity(formParams, charset)); + httpPost.setHeader("Content-Type", contentType+" charset=" + encode); + + //HTTP HEADER에 client id, client secret를 base64Encode 한 후 추가 + String cId = oAuthCredentialVo.getClientId(); + String cSecret = oAuthCredentialVo.getClientSecret(); + String authValue = cId + ":" + cSecret; + String encodedAuth = Base64.getEncoder().encodeToString(authValue.getBytes(StandardCharsets.UTF_8)); + httpPost.setHeader("Authorization","Basic "+ encodedAuth); + + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + + if (useForwardProxy) { + java.net.URL url = new java.net.URL(forwardProxyUrl); + + // 프로토콜, 호스트, 포트 추출 + String protocol = url.getProtocol(); + String host = url.getHost(); + int port = url.getPort(); + HttpHost proxy = new HttpHost(protocol, host, port); + requestConfigBuilder.setProxy(proxy); + } + + RequestConfig requestConfig = requestConfigBuilder + .setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout)) + .setResponseTimeout(Timeout.ofMilliseconds(timeout)).build(); + httpPost.setConfig(requestConfig); + + logger.debug("uri = [" + uri + "]"); + logger.debug("oauthClientId = [" + oAuthCredentialVo.getClientId() + "]"); + logger.debug("oauthClientSecret = [" + oAuthCredentialVo.getClientSecret() + "]"); + logger.debug("oauthScope = [" + oAuthCredentialVo.getScope() + "]"); + logger.debug("oauthGrantType = [" + oAuthCredentialVo.getGrantType() + "]"); + logger.debug("oauthauthValue = [" + authValue + "]"); + logger.debug("oauthauthEncodedAuth = [" + encodedAuth + "]"); + logger.debug("contentType = [" + contentType + "]"); + logger.debug("encode = [" + encode + "]"); + + OAuth2AccessTokenVO accessToken = new OAuth2AccessTokenVO(); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + if (response.getCode() != 200) { + throw new Exception("NiceOn token receive status fail value= " + response.getCode()); + } + + logger.info("HttpClientAccessTokenServiceWithBase64NiceOn==>" + response.getCode()); + + HttpEntity entity = response.getEntity(); + String responseString = EntityUtils.toString(entity, encode); + logger.debug("Base64NiceOn oauthToken RECV = [" + responseString + "]"); + + if (StringUtils.isNotBlank(responseString)) { + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode responseJSON = objectMapper.readTree(responseString); + + if (responseJSON.has("access_token") && responseJSON.has("token_type") + && responseJSON.has("expires_in") && responseJSON.has("scope")) { + accessToken.setAccessToken(responseJSON.get("access_token").asText()); + accessToken.setTokenType(responseJSON.get("token_type").asText()); + + long expiresIn = responseJSON.get("expires_in").asLong(); + accessToken.setExpiration(new Date(currentTime + expiresIn * 1000L)); + accessToken.setScope(responseJSON.get("scope").asText()); + } + + if (responseJSON.has("client_use_code")) { + accessToken.setClientUseCode(responseJSON.get("client_use_code").asText()); + } + + logger.debug("oauthToken =" + accessToken.toString()); + + return accessToken; + } else { + throw new Exception("oauth token return null"); + } + } catch (Exception e) { + e.printStackTrace(); + logger.error("[HttpClientAccessTokenServiceWithBase64NiceOn] retrieve accessToken exception :" + e.getMessage()); + return accessToken; + } + } + } + + /** + * URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다. + * + * @param baseUrl 기본 URL + * @param pathToAdd 추가할 경로 + * @return 완성된 URL 문자열 + */ + public static String appendPath(String baseUrl, String pathToAdd) { + if (!baseUrl.endsWith("/") && !pathToAdd.startsWith("/")) { + return baseUrl + "/" + pathToAdd; + } else if (baseUrl.endsWith("/") && pathToAdd.startsWith("/")) { + return baseUrl + pathToAdd.substring(1); + } else { + return baseUrl + pathToAdd; + } + } +}