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 86a47aa..50eb1f9 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 @@ -91,7 +91,7 @@ import com.kjbank.encrypt.exchange.crypto.AES256GCMCipher; import com.kjbank.encrypt.exchange.crypto.ECDHESA256Cipher; import com.eactive.eai.authserver.service.OAuth2Manager; import java.util.Set; - +import java.util.HashSet; /** * 1. 기능 : EAI HTTP OutBound 용 어댑터로 수동 시스템의 HTTP 웹 컴포넌트를 GET/POST 방식으로 호출할 수 있는 @@ -306,7 +306,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName()); // 토큰이 없거나 만료 됐으면 재발급 - if (accessToken == null || accessToken.isExpired()) { + //if (accessToken == null || accessToken.isExpired()) { + if (accessToken == null || accessToken.isExpired() || accessToken.getAccessToken() == null ) { // jwhong String oldToken = accessToken == null ? null : accessToken.getAccessToken(); accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken); @@ -321,9 +322,13 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong if (useAdapterToken && "AES256-NICEON".equals(encryptAlgorithm)) { String niceToken = accessToken.getAccessToken(); - String clientId = JwtClientIdExtractor(niceToken); + 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 = System.currentTimeMillis(); auth = niceToken + ":" + currentTime + ":" + clientId ; + logger.debug("HttpClientAdapterServiceRest] NiceOn auth = [" + auth + "]"); auth = Base64.getEncoder().encodeToString(auth.getBytes("UTF-8")); setNiceOnAuthHeaders(method, auth); @@ -827,46 +832,96 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp // map에 담기 Map map = new HashMap<>(); for (String entry : entries) { - String[] kv = entry.split("=", 2); - if (kv.length == 2) { - map.put(kv[0].trim(), kv[1].trim()); - } + 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; - } + if (k.equalsIgnoreCase("authorization")) { + authorization = map.get(k); + break; + } } - } + } } catch (Exception e) { - e.printStackTrace(); + e.printStackTrace(); } - + String jwtToken = ""; - + if (authorization != null && authorization.startsWith("Bearer ")) { - jwtToken = authorization.substring(7); // "Bearer " 이후의 JWT만 추출 + jwtToken = authorization.substring(7); // "Bearer " 이후의 JWT만 추출 } + + + String headerRelay = PropManager.getInstance().getProperty("HEADER_RELAY_ADAPTER","adapter.name"); + Set values = new HashSet<>(Arrays.asList(headerRelay.split(","))); + String inputAdapter = tempProp.getProperty(HttpAdapterServiceKey.OUT_ADAPTER_GROUP_NAME, ""); + + String[] parts = inputAdapter.split("_"); + String target = parts[1]; // adapter name의 앞의 3자리 + String adapterName = target.substring(0, 3); + if (values.contains(adapterName)) { + + /* 업체정보 */ + String clientId = JwtClientIdExtractor(jwtToken); + method.setHeader("clientId", clientId); + + /* APIM 거래추적자 */ + String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, ""); + method.setHeader("traceId", uuid); + + /* GUID */ + String guid = tempProp.getProperty(TransactionContextKeys.GUID, ""); + method.setHeader("guid", guid); + + /* 최초요청시간 */ + String receivedTimestamp = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME, ""); + method.setHeader("receivedTimestamp", receivedTimestamp); + } + + /* 업체정보 */ - String clientId = JwtClientIdExtractor(jwtToken); - method.setHeader("clientId", clientId); - - /* APIM 거래추적자 */ - String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, ""); - method.setHeader("traceId", uuid); - - /* GUID */ - String guid = tempProp.getProperty(TransactionContextKeys.GUID, ""); - method.setHeader("guid", guid); - - /* 최초요청시간 */ - String receivedTimestamp = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME, ""); - method.setHeader("receivedTimestamp", receivedTimestamp); - - return jwtToken; + //String clientId = JwtClientIdExtractor(jwtToken); + //method.setHeader("clientId", clientId); + + /* APIM 거래추적자 */ + //String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, ""); + //method.setHeader("traceId", uuid); + + /* GUID */ + //String guid = tempProp.getProperty(TransactionContextKeys.GUID, ""); + //method.setHeader("guid", guid); + + /* 최초요청시간 */ + //String receivedTimestamp = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME, ""); + //method.setHeader("receivedTimestamp", receivedTimestamp); + + return jwtToken; } + + private static Map parseInboundHeader(String str) { + java.util.HashMap 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; + } @SuppressWarnings("deprecation") private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method, String adapterName, String niceAuth, String inboundToken) { diff --git a/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceKey.java b/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceKey.java index 9fdc548..cbc6ad4 100644 --- a/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceKey.java +++ b/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceKey.java @@ -80,6 +80,7 @@ public interface HttpAdapterServiceKey { static final String INBOUND_REQUESTED_TIME = "INBOUND_REQUESTED_TIME"; // jwhong static final String GUID = "GUID"; // jwhong static final String API_SERVICE_CODE = "API_SERVICE_CODE"; // jwhong + static final String OUT_ADAPTER_GROUP_NAME = "OUT_ADAPTER_GROUP_NAME"; // jwhong //응답 처리 용 표준 전문 오브젝트 static final String STANDARD_MESSAGE_OBJECT = "STANDARD_MESSAGE_OBJECT"; diff --git a/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceSupport.java b/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceSupport.java index 0077de4..8be66b3 100644 --- a/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceSupport.java +++ b/src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceSupport.java @@ -9,14 +9,11 @@ import org.apache.commons.lang3.StringUtils; import com.eactive.eai.adapter.AdapterGroupVO; import com.eactive.eai.adapter.AdapterManager; -import com.eactive.eai.adapter.AdapterPropManager; -import com.eactive.eai.adapter.AdapterVO; import com.eactive.eai.adapter.RequestDispatcher; import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter; -import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory; +import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb; import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType; -import com.eactive.eai.data.entity.onl.adapter.AdapterProp; public abstract class HttpAdapterServiceSupport implements HttpAdapterService, HttpAdapterServiceKey { protected long slowTranTime = 2000L; @@ -65,7 +62,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H if (!"HTC".equals(group.getType())) { String allowIp = prop.getProperty(ALLOW_IP,""); if (StringUtils.isNotBlank(allowIp)) { - HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString()); + HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString()); if (adapterFilter != null) { message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response); } @@ -83,7 +80,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H continue; } - HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim()); + HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim()); if (adapterFilter != null) { message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response); } else { @@ -106,7 +103,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H continue; } - HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim()); + HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim()); if (adapterFilter != null) { resultMessage = adapterFilter.doPostFilter(adptGrpName, adptName, resultMessage, prop, request, response); diff --git a/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/ApiAuthFilter.java b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/ApiAuthFilter.java index 569b64b..2babcee 100644 --- a/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/ApiAuthFilter.java +++ b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/ApiAuthFilter.java @@ -109,6 +109,7 @@ public class ApiAuthFilter implements HttpAdapterFilter { return message; } + boolean isPassScope = false; switch (eaiMessage.getAuthType()){ case "oauth": try { @@ -127,10 +128,25 @@ public class ApiAuthFilter implements HttpAdapterFilter { OAuth2Manager manager = OAuth2Manager.getInstance(); HashSet scopeSet = manager.getApiScopeMap().get(apiId); - 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, 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 if (!verifyClientId(prop, signedJWT)) { @@ -144,26 +160,6 @@ public class ApiAuthFilter implements HttpAdapterFilter { throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Token invalid"); } break; -// case "bearer": -// try { -// String token = ApiAuthFilter.extractJWTToken(request); -// BearerTokenInfo CATokenInfo = CATokenStore.get(token); -// -// if (CATokenInfo == null) { -// throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Token invalid"); -// } -// -// if (CATokenInfo.isExpired()) { -// throw new JwtAuthException(ERROR_TOKEN_EXPIRED, "Access token expired"); -// } -// } catch (JwtAuthException e) { -// logger.debug(e.getMessage()); -// throw e; -// } catch (Exception e) { -// logger.error(e.getMessage()); -// throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Token invalid"); -// } -// break; case "api_key": String apiKeyName = PropManager.getInstance().getProperty("ApiConfig", "api.key.name", "x-api-key"); String apiKey = request.getHeader(apiKeyName); @@ -171,19 +167,22 @@ public class ApiAuthFilter implements HttpAdapterFilter { throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Invalid or missing API key \""+apiKeyName+"\" in Http Header"); } prop.setProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, apiKey); + isPassScope = true; break; case "none": return message; } - String clientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID); - ClientVO clientVO = OAuth2Manager.getInstance().getClientVO(clientId); - if(clientVO == null){ - throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Unregistered APP"); - } + if (isPassScope) { + String clientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID); + ClientVO clientVO = OAuth2Manager.getInstance().getClientVO(clientId); + if(clientVO == null){ + throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Unregistered APP"); + } - if(clientVO.getApiMap().containsKey(eaiSvcCd) == false){ - throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Unregistered API ["+eaiSvcCd+"] in ["+clientVO.getClientName()+"] APP"); + if(clientVO.getApiMap().containsKey(eaiSvcCd) == false){ + throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Unregistered API ["+eaiSvcCd+"] in ["+clientVO.getClientName()+"] APP"); + } } return message; diff --git a/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HmacSha256VerifyFilterKjb.java b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HmacSha256VerifyFilterKjb.java new file mode 100644 index 0000000..4cd3bcf --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HmacSha256VerifyFilterKjb.java @@ -0,0 +1,152 @@ +package com.eactive.eai.adapter.http.dynamic.filter; + +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Properties; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +//import org.apache.commons.net.util.Base64; +import org.springframework.http.HttpStatus; +import org.apache.commons.lang3.StringUtils; + +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport; +import com.eactive.eai.authserver.service.OAuth2Manager; +import com.eactive.eai.authserver.vo.ClientVO; +import com.eactive.eai.common.TransactionContextKeys; +import com.eactive.eai.common.util.Logger; + +public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter { + + static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); + + public HmacSha256VerifyFilterKjb() { + super(); + } + + @Override + public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop, + HttpServletRequest request, HttpServletResponse response) throws Exception { + + String inboundMethod = prop.getProperty(HttpAdapterServiceKey.INBOUND_METHOD); + String inboundUri = prop.getProperty(HttpAdapterServiceKey.INBOUND_URI); + String inboundBody = prop.getProperty(HttpAdapterServiceKey.INBOUND_REQUEST_MESSAGE); + + try { + String xObpPartnercode = request.getHeader("x-obp-partnercode"); + String xObpSignatureUrl = request.getHeader("x-obp-signature-url"); + String xObpSignatureBody = request.getHeader("x-obp-signature-body"); + if (StringUtils.isBlank(xObpPartnercode) + || StringUtils.isBlank(xObpSignatureUrl) + || StringUtils.isBlank(xObpSignatureBody)) { + throw new JwtAuthException( + String.valueOf(HttpStatus.UNAUTHORIZED.value()), + "Can not find mandatory Http Header"); + } + + if (StringUtils.isNotBlank(prop.getProperty(HttpAdapterServiceKey.INBOUND_QUERY_STRING))) { + inboundUri = inboundUri + "?" + prop.getProperty(HttpAdapterServiceKey.INBOUND_QUERY_STRING); + inboundBody = ""; + } + String chkUrl = inboundMethod + "&" + inboundUri; + String chkBody = inboundBody; + String clientSecret = assignClientSecret(prop, request); + + String macUrl = calculateHMAC(chkUrl, clientSecret); + String macBody = calculateHMAC(chkBody, clientSecret); + + if (!StringUtils.equals(xObpSignatureUrl, macUrl)) { + throw new JwtAuthException( + String.valueOf(HttpStatus.UNAUTHORIZED.value()), + "Signature verifying failed : x-obp-signature-url"); + } + + if (!StringUtils.equals(xObpSignatureBody, macBody)) { + throw new JwtAuthException( + String.valueOf(HttpStatus.UNAUTHORIZED.value()), + "Signature verifying failed : x-obp-signature-body"); + } + + } catch (JwtAuthException e) { + logger.debug(e.getMessage()); + throw e; + } catch (Exception e) { + logger.error(e.getMessage()); + throw new JwtAuthException( + String.valueOf(HttpStatus.UNAUTHORIZED.value()), + "Signature verifying failed(unkown)"); + } + + return message; + } + + public static String calculateHMAC(String data, String key) + throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")); + return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes("UTF-8"))); + } + + private String assignClientSecret(Properties prop, HttpServletRequest request) throws Exception { + String clientId = assignClientId(prop, request); + if (StringUtils.isBlank(clientId)) { + throw new JwtAuthException( + String.valueOf(HttpStatus.UNAUTHORIZED.value()), + "Can not find clientId info"); + } + + ClientVO client = (ClientVO) OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId); + if (client == null) { + throw new JwtAuthException( + String.valueOf(HttpStatus.UNAUTHORIZED.value()), + "Can not find client info"); + } + + return client.getClientSecret(); + } + + private String assignClientId(Properties prop, HttpServletRequest request) { + String clientId = null; + try { + // 이전 filter에서 셋팅한 clientId 확보 + clientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID); + + // 이전 filter에서 셋팅한 값이 없다면 header 정보에서 확보 + if (StringUtils.isBlank(clientId)) { + clientId = request.getHeader(HttpAdapterServiceSupport.HEADER_NAME_CLIENT_ID); + } + } catch (Exception e) { + logger.error(e.getMessage()); + } + + return clientId; + } + + @Override + public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop, + HttpServletRequest request, HttpServletResponse response) throws Exception { + + String outboundBody = (String) resultMessage; + + try { + String clientSecret = assignClientSecret(prop, request); + String macBody = calculateHMAC(outboundBody, clientSecret); + + response.addHeader("x-obp-signature-body", macBody); + response.addHeader("x-obp-partnercode", request.getHeader("x-obp-partnercode")); + response.addHeader("x-obp-txid", prop.getProperty(TransactionContextKeys.TRANSACTION_UUID,"")); + } catch (Exception e) { + logger.error(e.getMessage()); + } + + return resultMessage; + } + +} diff --git a/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterFactoryKjb.java b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterFactoryKjb.java new file mode 100644 index 0000000..977f92f --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterFactoryKjb.java @@ -0,0 +1,64 @@ +package com.eactive.eai.adapter.http.dynamic.filter; + +import java.util.concurrent.ConcurrentHashMap; + +import com.eactive.eai.common.util.Logger; + +public class HttpAdapterFilterFactoryKjb extends HttpAdapterFilterFactory { + static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); + static String basePackage = "com.eactive.eai.custom.adapter.http.dynamic.filter"; + static private ConcurrentHashMap h = new ConcurrentHashMap(); + + public HttpAdapterFilterFactoryKjb() { + super(); + } + + @SuppressWarnings("rawtypes") + public static HttpAdapterFilter createFilter(String type) { + if (h.containsKey(type)) { + return (HttpAdapterFilter) h.get(type); + } + + HttpAdapterFilter filter = null; + switch (HttpAdapterFilterType.getValue(type)) { + case APIAUTHFILTER: + filter = new ApiAuthFilter(); + break; + case JWTAUTHFILTER: + filter = new JwtAuthFilter(); + break; + case IPWHITELISTFILTER: + filter = new IpWhiteListFilter(); + break; + case ADAPTERALLOWIPLISTFILTER: + filter = new AdapterAllowIPListFilter(); + break; + case HMAC_SHA256: + filter = new HmacSha256VerifyFilterKjb(); + break; + default: + filter = classForName(type); + if (filter == null) { + filter = classForName(basePackage + "." + type); + } + break; + } + + if (filter != null) { + h.put(type, filter); + return filter; + } else { + return null; + } + } + + private static HttpAdapterFilter classForName(String type) { + try { + Class cl = Class.forName(type); + return (HttpAdapterFilter) cl.newInstance(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + logger.error("Cannot create a HttpAdapterFilter. - {}", type); + return null; + } + } +} diff --git a/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterType.java b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterType.java index 6231c74..c522614 100644 --- a/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterType.java +++ b/src/main/java/com/eactive/eai/adapter/http/dynamic/filter/HttpAdapterFilterType.java @@ -5,6 +5,7 @@ public enum HttpAdapterFilterType { JWTAUTHFILTER, IPWHITELISTFILTER, ADAPTERALLOWIPLISTFILTER, + HMAC_SHA256, UNKNOWN; public static HttpAdapterFilterType getValue(String type) { 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 295c2f3..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)) { @@ -114,9 +113,7 @@ public class HttpClientAccessTokenServiceWithBase64Header implements HttpClientA "uri:{}, charset:{}, transactionTimeout:{}, connectionTimeout:{}, useForwardProxy:{}, forwardProxyUrl:{}", uri, encode, timeout, connectionTimeout, useForwardProxy, forwardProxyUrl); - - - + // mTLS config with default connection parameters boolean useMtls = StringUtils.equalsIgnoreCase(adapterProp.getProperty(HttpClientAdapterServiceKey.USE_MTLS), "Y"); @@ -237,17 +234,19 @@ public class HttpClientAccessTokenServiceWithBase64Header implements HttpClientA 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("OAuth token receive status fail value= " + response.getCode()); } + + logger.info("HttpClientAccessTokenServiceWithBase64Header==>" + response.getCode()); HttpEntity entity = response.getEntity(); String responseString = EntityUtils.toString(entity, encode); - logger.debug("oauthToken RECV = [" + responseString + "]"); + logger.debug("Base64Header oauthToken RECV = [" + responseString + "]"); if (StringUtils.isNotBlank(responseString)) { - OAuth2AccessTokenVO accessToken = new OAuth2AccessTokenVO(); ObjectMapper objectMapper = new ObjectMapper(); JsonNode responseJSON = objectMapper.readTree(responseString); @@ -272,6 +271,10 @@ public class HttpClientAccessTokenServiceWithBase64Header implements HttpClientA } else { throw new Exception("oauth token return null"); } + } catch (Exception e) { + e.printStackTrace(); + logger.error("[HttpClientAccessTokenServiceWithBase64Header] retrieve accessToken exception :" + e.getMessage()); + return accessToken; } } } 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..88cf398 --- /dev/null +++ b/src/main/java/com/eactive/eai/authoutbound/client/impl/HttpClientAccessTokenServiceWithBase64NiceOn.java @@ -0,0 +1,308 @@ + +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("grant_type", oAuthCredentialVo.getGrantType())); + formParams.add(new BasicNameValuePair("scope", oAuthCredentialVo.getScope())); + //String postParameters = "grant_type=" + oAuthCredentialVo.getGrantType() + "&scope=" + oAuthCredentialVo.getScope(); + + + // 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.setEntity(new StringEntity(postParameters, StandardCharsets.UTF_8)); + 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 responseJSONRtn = objectMapper.readTree(responseString); + JsonNode responseJSON = null; + String gwRsltCd = responseJSONRtn.get("dataHeader").get("GW_RSLT_CD").asText(); + + if ("1200".equals(gwRsltCd)) { + responseJSON = responseJSONRtn.get("dataBody"); + } + + 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()); + accessToken.setClientId(cId); + } + + if (responseJSON.has("client_use_code")) { + accessToken.setClientUseCode(responseJSON.get("client_use_code").asText()); + } + + logger.debug("NiceOn 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; + } + } +} diff --git a/src/main/java/com/eactive/eai/authserver/dao/ScopeDAO.java b/src/main/java/com/eactive/eai/authserver/dao/ScopeDAO.java index 83676f6..d24d681 100644 --- a/src/main/java/com/eactive/eai/authserver/dao/ScopeDAO.java +++ b/src/main/java/com/eactive/eai/authserver/dao/ScopeDAO.java @@ -31,9 +31,15 @@ public class ScopeDAO extends BaseDAO { Map> clientEntitymap = new HashMap<>(); scopeEntities.forEach(scopeEntity -> { - HashSet set = new HashSet<>(); - String scopeId = scopeEntity.getId().getBzwksvckeyname() + scopeEntity.getId().getScopeid(); - clientEntitymap.put(scopeId, set); + String apiId = scopeEntity.getId().getBzwksvckeyname(); + HashSet scopeSet = clientEntitymap.get(apiId); + if (scopeSet == null) { + scopeSet = new HashSet(); + clientEntitymap.put(apiId, scopeSet); + } + String scopeId = scopeEntity.getId().getScopeid(); + clientEntitymap.get(apiId).add(scopeId); +// clientEntitymap.put(scopeId, set); }); return clientEntitymap; @@ -48,7 +54,7 @@ public class ScopeDAO extends BaseDAO { ScopeEntity scopeEntity = scopeLoader.findByIdBzwksvckeyname(apiId); - scopeSet.add(scopeEntity.getId().getBzwksvckeyname()); + scopeSet.add(scopeEntity.getId().getScopeid()); return scopeSet; } catch (Exception e) { throw new DAOException(ExceptionUtil.getErrorCode(e, "RECEAIAUC001")); diff --git a/src/main/java/com/eactive/eai/common/inflow/InflowControlDAO.java b/src/main/java/com/eactive/eai/common/inflow/InflowControlDAO.java index 8ff7b3a..9cbd12e 100644 --- a/src/main/java/com/eactive/eai/common/inflow/InflowControlDAO.java +++ b/src/main/java/com/eactive/eai/common/inflow/InflowControlDAO.java @@ -26,6 +26,7 @@ import com.eactive.eai.data.entity.onl.inflow.InflowControlLogId; @Service @Transactional public class InflowControlDAO extends BaseDAO { + private static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); @Autowired @@ -227,4 +228,5 @@ public class InflowControlDAO extends BaseDAO { vo.setActivate(!"0".equals(group.getUseYn())); return vo; } + } \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/outbound/DefaultProcess.java b/src/main/java/com/eactive/eai/outbound/DefaultProcess.java index 691e07a..65e10d7 100644 --- a/src/main/java/com/eactive/eai/outbound/DefaultProcess.java +++ b/src/main/java/com/eactive/eai/outbound/DefaultProcess.java @@ -37,6 +37,7 @@ import com.eactive.eai.message.manager.StandardMessageManager; import com.eactive.eai.message.mapper.MessageMapper; import com.eactive.eai.message.service.InterfaceMapper; import com.ext.eai.common.stdmessage.STDMessageKeys; +import com.eactive.eai.common.message.ServiceMessage; // jwhong public abstract class DefaultProcess extends Process { protected static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); @@ -252,6 +253,13 @@ public abstract class DefaultProcess extends Process { } // 송신결과확인 checkSendResult(); + // 400 Async 에 대한 ack 오는것 500 으로 log에 저장, jwhong for kjbank + if(isAsyncSyncPattern()){ + StandardMessage standardMessage = this.resEaiMsg.getStandardMessage(); + standardMessage.setBizData(this.resObject, this.outboundCharset); + this.logPssSno = 500; + logSenderCtrlSendRes(); + } // SA if (isSyncAsyncPattern()) { try { @@ -474,6 +482,18 @@ public abstract class DefaultProcess extends Process { return false; } } + + // jwhong + public boolean isAsyncSyncPattern() { + ServiceMessage firstService = this.reqEaiMsg.getSvcMsg(0); + ServiceMessage secondService = this.reqEaiMsg.getSvcMsg(1); + if (EAIMessageKeys.ASYNC_SVC.equals(this.reqEaiMsg.getSvcTsmtUsgTp()) + && EAIMessageKeys.SYNC_SVC.equals(firstService.getPsvItfTp()) + && EAIMessageKeys.ASYNC_SVC.equals(secondService.getPsvItfTp())){ + return true; + } + return false; + } public boolean isSyncAsyncPattern() { if (!MessageUtil.checkRspErrCd(this.resEaiMsg.getRspErrCd())) { diff --git a/src/main/java/com/openbanking/eai/common/token/OAuth2AccessTokenVO.java b/src/main/java/com/openbanking/eai/common/token/OAuth2AccessTokenVO.java index 78d4fb5..920121c 100644 --- a/src/main/java/com/openbanking/eai/common/token/OAuth2AccessTokenVO.java +++ b/src/main/java/com/openbanking/eai/common/token/OAuth2AccessTokenVO.java @@ -24,6 +24,7 @@ public class OAuth2AccessTokenVO implements AccessTokenVO, Serializable private Date expiration; private String scope; private String clientUseCode; + private String clientId; public OAuth2AccessTokenVO() { @@ -35,6 +36,7 @@ public class OAuth2AccessTokenVO implements AccessTokenVO, Serializable this.expiration = oAuth2AccessTokenVO.getExpiration(); this.scope = oAuth2AccessTokenVO.getScope(); this.clientUseCode = oAuth2AccessTokenVO.getClientUseCode(); + this.clientId = oAuth2AccessTokenVO.getClientId(); } public String getAccessToken() { @@ -92,6 +94,14 @@ public class OAuth2AccessTokenVO implements AccessTokenVO, Serializable public void setClientUseCode(String clientUseCode) { this.clientUseCode = clientUseCode; } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } public String toString() { return ReflectionToStringBuilder.toString(this, ToStringStyle.DEFAULT_STYLE);