diff --git a/src/main/java/com/eactive/eai/adapter/db/sql/DBInboudSqlAdapterListener.java b/src/main/java/com/eactive/eai/adapter/db/sql/DBInboudSqlAdapterListener.java index bfc888c..c286398 100644 --- a/src/main/java/com/eactive/eai/adapter/db/sql/DBInboudSqlAdapterListener.java +++ b/src/main/java/com/eactive/eai/adapter/db/sql/DBInboudSqlAdapterListener.java @@ -111,27 +111,27 @@ public class DBInboudSqlAdapterListener extends AdapterListenerSupport { } // TODO : Application Test Code. // in real system, remove this block - else { - prop = new Properties(); - - prop.setProperty(DBAdapterKeys.RUN_CRONEXP, "0/10 * * * * ?"); - prop.setProperty(DBAdapterKeys.RUN_INTVL, "1000"); - prop.setProperty(DBAdapterKeys.RUN_RETRY, "N"); - prop.setProperty(DBAdapterKeys.RUN_RETRYCNT, "2"); - prop.setProperty(DBAdapterKeys.RUN_RETRYINTVL, "5000"); - prop.setProperty(DBAdapterKeys.RUN_MSGTYPE, "JSON"); - - prop.setProperty(DBAdapterKeys.DRIVER, "oracle.jdbc.driver.OracleDriver"); - prop.setProperty(DBAdapterKeys.URL, "jdbc:oracle:thin:@localhost:1521:XE"); - prop.setProperty(DBAdapterKeys.USER, "eai"); - prop.setProperty(DBAdapterKeys.PVAL, "eaiadmin"); -// prop.setProperty(DBAdapterKeys.SELECT, "select * from APIJOBSCHE"); - prop.setProperty(DBAdapterKeys.SELECT, "select * from EAIINF01 where RFLAG IS null ORDER BY UNID"); - prop.setProperty(DBAdapterKeys.UPDATE, "update EAIINF01 set RFLAG = 'C' where UNID = '%UNID%' "); - - debug("Start Scheduler"); - adapterName = "_EAI_IN_DBQ_SyC"; - } +// else { +// prop = new Properties(); +// +// prop.setProperty(DBAdapterKeys.RUN_CRONEXP, "0/10 * * * * ?"); +// prop.setProperty(DBAdapterKeys.RUN_INTVL, "1000"); +// prop.setProperty(DBAdapterKeys.RUN_RETRY, "N"); +// prop.setProperty(DBAdapterKeys.RUN_RETRYCNT, "2"); +// prop.setProperty(DBAdapterKeys.RUN_RETRYINTVL, "5000"); +// prop.setProperty(DBAdapterKeys.RUN_MSGTYPE, "JSON"); +// +// prop.setProperty(DBAdapterKeys.DRIVER, "oracle.jdbc.driver.OracleDriver"); +// prop.setProperty(DBAdapterKeys.URL, "jdbc:oracle:thin:@localhost:1521:XE"); +// prop.setProperty(DBAdapterKeys.USER, "eai"); +// prop.setProperty(DBAdapterKeys.PVAL, "eaiadmin"); +//// prop.setProperty(DBAdapterKeys.SELECT, "select * from APIJOBSCHE"); +// prop.setProperty(DBAdapterKeys.SELECT, "select * from EAIINF01 where RFLAG IS null ORDER BY UNID"); +// prop.setProperty(DBAdapterKeys.UPDATE, "update EAIINF01 set RFLAG = 'C' where UNID = '%UNID%' "); +// +// debug("Start Scheduler"); +// adapterName = "_EAI_IN_DBQ_SyC"; +// } // TODO : Application Test Code End. prop.setProperty(DBAdapterKeys.ADAPTER_NAME, adapterName); diff --git a/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialDao.java b/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialDao.java index 32c9d3b..c204ce2 100644 --- a/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialDao.java +++ b/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialDao.java @@ -12,6 +12,7 @@ import com.eactive.eai.common.util.Logger; import com.eactive.eai.data.entity.onl.authoutbound.OutboundOAuthCredential; import com.eactive.eai.data.entity.onl.lifecycle.LifeCycle; import com.eactive.eai.data.entity.onl.property.PropGroup; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -60,7 +61,9 @@ public class OutboundOAuthCredentialDao extends BaseDAO { public void decryptClientSecret(OutboundOAuthCredentialVo vo) throws Exception { String clientSecret = vo.getClientSecret(); - String decrypted = Seed.decrypt(clientSecret); - vo.setClientSecret(decrypted); + if (StringUtils.isNotBlank(clientSecret)) { + String decrypted = Seed.decrypt(clientSecret); + vo.setClientSecret(decrypted); + } } } diff --git a/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialVo.java b/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialVo.java index 8ae4a65..1466423 100644 --- a/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialVo.java +++ b/src/main/java/com/eactive/eai/authoutbound/OutboundOAuthCredentialVo.java @@ -2,6 +2,7 @@ package com.eactive.eai.authoutbound; import lombok.Data; import org.hibernate.annotations.Comment; +import org.mapstruct.Mapping; import javax.persistence.Column; import java.time.LocalDateTime; @@ -29,4 +30,10 @@ public class OutboundOAuthCredentialVo { private LocalDateTime createdAt; private LocalDateTime updatedAt; + + private String contentType; + + private String bodyJson; + + private String headerJson; } diff --git a/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithDefault.java b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithDefault.java new file mode 100644 index 0000000..d1593f9 --- /dev/null +++ b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithDefault.java @@ -0,0 +1,348 @@ + +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.annotation.JsonInclude; +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.entity.UrlEncodedFormEntity; +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.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 javax.net.ssl.SSLContext; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.*; + +/** + * 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 HttpClientAccessTokenServiceWithDefault implements HttpClientAccessTokenServiceByDB { + + public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + + private static boolean testMode = TestModeChecker.isTestMode(); + + /** + * 1. 기능 : 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항 + * + * @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 = oAuthCredentialVo.getContentType(); + + if(StringUtils.isBlank(contentType)){ + contentType="application/x-www-form-urlencoded"; + if(logger.isDebug()){ + logger.debug("Content-Type not specified. Using default: 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"); + + if(logger.isDebug()) { + logger.debug( + "contentType:{}, uri:{}, charset:{}, transactionTimeout:{}, connectionTimeout:{}, useForwardProxy:{}, forwardProxyUrl:{}", + contentType, uri, encode, timeout, connectionTimeout, useForwardProxy, forwardProxyUrl); + } + + + // 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); + + //HTTP HEADER Setting + httpPost.setHeader("Content-Type", contentType+"; charset=" + encode); + // 화면에서 추가 한 bodyJson을 Map으로 변환 후 합치기 + String addHeaderJson = oAuthCredentialVo.getHeaderJson(); + if (addHeaderJson != null && !addHeaderJson.isEmpty()) { + ObjectMapper headerJsonMapper = new ObjectMapper(); + Map addHeaderMap = headerJsonMapper.readValue(addHeaderJson, Map.class); + for (Map.Entry entry : addHeaderMap.entrySet()) { + httpPost.setHeader(entry.getKey(), entry.getValue().toString()); + } + } + + //HTTP BODY Setting + // content Type 에 따라 전달 방법 분기 + if(contentType.equals("application/json")){ + + //json body 개별 컬럼 setting + Map jsonMap = new HashMap<>(); + jsonMap.put("client_id", oAuthCredentialVo.getClientId()); + jsonMap.put("client_secret", oAuthCredentialVo.getClientSecret()); + jsonMap.put("scope", oAuthCredentialVo.getScope()); + jsonMap.put("grant_type", oAuthCredentialVo.getGrantType()); + + // 화면에서 추가 한 bodyJson을 Map으로 변환 후 합치기 + String addBodyJson = oAuthCredentialVo.getBodyJson(); + if (addBodyJson != null && !addBodyJson.isEmpty()) { + ObjectMapper bodyJsonMapper = new ObjectMapper(); + Map addBodyMap = bodyJsonMapper.readValue(addBodyJson, Map.class); + jsonMap.putAll(addBodyMap); + } + ObjectMapper objMapper = new ObjectMapper(); + objMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); //빈값이 아닐 때만 param에 담기 + String authJsonBody = objMapper.writeValueAsString(jsonMap); + + httpPost.setEntity(new StringEntity(authJsonBody)); + + } else if (contentType.equals("application/x-www-form-urlencoded")){ + + //빈값이 아닐 때만 param에 담기 + List formParams = new ArrayList<>(); + addIfNotNull(formParams, "client_id", oAuthCredentialVo.getClientId()); + addIfNotNull(formParams, "client_secret", oAuthCredentialVo.getClientSecret()); + addIfNotNull(formParams, "scope", oAuthCredentialVo.getScope()); + addIfNotNull(formParams, "grant_type", oAuthCredentialVo.getGrantType()); + + // 화면에서 추가 한 bodyJson을 Map으로 변환 후 추가 + String addBodyJson = oAuthCredentialVo.getBodyJson(); + if (addBodyJson != null && !addBodyJson.isEmpty()) { + ObjectMapper bodyJsonMapper = new ObjectMapper(); + Map addBodyMap = bodyJsonMapper.readValue(addBodyJson, Map.class); + for (Map.Entry entry : addBodyMap.entrySet()) { + formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString())); + } + } + httpPost.setEntity(new UrlEncodedFormEntity(formParams, charset)); + + } + + 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("contentType = [" + contentType + "]"); + logger.debug("encode = [" + encode + "]"); + logger.debug("bodyJson = ["+ oAuthCredentialVo.getBodyJson() + "]"); + logger.debug("bodyHeader = ["+ oAuthCredentialVo.getHeaderJson() + "]"); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + if (response.getCode() != 200) { + throw new Exception("OAuth token receive status fail value= " + response.getCode()); + } + + HttpEntity entity = response.getEntity(); + String responseString = EntityUtils.toString(entity, encode); + logger.debug("oauthToken RECV = [" + responseString + "]"); + + if (StringUtils.isNotBlank(responseString)) { + OAuth2AccessTokenVO accessToken = new OAuth2AccessTokenVO(); + + 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"); + } + } + } + } + + /** + * 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; + } + } + private void addIfNotNull(List params, String name, String value) { + if (StringUtils.isNotBlank(value)) { + params.add(new BasicNameValuePair(name, value)); + } + } +}