From e21f134c112c344be3d68affa2666d4a8238ed94 Mon Sep 17 00:00:00 2001 From: curry772 Date: Tue, 7 Apr 2026 00:02:02 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feature:=20DJErp=20OAuth=20bypass=20?= =?UTF-8?q?=EC=9D=B8=EB=B0=94=EC=9A=B4=EB=93=9C=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DJErpApiAdapterController: /dj/** 경로 전용 inbound 라우팅 컨트롤러 - DJErpApiAdapterService: DJErp 전용 어댑터 서비스 - DJErpOAuth2Controller: /dj/oauth/token, /dj/oauth2/token 토큰 발급 (Spring Security 우회) - DJErpOAuth2AccessTokenRequest/Response: 토큰 요청/응답 VO - HttpClient5AdapterServiceBypass: HttpClient5 기반 bypass 어댑터 (OAuth 토큰 자동 갱신 포함) - RestAdapterMethodUrlFallbackRequestAction: URI 경로 세그먼트 축소 fallback 키 탐색 Action - ApiAdapterController: 전체 주석처리 (비활성화) - elink-online-common 서브모듈 업데이트 Co-Authored-By: Claude Sonnet 4.6 --- elink-online-common | 2 +- elink-online-core | 2 +- elink-online-core-jpa | 2 +- .../controller/ApiAdapterController.java | 760 +++++++------- .../controller/DJErpApiAdapterController.java | 370 +++++++ .../service/DJErpApiAdapterService.java | 972 ++++++++++++++++++ .../custom/DJErpOAuth2AccessTokenRequest.java | 55 + .../DJErpOAuth2AccessTokenResponse.java | 79 ++ .../custom/DJErpOAuth2Controller.java | 266 +++++ .../impl/HttpClient5AdapterServiceBypass.java | 664 ++++++++++++ ...AdapterMethodUrlFallbackRequestAction.java | 104 ++ 11 files changed, 2893 insertions(+), 383 deletions(-) create mode 100644 src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java create mode 100644 src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java create mode 100644 src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenRequest.java create mode 100644 src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenResponse.java create mode 100644 src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java create mode 100644 src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java create mode 100644 src/main/java/com/eactive/eai/custom/inbound/action/RestAdapterMethodUrlFallbackRequestAction.java diff --git a/elink-online-common b/elink-online-common index 7aa230f..791e362 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 7aa230f1569e07ae734994f1cf1019dd93ffcccc +Subproject commit 791e362a744ae840b4b4f32965280175c492e857 diff --git a/elink-online-core b/elink-online-core index 8b24475..b27138d 160000 --- a/elink-online-core +++ b/elink-online-core @@ -1 +1 @@ -Subproject commit 8b24475d2d80ee8acad5c40996b4171dd615a7a0 +Subproject commit b27138d8f7994c83b51e281c47c06d51b82df770 diff --git a/elink-online-core-jpa b/elink-online-core-jpa index 92aedde..76342bf 160000 --- a/elink-online-core-jpa +++ b/elink-online-core-jpa @@ -1 +1 @@ -Subproject commit 92aeddecd0233cbfd971ed52c33a20834711a3aa +Subproject commit 76342bfaef2578ce322791c9f9a862dd98fb3010 diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java index 4f2cb8b..c9b82d0 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java @@ -1,380 +1,380 @@ -package com.eactive.eai.adapter.controller; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.StringUtils; -// jwhong -import org.json.simple.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.util.AntPathMatcher; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -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.Keys; -import com.eactive.eai.adapter.http.HttpStatusException; -import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; -import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; -import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterManager; -import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterUri; -import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter; -import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb; -import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; -import com.eactive.eai.adapter.service.ApiAdapterService; -import com.eactive.eai.common.TransactionContextKeys; -import com.eactive.eai.common.exception.ExceptionUtil; -import com.eactive.eai.common.server.EAIServerManager; -import com.eactive.eai.common.stdmessage.STDMessageManager; -import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO; -import com.eactive.eai.common.util.DatetimeUtil; -import com.eactive.eai.common.util.InboundErrorLogger; -import com.eactive.eai.common.util.Logger; -import com.eactive.eai.common.util.MessageUtil; -import com.eactive.eai.common.util.UUIDGenerator; -import com.eactive.eai.inbound.action.ActionException; -import com.eactive.eai.inbound.action.ActionFactory; -import com.eactive.eai.inbound.action.RequestAction; -import com.eactive.eai.inbound.error.InboundErrorInfoVO; - -@RestController -public class ApiAdapterController implements HttpAdapterServiceKey { - static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); - - @Autowired - ApiAdapterService service; - - /** - * KJBANK 요구사항으로 /mapi/oauth2/token 과 같은 형태로 토큰 발급거래를 수행해야해서 예외처리함 - * - * @See com.eactive.eai.authserver.config.AuthorizationServerConfig.configure(AuthorizationServerEndpointsConfigurer - * endpoints) - */ - @RequestMapping(value = { "/api/*", "/mapi/*", "/api/*/{path:^(?!.*oauth).*$}/**", - "/mapi/{path:^(?!.*oauth2).*$}/**" }) -// @RequestMapping(value = {"/api/**"}) - public ResponseEntity callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws Exception { - // /ONLWeb/api/v1/public/getUserInfo.svc - String apiUri = servletRequest.getRequestURI(); - // /api/v1/public/getUserInfo.svc - apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath()); - apiUri = StringUtils.removeEnd(apiUri, "/"); - - HttpDynamicInAdapterUri adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), ""); - - if (logger.isDebug()) { - 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 - long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함 - String receivedTimeStr = String.valueOf(receivedTimeMillis); - - String adapterGroupName = adptUri.getAdptGrpName(); - String adapterName = adptUri.getAdptName(); - AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); - AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName); - if (adapterVO == null) { - String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, - "Adapter not found error"); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) - .body(errorMsg); - } - - Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName()); - - String adptMsgType = adapterVO.getAdapterGroupVO().getMessageType(); - String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8"); - MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode); - String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT); - - String responseType = httpProp.getProperty(RESPONSE_TYPE, "SYNC"); - ResponseEntity responseEntity = null; - Properties transactionProp = new Properties(); - // jwhong, put api received time, eaiSvcCode - transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr); - - // ** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다 -// String bzwkSvcKeyName = ""; // Adapter Group별 Action Class에 따라 구성이 달라짐. 예) -// // _AGW_IN_RST_SyS:POST/account/{acc_no} -// String adapterGrpName = ""; // Adapter Group명 예) _AGW_IN_RST_SyS : API_PATH(/api/test) - String apiSvcCode = ""; // eaiSvcCd 예) LONNCHCON00005S2 - String apiFullPathKey = ""; // 예) POST|/api/test/account/list, POST|/api/test/account/{acc_no} - Map pathVariables = null; - try { - // PathVariable(ex:/api/test/account/{acc_no}) 대응 및 DAO조회 제거. - // /api/test/account/1234567 호출시 /api/test/account/{acc_no} API 로 식별. - -// String methodAndUri = getRequestRuledPath(servletRequest, apiUri, adapterVO, transactionProp); - String methodAndUri = servletRequest.getMethod() + "|" + apiUri; - STDMessageManager manager = STDMessageManager.getInstance(); - STDMsgInfoAddOnVO stdMsgInfo = manager.getStdMsgInfoAddOn(methodAndUri); -// bzwkSvcKeyName = stdMsgInfo.getBzwksvckeyname(); - apiSvcCode = stdMsgInfo.getEaiSvcCd(); - transactionProp.put(API_SERVICE_CODE, apiSvcCode); - -// adapterGrpName = stdMsgInfo.gAdapterGroupName(); - apiFullPathKey = stdMsgInfo.getApiFullPath(); - if (STDMessageManager.isPathVariable(apiFullPathKey)) { - pathVariables = new AntPathMatcher().extractUriTemplateVariables(apiFullPathKey, methodAndUri); - } - -// adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), adapterGrpName); - } catch (Exception e) { -// adptUri = null; - } - // *** - if (pathVariables != null) { - transactionProp.put(INBOUND_PATH_VARIABLES, pathVariables); - } - - try { - String responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO, - transactionProp); - // if (RESPONSE_TYPE_ASYNC.equals(responseType)) { // table의 값이 ASYNC 로 들어가 있는데 - // response_type_async는 ASYN 로 되어 있어 아래처럼 비교함 jwhong 2025 - if ("ASYNC".equals(responseType)) { - // responseEntity = ResponseEntity.ok("dummy"); // comment by jwhong - // servletResponse.addHeader("traceId", responseData); // uuid를 response header에 - servletResponse.addHeader("traceId", - transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에 - int httpStatus = servletResponse.getStatus(); - if (httpStatus == 200) { - // 업체별 aync response message 가 다르다. - String asyncMsgStyle = httpProp.getProperty("ASYNC_RTNMSG_TYPE", ""); - String responseBody = ""; - boolean encryptAsyncAckApply = StringUtils - .equalsIgnoreCase(httpProp.getProperty("ASYNC_ENCRYPT_ACK", "N"), "Y"); - - if (asyncMsgStyle == null || asyncMsgStyle.trim().isEmpty()) { - responseBody = makeResponseBodyMsg(); - } else { - responseBody = asyncMsgStyle; - } - if (encryptAsyncAckApply) { - responseBody = service.doPostEncryption(responseBody, transactionProp, servletRequest); - } - - responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseBody); - // jwhong - } else { - responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData); - } - } else { -// responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData); -// 버즈빌 포인트 적립 처리하기 위하여 정상응답이더라도 응답코드(apiRsltCd) 값이 200이 아닌 경우 처리를 위하여 수정 -// Filter에서 설정한 response status값을 responseEntity 생성시 적용 modify by lwk 2025.03.24 - - servletResponse.addHeader("traceId", - transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에 - int httpStatus = servletResponse.getStatus(); - if (httpStatus != 200) { - responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData); - } else { - responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData); - } - } - - } catch (Exception e) { - - - logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); - - String errorMsg = null; - - if( e instanceof HttpStatusException ) { - logger.warn("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage()); - HttpStatusException e1 = (HttpStatusException) e; - errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, - MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat); - responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(errorMsg); - } - else if( e instanceof JwtAuthException ) - { - logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); - errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, - MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage(), errorResponseFormat); - responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(errorMsg); - } - else - { - logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); - errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, - MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat); - responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType) - .body(errorMsg); - } - String postFilterNames = httpProp.getProperty("POST_FILTERS",""); - - if( postFilterNames.contains("HMAC_SHA256") ) { - HttpAdapterFilter filter = HttpAdapterFilterFactoryKjb.createFilter("HMAC_SHA256"); - filter.doPostFilter(adapterGroupName, postFilterNames, errorMsg, transactionProp, servletRequest, servletResponse); - } - - - } finally { - /** - * 로깅 인터셉터에 데이터를 전달하기 위한 처리 이미 거래처리가 끝나서 영향도가 없을만한 servletRequest에 Attribute로 전달 - * 추후 더 좋은방법이 생길경우 개선 요망 - */ - try { - - servletRequest.setAttribute(TransactionContextKeys.TRANSACTION_PROP, transactionProp); - - if (!"ASYNC".equals(responseType)) { - String uuid = transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID); - String url = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_EXTURI); - String method = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_METHOD); - int httpStatusCode = servletResponse.getStatus(); - - Map headerMap = new HashMap<>(); - // 응답 헤더 로깅 - for (String headerName : servletResponse.getHeaderNames()) { - String headerValue = servletResponse.getHeader(headerName); - logger.debug(String.format("httpHeader logging headerKey=%s, headerValue=%s", - headerName, headerValue)); - headerMap.put(headerName, headerValue); - } - //HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, 400, adapterGroupName,adapterName, headerMap, url, method, httpStatusCode); - } - } catch (Exception e) { - logger.warn("http header db logging fail.", e); - } - } - return responseEntity; - } - - /** - * url에서 뒤쪽 /이후를 제거하면서 찾는다.
- * /api/v1/public/getUserInfo.svc - * - * @param apiUri - * @return - */ - private HttpDynamicInAdapterUri findAdptUri(String apiUri, HttpDynamicInAdapterManager manager, - String adapterGrpName) throws Exception { - if (StringUtils.isBlank(apiUri)) { - return null; - } - - // * manager 출력 - logger.warn("===== adptUriMap 상세 내용 ====="); - for (Map.Entry entry : manager.adptUriMap.entrySet()) { - HttpDynamicInAdapterUri uriObj = entry.getValue(); - logger.warn("GroupName=" + uriObj.getAdptGrpName() + ", AdapterName=" + uriObj.getAdptName() + ", URI=" - + uriObj.getUri()); - } - logger.warn("================================"); - - // - for (int i = 0; i < 10; i++) { - HttpDynamicInAdapterUri adptUri = manager.getAdptUri(apiUri); - if (adptUri != null) { - if (StringUtils.isNotEmpty(adapterGrpName)) { - if(adptUri.getAdptGrpName().equals(adapterGrpName)) { - return adptUri; - } - } else { - return adptUri; - } - } - - // /api/v1/public - apiUri = StringUtils.substringBeforeLast(apiUri, "/"); - if (apiUri.length() < 4) { // /api - return null; - } - } - - return null; - } - - private void logError(HttpServletRequest request) { - InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO(); - - EAIServerManager eaiServerManager = EAIServerManager.getInstance(); - String serverName = eaiServerManager.getLocalServerName(); - String uuid = null; - StringBuffer sb = new StringBuffer(); - - String instanceid1 = serverName.substring(0, 2); - String instanceid2 = serverName.substring(serverName.length() - 2, serverName.length()); - String instid = instanceid1 + instanceid2; - uuid = instid + UUIDGenerator.getUUID(); - - String[] msgArgs = new String[1]; - msgArgs[0] = request.getRequestURI(); - - String errorCode = "RECEAIIRP010"; - String errorMsg = ExceptionUtil.make(new Exception("cat not find uri"), errorCode, msgArgs); - - errorInfoVO.setEaiSvcSno(uuid); // EAI서비스일련번호 - errorInfoVO.setAdptBwkGrpNm("HTTP_IN_NO_URI"); // 어댑터업무그룹명 - errorInfoVO.setErrCd(errorCode); // 에러코드 - errorInfoVO.setErrTxt(errorMsg); // 에러내용 - errorInfoVO.setErrTm(DatetimeUtil.getCurrentTime(new Date().getTime())); // 에러발생시각 - errorInfoVO.setErrDstcd(" "); - sb.append("Remote Addr : ").append(request.getRemoteAddr()).append("\n").append("Request URI : ") - .append(request.getRequestURI()); - errorInfoVO.setBwkDataTxt(sb.toString()); // 업무데이터내용 - errorInfoVO.setEaiSvrInstNm(serverName); // EAI서버인스턴스명 - - InboundErrorLogger.error(errorInfoVO); - } - - private String makeResponseBodyMsg() { - - Map dataMap = new HashMap<>(); - dataMap.put("result", 1); - Map map = new HashMap<>(); - - map.put("code", "200"); - map.put("data", dataMap); - map.put("message", "정상 처리 되었습니다."); - - JSONObject json = new JSONObject(); - json.putAll(map); - - return json.toJSONString(); - } - - /* - * private String makeResponseBodyMsg(String asyncMsgStyle) { - * - * Map dataMap = new HashMap<>(); dataMap.put("result", 1); - * Map map = new HashMap<>(); - * - * switch (asyncMsgStyle) { case "TB_SUC": map.put("rspCode", "TB_SUC_000"); - * map.put("rspMsg", "정상"); map.put("data", dataMap); break; case "ONLYDATA": - * map.put("data", dataMap); map.put("success", "true"); break; default: - * map.put("code", "200"); map.put("data", dataMap); map.put("message", - * "정상 처리 되었습니다."); break; } - * - * JSONObject json = new JSONObject(); json.putAll(map); - * - * return json.toJSONString(); } - */ -} \ No newline at end of file +//package com.eactive.eai.adapter.controller; +// +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.Date; +//import java.util.Enumeration; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.Properties; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import org.apache.commons.lang3.StringUtils; +//// jwhong +//import org.json.simple.JSONObject; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.MediaType; +//import org.springframework.http.ResponseEntity; +//import org.springframework.util.AntPathMatcher; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RestController; +// +//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.Keys; +//import com.eactive.eai.adapter.http.HttpStatusException; +//import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; +//import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +//import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterManager; +//import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterUri; +//import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter; +//import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb; +//import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; +//import com.eactive.eai.adapter.service.ApiAdapterService; +//import com.eactive.eai.common.TransactionContextKeys; +//import com.eactive.eai.common.exception.ExceptionUtil; +//import com.eactive.eai.common.server.EAIServerManager; +//import com.eactive.eai.common.stdmessage.STDMessageManager; +//import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO; +//import com.eactive.eai.common.util.DatetimeUtil; +//import com.eactive.eai.common.util.InboundErrorLogger; +//import com.eactive.eai.common.util.Logger; +//import com.eactive.eai.common.util.MessageUtil; +//import com.eactive.eai.common.util.UUIDGenerator; +//import com.eactive.eai.inbound.action.ActionException; +//import com.eactive.eai.inbound.action.ActionFactory; +//import com.eactive.eai.inbound.action.RequestAction; +//import com.eactive.eai.inbound.error.InboundErrorInfoVO; +// +//@RestController +//public class ApiAdapterController implements HttpAdapterServiceKey { +// static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); +// +// @Autowired +// ApiAdapterService service; +// +// /** +// * KJBANK 요구사항으로 /mapi/oauth2/token 과 같은 형태로 토큰 발급거래를 수행해야해서 예외처리함 +// * +// * @See com.eactive.eai.authserver.config.AuthorizationServerConfig.configure(AuthorizationServerEndpointsConfigurer +// * endpoints) +// */ +// @RequestMapping(value = { "/api/*", "/mapi/*", "/api/*/{path:^(?!.*oauth).*$}/**", +// "/mapi/{path:^(?!.*oauth2).*$}/**" }) +//// @RequestMapping(value = {"/api/**"}) +// public ResponseEntity callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse) +// throws Exception { +// // /ONLWeb/api/v1/public/getUserInfo.svc +// String apiUri = servletRequest.getRequestURI(); +// // /api/v1/public/getUserInfo.svc +// apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath()); +// apiUri = StringUtils.removeEnd(apiUri, "/"); +// +// HttpDynamicInAdapterUri adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), ""); +// +// if (logger.isDebug()) { +// 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 +// long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함 +// String receivedTimeStr = String.valueOf(receivedTimeMillis); +// +// String adapterGroupName = adptUri.getAdptGrpName(); +// String adapterName = adptUri.getAdptName(); +// AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); +// AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName); +// if (adapterVO == null) { +// String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, +// "Adapter not found error"); +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) +// .body(errorMsg); +// } +// +// Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName()); +// +// String adptMsgType = adapterVO.getAdapterGroupVO().getMessageType(); +// String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8"); +// MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode); +// String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT); +// +// String responseType = httpProp.getProperty(RESPONSE_TYPE, "SYNC"); +// ResponseEntity responseEntity = null; +// Properties transactionProp = new Properties(); +// // jwhong, put api received time, eaiSvcCode +// transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr); +// +// // ** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다 +//// String bzwkSvcKeyName = ""; // Adapter Group별 Action Class에 따라 구성이 달라짐. 예) +//// // _AGW_IN_RST_SyS:POST/account/{acc_no} +//// String adapterGrpName = ""; // Adapter Group명 예) _AGW_IN_RST_SyS : API_PATH(/api/test) +// String apiSvcCode = ""; // eaiSvcCd 예) LONNCHCON00005S2 +// String apiFullPathKey = ""; // 예) POST|/api/test/account/list, POST|/api/test/account/{acc_no} +// Map pathVariables = null; +// try { +// // PathVariable(ex:/api/test/account/{acc_no}) 대응 및 DAO조회 제거. +// // /api/test/account/1234567 호출시 /api/test/account/{acc_no} API 로 식별. +// +//// String methodAndUri = getRequestRuledPath(servletRequest, apiUri, adapterVO, transactionProp); +// String methodAndUri = servletRequest.getMethod() + "|" + apiUri; +// STDMessageManager manager = STDMessageManager.getInstance(); +// STDMsgInfoAddOnVO stdMsgInfo = manager.getStdMsgInfoAddOn(methodAndUri); +//// bzwkSvcKeyName = stdMsgInfo.getBzwksvckeyname(); +// apiSvcCode = stdMsgInfo.getEaiSvcCd(); +// transactionProp.put(API_SERVICE_CODE, apiSvcCode); +// +//// adapterGrpName = stdMsgInfo.gAdapterGroupName(); +// apiFullPathKey = stdMsgInfo.getApiFullPath(); +// if (STDMessageManager.isPathVariable(apiFullPathKey)) { +// pathVariables = new AntPathMatcher().extractUriTemplateVariables(apiFullPathKey, methodAndUri); +// } +// +//// adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), adapterGrpName); +// } catch (Exception e) { +//// adptUri = null; +// } +// // *** +// if (pathVariables != null) { +// transactionProp.put(INBOUND_PATH_VARIABLES, pathVariables); +// } +// +// try { +// String responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO, +// transactionProp); +// // if (RESPONSE_TYPE_ASYNC.equals(responseType)) { // table의 값이 ASYNC 로 들어가 있는데 +// // response_type_async는 ASYN 로 되어 있어 아래처럼 비교함 jwhong 2025 +// if ("ASYNC".equals(responseType)) { +// // responseEntity = ResponseEntity.ok("dummy"); // comment by jwhong +// // servletResponse.addHeader("traceId", responseData); // uuid를 response header에 +// servletResponse.addHeader("traceId", +// transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에 +// int httpStatus = servletResponse.getStatus(); +// if (httpStatus == 200) { +// // 업체별 aync response message 가 다르다. +// String asyncMsgStyle = httpProp.getProperty("ASYNC_RTNMSG_TYPE", ""); +// String responseBody = ""; +// boolean encryptAsyncAckApply = StringUtils +// .equalsIgnoreCase(httpProp.getProperty("ASYNC_ENCRYPT_ACK", "N"), "Y"); +// +// if (asyncMsgStyle == null || asyncMsgStyle.trim().isEmpty()) { +// responseBody = makeResponseBodyMsg(); +// } else { +// responseBody = asyncMsgStyle; +// } +// if (encryptAsyncAckApply) { +// responseBody = service.doPostEncryption(responseBody, transactionProp, servletRequest); +// } +// +// responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseBody); +// // jwhong +// } else { +// responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData); +// } +// } else { +//// responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData); +//// 버즈빌 포인트 적립 처리하기 위하여 정상응답이더라도 응답코드(apiRsltCd) 값이 200이 아닌 경우 처리를 위하여 수정 +//// Filter에서 설정한 response status값을 responseEntity 생성시 적용 modify by lwk 2025.03.24 +// +// servletResponse.addHeader("traceId", +// transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에 +// int httpStatus = servletResponse.getStatus(); +// if (httpStatus != 200) { +// responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData); +// } else { +// responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData); +// } +// } +// +// } catch (Exception e) { +// +// +// logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); +// +// String errorMsg = null; +// +// if( e instanceof HttpStatusException ) { +// logger.warn("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage()); +// HttpStatusException e1 = (HttpStatusException) e; +// errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, +// MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat); +// responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(errorMsg); +// } +// else if( e instanceof JwtAuthException ) +// { +// logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); +// errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, +// MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage(), errorResponseFormat); +// responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(errorMsg); +// } +// else +// { +// logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); +// errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, +// MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat); +// responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType) +// .body(errorMsg); +// } +// String postFilterNames = httpProp.getProperty("POST_FILTERS",""); +// +// if( postFilterNames.contains("HMAC_SHA256") ) { +// HttpAdapterFilter filter = HttpAdapterFilterFactoryKjb.createFilter("HMAC_SHA256"); +// filter.doPostFilter(adapterGroupName, postFilterNames, errorMsg, transactionProp, servletRequest, servletResponse); +// } +// +// +// } finally { +// /** +// * 로깅 인터셉터에 데이터를 전달하기 위한 처리 이미 거래처리가 끝나서 영향도가 없을만한 servletRequest에 Attribute로 전달 +// * 추후 더 좋은방법이 생길경우 개선 요망 +// */ +// try { +// +// servletRequest.setAttribute(TransactionContextKeys.TRANSACTION_PROP, transactionProp); +// +// if (!"ASYNC".equals(responseType)) { +// String uuid = transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID); +// String url = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_EXTURI); +// String method = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_METHOD); +// int httpStatusCode = servletResponse.getStatus(); +// +// Map headerMap = new HashMap<>(); +// // 응답 헤더 로깅 +// for (String headerName : servletResponse.getHeaderNames()) { +// String headerValue = servletResponse.getHeader(headerName); +// logger.debug(String.format("httpHeader logging headerKey=%s, headerValue=%s", +// headerName, headerValue)); +// headerMap.put(headerName, headerValue); +// } +// //HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, 400, adapterGroupName,adapterName, headerMap, url, method, httpStatusCode); +// } +// } catch (Exception e) { +// logger.warn("http header db logging fail.", e); +// } +// } +// return responseEntity; +// } +// +// /** +// * url에서 뒤쪽 /이후를 제거하면서 찾는다.
+// * /api/v1/public/getUserInfo.svc +// * +// * @param apiUri +// * @return +// */ +// private HttpDynamicInAdapterUri findAdptUri(String apiUri, HttpDynamicInAdapterManager manager, +// String adapterGrpName) throws Exception { +// if (StringUtils.isBlank(apiUri)) { +// return null; +// } +// +// // * manager 출력 +// logger.warn("===== adptUriMap 상세 내용 ====="); +// for (Map.Entry entry : manager.adptUriMap.entrySet()) { +// HttpDynamicInAdapterUri uriObj = entry.getValue(); +// logger.warn("GroupName=" + uriObj.getAdptGrpName() + ", AdapterName=" + uriObj.getAdptName() + ", URI=" +// + uriObj.getUri()); +// } +// logger.warn("================================"); +// +// // +// for (int i = 0; i < 10; i++) { +// HttpDynamicInAdapterUri adptUri = manager.getAdptUri(apiUri); +// if (adptUri != null) { +// if (StringUtils.isNotEmpty(adapterGrpName)) { +// if(adptUri.getAdptGrpName().equals(adapterGrpName)) { +// return adptUri; +// } +// } else { +// return adptUri; +// } +// } +// +// // /api/v1/public +// apiUri = StringUtils.substringBeforeLast(apiUri, "/"); +// if (apiUri.length() < 4) { // /api +// return null; +// } +// } +// +// return null; +// } +// +// private void logError(HttpServletRequest request) { +// InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO(); +// +// EAIServerManager eaiServerManager = EAIServerManager.getInstance(); +// String serverName = eaiServerManager.getLocalServerName(); +// String uuid = null; +// StringBuffer sb = new StringBuffer(); +// +// String instanceid1 = serverName.substring(0, 2); +// String instanceid2 = serverName.substring(serverName.length() - 2, serverName.length()); +// String instid = instanceid1 + instanceid2; +// uuid = instid + UUIDGenerator.getUUID(); +// +// String[] msgArgs = new String[1]; +// msgArgs[0] = request.getRequestURI(); +// +// String errorCode = "RECEAIIRP010"; +// String errorMsg = ExceptionUtil.make(new Exception("cat not find uri"), errorCode, msgArgs); +// +// errorInfoVO.setEaiSvcSno(uuid); // EAI서비스일련번호 +// errorInfoVO.setAdptBwkGrpNm("HTTP_IN_NO_URI"); // 어댑터업무그룹명 +// errorInfoVO.setErrCd(errorCode); // 에러코드 +// errorInfoVO.setErrTxt(errorMsg); // 에러내용 +// errorInfoVO.setErrTm(DatetimeUtil.getCurrentTime(new Date().getTime())); // 에러발생시각 +// errorInfoVO.setErrDstcd(" "); +// sb.append("Remote Addr : ").append(request.getRemoteAddr()).append("\n").append("Request URI : ") +// .append(request.getRequestURI()); +// errorInfoVO.setBwkDataTxt(sb.toString()); // 업무데이터내용 +// errorInfoVO.setEaiSvrInstNm(serverName); // EAI서버인스턴스명 +// +// InboundErrorLogger.error(errorInfoVO); +// } +// +// private String makeResponseBodyMsg() { +// +// Map dataMap = new HashMap<>(); +// dataMap.put("result", 1); +// Map map = new HashMap<>(); +// +// map.put("code", "200"); +// map.put("data", dataMap); +// map.put("message", "정상 처리 되었습니다."); +// +// JSONObject json = new JSONObject(); +// json.putAll(map); +// +// return json.toJSONString(); +// } +// +// /* +// * private String makeResponseBodyMsg(String asyncMsgStyle) { +// * +// * Map dataMap = new HashMap<>(); dataMap.put("result", 1); +// * Map map = new HashMap<>(); +// * +// * switch (asyncMsgStyle) { case "TB_SUC": map.put("rspCode", "TB_SUC_000"); +// * map.put("rspMsg", "정상"); map.put("data", dataMap); break; case "ONLYDATA": +// * map.put("data", dataMap); map.put("success", "true"); break; default: +// * map.put("code", "200"); map.put("data", dataMap); map.put("message", +// * "정상 처리 되었습니다."); break; } +// * +// * JSONObject json = new JSONObject(); json.putAll(map); +// * +// * return json.toJSONString(); } +// */ +//} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java new file mode 100644 index 0000000..06b5502 --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java @@ -0,0 +1,370 @@ +package com.eactive.eai.adapter.controller; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +// jwhong +import org.json.simple.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +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.http.HttpStatusException; +import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterManager; +import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterUri; +import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter; +import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb; +import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; +import com.eactive.eai.adapter.service.DJErpApiAdapterService; +import com.eactive.eai.common.TransactionContextKeys; +import com.eactive.eai.common.exception.ExceptionUtil; +import com.eactive.eai.common.server.EAIServerManager; +import com.eactive.eai.common.stdmessage.STDMessageManager; +import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO; +import com.eactive.eai.common.util.DatetimeUtil; +import com.eactive.eai.common.util.InboundErrorLogger; +import com.eactive.eai.common.util.Logger; +import com.eactive.eai.common.util.MessageUtil; +import com.eactive.eai.common.util.UUIDGenerator; +import com.eactive.eai.inbound.error.InboundErrorInfoVO; + +@RestController +public class DJErpApiAdapterController implements HttpAdapterServiceKey { + static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); + + @Autowired + DJErpApiAdapterService service; + + /** + * DJErp OAuth 조기 적용을 위한 Controller + * + * @See com.eactive.eai.authserver.config.AuthorizationServerConfig.configure(AuthorizationServerEndpointsConfigurer + * endpoints) + */ + @RequestMapping(value = { "/dj/{path:^(?!oauth).*$}", "/dj/{path:^(?!oauth).*$}/**" }) + public ResponseEntity callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse) + throws Exception { + // /ONLWeb/api/v1/public/getUserInfo.svc + String apiUri = servletRequest.getRequestURI(); + // /api/v1/public/getUserInfo.svc + apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath()); + apiUri = StringUtils.removeEnd(apiUri, "/"); + + HttpDynamicInAdapterUri adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), ""); + + if (logger.isDebug()) { + 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 + long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함 + String receivedTimeStr = String.valueOf(receivedTimeMillis); + + String adapterGroupName = adptUri.getAdptGrpName(); + String adapterName = adptUri.getAdptName(); + AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); + AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName); + if (adapterVO == null) { + String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, + "Adapter not found error"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) + .body(errorMsg); + } + + Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName()); + + String adptMsgType = adapterVO.getAdapterGroupVO().getMessageType(); + String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8"); + MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode); + String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT); + + String responseType = httpProp.getProperty(RESPONSE_TYPE, "SYNC"); + ResponseEntity responseEntity = null; + Properties transactionProp = new Properties(); + // jwhong, put api received time, eaiSvcCode + transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr); + + // ** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다 +// String bzwkSvcKeyName = ""; // Adapter Group별 Action Class에 따라 구성이 달라짐. 예) +// // _AGW_IN_RST_SyS:POST/account/{acc_no} +// String adapterGrpName = ""; // Adapter Group명 예) _AGW_IN_RST_SyS : API_PATH(/api/test) + String apiSvcCode = ""; // eaiSvcCd 예) LONNCHCON00005S2 + String apiFullPathKey = ""; // 예) POST|/api/test/account/list, POST|/api/test/account/{acc_no} + Map pathVariables = null; + try { + // PathVariable(ex:/api/test/account/{acc_no}) 대응 및 DAO조회 제거. + // /api/test/account/1234567 호출시 /api/test/account/{acc_no} API 로 식별. + +// String methodAndUri = getRequestRuledPath(servletRequest, apiUri, adapterVO, transactionProp); + String methodAndUri = servletRequest.getMethod() + "|" + apiUri; + STDMessageManager manager = STDMessageManager.getInstance(); + STDMsgInfoAddOnVO stdMsgInfo = manager.getStdMsgInfoAddOn(methodAndUri); +// bzwkSvcKeyName = stdMsgInfo.getBzwksvckeyname(); + apiSvcCode = stdMsgInfo.getEaiSvcCd(); + transactionProp.put(API_SERVICE_CODE, apiSvcCode); + +// adapterGrpName = stdMsgInfo.gAdapterGroupName(); + apiFullPathKey = stdMsgInfo.getApiFullPath(); + if (STDMessageManager.isPathVariable(apiFullPathKey)) { + pathVariables = new AntPathMatcher().extractUriTemplateVariables(apiFullPathKey, methodAndUri); + } + +// adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), adapterGrpName); + } catch (Exception e) { +// adptUri = null; + } + // *** + if (pathVariables != null) { + transactionProp.put(INBOUND_PATH_VARIABLES, pathVariables); + } + + try { + String responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO, + transactionProp); + // if (RESPONSE_TYPE_ASYNC.equals(responseType)) { // table의 값이 ASYNC 로 들어가 있는데 + // response_type_async는 ASYN 로 되어 있어 아래처럼 비교함 jwhong 2025 + if ("ASYNC".equals(responseType)) { + // responseEntity = ResponseEntity.ok("dummy"); // comment by jwhong + // servletResponse.addHeader("traceId", responseData); // uuid를 response header에 + servletResponse.addHeader("traceId", + transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에 + int httpStatus = servletResponse.getStatus(); + if (httpStatus == 200) { + // 업체별 aync response message 가 다르다. + String asyncMsgStyle = httpProp.getProperty("ASYNC_RTNMSG_TYPE", ""); + String responseBody = ""; + boolean encryptAsyncAckApply = StringUtils + .equalsIgnoreCase(httpProp.getProperty("ASYNC_ENCRYPT_ACK", "N"), "Y"); + + if (asyncMsgStyle == null || asyncMsgStyle.trim().isEmpty()) { + responseBody = makeResponseBodyMsg(); + } else { + responseBody = asyncMsgStyle; + } + if (encryptAsyncAckApply) { + responseBody = service.doPostEncryption(responseBody, transactionProp, servletRequest); + } + + responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseBody); + // jwhong + } else { + responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData); + } + } else { +// responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData); +// 버즈빌 포인트 적립 처리하기 위하여 정상응답이더라도 응답코드(apiRsltCd) 값이 200이 아닌 경우 처리를 위하여 수정 +// Filter에서 설정한 response status값을 responseEntity 생성시 적용 modify by lwk 2025.03.24 + + servletResponse.addHeader("traceId", + transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID)); // uuid를 response header에 + int httpStatus = servletResponse.getStatus(); + if (httpStatus != 200) { + responseEntity = ResponseEntity.status(httpStatus).contentType(mediaType).body(responseData); + } else { + responseEntity = ResponseEntity.ok().contentType(mediaType).body(responseData); + } + } + + } catch (Exception e) { + + + logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); + + String errorMsg = null; + + if( e instanceof HttpStatusException ) { + logger.warn("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage()); + HttpStatusException e1 = (HttpStatusException) e; + errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, + MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat); + responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(errorMsg); + } + else if( e instanceof JwtAuthException ) + { + logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); + errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, + MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage(), errorResponseFormat); + responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(errorMsg); + } + else + { + logger.error(adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e); + errorMsg = MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode, + MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage(), errorResponseFormat); + responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(mediaType) + .body(errorMsg); + } + String postFilterNames = httpProp.getProperty("POST_FILTERS",""); + + if( postFilterNames.contains("HMAC_SHA256") ) { + HttpAdapterFilter filter = HttpAdapterFilterFactoryKjb.createFilter("HMAC_SHA256"); + filter.doPostFilter(adapterGroupName, postFilterNames, errorMsg, transactionProp, servletRequest, servletResponse); + } + + + } finally { + /** + * 로깅 인터셉터에 데이터를 전달하기 위한 처리 이미 거래처리가 끝나서 영향도가 없을만한 servletRequest에 Attribute로 전달 + * 추후 더 좋은방법이 생길경우 개선 요망 + */ + try { + + servletRequest.setAttribute(TransactionContextKeys.TRANSACTION_PROP, transactionProp); + + if (!"ASYNC".equals(responseType)) { + String uuid = transactionProp.getProperty(TransactionContextKeys.TRANSACTION_UUID); + String url = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_EXTURI); + String method = transactionProp.getProperty(HttpClientAdapterServiceKey.INBOUND_METHOD); + int httpStatusCode = servletResponse.getStatus(); + + Map headerMap = new HashMap<>(); + // 응답 헤더 로깅 + for (String headerName : servletResponse.getHeaderNames()) { + String headerValue = servletResponse.getHeader(headerName); + logger.debug(String.format("httpHeader logging headerKey=%s, headerValue=%s", + headerName, headerValue)); + headerMap.put(headerName, headerValue); + } + //HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, 400, adapterGroupName,adapterName, headerMap, url, method, httpStatusCode); + } + } catch (Exception e) { + logger.warn("http header db logging fail.", e); + } + } + return responseEntity; + } + + /** + * url에서 뒤쪽 /이후를 제거하면서 찾는다.
+ * /api/v1/public/getUserInfo.svc + * + * @param apiUri + * @return + */ + private HttpDynamicInAdapterUri findAdptUri(String apiUri, HttpDynamicInAdapterManager manager, + String adapterGrpName) throws Exception { + if (StringUtils.isBlank(apiUri)) { + return null; + } + + // * manager 출력 + logger.warn("===== adptUriMap 상세 내용 ====="); + for (Map.Entry entry : manager.adptUriMap.entrySet()) { + HttpDynamicInAdapterUri uriObj = entry.getValue(); + logger.warn("GroupName=" + uriObj.getAdptGrpName() + ", AdapterName=" + uriObj.getAdptName() + ", URI=" + + uriObj.getUri()); + } + logger.warn("================================"); + + // + for (int i = 0; i < 10; i++) { + HttpDynamicInAdapterUri adptUri = manager.getAdptUri(apiUri); + if (adptUri != null) { + if (StringUtils.isNotEmpty(adapterGrpName)) { + if(adptUri.getAdptGrpName().equals(adapterGrpName)) { + return adptUri; + } + } else { + return adptUri; + } + } + + // /api/v1/public + apiUri = StringUtils.substringBeforeLast(apiUri, "/"); + if (apiUri.length() < 4) { // /api + return null; + } + } + + return null; + } + + private void logError(HttpServletRequest request) { + InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO(); + + EAIServerManager eaiServerManager = EAIServerManager.getInstance(); + String serverName = eaiServerManager.getLocalServerName(); + String uuid = null; + StringBuffer sb = new StringBuffer(); + + String instanceid1 = serverName.substring(0, 2); + String instanceid2 = serverName.substring(serverName.length() - 2, serverName.length()); + String instid = instanceid1 + instanceid2; + uuid = instid + UUIDGenerator.getUUID(); + + String[] msgArgs = new String[1]; + msgArgs[0] = request.getRequestURI(); + + String errorCode = "RECEAIIRP010"; + String errorMsg = ExceptionUtil.make(new Exception("cat not find uri"), errorCode, msgArgs); + + errorInfoVO.setEaiSvcSno(uuid); // EAI서비스일련번호 + errorInfoVO.setAdptBwkGrpNm("HTTP_IN_NO_URI"); // 어댑터업무그룹명 + errorInfoVO.setErrCd(errorCode); // 에러코드 + errorInfoVO.setErrTxt(errorMsg); // 에러내용 + errorInfoVO.setErrTm(DatetimeUtil.getCurrentTime(new Date().getTime())); // 에러발생시각 + errorInfoVO.setErrDstcd(" "); + sb.append("Remote Addr : ").append(request.getRemoteAddr()).append("\n").append("Request URI : ") + .append(request.getRequestURI()); + errorInfoVO.setBwkDataTxt(sb.toString()); // 업무데이터내용 + errorInfoVO.setEaiSvrInstNm(serverName); // EAI서버인스턴스명 + + InboundErrorLogger.error(errorInfoVO); + } + + private String makeResponseBodyMsg() { + + Map dataMap = new HashMap<>(); + dataMap.put("result", 1); + Map map = new HashMap<>(); + + map.put("code", "200"); + map.put("data", dataMap); + map.put("message", "정상 처리 되었습니다."); + + JSONObject json = new JSONObject(); + json.putAll(map); + + return json.toJSONString(); + } + + /* + * private String makeResponseBodyMsg(String asyncMsgStyle) { + * + * Map dataMap = new HashMap<>(); dataMap.put("result", 1); + * Map map = new HashMap<>(); + * + * switch (asyncMsgStyle) { case "TB_SUC": map.put("rspCode", "TB_SUC_000"); + * map.put("rspMsg", "정상"); map.put("data", dataMap); break; case "ONLYDATA": + * map.put("data", dataMap); map.put("success", "true"); break; default: + * map.put("code", "200"); map.put("data", dataMap); map.put("message", + * "정상 처리 되었습니다."); break; } + * + * JSONObject json = new JSONObject(); json.putAll(map); + * + * return json.toJSONString(); } + */ +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java b/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java new file mode 100644 index 0000000..2662d44 --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java @@ -0,0 +1,972 @@ +package com.eactive.eai.adapter.service; + +import com.eactive.eai.adapter.AdapterGroupVO; +import com.eactive.eai.adapter.AdapterPropManager; +import com.eactive.eai.adapter.AdapterVO; +import com.eactive.eai.adapter.Keys; +import com.eactive.eai.adapter.http.HttpMemoryLogger; +import com.eactive.eai.adapter.http.HttpMethodType; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport; +import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthFilter; +import com.eactive.eai.common.exception.ExceptionUtil; +import com.eactive.eai.common.message.MessageType; +import com.eactive.eai.common.util.CommonLib; +import com.eactive.eai.common.util.Logger; +import com.eactive.eai.inbound.action.ActionFactory; +import com.eactive.eai.inbound.action.RequestAction; +import com.eactive.eai.inbound.processor.Processor; +import com.eactive.eai.message.StandardMessageUtil; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; +import org.apache.mina.common.ByteBuffer; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.JSONParser; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.AntPathMatcher; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URLDecoder; +import java.util.*; + +//for encrypt/decrypt jwhong +import com.eactive.eai.authserver.service.OAuth2Manager; +import com.kjbank.encrypt.exchange.crypto.AES256Cipher; +import com.kjbank.encrypt.exchange.crypto.AESCipher; +import com.kjbank.encrypt.exchange.crypto.AES256GCMCipher; +import java.nio.charset.StandardCharsets; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import org.springframework.security.oauth2.provider.ClientDetails; +import com.nimbusds.jwt.SignedJWT; +import com.eactive.eai.common.context.ElinkTransactionContext; + + +@Service +public class DJErpApiAdapterService extends HttpAdapterServiceSupport { + static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); + + public static final String HEADER_GROUP = "HEADER_GROUP"; + public static final String HTTP_STATUS = "HTTP_STATUS"; // jwhong + // HEADER_GROUP JSON에 추가할 항목 정의, 없으면 전체 header 추가 + public static final String HEADER_KEYS = "HEADER_KEYS"; + public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod"; + + private static final String JSON_CONTENT_TYPE = "application/json"; + private static final String JSON_FIELD_NAME = "json-body"; + private static final String FILE_GROUP_NAME = "image-file"; + private static final String UPLOAD_ROOT_PATH = "UPLOAD_ROOT_PATH"; + public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id"; // jwhong + private boolean encryptResponseApply; // inbound에 대한 응답 암호화 여부 flag + + public String callApi(HttpServletRequest request, HttpServletResponse response, Properties httpProp, AdapterGroupVO adapterGroupVO, AdapterVO adapterVO, Properties transactionProp) throws Exception { + int traceLevel = 0; + + AdapterPropManager manager = null; + String adptGrpName = adapterGroupVO.getName(); + String adptName = adapterVO.getName(); + + String urlDecodeYn = httpProp.getProperty(URL_DECODE_YN, "N"); + String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8"); + + String traceLevelTemp = httpProp.getProperty(TRACE_LEVEL, "0"); + String relayRequestHeaderKeys = httpProp.getProperty(HEADER_KEYS); + String headerGroupName = httpProp.getProperty(HEADER_GROUP); + + boolean isParameterType = false; + String message = null; + + String paramValue = null; + String adptMsgType = null; + + transactionProp.put(INBOUND_METHOD, request.getMethod()); + transactionProp.put(INBOUND_URI, request.getRequestURI()); + transactionProp.put(INBOUND_QUERY_STRING, StringUtils.defaultString(request.getQueryString())); + transactionProp.put(INBOUND_HEADER, getHeaders(request)); + transactionProp.put(INBOUND_EXTPARAMS, StringUtils.defaultString(request.getQueryString())); + if (StringUtils.equals(adapterVO.getAdapterGroupVO().getType(), Keys.TYPE_REST) + || StringUtils.equals(adapterVO.getAdapterGroupVO().getType(), Keys.TYPE_HTTP_CUSTOM)) { + // /api/v1/public/getUserInfo.svc + String extUrl = StringUtils.removeStart(request.getRequestURI(), request.getContextPath()); + transactionProp.put(INBOUND_EXTURI, extUrl); + } else { + transactionProp.put(INBOUND_EXTURI, getExtUri(request)); + } + transactionProp.put(INBOUND_CLIENT_IP, getClientIp(request)); // Client IP 추가 + transactionProp.put(Processor.REQUEST_ACTION, adapterVO.getAdapterGroupVO().getRefClass()); + transactionProp.put(API_PATH, httpProp.getProperty(API_PATH, "")); + + //djerp bypass + transactionProp.put(INBOUND_REWRITE_PATH, + getRewritePath(transactionProp.getProperty(INBOUND_EXTURI), transactionProp.getProperty(API_PATH))); + transactionProp.put(INBOUND_REMOTE_ADDR, request.getRemoteAddr()); + transactionProp.put(INBOUND_SCHEME, request.getScheme()); + + transactionProp.put(PRE_FILTERS, httpProp.getProperty(PRE_FILTERS, "")); + transactionProp.put(POST_FILTERS, httpProp.getProperty(POST_FILTERS, "")); + transactionProp.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod()); + transactionProp.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, "")); + transactionProp.put("BLOCK_IP", httpProp.getProperty("BLOCK_IP", "")); + + //djerp bypass + Map pathVariables = assignPathVariables(request, adptGrpName, adptName, null, transactionProp); + if (pathVariables != null) { + transactionProp.put(INBOUND_PATH_VARIABLES, pathVariables); + } + + transactionProp.put(ENC_PATHS, httpProp.getProperty(ENC_PATHS, "")); + + transactionProp.put(ENCRYPT_ALGORITHM, httpProp.getProperty(ENCRYPT_ALGORITHM, "")); //jwhong + if (getHeaders(request).get(HEADER_NAME_CLIENT_ID) != null) { // jwhong + transactionProp.put(HEADER_NAME_CLIENT_ID, getHeaders(request).get(HEADER_NAME_CLIENT_ID)); + } + transactionProp.put(ENCRYPT_BASE_TYPE, httpProp.getProperty(ENCRYPT_BASE_TYPE, "")); //jwhong +// //transactionProp.put(INBOUND_TOKEN, getHeaders(request).get(INBOUND_TOKEN)); // jwhong +// transactionProp.put(INBOUND_TOKEN, getHeaders(request).get(INBOUND_TOKEN) != null ? getHeaders(request).get(INBOUND_TOKEN) : ""); //jwhong , null 이면 default로 "" put + String inboundToken = getHeaders(request).getOrDefault(INBOUND_TOKEN, "").toString(); + if (StringUtils.isNotBlank(inboundToken) && StringUtils.startsWith(inboundToken, "Bearer ")) { + inboundToken = inboundToken.substring(7); + } + transactionProp.put(INBOUND_TOKEN, inboundToken); + transactionProp.put(ENCRYPT_AES256_IV, httpProp.getProperty(ENCRYPT_AES256_IV, "")); //jwhong + transactionProp.put(ENCRYPT_AES256_KEY, httpProp.getProperty(ENCRYPT_AES256_KEY, "")); //jwhong + + + // SEED 컬럼암호하 시 Key로 사용함 + String seedkey = getHeaders(request).getOrDefault("x-obp-partnercode", "").toString(); + ElinkTransactionContext.setSeedKey(seedkey); + + try { + traceLevel = Integer.parseInt(traceLevelTemp); + } catch (Exception e) { + traceLevel = 0; + } + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + logger.debug("시작 >> encode = [" + encode + "]"); + + switch (HttpMethodType.getValue(request.getMethod())) { + case GET: + case DELETE: + isParameterType = true; + break; + case POST: + case PUT: + if (StringUtils.contains(request.getContentType(), "application/x-www-form-urlencoded")) { + isParameterType = true; + } else if ( StringUtils.isNoneBlank(request.getQueryString()) ) { // jwhong + isParameterType = true; + } else { + isParameterType = false; + } + break; + default: + break; + } + + if (isParameterType) { + paramValue = request.getQueryString(); + transactionProp.put(INBOUND_QUERY_STRING, StringUtils.defaultString(paramValue)); // Filter에서 QueryString 검증을 위해 저장 + if (paramValue == null) + paramValue = ""; + if (traceLevel >= 3) { + HttpMemoryLogger.txlog(adptGrpName + adptName, + "RECV " + "[" + paramValue + "]" + CommonLib.getDumpMessage(paramValue)); + } + + // json으로 변환 + StringBuilder sb = new StringBuilder(); + sb.append("{"); + Map paramMap = assignParameterMap(request, adptGrpName, adptName, null, transactionProp); + int i = 0; + for (Map.Entry entry : paramMap.entrySet()) { + if (i > 0) { + sb.append(","); + } + sb.append("\"").append(entry.getKey()).append("\":"); + String[] values = entry.getValue(); + if (values.length > 1) { + // ["111", "222"] + sb.append("["); + for (int j = 0; j < values.length; j++) { + if (j > 0) { + sb.append(","); + } + sb.append("\"").append(JSONValue.escape(values[j])).append("\""); + } + sb.append("]"); + } else { + sb.append("\"").append(JSONValue.escape(values[0])).append("\""); + } + + i++; + } + + sb.append("}"); + + paramValue = sb.toString(); + + if (logger.isDebug()) { + logger.debug("HttpAdapterServiceRest] RECV (" + adptGrpName + ") = [" + paramValue + "]\n" + + CommonLib.getDumpMessage(paramValue.getBytes(encode))); + } + } else { + if (request.getContentLength() > 0) { + ServletInputStream sis = request.getInputStream(); + ByteBuffer bb = ByteBuffer.allocate(1024).setAutoExpand(true); + int i = 0; + byte[] cbuf = new byte[1024]; + while ((i = sis.read(cbuf, 0, 1024)) != -1) { + if (i == 1024) { + bb.put(cbuf); + } else { + byte[] tail = new byte[i]; + System.arraycopy(cbuf, 0, tail, 0, i); + bb.put(tail); + } + } + byte[] data = new byte[bb.position()]; + bb.position(0); + bb.get(data); + paramValue = new String(data, encode); + + if (traceLevel >= 3) { + HttpMemoryLogger.txlog(adptGrpName + adptName, + "RECV " + "[" + paramValue + "]" + CommonLib.getDumpMessage(data)); + } + + if (logger.isDebug()) { + logger.debug("HttpAdapterServiceRest] RECV (" + adptGrpName + ") = [" + paramValue + "]\n" + + CommonLib.getDumpMessage(data)); + } + } + } + + if (paramValue == null) { // parameter가 없는 경우때문에 처리 + paramValue = ""; + } + // 순수한 Body값을 저장을 위해 위치 변경. + transactionProp.put(INBOUND_REQUEST_MESSAGE, paramValue); + + // paramValue가 null이 아닌 빈문자열(""," ")인 경우에 대비하여 조건 수정. +// if (StringUtils.isNotBlank(paramValue)) { // jwhong decrypt +// paramValue = doPreDecryption(paramValue, transactionProp, request); +// } + + if ("Y".equals(urlDecodeYn) && isParameterType) { + message = URLDecoder.decode(paramValue); + } else { + message = paramValue; + } + + if (logger.isDebug()) { + String[] msgArgs = new String[2]; + msgArgs[0] = adptGrpName; + msgArgs[1] = message; + String resMsg = ExceptionUtil.make("RICEAIAHA005", msgArgs); + logger.debug(resMsg); + } + + + adptMsgType = adapterVO.getAdapterGroupVO().getMessageType(); +// HttpHeaders responseHeaders = new HttpHeaders(); +// responseHeaders.setContentType(MediaType.valueOf("application/json;charset=" + encode)); + + + // HEADER_GROUP 셋팅 + if (MessageType.JSON.equals(adptMsgType) && StringUtils.isNotBlank(headerGroupName) + && StringUtils.isNotBlank(relayRequestHeaderKeys)) { + JSONObject jsonMessage = (JSONObject) JSONValue.parse(message); + JSONObject headerJson = new JSONObject(); + if (StringUtils.equalsIgnoreCase(relayRequestHeaderKeys, "ALL")) { + for (Enumeration e = request.getHeaderNames(); e.hasMoreElements(); ) { + String key = e.nextElement(); + headerJson.put(key, request.getHeader(key)); + } + } else { + String[] relayKeyArr = org.springframework.util.StringUtils + .tokenizeToStringArray(relayRequestHeaderKeys, ","); + + for (String key : relayKeyArr) { + String headerValue = request.getHeader(key); + if (StringUtils.isNotBlank(headerValue)) { + headerJson.put(key, headerValue); + } + } + } + + if (headerJson.size() > 0) { + jsonMessage.put(headerGroupName, headerJson); + message = jsonMessage.toJSONString(); + } + } + + if (message == null) { + message = ""; + } + + // 위치변경 : 가공되지 않은 Body값을 저장하기 위하여 위쪽으로 이동. +// transactionProp.put(INBOUND_REQUEST_MESSAGE, message); + // 로컬 서비스 호출 ,encoding 처리 추가 + String result = (String) service(adptGrpName, adptName, message, transactionProp, request, response); + + applyOutboundResponseHeaders(transactionProp, response); + + if (logger.isDebug()) { + logger.debug("HttpAdapterServiceRest] result " + encode + " (" + adptGrpName + ") = [" + result + "]"); + } + + stopWatch.stop(); + + String syncAsyncType = transactionProp.getProperty(INBOUND_SYNC_ASYNC_TYPE); + + if (!"ASYN".equals(syncAsyncType) && MessageType.JSON.equals(adptMsgType) && StringUtils.isNotBlank(headerGroupName)) { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode rootNode = (ObjectNode) mapper.readTree(result); + JsonNode headerGroup = rootNode.get(headerGroupName); + if(headerGroup != null) { + for(Iterator it = headerGroup.fieldNames(); it.hasNext();) { + String name = it.next(); + String value = headerGroup.get(name).asText(); + response.addHeader(name, value); + } + rootNode.remove(headerGroupName); + result = mapper.writeValueAsString(rootNode); + } + } + + // KJBank는 요청에 대한 응답 (Sync응답, Aync 에 대한 Ack응답) 에 대하여 암호화 하지 않는다. 무조건 안한다. 따라서 아래부분은 구현은 했지만 사용하지 않는다. + // 위 2가지 경우 암호화 하는걸로 요청 변경되어 아래 암호화 수행하도록 수정함 +// encryptResponseApply = StringUtils.equalsIgnoreCase(httpProp.getProperty("ENCRYPT_RESPONSE_APPLY", "N"), "Y"); +// if ( encryptResponseApply) { +// result = doPostEncryption(result, transactionProp, request); // jwhong Encrypt +// } + return result; + } + + + /** + * HttpClientAdapterServiceBypass.assignRelayDataToInbound() 에서 + * transactionProp에 저장한 OUTBOUND_RESPONSE_HEADERS를 response에 적용한다. + */ + @SuppressWarnings("unchecked") + private void applyOutboundResponseHeaders(Properties transactionProp, HttpServletResponse response) { + Map outboundPropertyMap = (Map) transactionProp.get(OUTBOUND_PROPERTY_MAP); + if (outboundPropertyMap == null) { + return; + } + Properties headerProp = (Properties) outboundPropertyMap.get(OUTBOUND_RESPONSE_HEADERS); + if (headerProp == null) { + return; + } + + String[] hopByHopHeaders = { "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", + "TE", "Trailers", "Transfer-Encoding", "Upgrade" }; + + for (Map.Entry entry : headerProp.entrySet()) { + String key = (String) entry.getKey(); + String value = (String) entry.getValue(); + + if (StringUtils.equalsAnyIgnoreCase(key, hopByHopHeaders)) { + continue; + } + if (StringUtils.equalsIgnoreCase(key, HttpHeaders.CONTENT_LENGTH)) { + continue; + } + if (StringUtils.equalsIgnoreCase(key, HTTP_STATUS)) { + if (StringUtils.isNotBlank(value)) { + try { + response.setStatus(Integer.parseInt(value)); + } catch (NumberFormatException ex) { + // ignore + } + } + continue; + } + response.addHeader(key, value); + } + } + + // jwhong decrypt + + private String doPreDecryption(String eaiBody, Properties transactionProp, HttpServletRequest request) throws Exception { + String decryptAlgorithm = transactionProp.getProperty(ENCRYPT_ALGORITHM, ""); + String xElinkClientId = transactionProp.getProperty(HEADER_NAME_CLIENT_ID, ""); // 이 값이 OAuth의 client id 값이다 . http header 에 포함되어 온다. jwhong + String encryptBaseKeyType = transactionProp.getProperty(ENCRYPT_BASE_TYPE); + //String inboundToken2 = transactionProp.getProperty(INBOUND_TOKEN, ""); // 이건 token 값이 아니고 authorization 값이다 + String inboundToken = ""; + + String secretAES256Iv = transactionProp.getProperty("ENCRYPT_AES256_IV"); + String secretAES256Key = transactionProp.getProperty("ENCRYPT_AES256_KEY"); + + if ( decryptAlgorithm == "" ) { // 복호화 대상이 아님 + return eaiBody; + } + + inboundToken = JwtTokenExtractor(request); + xElinkClientId = JwtClientIdExtractor(inboundToken); // token에서 client id를 추출함 + + // 암호화 key를 token 내용으로 할지 client secret 내용으로 할지 결정함. adapter property에 정의함 + // ENCRYPT_ALGORITHM 을 설정했는데 ENCRYPT_BASE_TYPE을 설정안하면 Decrypt 수행시 Exception 발생함 + String clientSecret = ""; + if ("TOKEN".equalsIgnoreCase(encryptBaseKeyType)) { + clientSecret = inboundToken; + } else if ( "SECRETKEY".equalsIgnoreCase(encryptBaseKeyType)) { + OAuth2Manager manager = OAuth2Manager.getInstance(); + ClientDetails clientDetail = manager.getClientDeatilsStore().get(xElinkClientId); + + if ( clientDetail != null ) { // 여기서 not null 이라는 것은 apim oauth server에서 발급된 token 임. + // 즉 KJBank 에서 요청이 들어온 것이다. + clientSecret = clientDetail.getClientSecret(); + } // 그럼 업체에서 요청이 들어오면?? 업체도 apim oauth server에서 발급된 token을 사용하는것이다. + } + + byte[] decryptedMessage = null; + decryptedMessage = doInboundPreDecrypt(eaiBody, decryptAlgorithm,clientSecret,secretAES256Iv, secretAES256Key ); + + if (decryptedMessage == null) { + throw new Exception("[ApiAdapterService] Decrypt Error : Invalid encrypted message"); + } + + + String resultMessage = new String(decryptedMessage, StandardCharsets.UTF_8 ); + return resultMessage; + //return new String(decryptedMessage, StandardCharsets.UTF_8 ); + } + + + // jwhong encrypt + public String doPostEncryption(String eaiBody, Properties transactionProp, HttpServletRequest request) throws Exception { + + String decryptAlgorithm = transactionProp.getProperty(ENCRYPT_ALGORITHM, ""); + String xElinkClientId = transactionProp.getProperty(HEADER_NAME_CLIENT_ID, ""); // 이 값이 OAuth의 client id 값이다 . http header 에 포함되어 온다. jwhong + String encryptBaseKeyType = transactionProp.getProperty(ENCRYPT_BASE_TYPE); + String inboundToken = ""; + + String secretAES256Iv = transactionProp.getProperty("ENCRYPT_AES256_IV"); + String secretAES256Key = transactionProp.getProperty("ENCRYPT_AES256_KEY"); + + if ( decryptAlgorithm == "" ) { // 복호화 대상이 아님 + return eaiBody; + } + + inboundToken = JwtTokenExtractor(request); + xElinkClientId = JwtClientIdExtractor(inboundToken); // token에서 client id를 추출함 + + // 암호화 key를 token 내용으로 할지 client secret 내용으로 할지 결정함. adapter property에 정의함 + // ENCRYPT_ALGORITHM 을 설정했는데 ENCRYPT_BASE_TYPE을 설정안하면 Decrypt 수행시 Exception 발생함 + String clientSecret = ""; + if ("TOKEN".equalsIgnoreCase(encryptBaseKeyType)) { + clientSecret = inboundToken; + } else if ( "SECRETKEY".equalsIgnoreCase(encryptBaseKeyType)) { + OAuth2Manager manager = OAuth2Manager.getInstance(); + ClientDetails clientDetail = manager.getClientDeatilsStore().get(xElinkClientId); + + if ( clientDetail != null ) { + clientSecret = clientDetail.getClientSecret(); + } + } + + String encryptedMessage = null; + encryptedMessage = doInboundPostEncrypt(eaiBody, decryptAlgorithm,clientSecret,secretAES256Iv, secretAES256Key ); + + if (encryptedMessage == null) { + throw new Exception("[ApiAdapterService] Encrypt Error : Invalid PlainText message"); + } + + return encryptedMessage; + //String resultMessage = new String(encryptedMessage, StandardCharsets.UTF_8 ); + //return resultMessage; + } + + + private byte[] convertObjectToBytes(Object obj) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(obj); + oos.flush(); + return bos.toByteArray(); + } + + private byte[] convertObjectToBase64Bytes(Object obj) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(obj); + oos.flush(); + oos.close(); + + // 직렬화된 byte[] → Base64 문자열 → 다시 byte[]로 변환 + String base64String = Base64.getEncoder().encodeToString(bos.toByteArray()); + return base64String.getBytes(StandardCharsets.UTF_8); + } + + public static Object convertBytesToObject(byte[] bytes) throws IOException, ClassNotFoundException { + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis)) { + return ois.readObject(); + } + } + + + private byte[] doInboundPreDecrypt(String eaiStringBody, String decryptAlgorithm, String clientSecretKey, String secretAES256Iv, String secretAES256Key) throws Exception { + + byte[] decryptedMessage = null; + //String eaiStringBody = new String(eaiBody, StandardCharsets.UTF_8); + + eaiStringBody = extractBodyMsg(decryptAlgorithm, eaiStringBody); + + if (eaiStringBody == null) { + throw new Exception("[ApiAdapterService] Cannot decrypt Error : input is plain text"); + } + + //test source + logger.debug("Base64 문자열: " + eaiStringBody); + logger.debug("Base64 문자열 길이: " + eaiStringBody.length()); + logger.debug("Base64 문자열 끝: " + eaiStringBody.substring(eaiStringBody.length() - 10)); + + // Base64 디코딩 테스트 + try { + byte[] decoded = Base64.getDecoder().decode(eaiStringBody); + logger.debug("디코딩 성공, 길이: " + decoded.length); + } catch (IllegalArgumentException e) { + logger.debug("Base64 디코딩 실패: " + e.getMessage()); + } + // end test source + + switch (decryptAlgorithm) { + case "AES128": + case "AES128-TOSS": + try { + String key128 = clientSecretKey.substring(22,38); //togetherEncoder 경우는 substring(44,60) 이네?? + //String decryptedStrMessage = AESCipher.decrypt(eaiStringBody, key128); + String decryptedStrMessage = AESCipher.decryptExistException(eaiStringBody, key128); + decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES128 Decryption error=" + e.getMessage()); + } + break; + case "AES128-TOGETHER": + try { + String key128 = clientSecretKey.substring(44,60); //togetherEncoder 경우는 substring(44,60) 이네?? + //String decryptedStrMessage = AESCipher.decrypt(eaiStringBody, key128); + String decryptedStrMessage = AESCipher.decryptExistException(eaiStringBody, key128); + decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES128 Decryption error=" + e.getMessage()); + } + break; + case "AES256": + try { + String key = clientSecretKey.substring(10,42); + String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, key); + decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage()); + } + break; + case "AES256-KAKAO": + try { + String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, secretAES256Iv, secretAES256Key); + decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage()); + } + break; + case "AES256-TOSS": + try { + String decryptedStrMessage = AES256Cipher.decrypt(eaiStringBody, secretAES256Iv, secretAES256Key); + decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage()); + } + break; + + case "AES256GCM": + try { + byte[] key = AES256GCMCipher.generateKey(); + AES256GCMCipher cipher = new AES256GCMCipher(key); + byte[] aad = "metadata".getBytes(); + + String decryptedStrMessage = cipher.decrypt(eaiStringBody , aad); + decryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256GCM Encryption error=" + e.getMessage()); + } + break; + default: + try { + decryptedMessage = eaiStringBody.getBytes(); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] doOutboundPostFilter Array copy error=" + e.getMessage()); + } + break; + } + + return decryptedMessage; + } + + private String extractBodyMsg(String decryptAlgorithm, String eaiStringBody) { + + String decryptedJsonKey = ""; + String decryptedBodyMessage = ""; + + decryptedJsonKey = getJsonKey(decryptAlgorithm); + + try { + JSONParser parser = new JSONParser(); + JSONObject jsonObject = (JSONObject) parser.parse(eaiStringBody); + decryptedBodyMessage = (String) jsonObject.get(decryptedJsonKey); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClient5AdapterServiceRest] extractBodyMsg error : " + e.getMessage()); + } + + return decryptedBodyMessage; + + } + + private String getJsonKey(String Algorithm) { + + String jsonKey = ""; + + switch (Algorithm) { + case "AES128": + case "AES128-TOSS": + jsonKey = "preScreeningRequest"; + break; + case "AES128-TOGETHER": + jsonKey = "obpTxData"; + break; + case "AES256": + jsonKey = "preScreeningRequest"; + break; + case "AES256-KAKAO": + jsonKey = "encrypted_data"; + break; + case "AES256-TOSS": + jsonKey = "encryptedData"; + break; + case "AES256-NICEON": + jsonKey = "enc_data"; + break; + case "AES256GCM": + break; + default: + break; + } + return jsonKey; + } + + private String doInboundPostEncrypt(String eaiStringBody, String encryptAlgorithm, String clientSecretKey, String secretAES256Iv, String secretAES256Key) { + + //byte[] encryptedMessage = null; + String encryptedStrMessage = null; + String encryptedJsonKey = ""; + + encryptedJsonKey = getJsonKey(encryptAlgorithm); + + switch (encryptAlgorithm) { + case "AES128": + case "AES128-TOSS": + try { + String key128 = clientSecretKey.substring(22,38); //togetherEncoder 경우는 substring(44,60) 이네?? + encryptedStrMessage = AESCipher.encrypt(eaiStringBody, key128); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES128 Encryption error=" + e.getMessage()); + } + break; + case "AES128-TOGETHER": + try { + String key128 = clientSecretKey.substring(44,60); //togetherEncoder 경우는 substring(44,60) 이네?? + encryptedStrMessage = AESCipher.encrypt(eaiStringBody, key128); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES128 Encryption error=" + e.getMessage()); + } + break; + case "AES256": + try { + String key = clientSecretKey.substring(10,42); + encryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, key); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage()); + } + break; + case "AES256-KAKAO": + try { + encryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, secretAES256Iv, secretAES256Key); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage()); + } + break; + case "AES256-TOSS": + try { + encryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, secretAES256Iv, secretAES256Key); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage()); + } + break; + + case "AES256GCM": + try { + byte[] key = AES256GCMCipher.generateKey(); + AES256GCMCipher cipher = new AES256GCMCipher(key); + byte[] aad = "metadata".getBytes(); + + encryptedStrMessage = cipher.encrypt(eaiStringBody , aad); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); + + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] AES256GCM Encryption error=" + e.getMessage()); + } + break; + default: + try { + encryptedStrMessage = eaiStringBody; + //encryptedMessage = eaiStringBody.getBytes(); + } catch (Exception e) { + e.printStackTrace(); + logger.error("HttpClientAdapterServiceRest] doOutboundPostFilter Array copy error=" + e.getMessage()); + } + break; + } + + Map map = new HashMap<>(); + map.put(encryptedJsonKey, encryptedStrMessage); + JSONObject json = new JSONObject(); + json.putAll(map); + return json.toJSONString(); + //return encryptedMessage; + } + + + + private String JwtTokenExtractor(HttpServletRequest request) { + + String Token = ""; + try { + Token = JwtAuthFilter.extractJWTToken(request); + } catch (Exception e) { + logger.error(e.getMessage()); + } + + logger.debug("Token is: " + Token); + return Token; + + } + + private String JwtClientIdExtractor(String inboundToken) { + + String tokenClientId =""; + try { + SignedJWT signedJWT = SignedJWT.parse(inboundToken); + tokenClientId = (String) signedJWT.getJWTClaimsSet().getClaim(PAYLOAD_PARAM_NAME_CLIENT_ID); + } catch (Exception e) { + logger.error(e.getMessage()); + tokenClientId = ""; + } + + logger.debug("Token Client ID: " + tokenClientId); + return tokenClientId; + + } + + // jwhong until here + + + private Map assignParameterMap(HttpServletRequest request, String adptGrpName, String adptName, + Object requestBytes, Properties prop) { + // PathVariable 체크 + if (StringUtils.equalsAnyIgnoreCase(request.getMethod(), HttpMethod.GET.name(), HttpMethod.DELETE.name()) + && StringUtils.isBlank(request.getQueryString())) { + try { + String actionName = prop.getProperty(Processor.REQUEST_ACTION); + RequestAction action = ActionFactory.createAction(actionName); + action.setAdapterInfo(adptGrpName, adptName, prop); + String[] keys = action.perform(requestBytes); + String requestPath = keys[0]; + + // PathVariable 지원 추가 + String ruledPath = StandardMessageUtil.getMatchedKey(requestPath, actionName); + if (!StringUtils.equals(requestPath, ruledPath) && StringUtils.contains(ruledPath, "{")) { + Map paramMap = new AntPathMatcher().extractUriTemplateVariables(ruledPath, + requestPath); + if (paramMap != null && paramMap.size() > 0) { + Map returnMap = new HashMap<>(); + for (String key : paramMap.keySet()) { + if (StringUtils.equalsIgnoreCase(key, "method")) { + continue; + } + returnMap.put(key, new String[] { paramMap.get(key) }); + } + + return returnMap; + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + } + } + + return request.getParameterMap(); + } + + private String getRewritePath(String extUri, String basePath) { + return StringUtils.removeStart(extUri, StringUtils.removeEnd(basePath, "/")); + } + + private Map assignPathVariables(HttpServletRequest request, String adptGrpName, String adptName, + Object requestBytes, Properties prop) { + Map variablesMap = null; + // QueryString 있을때는 체크(X) + if (StringUtils.isBlank(request.getQueryString())) { + try { + String actionName = prop.getProperty(Processor.REQUEST_ACTION); + RequestAction action = ActionFactory.createAction(actionName); + action.setAdapterInfo(adptGrpName, adptName, prop); + String[] keys = action.perform(requestBytes); + String requestPath = keys[0]; + + // PathVariable 지원 추가 + String ruledPath = StandardMessageUtil.getMatchedKey(requestPath, actionName); + if (!StringUtils.equals(requestPath, ruledPath) && StringUtils.contains(ruledPath, "{")) { + variablesMap = new AntPathMatcher().extractUriTemplateVariables(ruledPath, requestPath); + } + } catch (Exception e) { + logger.error(e.getMessage()); + } + } + + return variablesMap; + } + + private Properties getHeaders(HttpServletRequest request) { + Properties prop = new Properties(); + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String key = headerNames.nextElement(); + // 동일 이름의 헤더가 여러 개인 경우 콤마로 합침 (RFC 7230) + Enumeration values = request.getHeaders(key); + StringBuilder sb = new StringBuilder(); + while (values.hasMoreElements()) { + if (sb.length() > 0) sb.append(", "); + sb.append(values.nextElement()); + } + prop.setProperty(key, sb.toString()); + } + + return prop; + } + + private String getExtUri(HttpServletRequest request) { + String orgUri = request.getRequestURI().replaceAll(request.getContextPath(), ""); + String uri = getExtUri(orgUri, 3); + if (uri != null && uri.trim().length() > 0) { + return "/" + uri; + } else { + return ""; + } + } + + private static String getExtUri(String url, int length) { + String[] urls = url.split("/"); + List newUrls = new ArrayList<>(); + Collections.addAll(newUrls, urls); + return StringUtils.join(newUrls.subList(length, urls.length).toArray(), "/"); + } + + @Override + @Deprecated + public void service(String adptGrpName, String adptName, HttpServletRequest request, HttpServletResponse response) { + + } + + /** + * HTTP 요청에서 클라이언트 IP를 추출합니다. + * X-Forwarded-For 헤더가 있는 경우 이를 우선적으로 사용하고, + * 없는 경우 remoteAddr을 사용합니다. + */ + private String getClientIp(HttpServletRequest request) { + String ipAddress = request.getHeader("X-Forwarded-For"); + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("Proxy-Client-IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("WL-Proxy-Client-IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("HTTP_CLIENT_IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getRemoteAddr(); + } + return ipAddress; + } + + private String assignApiId(String adptGrpName, String adptName, Object message, Properties prop, + HttpServletRequest request) { + String apiId = null; + try { + String actionName = prop.getProperty(Processor.REQUEST_ACTION); + RequestAction action = ActionFactory.createAction(actionName); + action.setAdapterInfo(adptGrpName, adptName, prop); + String[] keys = action.perform(message); + apiId = keys[0]; + + // PathVariable 지원 추가 + apiId = StandardMessageUtil.getMatchedKey(apiId, actionName); + } catch (Exception e) { + logger.error(e.getMessage()); + } + +// // header 에서 확보 +// String apiId = request.getHeader(HEADER_NAME_API_CODE); +// if (StringUtils.isBlank(apiId)) { +// // url에서 확보 +// // /ONLWeb/api/v1/public/getUserInfo.svc/ +// apiId = request.getRequestURI(); +// // /ONLWeb/api/v1/public/getUserInfo.svc +// apiId = StringUtils.removeEnd(apiId, "/"); +// // getUserInfo.svc +// apiId = StringUtils.substringAfterLast(apiId, "/"); +// } + + return apiId; + } +} diff --git a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenRequest.java b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenRequest.java new file mode 100644 index 0000000..21eba2a --- /dev/null +++ b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenRequest.java @@ -0,0 +1,55 @@ +package com.eactive.eai.authserver.custom; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("serial") +public class DJErpOAuth2AccessTokenRequest implements Serializable { + + @JsonProperty("grant_type") + private String grantType; + @JsonProperty("client_id") + private String clientId; + @JsonProperty("client_secret") + private String clientSecret; + private String scope; + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + @Override + public String toString() { + return "DJErpOAuth2AccessTokenRequest [grantType=" + grantType + ", clientId=" + clientId + + ", clientSecret=" + clientSecret + ", scope=" + scope + "]"; + } +} diff --git a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenResponse.java b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenResponse.java new file mode 100644 index 0000000..d7429eb --- /dev/null +++ b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2AccessTokenResponse.java @@ -0,0 +1,79 @@ +package com.eactive.eai.authserver.custom; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@SuppressWarnings("serial") +@JsonInclude(Include.NON_NULL) +@JsonPropertyOrder({ "access_token", "token_type", "expires_in", "scope", "jti", "client_id" }) +public class DJErpOAuth2AccessTokenResponse implements Serializable { + + @JsonProperty("access_token") + private String accessToken; + @JsonProperty("token_type") + private String tokenType; + @JsonProperty("expires_in") + private Long expiresIn; + private String scope; + private String jti; + @JsonProperty("client_id") + private String clientId; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public Long getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Long expiresIn) { + this.expiresIn = expiresIn; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getJti() { + return jti; + } + + public void setJti(String jti) { + this.jti = jti; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + @Override + public String toString() { + return "DJErpOAuth2AccessTokenResponse [accessToken=" + accessToken + ", tokenType=" + tokenType + + ", expiresIn=" + expiresIn + ", scope=" + scope + ", jti=" + jti + ", clientId=" + clientId + "]"; + } +} diff --git a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java new file mode 100644 index 0000000..58cc58c --- /dev/null +++ b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java @@ -0,0 +1,266 @@ +package com.eactive.eai.authserver.custom; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; +import com.eactive.eai.authserver.config.RequestContextData; +import com.eactive.eai.authserver.dao.TokenIssuanceLogDAO; +import com.eactive.eai.authserver.service.OAuth2Manager; +import com.eactive.eai.authserver.util.BeanUtils; +import com.eactive.eai.authserver.vo.ClientVO; +import com.eactive.eai.common.dao.DAOException; +import com.eactive.eai.common.logger.EAIDBLogControl; +import com.eactive.eai.common.util.Logger; +import com.eactive.eai.common.util.MessageUtil; +import com.eactive.eai.common.util.UUID; +import com.eactive.eai.data.entity.onl.authserver.TokenIssuanceLog; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.security.Principal; + +@Controller +public class DJErpOAuth2Controller { + public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + + private static final String HEADER_TRACEID = "traceId"; + private static final String SERVICE_CODE_ACCESS_TOKEN = "00"; + + @Autowired + private TokenIssuanceLogDAO tokenIssuanceLogDAO; + + @RequestMapping(value = { "/dj/oauth/token", "/dj/oauth2/token" }, method = RequestMethod.POST, + consumes = "application/json", produces = "application/json; charset=UTF-8") + @ResponseBody + public ResponseEntity tokenJson(@RequestBody DJErpOAuth2AccessTokenRequest tokenRequest, + HttpServletRequest request, HttpServletResponse response) { + return issueToken(tokenRequest, request, response); + } + + @RequestMapping(value = { "/dj/oauth/token", "/dj/oauth2/token" }, method = RequestMethod.POST, + consumes = "application/x-www-form-urlencoded", produces = "application/json; charset=UTF-8") + @ResponseBody + public ResponseEntity tokenForm(@RequestParam Map params, + HttpServletRequest request, HttpServletResponse response) { + DJErpOAuth2AccessTokenRequest tokenRequest = new DJErpOAuth2AccessTokenRequest(); + tokenRequest.setGrantType(params.get("grant_type")); + tokenRequest.setClientId(params.get("client_id")); + tokenRequest.setClientSecret(params.get("client_secret")); + tokenRequest.setScope(params.get("scope")); + return issueToken(tokenRequest, request, response); + } + + @GetMapping(value = { "/dj/oauth/token", "/dj/oauth2/token" }, produces = "application/json; charset=UTF-8") + @ResponseBody + public ResponseEntity tokenGet(@RequestParam Map params, + HttpServletRequest request, HttpServletResponse response) { + DJErpOAuth2AccessTokenRequest tokenRequest = new DJErpOAuth2AccessTokenRequest(); + tokenRequest.setGrantType(params.get("grant_type")); + tokenRequest.setClientId(params.get("client_id")); + tokenRequest.setClientSecret(params.get("client_secret")); + tokenRequest.setScope(params.get("scope")); + return issueToken(tokenRequest, request, response); + } + + private ResponseEntity issueToken(DJErpOAuth2AccessTokenRequest tokenRequest, + HttpServletRequest request, HttpServletResponse response) { + + if (logger.isDebug()) { + logger.debug(tokenRequest.toString()); + } + + RequestContextData data = new RequestContextData(); + data.setClientId(tokenRequest.getClientId()); + data.setIpAddress(extractIpAddress(request)); + data.setGrantType(tokenRequest.getGrantType()); + data.setScope(tokenRequest.getScope()); + data.setUsername("none"); + data.setResource("none"); + RequestContextData.ThreadLocalRequestContext.set(data); + + String grantType = tokenRequest.getGrantType(); + String clientId = tokenRequest.getClientId(); + String clientSecret = tokenRequest.getClientSecret(); + String scopes = tokenRequest.getScope(); + + try { + if (!StringUtils.equals(grantType, "client_credentials")) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), + "Invalid Mandatory Field {grantType}"); + } + + String[] scopeArr = org.springframework.util.StringUtils.tokenizeToStringArray(scopes, " "); + Set scopeSet = new HashSet<>(); + for (String scope : scopeArr) { + scopeSet.add(scope); + } + + ClientDetails clientDetails = OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId); + verifyClient(clientDetails, clientId, clientSecret, scopeSet); + + String traceId = UUID.randomUUID().toString().replace("-", ""); + response.setHeader(HEADER_TRACEID, traceId); + + HashMap authorizationParameters = new HashMap<>(); + authorizationParameters.put(OAuth2Utils.GRANT_TYPE, grantType); + authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId); + authorizationParameters.put("client_secret", clientSecret); + + Set responseType = new HashSet<>(); + responseType.add(grantType); + + OAuth2Request authorizationRequest = new OAuth2Request(authorizationParameters, clientId, null, true, + scopeSet, null, "", responseType, null); + + Principal principal = new OAuth2Authentication(authorizationRequest, null); + ResponseEntity result = tokenEndpoint().postAccessToken(principal, authorizationParameters); + OAuth2AccessToken token = result.getBody(); + + DJErpOAuth2AccessTokenResponse responseToken = new DJErpOAuth2AccessTokenResponse(); + responseToken.setAccessToken(token.getValue()); + responseToken.setTokenType(token.getTokenType()); + responseToken.setExpiresIn(Long.valueOf(token.getExpiresIn())); + responseToken.setScope(StringUtils.join(token.getScope(), " ")); + responseToken.setJti((String) token.getAdditionalInformation().get("jti")); + responseToken.setClientId((String) token.getAdditionalInformation().get("client_id")); + + logTokenIssuance(true, false, "Token issued successfully", token.getValue()); + + ObjectMapper mapper = new ObjectMapper(); + return ResponseEntity.ok(mapper.writeValueAsString(responseToken)); + + } catch (JwtAuthException e) { + logger.info("Token request[/dj/oauth2/token] => clientId: {}, grantType: {}, scope: {}", + clientId, grantType, scopes); + logger.error(e.getMessage()); + logTokenIssuance(false, false, e.getMessage(), null); + String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage()); + int statusCode = NumberUtils.toInt(StringUtils.left(e.getCode(), 3), HttpStatus.UNAUTHORIZED.value()); + return ResponseEntity.status(statusCode).body(errorJson); + + } catch (Exception e) { + logger.info("Token request[/dj/oauth2/token] => clientId: {}, grantType: {}, scope: {}", + clientId, grantType, scopes); + logger.error(e.getMessage()); + logTokenIssuance(false, false, e.getMessage(), null); + String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, "Unauthorized. [Unknown]"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorJson); + + } finally { + RequestContextData.ThreadLocalRequestContext.clear(); + } + } + + private void verifyClient(ClientDetails clientDetails, String clientId, String clientSecret, Set scopeSet) + throws JwtAuthException { + + if (StringUtils.isBlank(clientId)) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), + "Invalid Mandatory Field {client_id}"); + } + if (clientDetails == null) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"), + "Unauthorized. [client not found]"); + } + if (StringUtils.isBlank(clientSecret)) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), + "Invalid Mandatory Field {client_secret}"); + } + if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"), + "Unauthorized. [Bad client credentials(Client Secret is not match)]"); + } + if (scopeSet.isEmpty()) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), + "Invalid Mandatory Field {scope}"); + } + if (!clientDetails.getScope().containsAll(scopeSet)) { + throw new JwtAuthException( + String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"), + "Unauthorized. [Bad client credentials(Include unacceptable scope)]"); + } + } + + private String extractIpAddress(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) ip = request.getHeader("Proxy-Client-IP"); + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) ip = request.getHeader("WL-Proxy-Client-IP"); + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) ip = request.getHeader("HTTP_CLIENT_IP"); + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) ip = request.getRemoteAddr(); + return ip; + } + + private TokenEndpoint tokenEndpoint() { + return BeanUtils.getBean("tokenEndpoint", TokenEndpoint.class); + } + + private void logTokenIssuance(boolean isSuccess, boolean isRestrictionExempt, String resultMessage, String accessToken) { + try { + if (!EAIDBLogControl.isEnable()) { + logger.warn("DB logging is disabled. Skipping token issuance logging."); + return; + } + RequestContextData data = RequestContextData.ThreadLocalRequestContext.get(); + + TokenIssuanceLog log = new TokenIssuanceLog(); + String clientId = data.getClientId(); + log.setClientId(clientId); + + if (StringUtils.isNotBlank(clientId)) { + try { + ClientVO clientVO = OAuth2Manager.getInstance().getClientInfo(clientId); + log.setAppName(clientVO.getClientName()); + log.setOrgId(clientVO.getOrgId()); + log.setOrgName(clientVO.getOrgName()); + } catch (DAOException e) { + logger.warn("cannot find clientVO - " + clientId); + } + } + + log.setGrantType(data.getGrantType()); + log.setScope(data.getScope()); + log.setIpAddress(data.getIpAddress()); + log.setSuccessYn(isSuccess ? "Y" : "N"); + log.setIssuanceDateTime(LocalDateTime.now()); + log.setRestrictionExemptYn(isRestrictionExempt ? "Y" : "N"); + log.setResultMessage(resultMessage); + log.setAccessToken(accessToken); + + tokenIssuanceLogDAO.saveTokenIssuanceLog(log); + } catch (Throwable th) { + logger.error("Error while logging token issuance: " + th.getMessage(), th); + } + } +} diff --git a/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java new file mode 100644 index 0000000..19e6f16 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java @@ -0,0 +1,664 @@ +package com.eactive.eai.custom.adapter.http.client.impl; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Formatter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.protocol.BasicHttpContext; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.springframework.http.HttpHeaders; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import com.eactive.eai.adapter.http.HttpMemoryLogger; +import com.eactive.eai.adapter.http.HttpMethodType; +import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport; +import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey; +import com.eactive.eai.adapter.http.client.HttpClientAdapterVO; +import com.eactive.eai.adapter.http.client.impl.HttpClientAdapterServiceRest; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +import com.eactive.eai.adapter.http.dynamic.impl.HttpAdapterServiceBypass; +import com.eactive.eai.common.TransactionContextKeys; +import com.eactive.eai.common.exception.ExceptionUtil; +import com.eactive.eai.common.util.CommonLib; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.openbanking.eai.common.token.AccessTokenManager; +import com.openbanking.eai.common.token.AccessTokenVO; +import com.openbanking.eai.common.token.OAuth2AccessTokenVO; + +public class HttpClient5AdapterServiceBypass extends HttpClient5AdapterServiceSupport + implements HttpClientAdapterServiceKey { + public static final String TYPE_BYPASS_REQUEST = "bypassRequest"; + public static final String REST_OPTION = "REST_OPTION"; + + protected boolean doSendUrlFragment = true; + protected boolean doHandleCompression = false; + protected boolean doForwardIP = false; + + @SuppressWarnings({ "unchecked" }) + public Object execute(Properties prop, Object data, Properties tempProp) throws Exception { + HttpClientAdapterVO vo = super.setting(prop, tempProp); + + boolean useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y"); + + // ex) $.dataHeader.GW_RSLT_CD + String tokenErrorCodeKey = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_KEY"); + String tokenErrorCodeValues = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_VALUES"); + String tokenErrorHttpStatusCode = prop.getProperty("ADAPTER_TOKEN_ERROR_HTTP_STATUS_CODE"); // 200 or 400번대 + + /** + * @formatter:off + * TSEAIHE02.RESTOPTION 정보 => JSON 형태로 구성 + * - type : simpleRequest, variableUrlRequest + * - extraPath : 어댑터 프로퍼티 URL에 추가될 HTTP REST URL + * - method : get, delete, post, put + * - contentType : application/json, application/x-www-form-urlencoded + * - adapterTokenUseYn: Y, N(default) + * ex) + * {"type":"variableUrlRequest","extraPath":"/aaa/{userId}","uriVariables":["userId"]} + * @formatter:on + */ + String restOptionData = tempProp.getProperty(REST_OPTION, "{}"); + + JSONObject restOptionObject = parseJson(restOptionData); + + String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn"); + if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다. + useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y"); + } + + String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST"); + String inboundRewritePath = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REWRITE_PATH, ""); + String inboundQueryString = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_QUERY_STRING, ""); + Properties inboundHeaders = (Properties) tempProp.get(HttpAdapterServiceKey.INBOUND_HEADER); + Map inboundPathVariables = (Map) tempProp + .get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES); + + String restMethod = (String) restOptionObject.get("method"); + if (StringUtils.isBlank(restMethod)) { + restMethod = inboundMethod; + } + + String url = getRewriteUrl(vo, inboundRewritePath, inboundQueryString, restOptionObject, inboundPathVariables); + if (logger.isDebug()) { + logger.debug("HttpClient5AdapterServiceBypass] url = [" + url + "]"); + } + + HttpClient mclient = this.client; + HttpUriRequestBase method = generateMethod(restMethod, url); + + assignRequestHeaders(method, inboundHeaders, tempProp); + + if (hasBody(data, method)) { + byte[] bodyBytes; + if (data instanceof byte[]) { + bodyBytes = (byte[]) data; + } else if (data instanceof String) { + bodyBytes = ((String) data).getBytes(vo.getEncode()); + } else { + bodyBytes = new byte[0]; + } + method.setEntity(new ByteArrayEntity(bodyBytes, null)); + } + + String contentType = (String) restOptionObject.get("contentType"); + if (StringUtils.isBlank(contentType) && inboundHeaders != null) { + contentType = getIgnoreCaseProp(inboundHeaders, HttpHeaders.CONTENT_TYPE); + contentType = StringUtils.substringBefore(contentType, ";"); + } + + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + configureHttpClient(requestConfigBuilder, vo, method); + + OAuth2AccessTokenVO accessToken = null; + if (useAdapterToken) { + AccessTokenManager tokenManager = AccessTokenManager.getInstance(); + accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName()); + + // 토큰이 없거나 만료 됐으면 재발급 + if (accessToken == null || accessToken.isExpired()) { + String oldToken = accessToken == null ? null : accessToken.getAccessToken(); + accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop, + oldToken); + } + + if (logger.isDebug()) { + logger.debug("HttpClient5AdapterServiceBypass] SEND (" + vo.getAdapterGroupName() + ") TOKEN = [" + + accessToken + "]"); + } + + setAuthHeaders(method, accessToken); + + if (logger.isDebug()) { + logger.debug("HttpClient5AdapterServiceBypass] SEND (" + vo.getAdapterGroupName() + + ") RequestHeader [Authorization=" + method.getFirstHeader("Authorization") + "]"); + } + } + + // 로깅 인터셉터에 전달할 컨텍스트 정보 설정 + String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID); + HttpContext context = new BasicHttpContext(); + context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid); + context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName()); + context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName()); + Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO); + if (logProcessNo != null && logProcessNo > 0) { + context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo); + } + + int status = -1; + + try { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + byte[] responseMessage = null; + Header[] responseHeaders = null; + + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), + "SEND [Bypass Request..]" + CommonLib.getDumpMessage(data)); + } + try { + if (logger.isDebug()) { + logger.debug("[method getName]" + method.getMethod()); + logger.debug("[method getRequestHeaders]" + java.util.Arrays.toString(method.getHeaders())); + logger.debug("[method getURI]" + method.getPath()); + } + try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method, context)) { + status = response.getCode(); + responseHeaders = response.getHeaders(); + responseMessage = EntityUtils.toByteArray(response.getEntity()); + if (logger.isDebug()) { + logger.debug("[received status]" + status); + } + } + } catch (ConnectException e) { + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClient5AdapterServiceBypass] responseType =" + vo.getResponseType()); + logger.debug("HttpClient5AdapterServiceBypass] RECV (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(responseMessage)); + } + throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage()); + } + + stopWatch.stop(); + + /** + * @formatter:off + * OAuth 토큰 응답 체크(Adapter properties로 설정) + * 응답코드에 따라 유효한 토큰 확인(토큰 재발급 여부 확인) + * API 서비스 마다 정책이 다름(보통 400대에서 체크하나 200에서도 체크할 수 있음) + * + * TOKEN_ERROR_CODE_KEY: 응답 json error code key + * __ex) $.dataHeader.resultCode + * TOKEN_ERROR_CODE_VALUES: 토큰 재발급이 필요한 응답 error codes(ex: O0001,O0002,O0003) + * TOKEN_ERROR_HTTP_STATUS_CODE: 보통 400대에서 체크하나 API 제공자에 따라 200에서도 체클할수 있음 + * ex) TOKEN_ERROR_HTTP_STATUS_CODE = 200 + * @formatter:on + */ + boolean needReissue = false; + + if (status == 200) { + if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) { + needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues, + vo.getEncode()); + } + } else if (status >= 400 && status < 500) { + if (useAdapterToken) { + needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues, + vo.getEncode()); + } + + if (!needReissue) { + // body 값이 없을때만 exception 처리하고, body가 있으면 bypass 한다. + if (responseMessage == null || responseMessage.length == 0) { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), url, restMethod, responseMessage); + throw new Exception(errMsg); + } + } + } else if (status != 200) { + responseMessage = responseMessage != null ? responseMessage : new byte[0]; + // body 값이 없을때만 exception 처리하고, body가 있으면 bypass 한다. + if (responseMessage.length == 0) { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), url, restMethod, responseMessage); + throw new Exception(errMsg); + } + } + + if (useAdapterToken) { + if (responseMessage == null) { + throw new Exception("responseMessage is NULL, HttpStatus=" + status); + } + + // OAuth 토큰 재요청 코드 확인 + if (needReissue) { + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClient5AdapterServiceBypass] retry access token response code = [" + + vo.getResponseType() + "] message = [" + new String(responseMessage, vo.getEncode()) + + "]"); + } + + // 토큰 재발급 + AccessTokenManager tokenManager = AccessTokenManager.getInstance(); + String oldToken = accessToken == null ? null : accessToken.getAccessToken(); + OAuth2AccessTokenVO newAccessToken = (OAuth2AccessTokenVO) tokenManager + .retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken); + method.setHeader("Authorization", newAccessToken.getAuthorization()); + setAuthHeaders(method, newAccessToken); + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClient5AdapterServiceBypass] SEND (" + vo.getAdapterGroupName() + + ") RETRY TOKEN = [" + newAccessToken + "]"); + } + + try (CloseableHttpResponse retryResponse = (CloseableHttpResponse) mclient.execute(method, context)) { + status = retryResponse.getCode(); + responseHeaders = retryResponse.getHeaders(); + responseMessage = EntityUtils.toByteArray(retryResponse.getEntity()); + } catch (IOException e) { + throw new Exception("retry excuteMethod Exception = " + e.getMessage()); + } + + if (status != 200) { + responseMessage = responseMessage != null ? responseMessage : new byte[0]; + if (responseMessage.length == 0) { + String errMsg = String.format( + "http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s", + status, vo.getAdapterGroupName(), vo.getAdapterName(), url, restMethod, + responseMessage); + throw new Exception(errMsg); + } + } + + if (logger.isDebug() && "N".equals(vo.getTestCallYn())) { + logger.debug("HttpClient5AdapterServiceBypass] RETRY responseType =" + vo.getResponseType()); + logger.debug("HttpClient5AdapterServiceBypass] RETRY RECV (" + vo.getAdapterGroupName() + ") = " + + CommonLib.getDumpMessage(responseMessage)); + } + } + } + + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), + "RECV [Bypass Request..]" + CommonLib.getDumpMessage(responseMessage)); + } + assignRelayDataToInbound(tempProp, responseHeaders, status); + return responseMessage; + } catch (SocketTimeoutException ste) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), + "HttpClient5AdapterServiceBypass] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : " + + ste.toString(), + ste); + } + logger.error("HttpClient5AdapterServiceBypass] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : " + + ste.toString(), ste); + throw ste; + } catch (ConnectException ce) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), + "HttpClient5AdapterServiceBypass] Connection Exception (" + vo.getAdapterGroupName() + ") : " + + ce.toString(), + ce); + } + logger.error("HttpClient5AdapterServiceBypass] Connection Exception (" + vo.getAdapterGroupName() + ") : " + + ce.toString(), ce); + throw ce; + } catch (Exception e) { + if (vo.getTraceLevel() >= 3) { + HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), e.toString(), e); + } + logger.error( + "HttpClient5AdapterServiceBypass] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(), + e); + throw e; + } finally { + if (status != HttpStatus.SC_OK) { + String[] msgArgs = new String[1]; + msgArgs[0] = String.valueOf(status); + String resMsg = ExceptionUtil.make("RDCEAIAHA013", msgArgs); + if (logger.isDebug()) { + logger.debug(resMsg); + } + } + } + } + + private boolean hasBody(Object data, HttpUriRequestBase method) { + if (data == null) { + return false; + } + // GET, DELETE는 표준 HTTP에서 body를 지원하지 않음 + return method instanceof HttpPost || method instanceof HttpPut; + } + + private HttpUriRequestBase generateMethod(String restMethod, String url) { + switch (HttpMethodType.getValue(restMethod)) { + case GET: + return new HttpGet(url); + case DELETE: + return new HttpDelete(url); + case POST: + return new HttpPost(url); + case PUT: + return new HttpPut(url); + default: + return new HttpPost(url); + } + } + + private String getRewriteUrl(HttpClientAdapterVO vo, String inboundRewritePath, String queryString, + JSONObject restOptionObject, Map inboundPathVariables) { + String fragment = null; + if (StringUtils.isNotBlank(queryString)) { + int fragIdx = queryString.indexOf('#'); + if (fragIdx >= 0) { + fragment = queryString.substring(fragIdx + 1); + queryString = queryString.substring(0, fragIdx); + } + } + + String restExtraPath = (String) restOptionObject.get("extraPath"); + if (StringUtils.isBlank(restExtraPath)) { + StringBuilder uri = new StringBuilder(500); + String baseUrl = StringUtils.removeEnd(vo.getUrl(), "/"); + String rewritePath = inboundRewritePath != null && !inboundRewritePath.startsWith("/") + ? "/" + inboundRewritePath : inboundRewritePath; + uri.append(baseUrl).append(StringUtils.defaultString(rewritePath)); + + if (StringUtils.isNotBlank(queryString)) { + uri.append('?'); + uri.append(encodeUriQuery(queryString, false)); + } + + if (doSendUrlFragment && fragment != null) { + uri.append('#'); + uri.append(encodeUriQuery(fragment, false)); + } + + return uri.toString(); + } else { + String type = (String) restOptionObject.get("type"); + String url = getUrl(vo.getUrl(), restExtraPath); + + if (StringUtils.equalsIgnoreCase(type, HttpClientAdapterServiceRest.TYPE_VARIABLE_URL_REQUEST)) { + inboundPathVariables = mergeInboundPathVariables(inboundPathVariables, queryString); + List urlVariableValueList = new ArrayList<>(); + JSONArray uriVariables = (JSONArray) restOptionObject.get("uriVariables"); + if (uriVariables != null && !uriVariables.isEmpty() && inboundPathVariables != null + && !inboundPathVariables.isEmpty()) { + for (Object tempObject : uriVariables) { + String urlVaribleId = (String) tempObject; + String uriVariableValue = inboundPathVariables.get(urlVaribleId); + urlVariableValueList.add(uriVariableValue); + } + + if (!urlVariableValueList.isEmpty()) { + UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build(); + Object[] urls = urlVariableValueList.toArray(); + url = uriComponents.expand(urls).toUriString(); + } + } + } else { + if (StringUtils.isNotBlank(queryString)) { + url += "?" + encodeUriQuery(queryString, false); + } + + if (doSendUrlFragment && fragment != null) { + url += "#" + encodeUriQuery(fragment, false); + } + } + + return url; + } + } + + private Map mergeInboundPathVariables(Map inboundPathVariables, + String queryString) { + if (StringUtils.isBlank(queryString)) { + return inboundPathVariables; + } + + if (inboundPathVariables == null) { + inboundPathVariables = new LinkedHashMap<>(); + } + + String[] pairs = queryString.split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + inboundPathVariables.put(pair.substring(0, idx), pair.substring(idx + 1)); + } + + return inboundPathVariables; + } + + private String getUrl(String baseUrl, String extraPath) { + if (StringUtils.isBlank(extraPath)) { + return baseUrl; + } + + if (StringUtils.contains(extraPath, "http://") || StringUtils.contains(extraPath, "https://")) { + return extraPath; + } + + String targetUrl = baseUrl; + if (!targetUrl.endsWith("/")) { + targetUrl += "/"; + } + if (extraPath.startsWith("/")) { + targetUrl += extraPath.substring(1); + } else { + targetUrl += extraPath; + } + + return targetUrl; + } + + protected CharSequence encodeUriQuery(CharSequence in, boolean encodePercent) { + StringBuilder outBuf = null; + Formatter formatter = null; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + boolean escape = true; + if (c < 128) { + if (asciiQueryChars.get(c) && !(encodePercent && c == '%')) { + escape = false; + } + } else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) { + escape = false; + } + if (!escape) { + if (outBuf != null) + outBuf.append(c); + } else { + if (outBuf == null) { + outBuf = new StringBuilder(in.length() + 5 * 3); + outBuf.append(in, 0, i); + formatter = new Formatter(outBuf); + } + formatter.format("%%%02X", (int) c); + } + } + return outBuf != null ? outBuf : in; + } + + protected static final BitSet asciiQueryChars; + static { + char[] c_unreserved = "_-!.~'()*".toCharArray(); + char[] c_punct = ",;:$&+=".toCharArray(); + char[] c_reserved = "/@".toCharArray(); + asciiQueryChars = new BitSet(128); + for (char c = 'a'; c <= 'z'; c++) + asciiQueryChars.set(c); + for (char c = 'A'; c <= 'Z'; c++) + asciiQueryChars.set(c); + for (char c = '0'; c <= '9'; c++) + asciiQueryChars.set(c); + for (char c : c_unreserved) + asciiQueryChars.set(c); + for (char c : c_punct) + asciiQueryChars.set(c); + for (char c : c_reserved) + asciiQueryChars.set(c); + asciiQueryChars.set('%'); + } + + private void assignRelayDataToInbound(Properties prop, Header[] responseHeaders, int status) { + Properties headerProp = new Properties(); + if (responseHeaders != null) { + for (Header header : responseHeaders) { + String name = header.getName(); + String value = header.getValue(); + String existing = headerProp.getProperty(name); + if (existing != null) { + // 동일 이름의 헤더가 여러 개인 경우 콤마로 합침 (RFC 7230) + headerProp.put(name, existing + ", " + value); + } else { + headerProp.put(name, value); + } + } + } + + headerProp.put(HttpAdapterServiceBypass.HTTP_STATUS, String.valueOf(status)); + + Map map = new HashMap(); + map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, headerProp); + + prop.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map); + } + + private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception { + method.setHeader("Authorization", + String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken())); + } + + private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues, + String encode) { + if (responseMessage == null) { + return false; + } + + if (StringUtils.isBlank(tokenErrorCodeKey)) { + return false; + } + + if (StringUtils.isBlank(tokenErrorCodeValues)) { + return false; + } + + try { + DocumentContext jsonContext = JsonPath.parse(new String(responseMessage, encode)); + String responseCode = jsonContext.read(tokenErrorCodeKey); + if (StringUtils.isBlank(responseCode)) { + return false; + } + + String[] arr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenErrorCodeValues, ","); + return ArrayUtils.contains(arr, responseCode); + } catch (Exception e) { + logger.error("HttpClient5AdapterServiceBypass] checkTokenRetry error=" + e.getMessage()); + } + + return false; + } + + private void assignRequestHeaders(HttpUriRequestBase method, Properties headerProp, Properties inProp) { + if (headerProp == null) { + return; + } + + for (Entry e : headerProp.entrySet()) { + String key = (String) e.getKey(); + String value = (String) e.getValue(); + + if (StringUtils.equalsAnyIgnoreCase(key, HttpAdapterServiceBypass.HOP_BY_HOP_HEADERS) + || StringUtils.equalsIgnoreCase(key, HttpHeaders.CONTENT_LENGTH)) { + continue; + } + + if (doHandleCompression && StringUtils.equalsIgnoreCase(key, HttpHeaders.ACCEPT_ENCODING)) { + continue; + } + + method.addHeader(key, value); + + if (logger.isDebugEnabled()) { + logger.debug("Request Header :" + key + "=[" + value + "]"); + } + } + + if (doForwardIP) { + String forHeaderName = "X-Forwarded-For"; + String forHeader = inProp.getProperty(HttpAdapterServiceKey.INBOUND_REMOTE_ADDR); + if (StringUtils.isNotBlank(forHeader)) { + String existingForHeader = headerProp.getProperty(forHeaderName); + if (existingForHeader != null) { + forHeader = existingForHeader + ", " + forHeader; + } + method.addHeader(forHeaderName, forHeader); + } + + String protoHeaderName = "X-Forwarded-Proto"; + String protoHeader = inProp.getProperty(HttpAdapterServiceKey.INBOUND_SCHEME); + if (StringUtils.isNotBlank(protoHeader)) { + method.addHeader(protoHeaderName, protoHeader); + } + } + } + + @SuppressWarnings("deprecation") + private JSONObject parseJson(String message) throws Exception { + if (message == null) { + return null; + } + + return (JSONObject) JSONValue.parse(message); + } + + public static final String getIgnoreCaseProp(Properties prop, String key) { + if (prop == null) { + return null; + } + + for (Entry e : prop.entrySet()) { + if (StringUtils.equalsIgnoreCase(key, (String) e.getKey())) { + return (String) e.getValue(); + } + } + + return null; + } +} diff --git a/src/main/java/com/eactive/eai/custom/inbound/action/RestAdapterMethodUrlFallbackRequestAction.java b/src/main/java/com/eactive/eai/custom/inbound/action/RestAdapterMethodUrlFallbackRequestAction.java new file mode 100644 index 0000000..a86e2f2 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/inbound/action/RestAdapterMethodUrlFallbackRequestAction.java @@ -0,0 +1,104 @@ +package com.eactive.eai.custom.inbound.action; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import com.eactive.eai.adapter.AdapterGroupVO; +import com.eactive.eai.adapter.AdapterManager; +import com.eactive.eai.adapter.Keys; +import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; +import com.eactive.eai.adapter.http.dynamic.impl.HttpAdapterServiceRest; +import com.eactive.eai.common.exception.ExceptionUtil; +import com.eactive.eai.common.stdmessage.STDMessageManager; +import com.eactive.eai.common.util.MessageKeyExtractor; +import com.eactive.eai.inbound.action.ActionException; +import com.eactive.eai.inbound.action.RequestActionSupport; + +public class RestAdapterMethodUrlFallbackRequestAction extends RequestActionSupport { + + /** + * 1. 기능 : Inbound Adapter를 통해 전달받은 비표준메시지에서 표준메시지를 생성하기 위한 키값을 추출 + * 2. 처리 개요 : + * - TYPE_HTTP_CUSTOM / TYPE_REST: 전체 URI로 키를 먼저 조회하고, + * 없으면 뒤 경로를 하나씩 제거하면서 일치하는 키를 탐색한다. + * - 그 외: MessageKeyExtractor를 통해 추출 + * + * @param message Object 타입의 요청메시지 + * @return String 비표준메시지의 메시지키값 + **/ + public String[] perform(Object message) throws ActionException { + String[] key = null; + + AdapterManager adapterManager = AdapterManager.getInstance(); + AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adapterGroupName); + String adptGrpName = adptGrpVO.getName(); + + if (StringUtils.equals(adptGrpVO.getType(), Keys.TYPE_HTTP_CUSTOM) + || StringUtils.equals(adptGrpVO.getType(), Keys.TYPE_REST)) { + try { + String requestMethod = prop + .getProperty(HttpAdapterServiceRest.PROPERTIES_NAME_HTTP_REQUEST_METHOD); + + // /api/v1/public/getUserInfo.svc + String inboundUri = prop.getProperty(HttpAdapterServiceKey.INBOUND_EXTURI); + String apiPath = prop.getProperty(HttpAdapterServiceKey.API_PATH); + + // /getUserInfo.svc + String apiUri = org.apache.commons.lang.StringUtils.removeStart(inboundUri, apiPath); + + Set allKeys = new HashSet<>(Arrays.asList( + STDMessageManager.getInstance().getAllSTDMessageKeys())); + + // 전체 URI부터 시작해서 뒤 경로를 하나씩 제거하며 일치하는 키를 탐색 + String searchUri = apiUri; + for (int i = 0; i < 10; i++) { + String candidateKey = adptGrpName + ":" + requestMethod + "|" + + StringUtils.removeStart(searchUri, "/"); + if (allKeys.contains(candidateKey)) { + return new String[] { candidateKey }; + } + String shortened = StringUtils.substringBeforeLast(searchUri, "/"); + if (StringUtils.isBlank(shortened)) { + break; + } + searchUri = shortened; + } + + // 일치하는 키가 없으면 원래 키로 fallback + String apiKey = adptGrpName + ":" + requestMethod + "|" + + StringUtils.removeStart(apiUri, "/"); + return new String[] { apiKey }; + + } catch (Exception e) { + throw new ActionException(ExceptionUtil.getErrorCode(e, "RECEAIIRA001")); + } + } + + if (message instanceof byte[]) { + try { + key = MessageKeyExtractor.getMessageKeyValueForRest(this.adapterGroupName, message, prop, "url", true); + } catch (Exception e) { + throw new ActionException(ExceptionUtil.getErrorCode(e, "RECEAIIRA001")); + } + if (logger.isDebug()) + logger.debug(adapterName + "] received data >> [" + new String((byte[]) message) + "]"); + } else { + try { + key = MessageKeyExtractor.getMessageKeyValueForRest(this.adapterGroupName, message, prop, "url", true); + } catch (Exception e) { + throw new ActionException(ExceptionUtil.getErrorCode(e, "RECEAIIRA001")); + } + if (logger.isDebug()) + logger.debug(adapterName + "] received data >> [" + message + "]"); + } + + for (int i = 0; i < key.length; i++) { + key[i] = adptGrpName.trim() + key[i]; + } + + return key; + } +} From 42cbafd67dc4a4ac424f9029ed3da654180f0fd1 Mon Sep 17 00:00:00 2001 From: curry772 Date: Tue, 7 Apr 2026 10:15:41 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feature:=20Content-Type=20charset=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=9D=B8=EB=B0=94=EC=9A=B4=EB=93=9C/?= =?UTF-8?q?=EC=95=84=EC=9B=83=EB=B0=94=EC=9A=B4=EB=93=9C=20=EC=9D=B8?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ .../service/DJErpApiAdapterService.java | 12 +++++++++++- .../impl/HttpClient5AdapterServiceBypass.java | 18 +++++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index df1df98..2336bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -235,3 +235,6 @@ gradle-app.setting # End of https://www.gitignore.io/api/java,macos,gradle,intellij /EAISIMWeb/ /.apt_generated_tests/ + +# Claude Code +.claude/ diff --git a/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java b/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java index 2662d44..2fde9ef 100644 --- a/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java +++ b/src/main/java/com/eactive/eai/adapter/service/DJErpApiAdapterService.java @@ -242,7 +242,17 @@ public class DJErpApiAdapterService extends HttpAdapterServiceSupport { byte[] data = new byte[bb.position()]; bb.position(0); bb.get(data); - paramValue = new String(data, encode); + String bodyEncode = encode; + String contentTypeHeader = request.getContentType(); + if (StringUtils.isNotBlank(contentTypeHeader)) { + try { + MediaType mediaType = MediaType.parseMediaType(contentTypeHeader); + if (mediaType.getCharset() != null) { + bodyEncode = mediaType.getCharset().name(); + } + } catch (Exception ignored) {} + } + paramValue = new String(data, bodyEncode); if (traceLevel >= 3) { HttpMemoryLogger.txlog(adptGrpName + adptName, diff --git a/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java index 19e6f16..b25ec75 100644 --- a/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java +++ b/src/main/java/com/eactive/eai/custom/adapter/http/client/impl/HttpClient5AdapterServiceBypass.java @@ -34,6 +34,7 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -315,8 +316,23 @@ public class HttpClient5AdapterServiceBypass extends HttpClient5AdapterServiceSu HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(), "RECV [Bypass Request..]" + CommonLib.getDumpMessage(responseMessage)); } + // 응답 Content-Type charset 기반 인코딩 결정, 없으면 어댑터 encoding 사용 + String responseEncode = vo.getEncode(); + if (responseHeaders != null) { + for (Header header : responseHeaders) { + if (StringUtils.equalsIgnoreCase(header.getName(), HttpHeaders.CONTENT_TYPE)) { + try { + MediaType mediaType = MediaType.parseMediaType(header.getValue()); + if (mediaType.getCharset() != null) { + responseEncode = mediaType.getCharset().name(); + } + } catch (Exception ignored) {} + break; + } + } + } assignRelayDataToInbound(tempProp, responseHeaders, status); - return responseMessage; + return responseMessage != null ? new String(responseMessage, responseEncode) : ""; } catch (SocketTimeoutException ste) { if (vo.getTraceLevel() >= 3) { HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), From 7fbe64f562f6f420f2b935da79c611a26d9f7da4 Mon Sep 17 00:00:00 2001 From: curry772 Date: Tue, 7 Apr 2026 10:18:21 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=EC=9E=91=EC=97=85=EB=82=B4=EC=97=AD=20md?= =?UTF-8?q?=20ignore=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2336bd7..641f72c 100644 --- a/.gitignore +++ b/.gitignore @@ -238,3 +238,4 @@ gradle-app.setting # Claude Code .claude/ +/작업내역*.md From dc1ee33517d35b0034071093eea9fca1eeb072a9 Mon Sep 17 00:00:00 2001 From: curry772 Date: Wed, 8 Apr 2026 14:14:24 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=8B=9C=20=ED=95=84=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD(grantType=20->=20grant=5Ftype)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eactive/eai/authserver/custom/DJErpOAuth2Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java index 58cc58c..ced4531 100644 --- a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java +++ b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java @@ -112,7 +112,7 @@ public class DJErpOAuth2Controller { if (!StringUtils.equals(grantType, "client_credentials")) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), - "Invalid Mandatory Field {grantType}"); + "Invalid Mandatory Field {grant_type}"); } String[] scopeArr = org.springframework.util.StringUtils.tokenizeToStringArray(scopes, " "); From f9a520eacceba13d3413f6b6fe2b8daa27f93f49 Mon Sep 17 00:00:00 2001 From: curry772 Date: Wed, 8 Apr 2026 14:16:48 +0900 Subject: [PATCH 5/8] =?UTF-8?q?HTTP=20Header=20=EB=A1=9C=EA=B7=B8=20off=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20-=20-Duse.http.header.log=3Dy=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80=EC=8B=9C=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interceptor/HttpResponseLoggingInterceptor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/eactive/eai/adapter/interceptor/HttpResponseLoggingInterceptor.java b/src/main/java/com/eactive/eai/adapter/interceptor/HttpResponseLoggingInterceptor.java index 2a8f8e3..19c061f 100644 --- a/src/main/java/com/eactive/eai/adapter/interceptor/HttpResponseLoggingInterceptor.java +++ b/src/main/java/com/eactive/eai/adapter/interceptor/HttpResponseLoggingInterceptor.java @@ -18,12 +18,18 @@ import java.util.Properties; public class HttpResponseLoggingInterceptor implements HandlerInterceptor { static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); + private static boolean httpHeaderLogMode = HttpAdapterExtraLogUtil.isHttpHeaderMode(); - @Override + @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.debug("httpHeader logging Interceptor start"); + if (!httpHeaderLogMode) { + logger.info("httpHeader logging off - use.http.header.log"); + return; + } + Properties transactionProp = (Properties) request.getAttribute(TransactionContextKeys.TRANSACTION_PROP); if(transactionProp == null || "ASYN".equals(transactionProp.getProperty(HttpAdapterServiceKey.INBOUND_SYNC_ASYNC_TYPE))){ logger.debug("httpHeader logging Interceptor ASYNC"); From 1efd2deb9d5f6fd782d953a539e19832963e4828 Mon Sep 17 00:00:00 2001 From: curry772 Date: Wed, 8 Apr 2026 16:41:29 +0900 Subject: [PATCH 6/8] =?UTF-8?q?/dj=20=EC=B2=98=EB=9F=BC=202=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EC=B2=AB=20url=EC=9D=B4=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eai/adapter/controller/DJErpApiAdapterController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java index 06b5502..d575301 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java +++ b/src/main/java/com/eactive/eai/adapter/controller/DJErpApiAdapterController.java @@ -294,7 +294,7 @@ public class DJErpApiAdapterController implements HttpAdapterServiceKey { // /api/v1/public apiUri = StringUtils.substringBeforeLast(apiUri, "/"); - if (apiUri.length() < 4) { // /api + if (apiUri.length() < 3) { // /api return null; } } From 7e7d107dde41665ca1068356e33b895e1588cd22 Mon Sep 17 00:00:00 2001 From: curry772 Date: Tue, 21 Apr 2026 08:31:43 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=ED=86=A0=EA=B8=89=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=EC=9D=B4=EB=A0=A5=20=EC=9D=B4=EC=A4=91=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eactive/eai/authserver/custom/DJErpOAuth2Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java index ced4531..07776a6 100644 --- a/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java +++ b/src/main/java/com/eactive/eai/authserver/custom/DJErpOAuth2Controller.java @@ -150,7 +150,7 @@ public class DJErpOAuth2Controller { responseToken.setJti((String) token.getAdditionalInformation().get("jti")); responseToken.setClientId((String) token.getAdditionalInformation().get("client_id")); - logTokenIssuance(true, false, "Token issued successfully", token.getValue()); + //logTokenIssuance(true, false, "Token issued successfully", token.getValue()); ObjectMapper mapper = new ObjectMapper(); return ResponseEntity.ok(mapper.writeValueAsString(responseToken)); From 65d61e646bcce98369c8c51bc135d6d54a93af2a Mon Sep 17 00:00:00 2001 From: curry772 Date: Wed, 22 Apr 2026 14:41:06 +0900 Subject: [PATCH 8/8] =?UTF-8?q?oAuth=20=EC=9A=B4=EC=98=81=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=84=9C=20=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../certificate/product/elink-oauth-prod.jks | Bin 2222 -> 2738 bytes .../certificate/product/elink-oauth-prod.pub | 14 +++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/resources/certificate/product/elink-oauth-prod.jks b/src/main/resources/certificate/product/elink-oauth-prod.jks index 1a23d3475ced91122c137a8f2ff64c75f1926ee3..af6404b60f4d54e199e54fbc385f67a2d6fb4252 100644 GIT binary patch literal 2738 zcma);X*d)N_r_<&%ovk>$P#6X8SB``l8F#w$yQm02!pbdtrCL~%43iAgd6>q_0dE~(|@cW6<{Ky^tW8}y9%Jk{+(in13{08kh8xXiI zKx6(H+0k-fzIEr;@DhD{U;DP$YJzwAN-&wL83dBy1_8y;>`>VMUj(s10BBJtn=8o# z=*a>G%7VGQKRukqwqM97*CDyaS4$JYMDTn6F#%4h(U7%^aBI~qF-+7XYknn{!IQ@hgm{e6 z%d=6o0`dcK(*5rUw9n5Ig3YrRD=)r8s82b~yKNzQP*bS^h@V_3V!{yHBO9Dt@(OIZ zk~LwT{MJum?hRLT^C8=)f?ai?53X9JtwL%(wct`Rqm_$1`b`u+Wx5ibZp2|@y1xv$ zO4G;1=r41s%Phg1@S;nA^AyAv4>y|=az_tIK?y4X1>2@{z&(HAAew3 zpu~cbTp87qTxDyqIA;chdr(Sxk6_WWZL@2oa1%|3cqNt?`lUAKx8KbS!^1Yaw~yR= z>zIzhCnz=gH?2;}g!udC{?tJuJHi@fD7{=3vHn_Xk^szh0}p*JG@aKXjAC@msW2P3(epXj-3vomz=84;nM`(e<^uObqtv zFs)$}eXQN)*-D$JjqT-@XEFy7sIUaGS88j}B=)l8GB)}zGmuB%+M^8Bfz6%f=l8l% z>8!q%`<9oxN5|F^>Rofy1k?pDz!Cuo~LFX7Rsz3?7S<1I|gwrG@Mbf ze$qm@Qik4xts$tO2dC^t+VKkNL_{2zs{$C1ZlWh+l47^}g(Jm&Fj}u-N?SpyGmSfL zg1fx@q|(*zeIH{X_3Q9sDj3xSRBBWUS}E0bPMh>@)Oh1cD0dl5mA$~;wUBNs z(VG?=F%l(%xZ5Lf8<8!cbY8^2&@EADeskMk^zKZwiuTLJSG=lns03!w&DhFwgOX-G z8*4Ei8U}&Yd@mbU3=1!eFQj~@sj6S6B>UL+K52AY-so-QS<4Q*Q+%iVVCu`rly-9; z4OukV^qvP~WMGcaIehkz&o+zmSu0Y2Fz(do$I5cUm(7Gf(K=vEsR>8@LRTf9@I^Jn z7BQX=iSsSaTH;(vEhy(UOKy1I2wUTbadp18q}cD~3<}`^pg3JdoA_%^Ub1oeQNFsL zh1l5_1MhyS)}qBI)mq=f?o|rppm&N5_#KZg_g;xzY2+Cm=6E|B0@~RL5pV4%?Mit%s2AO~(mxW>AG6*F8^E zIoCU%aA=iv$he9(g)M&J#ebc5!<(C!TG9`pUreTR)-h0oHtYQ~H~YL^)hVHp3T(046wg`urlS(NwAkfrYMA**pE zMXWqZ!LcLoqmwcYefkeB+4z(NPaX$&0(=2J0Dr)BfC3-@;0Cw>2nBef#s9JKEAxSQ z?R@+^#gsKQH89GW8fq#Sv=*8Oj{Rd|g+3;NgMUl@ARyp(^ZI84{BOX9J~cn+pui*}4KnD}+)e*yMN(XgP=8bR;l$O_p!6s{OUWcmIIEEFVHV78iqTLvHuJ1bwP ziHdqHbX$@1oa$_Js%__L`=(3}P9I$VE8fv+{2pLtBJezcd7O)6d=a01K?5BvzP{79 zJoMqY^&;k!0sv-wx-q8MDRaHHwk){NTrCd1eQA^*TsXqXr0ubEnP+-Bbl`iQrmvc0 z_=pUMHyUKJsv)`EaAEZW68GS=n!Ff}fnq@yx%r)miojYo01`K$rdQ!@GnNg~GlHhKB0Cgv^nOLV?k`ugykdsKgy#e|%YTx24BDgQ3LfO| zX}VC9l3A^>F(6yTX5GI_dgRz*{0c=jBs+*%)t6lXMzOdhBAc)PvV_WP*aazPj`SXK z^CYemMC{)3Zw^^?ER~l_ug=E4j*{3c6+1JGWXTxU)KnNhpzm@XR{+ij29-#iR{JUW5-a>WgwGb9+v`4HPvIcFQ_-L`T!K7Br0OJF@21_A+6o9jxD1=6k?FGM#~ z?SvRs%RtrK)xx6j=KbtlL5PWlqkA;)^%`AwDcPgGUGOl#s4ZMJB`HwIo)R^%QKCte z5fJ0OnRv4A014x0li|D%ZDz_hE7djnhy3CmtQ2avPq%~6+paVXr`Pz`-}JUXTcDNE z9Dn{e5D)?Yi!IcI@12`0_XvZ1)et!YB$P;WKdKft77cIq>$P%ONCWeE%xQlL$T1hd T9_Xn<-xA9DT$Ue)9A literal 2222 zcmcJQX*AS}8^`B2W`)-j`bMCqC?~CWf=Q+=F&a<8Gv%R>z2m*n?I|lqTB*mNR zOH=oAWdwNc_<};wp6MVE6u>hfTX+!|LJbB$fCFGO06;+yCS(pgluC{K-A~2P9CLTgpB~^^rO^`stpj`dmrK1XBfIh<8!hi)UCqpY29lh_JA*72)z= z7fO;zUclGY-Ahs^XRA9e8iKTGk6p*R;aAJor9BEZX3WK2-W$J=bog2UA%j_A`)GzU zR&LB=PE+CWBPG#Fk}LwesENscqcc`Ly$CDFqh1Du?oZan;6&!>*mL zgpfhSSj&xKY_+}*XK%1sUC+z}*E_a_h}fN1)A!0sN}v3C-)Y*hu}rn3vwv3b1+_CX zw@xJ7I3sGZZ`fWYt26S+fb;Ipq53kON63~Vn4@uQ=s@Sg?W*K9d6h?tuQR)5uao+I z(Btk)4Czt$-OCRa&<9iH%;iyPq58A3UbU$LiO(H0()b9^+Gsrz_Khz(q=jdrUO%eT$*t9GmJm zj|ma+TeY6Y=e&p6Gb$WWgFQ& zZ!xCxI5;qD}~r_f1YB!@7>Uk*nFfRNTO@ zcoyCOVU5F?T~Dv{1}ab5C53Y;5^st2m`ITph5At5!A&aS zVJ3b!^Wv#tjHQ_uP*~rCiu(S}WexYb#-eZUCJ+5UEO6IB(F6XPZOu>jx z4VMRb8Kg83`73wfjxe{3QocY|B)h9-T`Vll!mI?Ha&DP133(F#==1=^^=#9K73G#P zx;~DP>bPgc(b9Z9mAq)xLFC^n-m(umf;AL$jwmI1f2b&qc?s9B&lT@Hsf*TRF%_Pv z%oB{jT%`d$ImrW`p2QHV-n^!_xkSsqTCVlHxh1+7XoyBt#v$l{jE|Ss`sxL{&PuSC zjkJ|z1LYMCs7qfK#V}GK}5~M_Fs5`-gOtD>iI01z58Fi^bGhEL?kr_QVOhlrPBeiyuT>GcuwJ|1W~1{) z77-70C553h4jCD!iq0yPS5HX)Ce&rWL4a&#zk#t&9-v$CHgnz&i!s*5b!M@Yka=X~ zmE%swY_Z^%hmA~o_nudHr?74@k}&UjNXhrm!ZL*xUQJMZ6);zZf9_{VBnkVmorX6OWV-q`0~TPy;D{X9!3@J?RR5o_M+4!djYm zEj$sgqeIjoIQ&E%Jn8@V-xt6H_WiTzotGfM1Vll2CV&7l0RR+-VM`cUCmrQpb&-m-G9XSDP|3+n*sM1NVGp)1Y1(sS1Uk`~T^yW+|U;0ECylp`yT#(x=vN1K*({t-W;9&rXlx^( z9_-hynj9Pz!SgcLnojnlyNl(QfF^)bT6ncyuZv~hUx;O78IQMjPSc`W>JYF&xN7(p zd9!_yKEATr3RmMY^X)`VJRtU`tA(oC|3XwUQG2ADqjbiEv7T%SPmJweB6H4mf+keotchN71tY diff --git a/src/main/resources/certificate/product/elink-oauth-prod.pub b/src/main/resources/certificate/product/elink-oauth-prod.pub index 2d10ff9..985f76f 100644 --- a/src/main/resources/certificate/product/elink-oauth-prod.pub +++ b/src/main/resources/certificate/product/elink-oauth-prod.pub @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAix2aE0JUkUDGfGAWH23S -uVqh+yRzxVTZ8yy8gtz1MOUSLOFOtme6AgVyfVmOs7p/HyNl6oe9Iw3FM/70Mpn0 -0HLsPr38VL1e5Ez1gkONCmqx5YIVbVYgBmdfYtOhKyjRZl7nsprngq6pHeETNqHa -ama1WpZjrUQtdPya6bSrXbx0mnK3JhuFpuJqUL7oEqFOMuz3OxCN053iJnDyNNmT -SlI0XB78old2VbTY0l67FW4Kmp8YVMFm7mrxlnhUh2bhh49r1C9tsOTTXppNdyOk -0DjZ9jjwLFGUYNsFa4HeRHMg1YioyT2luw2gDoe06D6+CAYc893tgNa32+K+KURd -dQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqteC0VxYMc9Q4kYZpklI +8Zt/iqul2ccsvRV82U0m10RDmzR2zHHaw2sP/qtbx8l/gIpwP8pwGwVJ6tvYkH3i +6f17P4vkPhdV2OdqweDixOibkuytfbiNPpK5wVxs535Ps+9yVsPoTUyCcMEtLcBD +f5N+DmGpIm+OzJPzf95kpCHw7aVrStFu3KEgIJ8ppSZ6FCkj5Th4KnqtHl5kiYjv +Pda5tSqqpQd7fgRVvDsYdLP8HJYLsL2cMjAmrkdUtE/Gkkp3T93JUQf82tYc8df5 +KO/nD3r1MAhiKHpfdaDD8vaXSL2fJmKVQcENA+2PXg5/0/axMcGn0CQbjsATxvWa +mQIDAQAB -----END PUBLIC KEY-----