diff --git a/docs/레이아웃_조건부처리.md b/docs/레이아웃_조건부처리.md new file mode 100644 index 0000000..8c58170 --- /dev/null +++ b/docs/레이아웃_조건부처리.md @@ -0,0 +1,32 @@ +# 레이아웃 조건부 처리 (RefValue) + +FlatReader에서 GROUP/GRID 항목의 조건부 처리를 위한 RefValue 설정 방법 + +## 지원 조건 + +| 표현식 | 의미 | 설명 | +|--------|------|------| +| `NM` | EQUALS | 값이 NM과 일치하는 경우 | +| `NM\|EM` | OR | 값이 NM 또는 EM과 일치하는 경우 | +| `!EM` | NOT | 값이 EM이 아닌 경우 | +| `!NM\|EM` | NOT OR | 값이 NM도 아니고 EM도 아닌 경우 | + +## 레이아웃 설정 예시 + +| 항목명 | 설명 | Depth | Type | Node | OccMin | Length | OccMax | RefPath | RefValue | +|--------|------|-------|------|------|--------|--------|--------|---------|----------| +| Message | 메시지부 | 1 | 3 | 0 | 1 | 0 | 1 | Message.Msg_Header.MSG_HEDR_KNCD | NM\|EM | +| Msg_Header | 메시지헤더 | 2 | 3 | 0 | 1 | 0 | 1 | | | +| MSG_HEDR_KNCD | 메시지헤더종류코드 | 3 | 2 | 1 | 1 | 2 | 1 | | NM | + +## 동작 설명 + +위 예시에서 `Message` 항목은 `MSG_HEDR_KNCD` 값이 `NM` 또는 `EM`일 때만 처리됩니다. + +- `MSG_HEDR_KNCD = "NM"` → Message 처리됨 +- `MSG_HEDR_KNCD = "EM"` → Message 처리됨 +- `MSG_HEDR_KNCD = "XX"` → Message 스킵됨 + +## 변경 파일 + +- `elink-online-common/src/main/java/com/eactive/eai/message/parser/FlatReader.java` 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 213cc3a..f61d6ff 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java @@ -138,11 +138,12 @@ public class ApiAdapterController implements HttpAdapterServiceKey { // 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", 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", "").toUpperCase(); + String asyncMsgStyle = httpProp.getProperty("ASYNC_RTNMSG_TYPE", ""); String responseBody =""; if (asyncMsgStyle == null || asyncMsgStyle.trim().isEmpty()) { responseBody = makeResponseBodyMsg(); @@ -158,6 +159,8 @@ public class ApiAdapterController implements HttpAdapterServiceKey { // 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); diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java index 4e13781..d77aebf 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java @@ -1,85 +1,150 @@ package com.eactive.eai.adapter.controller; -import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.Charset; import javax.servlet.FilterChain; import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import com.eactive.eai.adapter.AdapterGroupVO; +import com.eactive.eai.adapter.AdapterManager; +import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterManager; +import com.eactive.eai.adapter.http.dynamic.HttpDynamicInAdapterUri; +import com.eactive.eai.common.stdmessage.STDMessageManager; +import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO; + public class ApiRequestBodyFilter extends OncePerRequestFilter { + + static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : "; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + - // 1. 필터 대상 + + String encoding = getInboundGroupAdapterEncoding(request); + if (!isTarget(request)) { filterChain.doFilter(request, response); return; } - // 2. 요청 전문 읽기 - String body = readBody(request); - if (body == null || body.isEmpty()) { + //원본 바디를 byte[]로 읽기 (개행 포함 그대로) 왜? 뒤에 필터에서 HMAC값 계산할수도 있어서 변조되면 안됨. 개행문자도 그대로 가야함. + byte[] rawBody = readBodyAsBytes(request); + + if (rawBody == null || rawBody.length == 0) { filterChain.doFilter(request, response); return; } - String modifiedBody = body; + byte[] finalBody = rawBody; try { - Object json = JSONValue.parse(body); - // 3. 예제: ROOTLESS ARRAY 보정 + String bodyStr = new String(rawBody, encoding); + Object json = JSONValue.parse(bodyStr); + if (json instanceof JSONArray) { - JSONObject wrap = new JSONObject(); - wrap.put("KJB_ROOTLESS_ARRAY", json); - modifiedBody = wrap.toJSONString(); + bodyStr = KJB_ROOTLESS_ARRAY + bodyStr + "}"; + finalBody = bodyStr.getBytes(encoding); } } catch (Exception e) { - // 변조 실패 → 원본 유지 - modifiedBody = body; + // JSON 파싱 실패 시 원본 유지 + finalBody = rawBody; } - - byte[] bytes = modifiedBody.getBytes(request.getCharacterEncoding() != null ? request.getCharacterEncoding() : Charset.defaultCharset().name() ); - // 5. Wrapper로 재주입 + // Wrapper로 재주입 HttpServletRequest wrapped = - new CachedBodyHttpServletRequest(request, bytes); + new CachedBodyHttpServletRequest(request, finalBody); filterChain.doFilter(wrapped, response); } private boolean isTarget(HttpServletRequest request) { String uri = request.getRequestURI(); - - return ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) - && (uri.startsWith("/api") || uri.startsWith("/mapi") ) && !uri.startsWith("/mapi/oauth2/token"); + && (uri.startsWith("/api") || uri.startsWith("/mapi")) + && !uri.startsWith("/mapi/oauth2/token"); } - private String readBody(HttpServletRequest request) throws IOException { - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = request.getReader()) { - String line; - while ((line = br.readLine()) != null) { - sb.append(line); + + private byte[] readBodyAsBytes(HttpServletRequest request) throws IOException { + try (ServletInputStream is = request.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[4096]; + int len; + while ((len = is.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + return baos.toByteArray(); + } + } + + private String getInboundGroupAdapterEncoding(HttpServletRequest request) { + String apiUri = request.getRequestURI(); + apiUri = StringUtils.removeStart(apiUri, request.getContextPath()); + apiUri = StringUtils.removeEnd(apiUri, "/"); + + String methodAndUri = request.getMethod() + "|" + apiUri; + String adapterGrpName; + HttpDynamicInAdapterUri adptUri = null; + + try { + STDMessageManager manager = STDMessageManager.getInstance(); + STDMsgInfoAddOnVO stdMsgInfo = manager.getStdMsgInfoAddOn(methodAndUri); + adapterGrpName = stdMsgInfo.gAdapterGroupName(); + + adptUri = findAdptUri(apiUri, HttpDynamicInAdapterManager.getInstance(), adapterGrpName); + } catch (Exception e) { + adptUri = null; + } + + AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO( adptUri.getAdptGrpName()); + + if( adapterGroupVO == null ) { + return Charset.defaultCharset().name(); + } + + return StringUtils.isNotBlank(adapterGroupVO.getMessageEncode()) ? adapterGroupVO.getMessageEncode() : Charset.defaultCharset().name(); //서버 jvm file.encoding 확인, file.encoding=utf-8 주고있음. + + } + + private HttpDynamicInAdapterUri findAdptUri(String apiUri, HttpDynamicInAdapterManager manager, String adapterGrpName) throws Exception { + if (StringUtils.isBlank(apiUri)) { + return null; + } + + + for (int i = 0; i < 10; i++) { + HttpDynamicInAdapterUri adptUri = manager.getAdptUri(apiUri); + if (adptUri != null) { + if (adptUri.getAdptGrpName().equals(adapterGrpName)) { + return adptUri; + } + } + + apiUri = StringUtils.substringBeforeLast(apiUri, "/"); + if (apiUri.length() < 4) { // /api + return null; } } - return sb.toString(); - } -} \ No newline at end of file + return null; + } +} diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java index 4827f88..8fb5489 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java @@ -13,6 +13,8 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @RestControllerAdvice public class ApiResponseAdvice implements ResponseBodyAdvice { + + static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : "; @Override public boolean supports(MethodParameter returnType, Class> converterType) { @@ -34,6 +36,7 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { } String removeRootlessArrayKeyFormResponseBody(String body) { + try { Object root = JSONValue.parse(body); diff --git a/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java b/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java index e831d91..6f4a3b2 100644 --- a/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java +++ b/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java @@ -239,8 +239,12 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { if (paramValue == null) { // parameter가 없는 경우때문에 처리 paramValue = ""; - } - else { // jwhong decrypt + } + // 순수한 Body값을 저장을 위해 위치 변경. + transactionProp.put(INBOUND_REQUEST_MESSAGE, paramValue); + + // paramValue가 null이 아닌 빈문자열(""," ")인 경우에 대비하여 조건 수정. + if (StringUtils.isNotBlank(paramValue)) { // jwhong decrypt paramValue = doPreDecryption(paramValue, transactionProp, request); } @@ -296,7 +300,8 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { message = ""; } - transactionProp.put(INBOUND_REQUEST_MESSAGE, message); + // 위치변경 : 가공되지 않은 Body값을 저장하기 위하여 위쪽으로 이동. +// transactionProp.put(INBOUND_REQUEST_MESSAGE, message); // 로컬 서비스 호출 ,encoding 처리 추가 String result = (String) service(adptGrpName, adptName, message, transactionProp, request, response); diff --git a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/AsyncReponseFilter.java b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/AsyncReponseFilter.java index 01eeb53..4b76ebf 100644 --- a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/AsyncReponseFilter.java +++ b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/AsyncReponseFilter.java @@ -14,6 +14,7 @@ public class AsyncReponseFilter implements HttpClientAdapterFilter, HttpAdapterS public static final String X_TRACE_ID = "partnerTraceId"; public static final String TRACE_ID = "traceId"; + @Override public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp) @@ -21,6 +22,7 @@ public class AsyncReponseFilter implements HttpClientAdapterFilter, HttpAdapterS logger.debug("AsyncReponseFilter] PreFilter Processing Start!!"); String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, ""); + String xloanToken = tempProp.getProperty(TransactionContextKeys.X_LOAN_TOKEN, ""); Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER"); if (filterHeaders == null) { @@ -35,7 +37,13 @@ public class AsyncReponseFilter implements HttpClientAdapterFilter, HttpAdapterS filterHeaders.put(X_TRACE_ID, inboundHeaderMap.get(X_TRACE_ID)); logger.debug("AsyncReponseFilter] Processing Key ["+X_TRACE_ID+"], value ["+inboundHeaderMap.get(X_TRACE_ID)+"]"); } + + if (inboundHeaderMap.containsKey(TransactionContextKeys.X_LOAN_TOKEN)||inboundHeaderMap.containsKey(TransactionContextKeys.X_LOAN_TOKEN.toLowerCase())) { + filterHeaders.put(TransactionContextKeys.X_LOAN_TOKEN, inboundHeaderMap.get(TransactionContextKeys.X_LOAN_TOKEN)); + logger.debug("AsyncReponseFilter] Processing Key ["+TransactionContextKeys.X_LOAN_TOKEN+"], value ["+inboundHeaderMap.get(TransactionContextKeys.X_LOAN_TOKEN)+"]"); + } } + filterHeaders.setProperty(TRACE_ID, traceId); logger.debug("AsyncReponseFilter] Processing Key ["+TRACE_ID+"], value ["+traceId+"]"); diff --git a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterFactory.java b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterFactory.java index 72f99ec..a4f04ad 100644 --- a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterFactory.java +++ b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterFactory.java @@ -24,6 +24,9 @@ public class HttpClient5AdapterFilterFactory { case SIMPLEFRAMEWORK: filter = new SimpleFrameworkFilter(); break; + case SIMPLEFRAMEWORKBODY: + filter = new SimpleframeworkBodyFilter(); + break; case JBOBP: filter = new JBOBPFilter(); break; diff --git a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterType.java b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterType.java index c927220..ae21d7f 100644 --- a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterType.java +++ b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/HttpClient5AdapterFilterType.java @@ -2,6 +2,7 @@ package com.eactive.eai.custom.kjb.adapter.http.client.filter; public enum HttpClient5AdapterFilterType { SIMPLEFRAMEWORK, + SIMPLEFRAMEWORKBODY, JBOBP, KFTCFACE, KFTCP2P, diff --git a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/SimpleframeworkBodyFilter.java b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/SimpleframeworkBodyFilter.java new file mode 100644 index 0000000..f051c2b --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/SimpleframeworkBodyFilter.java @@ -0,0 +1,26 @@ +package com.eactive.eai.custom.kjb.adapter.http.client.filter; + +import java.util.Properties; + +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +import com.eactive.eai.common.TransactionContextKeys; + +public class SimpleframeworkBodyFilter extends SimpleFrameworkFilter { + @Override + public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp) + throws Exception { + + Object returnMessage = super.doPreFilter(adptGrpName, adptName, prop, message, tempProp); + + JSONObject bizJsonStr = null; + + if ( returnMessage instanceof String ) { + bizJsonStr = (JSONObject) JSONValue.parse( (String)returnMessage); + bizJsonStr.put("GUID", tempProp.get(TransactionContextKeys.GUID)); + } + + return bizJsonStr != null ? bizJsonStr.toJSONString() : returnMessage; + } +} diff --git a/src/main/resources/certificate/KJBank-oAuth-dev.jks b/src/main/resources/certificate/KJBank-oAuth-dev.jks deleted file mode 100644 index ec007ca..0000000 Binary files a/src/main/resources/certificate/KJBank-oAuth-dev.jks and /dev/null differ diff --git a/src/main/resources/certificate/KJBank-oAuth-dev.pub b/src/main/resources/certificate/KJBank-oAuth-dev.pub deleted file mode 100644 index ef63b00..0000000 --- a/src/main/resources/certificate/KJBank-oAuth-dev.pub +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzY3zugQeOrTkol2fHIhD -/S8FqvNX6ijrU7v8wEnaIYVG1ay6ehAmlg6dc7J0Bvf1Ed5EvbDEI+4sa8n6egWa -zHSL4GXQXj9QFZGvRRah6nPhYiAvbLqHPwvG4QreIwx1Yt4tx63m8gFid6QjEsZP -DbWeX6bXW1scR+qtztkq2CGvOHlej5JHsdP350hH3Hq2sxdmTHurVwFJdfzBwDkU -MJlLwIEkkbACK1hrQhrEf1uKX8HtxrozSS8sso4wFrOPe8u/ykqYmSPlSfUsPvNr -cK/VroyX63RAE+giH84Y8mSHLr+mfEG3AhdJ1+ZqB9jBunTZDiGKn00mT4jJLwgr -BwIDAQAB ------END PUBLIC KEY----- diff --git a/src/main/resources/certificate/kjbank-oauth-dev.jks b/src/main/resources/certificate/kjbank-oauth-dev.jks new file mode 100644 index 0000000..53f814c Binary files /dev/null and b/src/main/resources/certificate/kjbank-oauth-dev.jks differ diff --git a/src/main/resources/certificate/kjbank-oauth-dev.pub b/src/main/resources/certificate/kjbank-oauth-dev.pub new file mode 100644 index 0000000..2460536 --- /dev/null +++ b/src/main/resources/certificate/kjbank-oauth-dev.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhXoqB4PrgZpJISjBe9ht +Y1R3Ne9ehxugWucwA4cBWAnYwvmrRAK0p8p+HuDikdZPq2hyAG02ucNt9xoru+Va +K4spNGieORbh0hmLRdvVBBM7Ureia3WhKED5abNnnPQw+jCLZNVYtO9rbtRhpUEG +t73sN1+0bOZlVBkbLenvWKJs2+tyE0gHhwgS8Fzrb++LHYN/c0KoTm+G9ITuPWoM +Vu/iME5WHqR95TkLKpx6s5J/le4BvncQDmkvKFIX9/DFN57orXQVB2lzDF8fMeWC +xaaHXtgfo2ercKKEdHQ5GFIgWN8+j2ZgU2JqfucazJpOpOGcizHiQKzh//NE7beD +IwIDAQAB +-----END PUBLIC KEY----- diff --git a/src/main/resources/certificate/product/KJBank-oAuth-prod.jks b/src/main/resources/certificate/product/KJBank-oAuth-prod.jks deleted file mode 100644 index f8af8d5..0000000 Binary files a/src/main/resources/certificate/product/KJBank-oAuth-prod.jks and /dev/null differ diff --git a/src/main/resources/certificate/product/KJBank-oAuth-prod.pub b/src/main/resources/certificate/product/KJBank-oAuth-prod.pub deleted file mode 100644 index 837a236..0000000 --- a/src/main/resources/certificate/product/KJBank-oAuth-prod.pub +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8M6NO6KfKRTwwOfZ/slx -fT/4vPzr1zXvgTHoVq/dyoCk8OeVS0OOJXln7ZqDPkKSlfd1X1fdDaEOfLJXfvPC -Hz7AOTBdr1/Y+Ro4slflO8d9/HbNkUZQTsbS/JqnpD20isP+sSJXHVOq5zEa4bbC -tGcpDIwoOIWqoCAA++jUSKhMZEiag5hWKUBi3wpLLjHiKtk9NZxvfCJO6GMJpN1c -O37cnudSan+oDGn3Twb579pK+gG3UZejQABhrqkv63MhMLrl74tXbHL9HX9shJjQ -zq8SQn2k/RbwLVE+USuDmN8BZkVmtQZRirr3d8yT+BlpgodeqMX8PYcztV+4VTvl -swIDAQAB ------END PUBLIC KEY----- diff --git a/src/main/resources/certificate/product/kjbank-oauth-prod.jks b/src/main/resources/certificate/product/kjbank-oauth-prod.jks new file mode 100644 index 0000000..1a62bde Binary files /dev/null and b/src/main/resources/certificate/product/kjbank-oauth-prod.jks differ diff --git a/src/main/resources/certificate/product/kjbank-oauth-prod.pub b/src/main/resources/certificate/product/kjbank-oauth-prod.pub new file mode 100644 index 0000000..bcc4c1f --- /dev/null +++ b/src/main/resources/certificate/product/kjbank-oauth-prod.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqCW0auFk5bqt7OZ2AtFw +xa/se93/hgNwDNC1kxKZpnvNM5YVj5gjlf0jOSVGNJlFTVvjIBcyPC7s1zlFDhgm +ebUKplRuw82lkIkS7mPjsqfPvMQlFjjLgrnECQ3AD86EkRcUT/bVliOtwLY6YJUa +5/1eqZCIRfLPVxOApSHuQLJ9JmSPhttwfKwTED3G2dB85D+QN5+Nre5nnWDjMScT +KOcyUZHOLO6m0UupBYU94XGJG4EEMwzSE/Lq1eJ49/wUsvaKY6Kz5G30d/ANwRZI +ex82Wp91ZPOasKKOB6vnQ/LNcMhhyYf6IzCliMUUFl53YeheXmvfWX6VLfWgjlLk +HQIDAQAB +-----END PUBLIC KEY-----