Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fcc112113 | |||
| b85301763f | |||
| 3ebe90d595 | |||
| bf03a97fa3 | |||
| 756523c5c3 | |||
| ec48226671 | |||
| 44e3f686a4 | |||
| 5551affe3e | |||
| a616cd6334 | |||
| 05a456b461 | |||
| 056583b542 | |||
| 2ed893e70d | |||
| 23eaf725b6 | |||
| 2354af516c | |||
| 209c1ba15d | |||
| 895d346bb6 | |||
| 940e5bb90c | |||
| 909cda9ffb | |||
| a538b9838e | |||
| d15b2d563e | |||
| 037fcd6302 | |||
| d9ff1fa40b | |||
| 4ce22937af | |||
| e1e2353139 | |||
| bc16dbcf13 | |||
| 176c9845d7 | |||
| 3ee53c469c | |||
| 6776153582 | |||
| ea09113dbe | |||
| 113b9227c9 | |||
| f3c2e64191 | |||
| 6809bf66b8 | |||
| 29a486bf9b | |||
| 7415828e82 | |||
| a4dbf4ab27 | |||
| 4f18d25a58 | |||
| 0e96e9470e | |||
| a51453a57f | |||
| 6eedac51f7 | |||
| 8d63586749 | |||
| 7b0699fdb9 | |||
| b6a5dce74a | |||
| 265a30ccf3 | |||
| d458d1b008 | |||
| 29b8b7eaac | |||
| 74302df5a2 | |||
| 9a15b5506f | |||
| 0b68b8166f | |||
| 8db66f8bef | |||
| c4a250db08 | |||
| db7a728344 | |||
| 813bb99952 | |||
| becd8612e8 | |||
| e208a2d64b | |||
| 444b7ad595 | |||
| 085dd024d8 | |||
| 3b6b3f6e4c | |||
| f4c28e8a27 | |||
| 3cda2873cc | |||
| c0efdd89d4 | |||
| 3eac4eeaa6 | |||
| c0c3f45d18 | |||
| 2686448791 | |||
| 3d4e5762c8 | |||
| f7859a77dc |
@@ -135,6 +135,8 @@ dependencies {
|
||||
|
||||
compileOnly group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
|
||||
|
||||
api "org.springframework.security:spring-security-jwt:1.1.1.RELEASE"
|
||||
|
||||
testRuntimeOnly 'com.h2database:h2:2.1.214'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
|
||||
Binary file not shown.
@@ -1,29 +0,0 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# comment start with #
|
||||
# level; // 0 : root ~ n
|
||||
# type; // 0: Message, 1: Field, 2:Group 3, Grid, 4 : Field Array, 9 : BIZ DATA
|
||||
# size // 0 : variable, > 0 fixed
|
||||
# fieldType; // 0: ELEMENT, 1: ATTRIBUTE,
|
||||
# length;
|
||||
# dataType; // 1: String, 2: Number,11: ZZ String, 12: LL Number
|
||||
#-----------------------------------------------------------------------------
|
||||
# name ,level ,type ,size ,fieldType ,length ,dataType ,,refPath, refValue,value
|
||||
#-----------------------------------------------------------------------------
|
||||
ElinkHeader ,0,0,1,0, 0,0,,,
|
||||
Header ,1,2,1,1, 0,0,,,
|
||||
StndCicsTrncd ,2,1,1,1,10,0,,,
|
||||
StndIntnlStndTelgmLen,2,1,1,1,10,11,,,0
|
||||
StndTranBaseYmd ,2,1,1,1,10,0,,,
|
||||
Common ,1,2,0,0, 0,0,Header.StndCicsTrncd,JI6H,null
|
||||
TranInfo ,2,2,1,0, 0,0,,,
|
||||
StndCicsTrncd ,3,1,1,0, 3,0,,,
|
||||
StndTelgmRecvTranCd ,3,1,1,0,10,0,,,
|
||||
StndPrcssRtdTranCd ,3,1,1,0,10,0,,,
|
||||
Array ,1,3,0,0, 3,0,,,
|
||||
Item1 ,2,1,1,0,10,0,,,JI6H
|
||||
Group ,2,3,1,0, 1,0,,,
|
||||
gitem1 ,3,1,1,0,10,1,,,726
|
||||
gitem2 ,3,1,1,0,10,0,,,20220905
|
||||
FArray ,1,4,0,0,10,0,,,
|
||||
bizData ,1,9,1,0, 0,0,,,
|
||||
zzData ,1,1,1,0, 2,10,,,ZZ
|
||||
|
@@ -1,41 +0,0 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# comment start with #
|
||||
# level; // 0 : root ~ n
|
||||
# type; // 0: Message, 1: Field, 2:Group 3, Grid, 4 : Field Array, 9 : BIZ DATA
|
||||
# size // 0 : variable, > 0 fixed
|
||||
# fieldType; // 0: ELEMENT, 1: ATTRIBUTE,
|
||||
# length;
|
||||
# dataType; // 1: String, 2: Number,11: ZZ String, 12: LL Number
|
||||
#-----------------------------------------------------------------------------
|
||||
# name ,level ,type ,size ,fieldType ,length ,dataType ,,refPath, refValue,value
|
||||
#-----------------------------------------------------------------------------
|
||||
# name ,level ,type ,size ,fieldType ,length ,dataType ,refPath, refValue, value
|
||||
STD_HNCPHEADER_REQ ,0 ,1 ,1 ,1 ,0 ,1 , , ,,표준헤더
|
||||
msg_len ,1 ,2 ,1 ,1 ,6 ,12 , , ,199,전문길이
|
||||
global_id ,1 ,2 ,1 ,1 ,29 ,1 , , ,GIDNO00000000001111,Global아이디
|
||||
service_id ,1 ,2 ,1 ,1 ,10 ,1 , , ,ELINKSVC01,거래코드
|
||||
service_cmt ,1 ,2 ,1 ,1 ,100 ,1 , , ,ELINK TEST Service,거래코드설명
|
||||
service_tn ,1 ,2 ,1 ,1 ,1 ,1 , , ,1,거래구분
|
||||
Session_id ,1 ,2 ,1 ,1 ,100 ,1 , , ,SESSIONID001,Session아이디
|
||||
encrypt_yn ,1 ,2 ,1 ,1 ,2 ,1 , , ,NO,전문암호화여부
|
||||
http_url ,1 ,2 ,1 ,1 ,100 ,1 , , ,,HTTPURL정보
|
||||
screen_id ,1 ,2 ,1 ,1 ,13 ,1 , , ,SCREEN0000001,화면ID
|
||||
scr_no ,1 ,2 ,1 ,1 ,30 ,1 , , ,000000000100000000020000000003,화면번호
|
||||
scr_dsc ,1 ,2 ,1 ,1 ,100 ,1 , , ,,화면명
|
||||
usr_id ,1 ,2 ,1 ,1 ,10 ,1 , , ,USER000001,사용자ID
|
||||
ip_addr ,1 ,2 ,1 ,1 ,47 ,1 , , ,,IPAddress
|
||||
ip_mac ,1 ,2 ,1 ,1 ,12 ,1 , , ,,ClientMAC Address
|
||||
com_code ,1 ,2 ,1 ,1 ,4 ,1 , , ,EACT,그룹사코드
|
||||
head_code ,1 ,2 ,1 ,1 ,7 ,1 , , ,EACT001,본부코드
|
||||
dept_cd ,1 ,2 ,1 ,1 ,7 ,1 , , ,ELINK01,부서코드
|
||||
conn_tp ,1 ,2 ,1 ,1 ,2 ,1 , , ,CH,요청채널구분
|
||||
Inter_chn ,1 ,2 ,1 ,1 ,2 ,1 , , ,EA,시스템구분
|
||||
recv_tm ,1 ,2 ,1 ,1 ,17 ,1 , , ,20220928000000000,전문수신시간
|
||||
send_tm ,1 ,2 ,1 ,1 ,17 ,1 , , ,,전문송신시간
|
||||
return_gubun ,1 ,2 ,1 ,1 ,1 ,1 , , ,S,전문응답구분
|
||||
return_value ,1 ,2 ,1 ,1 ,1 ,1 , , ,,처리결과
|
||||
msg_id ,1 ,2 ,1 ,1 ,7 ,1 , , ,,오류코드
|
||||
msg_nm ,1 ,2 ,1 ,1 ,250 ,1 , , ,,오류메시지
|
||||
filler ,1 ,2 ,1 ,1 ,125 ,1 , , ,,Filler
|
||||
bizData ,1 ,9 ,0 ,1 ,0 ,1 , , ,,업무데이터
|
||||
zzData ,1 ,2 ,0 ,1 ,2 ,11 , , ,,ZZ플래그
|
||||
|
@@ -1,11 +0,0 @@
|
||||
layout.file.type=CSV
|
||||
layout.file.path=./resources/standard-layout-sample.csv
|
||||
mapper.class=com.eactive.eai.message.test.TestInterfaceMapper
|
||||
mapper.definition=./resources/standard-message-mapping-config.properties
|
||||
reader.JSON=com.eactive.eai.message.parser.JsonReader
|
||||
reader.JSN=com.eactive.eai.message.parser.JsonReader
|
||||
reader.UJN=com.eactive.eai.message.parser.JsonReader
|
||||
reader.XML=com.eactive.eai.message.parser.XmlReader
|
||||
reader.UXL=com.eactive.eai.message.parser.XmlReader
|
||||
reader.ASC=com.eactive.eai.message.parser.FlatReader
|
||||
reader.FLAT=com.eactive.eai.message.parser.FlatReader
|
||||
@@ -1,20 +0,0 @@
|
||||
# layout : StandardMessage layout definition
|
||||
layout.file.type=CSV
|
||||
layout.file.path=./resources/standard-layout.csv
|
||||
layout.filter.FLAT=com.eactive.eai.message.filter.FlatMessageFilter
|
||||
# mapper : StandardMessage's fields getter/setter interface
|
||||
mapper.class=com.eactive.eai.message.service.DefaultInterfaceMapper
|
||||
mapper.definition=./resources/standard-message-mapping-config.properties
|
||||
# reader : parsing input data to StandardMessage
|
||||
reader.JSON=com.eactive.eai.message.parser.JsonReader
|
||||
reader.JSN=com.eactive.eai.message.parser.JsonReader
|
||||
reader.UJN=com.eactive.eai.message.parser.JsonReader
|
||||
reader.XML=com.eactive.eai.message.parser.XmlReader
|
||||
reader.UXL=com.eactive.eai.message.parser.XmlReader
|
||||
reader.ASC=com.eactive.eai.message.parser.FlatReader
|
||||
reader.FLAT=com.eactive.eai.message.parser.FlatReader
|
||||
encode.flat=euc-kr
|
||||
encode.value=euc-kr
|
||||
encode.xml=utf-8
|
||||
# version.layout.name=VERSION_SAMPLE
|
||||
# version.mapper.definition=./resources/version-message-mapping-config.properties
|
||||
@@ -1,28 +0,0 @@
|
||||
REQ_SYS_CODE=systemHeader.DMND_SYS_CD
|
||||
CHANNEL_TYPE_CD=stdcommon.chnlTycd
|
||||
CHANNEL_DETAIL_CD=stdcommon.chnlDtlsClcd
|
||||
SEND_TIME=stdheader.send_tm
|
||||
GUID=stdheader.global_id
|
||||
SYSTEM_TYPE=stdheader.Inter_chn
|
||||
RECV_TIME=stdheader.recv_tm
|
||||
RETURN_VALUE=stdheader.return_value
|
||||
GUID_ORG=stdcommon.ortrGuid
|
||||
GUID_SEQ=stdheader.global_id
|
||||
#SERVICE_ID=stdheader.service_id
|
||||
SERVICE_ID=service_id
|
||||
RETURN_SERVICE_ID=stdcommon.procsRsltRcmsSrvcId
|
||||
INTERFACE_ID=stdheader.service_id
|
||||
SESSION_ID=stdheader.Session_id
|
||||
#SEND_RECV_DIVISION=stdheader.return_gubun
|
||||
SEND_RECV_DIVISION=service_tn
|
||||
IN_EX_DIVISION=stdcommon.hmabDvcd
|
||||
OPERATION_ENV=stdcommon.sysOprtEnvDvcd
|
||||
FIRST_REQ_IP=stdheader.ip_addr
|
||||
RECOVER_YN=stdcommon.ortrRestrYn
|
||||
INST_CODE=stdheader.com_code
|
||||
TIMEOUT=stdheader.gnrzTmotTktm
|
||||
ERROR_CODE=stdheader.msg_id
|
||||
ERROR_MSG=stdheader.msg_nm
|
||||
USER_ID=stdheader.usr_id
|
||||
RESPONSE_TYPE=stdcommon.procsRsltDvcd
|
||||
SYNC_ASYNC_TYPE=stdcommon.txSynzDvcd
|
||||
@@ -1,26 +0,0 @@
|
||||
REQ_SYS_CODE=Inter_chn
|
||||
CHANNEL_TYPE_CD=
|
||||
CHANNEL_DETAIL_CD=conn_tp
|
||||
SEND_TIME=send_tm
|
||||
GUID=global_id
|
||||
SYSTEM_TYPE=service_tn
|
||||
RECV_TIME=recv_tm
|
||||
RETURN_VALUE=return_value
|
||||
GUID_ORG=stdcommon.ortrGuid
|
||||
GUID_SEQ=stdheader.global_id
|
||||
SERVICE_ID=service_id
|
||||
RETURN_SERVICE_ID=stdcommon.procsRsltRcmsSrvcId
|
||||
INTERFACE_ID=service_id
|
||||
SESSION_ID=Session_id
|
||||
SEND_RECV_DIVISION=service_tn
|
||||
IN_EX_DIVISION=stdcommon.hmabDvcd
|
||||
OPERATION_ENV=stdcommon.sysOprtEnvDvcd
|
||||
FIRST_REQ_IP=ip_addr
|
||||
RECOVER_YN=stdcommon.ortrRestrYn
|
||||
INST_CODE=stdheader.com_code
|
||||
TIMEOUT=stdheader.gnrzTmotTktm
|
||||
ERROR_CODE=msg_id
|
||||
ERROR_MSG=msg_nm
|
||||
USER_ID=usr_id
|
||||
RESPONSE_TYPE=stdcommon.procsRsltDvcd
|
||||
SYNC_ASYNC_TYPE=stdcommon.txSynzDvcd
|
||||
@@ -1,4 +0,0 @@
|
||||
service_id=header.if_id
|
||||
global_id=header.guid
|
||||
service_tn=header.req_type
|
||||
bizData=bizData
|
||||
@@ -6,6 +6,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -163,6 +164,9 @@ public class AdapterManager implements Lifecycle {
|
||||
}
|
||||
|
||||
public AdapterVO getAdapterVO(String adapterGroupName, String adapterName) {
|
||||
if(StringUtils.isAnyBlank(adapterGroupName, adapterName))
|
||||
return null;
|
||||
|
||||
AdapterGroupVO gvo = adapterGroups.get(adapterGroupName);
|
||||
if (gvo == null)
|
||||
return null;
|
||||
|
||||
@@ -2,9 +2,50 @@ package com.eactive.eai.adapter.handler;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
|
||||
public interface AdapterErrorMessageHandler {
|
||||
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName, Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception;
|
||||
/**
|
||||
* 비표준 -> 표준 거래
|
||||
* 아웃바운드 DefaultProcess에서 응답 표준헤더에 에러응답이 세팅되어 있는 경우, 비표준 응답 메시지 생성용
|
||||
*/
|
||||
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||
Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg) throws Exception;
|
||||
|
||||
/**
|
||||
* 비표준 -> 표준 거래
|
||||
* GW 내부 오류가 발생한 경우, RequestPrcessor에서 에러 응답을 생성하는 경우, ExceptionHandler에 의해 호출, 비표준 응답 메시지 생성용
|
||||
*/
|
||||
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName,
|
||||
String inboudnAdapterName, Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* 비표준 -> 표준 거래
|
||||
* GW RequestProcess 호출 전에 Adapter(Filter)에서 오류가 발생한 경우, ApiAdapterController에 Exception에 맞추어, 비표준 응답 메시지 생성용
|
||||
*/
|
||||
public Object generateNonStandardInboundErrorResponseMessage(String inboudnAdapterGroupName,
|
||||
String inboudnAdapterName, Properties callProp, Object inboundRequestData, Object inboundResponseData,
|
||||
Throwable e) throws Exception;
|
||||
|
||||
/**
|
||||
* 표준 -> 비표준 거래
|
||||
* 아웃바운드 어댑터에서 Exception이 발생했을 때, Outbound 어댑터에 의해서 호출하여 응답 메시지 생성
|
||||
* 수신한 응답 메시지가 있을 수도 있고 없을 수도 있다.
|
||||
*/
|
||||
default public Object generateOutboundErrorResponseMessage(String outboundadapterGroupName,
|
||||
String outboundadapterName, Properties callProp, Object outboundRequestData, Object outboundResponseData,
|
||||
Throwable e) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 표준 -> 비표준 거래
|
||||
* 아웃바운드 어댑터에서 Exception이 발생했을 때, Outbound Process 응답 메시지 생성
|
||||
*/
|
||||
default public Object generateOutboundErrorResponseMessage(String outboundadapterGroupName,
|
||||
String outboundadapterName, Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg)
|
||||
throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -2,13 +2,25 @@ package com.eactive.eai.adapter.handler;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
|
||||
public class DefaultAdapterErrorMessageHandler implements AdapterErrorMessageHandler {
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||
Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception {
|
||||
Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||
Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardInboundErrorResponseMessage(String adapterGroupName, String adapterName,
|
||||
Properties callProp, Object inboundRequestData, Object inboundResponseData, Throwable e) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
package com.eactive.eai.adapter.handler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
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.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.common.util.MessageUtil;
|
||||
import com.eactive.eai.message.StandardItem;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* 템플릿 기반 어댑터 에러 메시지 핸들러.
|
||||
*
|
||||
* [프로퍼티 설정]
|
||||
* AdapterPropManager 의 "AdapterErrorMessageHandler" 프로퍼티 그룹에서
|
||||
* "{adapterGroupId}.template" 키로 등록된 템플릿 문자열을 읽어
|
||||
* StandardMessage 의 필드 값으로 치환한 결과를 반환한다.
|
||||
*
|
||||
* [변수 치환 규칙]
|
||||
* 1. "${callprop.키}" → callProp.getProperty("키") 로 치환
|
||||
* "${callprop.키:기본값}" → 키 없거나 callProp null 이면 기본값 반환
|
||||
* 2. "${callprop[경로]}" → StandardMessage 경로 값을 키로 callProp 간접 조회
|
||||
* "${callprop[경로]:기본값}" → 키 결정 실패 또는 키 없으면 기본값 반환
|
||||
* 3. "${경로}" → StandardMessage.findItemValue("경로") 로 치환
|
||||
* "${경로:기본값}" → 경로 값이 null/공백이면 기본값 반환
|
||||
* 모든 형태에서 기본값 미지정 시 빈 문자열을 반환한다.
|
||||
*
|
||||
* [배열 반복 문법] (MSG_LIST 등 GRID 타입)
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
* { "code":"${outp_msg_cd}", "msg":"${outp_msg_ctnt}" }
|
||||
* {{/foreach}}
|
||||
* → MSG_LIST 각 행을 반복하며 행 내 필드명(접두어 없이)으로 치환,
|
||||
* 반복 결과를 쉼표(,)로 이어 붙임
|
||||
*
|
||||
* [사용 예 – JSON 템플릿]
|
||||
* {
|
||||
* "adapterName": "${callprop.ADAPTER_NAME}",
|
||||
* "resultCode": "${MSG.MAIN_MSG.outp_msg_cd}",
|
||||
* "resultMsg": "${MSG.MAIN_MSG.outp_msg_ctnt}",
|
||||
* "errors": [
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
* {
|
||||
* "code": "${outp_msg_cd}",
|
||||
* "msg": "${outp_msg_ctnt}",
|
||||
* "desc": "${outp_msg_desc}",
|
||||
* "field": "${err_occu_item_nm}"
|
||||
* }
|
||||
* {{/foreach}}
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* [사용 예 – XML 템플릿]
|
||||
* <error>
|
||||
* <adapter>${callprop.ADAPTER_NAME}</adapter>
|
||||
* <code>${MSG.MAIN_MSG.outp_msg_cd}</code>
|
||||
* <errors>
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
* <item><code>${outp_msg_cd}</code><msg>${outp_msg_ctnt}</msg></item>
|
||||
* {{/foreach}}
|
||||
* </errors>
|
||||
* </error>
|
||||
*
|
||||
* [callProp 중첩 Properties 참조]
|
||||
* callProp 의 값이 Properties 인 경우 점(.) 으로 체인 탐색이 가능하다.
|
||||
* callProp.put("OUTBOUND", subProps) // subProps.setProperty("IF_ID", "SVC001")
|
||||
* → 템플릿에서 ${callprop.OUTBOUND.IF_ID} 로 참조
|
||||
* 중간 값이 Properties 가 아닌 경우 전체 keyPath 를 키로 폴백 조회한다.
|
||||
*/
|
||||
public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandler {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
/** PropManager 에서 템플릿을 조회할 프로퍼티 그룹 이름 */
|
||||
static final String PROP_GROUP = "AdapterErrorMessageHandler";
|
||||
|
||||
/** callProp 참조 변수 접두어: ${callprop.키} */
|
||||
static final String CALLPROP_PREFIX = "callprop.";
|
||||
|
||||
/** callProp 간접 참조 접두어: ${callprop[표준전문경로]} */
|
||||
static final String CALLPROP_INDIRECT_PREFIX = "callprop[";
|
||||
|
||||
/** 예외 객체 필드 참조 접두어: ${exception.필드명} */
|
||||
static final String EXCEPTION_PREFIX = "exception.";
|
||||
|
||||
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 (renderRow 내부에서 사용) */
|
||||
private static final Pattern VAR_PATTERN =
|
||||
Pattern.compile("\\$\\{([^}]+)\\}");
|
||||
|
||||
/**
|
||||
* foreach 블록과 스칼라 변수를 순서대로 단일 패스로 처리하는 통합 패턴.
|
||||
* group(1) != null → foreach 블록: group(1)=경로, group(2)=블록 본문
|
||||
* group(3) != null → 스칼라 변수: group(3)=표현식
|
||||
*/
|
||||
private static final Pattern COMBINED_PATTERN =
|
||||
Pattern.compile(
|
||||
"\\{\\{#foreach\\s+([^}]+)\\}\\}(.*?)\\{\\{/foreach\\}\\}|\\$\\{([^}]+)\\}",
|
||||
Pattern.DOTALL);
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardErrorResponseMessage(
|
||||
String inboundAdapterGroupName,
|
||||
String inboundAdapterName,
|
||||
Properties callProp,
|
||||
Object outboundRequestData,
|
||||
EAIMessage resEaiMsg) throws Exception {
|
||||
|
||||
StandardMessage resStandardMessage = resEaiMsg.getStandardMessage();
|
||||
String templateKey = inboundAdapterGroupName + ".template";
|
||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
|
||||
if (StringUtils.isBlank(template)) {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("TemplateAdapterErrorMsgHandler] template not found. group=" + PROP_GROUP
|
||||
+ ", key=" + templateKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return render(template, resStandardMessage, callProp);
|
||||
}
|
||||
|
||||
/** 3-인자 위임: exception 없이 호출하는 기존 경로 유지 */
|
||||
String render(String template, StandardMessage msg, Properties callProp) {
|
||||
return render(template, msg, callProp, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿에 StandardMessage, callProp, exception 값을 치환해 최종 문자열을 반환한다.
|
||||
* foreach 블록과 스칼라 변수를 좌→우 순서로 단일 패스 처리한다.
|
||||
* package-private: 단위 테스트에서 직접 호출 가능
|
||||
*/
|
||||
String render(String template, StandardMessage msg, Properties callProp, Throwable exception) {
|
||||
// 관리 UI 등에서 HTML 인코딩된 엔티티를 복원 (예: # → #)
|
||||
template = template.replace("#", "#")
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(""", "\"");
|
||||
StringBuffer result = new StringBuffer();
|
||||
Matcher matcher = COMBINED_PATTERN.matcher(template);
|
||||
while (matcher.find()) {
|
||||
String replacement;
|
||||
if (matcher.group(1) != null) {
|
||||
// {{#foreach path}}...{{/foreach}} 블록
|
||||
String arrayPath = matcher.group(1).trim();
|
||||
String blockContent = matcher.group(2);
|
||||
replacement = (msg != null) ? renderForeach(arrayPath, blockContent, msg) : "";
|
||||
} else {
|
||||
// ${path} / ${callprop.키} / ${exception.필드} 스칼라 변수
|
||||
String expr = matcher.group(3).trim();
|
||||
replacement = resolveScalar(expr, msg, callProp, exception);
|
||||
}
|
||||
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 변수 표현식을 해석해 값을 반환한다.
|
||||
* 모든 형태에서 ':기본값' 접미사를 지원한다. 기본값 미지정 시 빈 문자열.
|
||||
* - "callprop[경로][:기본값]" → 표준전문 경로 값을 키로 callProp 간접 조회
|
||||
* - "callprop.키[:기본값]" → callProp 직접/중첩 조회
|
||||
* - "exception.필드[:기본값]" → 리플렉션으로 예외 객체 getter/필드 조회
|
||||
* - "경로[:기본값]" → StandardMessage.findItemValue(경로)
|
||||
*/
|
||||
private String resolveScalar(String expr, StandardMessage msg, Properties callProp, Throwable exception) {
|
||||
if (expr.startsWith(CALLPROP_INDIRECT_PREFIX)) {
|
||||
return resolveIndirectCallProp(expr, msg, callProp);
|
||||
}
|
||||
if (expr.startsWith(CALLPROP_PREFIX)) {
|
||||
String rest = expr.substring(CALLPROP_PREFIX.length());
|
||||
int colonIdx = rest.indexOf(':');
|
||||
String keyPath = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
|
||||
String defaultVal = colonIdx >= 0 ? rest.substring(colonIdx + 1) : "";
|
||||
if (callProp == null) return defaultVal;
|
||||
String value = resolveCallProp(callProp, keyPath);
|
||||
return value != null ? value : defaultVal;
|
||||
}
|
||||
if (expr.startsWith(EXCEPTION_PREFIX)) {
|
||||
String rest = expr.substring(EXCEPTION_PREFIX.length());
|
||||
int colonIdx = rest.indexOf(':');
|
||||
String fieldName = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
|
||||
String defaultVal = colonIdx >= 0 ? rest.substring(colonIdx + 1) : "";
|
||||
if (exception == null) return defaultVal;
|
||||
String value = resolveExceptionProperty(exception, fieldName);
|
||||
return value != null ? value : defaultVal;
|
||||
}
|
||||
int colonIdx = expr.indexOf(':');
|
||||
String path = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
||||
String defaultVal = colonIdx >= 0 ? expr.substring(colonIdx + 1) : "";
|
||||
String value = resolveMessagePath(msg, path);
|
||||
return StringUtils.isNotEmpty(value) ? value : defaultVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* StandardMessage 경로를 해석한다.
|
||||
* 1. 경로에 '[n]' 구문이 있으면 배열 인덱스 접근으로 처리한다.
|
||||
* 예) MSG.MSG_LIST[0].outp_msg_cd → MSG_LIST 첫 번째 행의 outp_msg_cd 값
|
||||
* 2. findItemValue(path) 로 직접 조회한다.
|
||||
* 3. null 이면 경로를 뒤에서부터 분리하며, 부모 경로 값이 JSON 문자열인 경우
|
||||
* 나머지 경로(subPath)를 키로 JSON 필드를 추출한다.
|
||||
* 예) DATA.BIZDATA.acctNo → findItemValue("DATA.BIZDATA") → JSON 파싱 → acctNo
|
||||
* DATA.BIZDATA.addr.city → findItemValue("DATA.BIZDATA") → JSON 파싱 → addr.city
|
||||
*/
|
||||
private String resolveMessagePath(StandardMessage msg, String path) {
|
||||
if (msg == null) return null;
|
||||
|
||||
int bracketStart = path.indexOf('[');
|
||||
if (bracketStart >= 0) {
|
||||
return resolveArrayIndex(msg, path, bracketStart);
|
||||
}
|
||||
|
||||
String value = msg.findItemValue(path);
|
||||
if (value != null) return value;
|
||||
|
||||
int dotIdx = path.lastIndexOf('.');
|
||||
while (dotIdx > 0) {
|
||||
String parentPath = path.substring(0, dotIdx);
|
||||
String subPath = path.substring(dotIdx + 1);
|
||||
String parentVal = msg.findItemValue(parentPath);
|
||||
if (StringUtils.isNotBlank(parentVal)) {
|
||||
String extracted = extractFromJson(parentVal, subPath);
|
||||
if (extracted != null) return extracted;
|
||||
}
|
||||
dotIdx = parentPath.lastIndexOf('.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 배열 인덱스 접근 구문 처리: {arrayPath}[{n}].{fieldName}
|
||||
* - arrayPath : GRID 타입 아이템 경로 (예: MSG.MSG_LIST)
|
||||
* - n : 0-based 행 인덱스
|
||||
* - fieldName : 행 내 컬럼명 (예: outp_msg_cd)
|
||||
* 인덱스가 범위를 벗어나거나 필드가 없으면 null 반환.
|
||||
*/
|
||||
private String resolveArrayIndex(StandardMessage msg, String path, int bracketStart) {
|
||||
if (msg == null) return null;
|
||||
|
||||
int bracketEnd = path.indexOf(']', bracketStart);
|
||||
if (bracketEnd < 0) return null;
|
||||
|
||||
String arrayPath = path.substring(0, bracketStart);
|
||||
String indexStr = path.substring(bracketStart + 1, bracketEnd);
|
||||
int index;
|
||||
try {
|
||||
index = Integer.parseInt(indexStr.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String fieldName = null;
|
||||
if (bracketEnd + 2 <= path.length() - 1 && path.charAt(bracketEnd + 1) == '.') {
|
||||
fieldName = path.substring(bracketEnd + 2);
|
||||
}
|
||||
if (StringUtils.isBlank(fieldName)) return null;
|
||||
|
||||
StandardItem arrayItem = msg.findItem(arrayPath);
|
||||
if (arrayItem == null) return null;
|
||||
|
||||
List<LinkedHashMap<String, StandardItem>> rows = arrayItem.getList();
|
||||
if (rows == null || index < 0 || index >= rows.size()) return null;
|
||||
|
||||
StandardItem item = rows.get(index).get(fieldName);
|
||||
return item != null ? StringUtils.defaultString(item.getValue()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열에서 dot 구분 경로로 필드 값을 추출한다.
|
||||
* 중간 경로가 JSON 오브젝트면 계속 탐색하고, 최종 값이 primitive 면 문자열로,
|
||||
* 오브젝트/배열이면 JSON 문자열 그대로 반환한다. 파싱 실패 시 null 반환.
|
||||
*/
|
||||
private String extractFromJson(String json, String fieldPath) {
|
||||
try {
|
||||
JsonElement el = new JsonParser().parse(json);
|
||||
for (String part : fieldPath.split("\\.", -1)) {
|
||||
if (!el.isJsonObject()) return null;
|
||||
el = el.getAsJsonObject().get(part);
|
||||
if (el == null) return null;
|
||||
}
|
||||
return el.isJsonPrimitive() ? el.getAsString() : el.toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ${callprop[경로]} 또는 ${callprop[경로]:기본값} 처리.
|
||||
* 1. 표준전문 경로로 callProp 키를 동적으로 결정한다.
|
||||
* 2. 결정된 키로 callProp 에서 값을 조회한다.
|
||||
* 3. 키 또는 값을 찾지 못하면 기본값(없으면 빈 문자열)을 반환한다.
|
||||
*/
|
||||
private String resolveIndirectCallProp(String expr, StandardMessage msg, Properties callProp) {
|
||||
int closeIdx = expr.indexOf(']');
|
||||
if (closeIdx < 0) return "";
|
||||
|
||||
String msgPath = expr.substring(CALLPROP_INDIRECT_PREFIX.length(), closeIdx);
|
||||
String defaultVal = (closeIdx + 1 < expr.length() && expr.charAt(closeIdx + 1) == ':')
|
||||
? expr.substring(closeIdx + 2)
|
||||
: "";
|
||||
|
||||
String key = msg.findItemValue(msgPath);
|
||||
if (StringUtils.isBlank(key)) return defaultVal;
|
||||
if (callProp == null) return defaultVal;
|
||||
|
||||
String value = resolveCallProp(callProp, key);
|
||||
return value != null ? value : defaultVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties 체인을 점(.) 구분자로 재귀 탐색한다.
|
||||
* - 첫 세그먼트의 값이 Properties 이면 나머지 경로로 재귀
|
||||
* - String 이면 반환, 그 외 Object 이면 toString() 반환
|
||||
* - 첫 세그먼트 값이 Properties 가 아닌 경우 전체 keyPath 로 폴백 조회
|
||||
*/
|
||||
private String resolveCallProp(Properties props, String keyPath) {
|
||||
int dotIdx = keyPath.indexOf('.');
|
||||
if (dotIdx < 0) {
|
||||
Object val = props.get(keyPath);
|
||||
if (val == null) return null;
|
||||
if (val instanceof String) return (String) val;
|
||||
return val.toString();
|
||||
}
|
||||
|
||||
String first = keyPath.substring(0, dotIdx);
|
||||
String rest = keyPath.substring(dotIdx + 1);
|
||||
Object val = props.get(first);
|
||||
if (val instanceof Properties) {
|
||||
return resolveCallProp((Properties) val, rest);
|
||||
}
|
||||
|
||||
// 중간 값이 Properties 가 아닌 경우: 전체 keyPath 를 키로 폴백
|
||||
Object direct = props.get(keyPath);
|
||||
if (direct == null) return null;
|
||||
if (direct instanceof String) return (String) direct;
|
||||
return direct.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* GRID 타입 배열 아이템을 반복하며 blockContent 를 각 행으로 치환,
|
||||
* 결과를 쉼표로 이어 반환한다. (foreach 블록 내부는 callProp 참조 미지원)
|
||||
*/
|
||||
private String renderForeach(String arrayPath, String blockContent, StandardMessage msg) {
|
||||
StandardItem arrayItem = msg.findItem(arrayPath);
|
||||
if (arrayItem == null) {
|
||||
if (logger.isWarn()) logger.warn("TemplateAdapterErrorMsgHandler] foreach path not found: " + arrayPath);
|
||||
return "";
|
||||
}
|
||||
|
||||
List<LinkedHashMap<String, StandardItem>> rows = arrayItem.getList();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<String> renderedItems = new ArrayList<>();
|
||||
for (LinkedHashMap<String, StandardItem> row : rows) {
|
||||
String rowText = renderRow(blockContent, row);
|
||||
if (StringUtils.isNotBlank(rowText)) {
|
||||
renderedItems.add(rowText);
|
||||
}
|
||||
}
|
||||
return String.join(",", renderedItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* 배열 한 행(LinkedHashMap) 기준으로 blockContent 의 ${fieldName} 을 치환한다.
|
||||
* fieldName 은 경로 없이 해당 행의 컬럼명만 사용한다.
|
||||
*/
|
||||
private String renderRow(String blockContent, LinkedHashMap<String, StandardItem> row) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
Matcher varMatcher = VAR_PATTERN.matcher(blockContent);
|
||||
while (varMatcher.find()) {
|
||||
String fieldName = varMatcher.group(1).trim();
|
||||
String value = "";
|
||||
StandardItem item = row.get(fieldName);
|
||||
if (item != null) {
|
||||
value = StringUtils.defaultString(item.getValue());
|
||||
}
|
||||
varMatcher.appendReplacement(result, Matcher.quoteReplacement(value));
|
||||
}
|
||||
varMatcher.appendTail(result);
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||
Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg) throws Exception {
|
||||
String templateKey = inboudnAdapterGroupName + ".sys.template";
|
||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
|
||||
if (StringUtils.isBlank(template)) {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("TemplateAdapterErrorMsgHandler] template not found. group=" + PROP_GROUP
|
||||
+ ", key=" + templateKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return render(template, resEaiMsg.getStandardMessage(), callProp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardInboundErrorResponseMessage(String adapterGroupName, String adapterName,
|
||||
Properties callProp, Object inboundRequestData, Object inboundResponseData, Throwable e) throws Exception {
|
||||
|
||||
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
||||
AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName);
|
||||
Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName());
|
||||
String adptMsgType = adapterVO.getAdapterGroupVO().getMessageType();
|
||||
String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8");
|
||||
|
||||
if (e instanceof HttpStatusException) {
|
||||
HttpStatusException e1 = (HttpStatusException) e;
|
||||
int httpCode = e1.getStatus();
|
||||
|
||||
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e1, httpCode);
|
||||
} else if (e instanceof JwtAuthException) {
|
||||
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e,
|
||||
HttpStatus.UNAUTHORIZED.value());
|
||||
} else {
|
||||
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 표준 -> 비표준 거래
|
||||
* 아웃바운드 어댑터에서 Exception이 발생했을 때, Outbound Process 응답 메시지 생성
|
||||
*/
|
||||
@Override
|
||||
public Object generateOutboundErrorResponseMessage(String outboundadapterGroupName,
|
||||
String outboundadapterName, Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg)
|
||||
throws Exception {
|
||||
String templateKey = outboundadapterGroupName + ".template";
|
||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
if (StringUtils.isBlank(template)) {
|
||||
return null;
|
||||
}
|
||||
return render(template, resEaiMsg.getStandardMessage(), callProp);
|
||||
}
|
||||
|
||||
private Object genInboundErrorResponse(String adapterGroupName, Properties callProp, Properties httpProp,
|
||||
String adptMsgType, String encode, Throwable e1, int httpCode) {
|
||||
String templateKey = adapterGroupName + ".in." + httpCode + ".template";
|
||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
if (StringUtils.isBlank(template)) {
|
||||
templateKey = adapterGroupName + ".in.template";
|
||||
template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
if (StringUtils.isBlank(template)) {
|
||||
String errorResponseFormat = httpProp.getProperty("ERROR_RESPONSE_FORMAT");
|
||||
return MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
|
||||
MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat);
|
||||
}
|
||||
}
|
||||
return render(template, null, callProp, e1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리플렉션으로 예외 객체의 필드 값을 동적으로 조회한다.
|
||||
* 탐색 순서: getXxx() getter → isXxx() getter → 필드 직접 접근
|
||||
* 상위 클래스(Throwable 포함)까지 계층적으로 탐색한다.
|
||||
*/
|
||||
private String resolveExceptionProperty(Throwable e, String fieldName) {
|
||||
if (StringUtils.isBlank(fieldName)) return null;
|
||||
String cap = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
|
||||
for (String prefix : new String[]{"get", "is"}) {
|
||||
Method m = findMethod(e.getClass(), prefix + cap);
|
||||
if (m != null) {
|
||||
try {
|
||||
Object val = m.invoke(e);
|
||||
return val != null ? val.toString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
Field f = findField(e.getClass(), fieldName);
|
||||
if (f != null) {
|
||||
try {
|
||||
f.setAccessible(true);
|
||||
Object val = f.get(e);
|
||||
return val != null ? val.toString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Method findMethod(Class<?> clazz, String name) {
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Method m = clazz.getDeclaredMethod(name);
|
||||
m.setAccessible(true);
|
||||
return m;
|
||||
} catch (NoSuchMethodException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Field findField(Class<?> clazz, String name) {
|
||||
while (clazz != null) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package com.eactive.eai.adapter.handler;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
|
||||
public class TemplateCodeConvertAdapterErrorMsgHandler extends TemplateAdapterErrorMsgHandler {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
/** PropManager 에서 코드 변환 설정을 조회할 프로퍼티 그룹 이름 */
|
||||
static final String PROP_GROUP = "AdapterErrorMessageHandler{CODE_CONVERT}";
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardErrorResponseMessage(
|
||||
String inboundAdapterGroupName,
|
||||
String inboundAdapterName,
|
||||
Properties callProp,
|
||||
Object outboundRequestData,
|
||||
EAIMessage resEaiMsg) throws Exception {
|
||||
|
||||
Object responseMsessage = super.generateNonStandardErrorResponseMessage(
|
||||
inboundAdapterGroupName,
|
||||
inboundAdapterName,
|
||||
callProp,
|
||||
outboundRequestData,
|
||||
resEaiMsg);
|
||||
|
||||
logger.debug("generateNonStandardErrorResponseMessage - {}", responseMsessage);
|
||||
|
||||
if (!(responseMsessage instanceof String)) {
|
||||
return responseMsessage;
|
||||
}
|
||||
|
||||
String jsonStr = (String) responseMsessage;
|
||||
if (StringUtils.isBlank(jsonStr)) {
|
||||
return responseMsessage;
|
||||
}
|
||||
|
||||
Properties props = PropManager.getInstance().getProperties(PROP_GROUP);
|
||||
if (props == null) {
|
||||
return responseMsessage;
|
||||
}
|
||||
|
||||
String fieldsKey = inboundAdapterGroupName + ".convert.fields";
|
||||
String fieldsValue = props.getProperty(fieldsKey);
|
||||
if (StringUtils.isBlank(fieldsValue)) {
|
||||
return responseMsessage;
|
||||
}
|
||||
|
||||
JsonNode rootNode = OBJECT_MAPPER.readTree(jsonStr);
|
||||
boolean modified = false;
|
||||
|
||||
for (String rawField : fieldsValue.split(",")) {
|
||||
String fieldPath = rawField.trim();
|
||||
if (StringUtils.isBlank(fieldPath)) continue;
|
||||
|
||||
String currentValue = getJsonValue(rootNode, fieldPath);
|
||||
if (currentValue == null) continue;
|
||||
|
||||
// key: inboundAdapterGroupName.{fieldPath}.{현재값} → 폴백: ...{fieldPath}.default
|
||||
String mappingKey = inboundAdapterGroupName + "." + fieldPath + "." + currentValue;
|
||||
String newValue = props.getProperty(mappingKey);
|
||||
if (newValue == null) {
|
||||
String defaultKey = inboundAdapterGroupName + "." + fieldPath + ".default";
|
||||
newValue = props.getProperty(defaultKey);
|
||||
}
|
||||
if (newValue == null) continue;
|
||||
|
||||
setJsonValue(rootNode, fieldPath, newValue);
|
||||
modified = true;
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("code convert: field={}, {} -> {}", fieldPath, currentValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
responseMsessage = OBJECT_MAPPER.writeValueAsString(rootNode);
|
||||
}
|
||||
|
||||
return responseMsessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* dot 구분 경로로 JsonNode 에서 값을 꺼낸다.
|
||||
* 최종 노드가 값 노드(primitive)가 아니면 null 반환.
|
||||
*/
|
||||
private String getJsonValue(JsonNode node, String fieldPath) {
|
||||
JsonNode current = node;
|
||||
for (String part : fieldPath.split("\\.", -1)) {
|
||||
if (current == null || !current.isObject()) return null;
|
||||
current = current.get(part);
|
||||
}
|
||||
if (current == null || current.isNull() || !current.isValueNode()) return null;
|
||||
return current.asText();
|
||||
}
|
||||
|
||||
/**
|
||||
* dot 구분 경로로 ObjectNode 의 최종 필드 값을 newValue 로 교체한다.
|
||||
* 중간 경로가 ObjectNode 가 아니면 무시한다.
|
||||
*/
|
||||
private void setJsonValue(JsonNode rootNode, String fieldPath, String newValue) {
|
||||
String[] parts = fieldPath.split("\\.", -1);
|
||||
JsonNode current = rootNode;
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
if (current == null || !current.isObject()) return;
|
||||
current = current.get(parts[i]);
|
||||
}
|
||||
if (current instanceof ObjectNode) {
|
||||
((ObjectNode) current).put(parts[parts.length - 1], newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+2
-2
@@ -570,8 +570,8 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
|
||||
public abstract Object execute(Properties prop, Object message, Properties tempProp) throws Exception;
|
||||
|
||||
protected boolean isTas(Properties tempProp) {
|
||||
String simYn = tempProp.getProperty("SIM_YN");
|
||||
String tranType = tempProp.getProperty("tranType");
|
||||
return EAIServerManager.getInstance().isTASEnabledEAIServer()
|
||||
&& StringUtils.equals(simYn, EAIMessageKeys.TRANTYPE_TAS);
|
||||
&& StringUtils.equals(tranType, EAIMessageKeys.TRANTYPE_TAS);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.eactive.eai.adapter.http.client.impl;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.hc.core5.http.ContentType;
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.net.WWWFormCodec;
|
||||
|
||||
/**
|
||||
* HttpComponent5에서 지원하는 UrlEncodedFormEntity 클래스는 chunked 옵션 설정을 지원하지 않아 새롭게 만들었음.
|
||||
*/
|
||||
public class CustomUrlEncodedFormEntity extends StringEntity {
|
||||
|
||||
public CustomUrlEncodedFormEntity(final Iterable<? extends NameValuePair> parameters, final Charset charset,
|
||||
final boolean chunked) {
|
||||
super(WWWFormCodec.format(parameters,
|
||||
charset != null ? charset : ContentType.APPLICATION_FORM_URLENCODED.getCharset()),
|
||||
charset != null ? ContentType.APPLICATION_FORM_URLENCODED.withCharset(charset)
|
||||
: ContentType.APPLICATION_FORM_URLENCODED,
|
||||
chunked);
|
||||
}
|
||||
|
||||
}
|
||||
+9
-3
@@ -7,6 +7,8 @@ import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.message.MessageType;
|
||||
import com.eactive.eai.common.util.CommonLib;
|
||||
import com.eactive.eai.common.util.TxFileLogger;
|
||||
|
||||
import org.apache.commons.httpclient.HttpStatus;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.StopWatch;
|
||||
@@ -118,7 +120,9 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
|
||||
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
|
||||
"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
|
||||
}
|
||||
|
||||
|
||||
TxFileLogger.logTxFile(tempProp, sendData, "[OUT_SEND]");
|
||||
|
||||
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||
HttpContext context = new BasicHttpContext();
|
||||
context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid);
|
||||
@@ -147,7 +151,8 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
|
||||
}
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
TxFileLogger.logTxFile(tempProp, responseString, "[OUT_RECV]");
|
||||
|
||||
|
||||
if (status >= 400 && status < 500) {
|
||||
String errMsg = String.format(
|
||||
@@ -172,7 +177,8 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
|
||||
+ CommonLib.getDumpMessage(responseMessage));
|
||||
}
|
||||
|
||||
return responseMessage;
|
||||
// return responseMessage;
|
||||
return responseString;
|
||||
|
||||
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
|
||||
if (vo.getTraceLevel() >= 3) {
|
||||
|
||||
+284
-133
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
@@ -35,7 +36,6 @@ 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.entity.UrlEncodedFormEntity;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||
import org.apache.hc.core5.http.ContentType;
|
||||
@@ -75,6 +75,11 @@ import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.message.MessageType;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.CommonLib;
|
||||
import com.eactive.eai.common.util.JSONUtils;
|
||||
import com.eactive.eai.common.util.TxFileLogger;
|
||||
import com.eactive.eai.common.util.XMLUtils;
|
||||
import com.eactive.eai.util.JsonPathUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.jayway.jsonpath.DocumentContext;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.kjbank.encrypt.exchange.crypto.AES256Cipher;
|
||||
@@ -116,7 +121,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
|
||||
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id"; // jwhong
|
||||
|
||||
static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : ";
|
||||
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
|
||||
|
||||
private boolean useAdapterToken;
|
||||
private boolean encryptResponseApply;
|
||||
@@ -138,17 +143,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
|
||||
// 2025.05.29 프로퍼티 그룹 AUTH_TOKEN이 없을때 Null Point Exception 발생하여 수정
|
||||
String bizCode = "";
|
||||
String authToken = "";
|
||||
try {
|
||||
Properties authProp = PropManager.getInstance().getProperties(AUTH_TOKEN); //Authorization
|
||||
bizCode = vo.getAdapterGroupName().substring(1, 4);
|
||||
authToken = authProp.getProperty(bizCode, "");
|
||||
} catch (Exception e) {
|
||||
logger.debug("Property Group AUTH_TOKEN is not Exist !!");
|
||||
}
|
||||
|
||||
logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken);
|
||||
|
||||
String headerGroupName = prop.getProperty(HEADER_GROUP);
|
||||
String relayResponseHeaderKeys = prop.getProperty(HEADER_KEYS, "");
|
||||
useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y");
|
||||
@@ -194,42 +188,56 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
|
||||
String rmethod = (String) restOptionObject.getOrDefault("method", inboundMethod);
|
||||
String contentType = (String) restOptionObject.getOrDefault("contentType", "application/json");
|
||||
|
||||
ContentType oContentType = ContentType.parse(contentType);
|
||||
Charset contentTypeCharset = oContentType.getCharset();
|
||||
String charset = vo.getEncode();
|
||||
if(contentTypeCharset != null)
|
||||
charset = contentTypeCharset.displayName();
|
||||
|
||||
logger.debug("HttpClient5AdapterServiceRest] charset = {}", charset);
|
||||
|
||||
String mimeType = oContentType.getMimeType();
|
||||
|
||||
String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn");
|
||||
if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다.
|
||||
useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y");
|
||||
}
|
||||
|
||||
String cnkUseYn = (String) restOptionObject.get("cnkUseYn");
|
||||
boolean chunked = false;
|
||||
if (StringUtils.isNotBlank(cnkUseYn)) {
|
||||
chunked = StringUtils.equalsIgnoreCase(cnkUseYn, "Y");
|
||||
}
|
||||
|
||||
HttpClient mclient = this.client;
|
||||
String sendData = "";
|
||||
Object sendData = "";
|
||||
if (data instanceof String) {
|
||||
sendData = (String) data;
|
||||
sendData = data;
|
||||
} else if (data instanceof byte[]) {
|
||||
sendData = new String((byte[]) data, vo.getEncode());
|
||||
} else if (data instanceof JSONObject) {
|
||||
sendData = data;
|
||||
}
|
||||
|
||||
////mclient.getParams().setContentCharset(vo.getEncode());
|
||||
|
||||
JSONObject dataObject = null;
|
||||
Object dataObject = null;
|
||||
Object httpHeader = null;
|
||||
Object dataContent = null;
|
||||
//Object dataContent = null;
|
||||
|
||||
if (MessageType.JSON.equals(messageType)) {
|
||||
|
||||
Object parsed = parseJsonGeneric(sendData);
|
||||
|
||||
Object parsed = JSONUtils.parseJsonGeneric(sendData);
|
||||
if(parsed instanceof JSONObject) {
|
||||
dataObject = parseJson(sendData);
|
||||
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
|
||||
httpHeader = dataObject.get(headerGroupName);
|
||||
dataObject.remove(headerGroupName);
|
||||
}
|
||||
|
||||
dataContent = dataObject;
|
||||
if (dataObject.containsKey("innerList")) {
|
||||
JSONArray innerList = (JSONArray) dataObject.get("innerList");
|
||||
dataContent = innerList;
|
||||
}
|
||||
JSONObject jsonObject = (JSONObject) parsed;
|
||||
dataObject = jsonObject;
|
||||
|
||||
} else if(parsed instanceof JSONArray) {
|
||||
// not support
|
||||
}
|
||||
} else if (MessageType.XML.equals(messageType)) {
|
||||
Document doc = XMLUtils.convertXmlDocument(sendData);
|
||||
dataObject = doc;
|
||||
}
|
||||
|
||||
// if (MessageType.JSON.equals(messageType)) {
|
||||
@@ -246,10 +254,29 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
Map<String, String> inboundPathVariables = (Map<String, String>) tempProp.get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES);
|
||||
// interface 에 설정된게 우선한다.
|
||||
String uri = null;
|
||||
if (dataObject == null) {
|
||||
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
|
||||
if(this.isTas(tempProp)) {
|
||||
uri = vo.getUrl();
|
||||
} else {
|
||||
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
|
||||
if (dataObject == null) {
|
||||
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
|
||||
} else {
|
||||
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
|
||||
}
|
||||
}
|
||||
|
||||
if(dataObject instanceof JSONObject) {
|
||||
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
|
||||
httpHeader = ((JSONObject)dataObject).get(headerGroupName);
|
||||
((JSONObject)dataObject).remove(headerGroupName);
|
||||
}
|
||||
} else if (dataObject instanceof Document) {
|
||||
Document doc = (Document)dataObject;
|
||||
if (doc != null && StringUtils.isNotBlank(headerGroupName)) {
|
||||
Element root = doc.getRootElement();
|
||||
Element httpHeaderElement = root.element(headerGroupName);
|
||||
root.remove(httpHeaderElement);
|
||||
httpHeader = httpHeaderElement;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("HttpClientAdapterServiceRest] dataObject2 = [" + dataObject + "]");
|
||||
@@ -349,6 +376,18 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
+ ") RequestHeader [" + method.getHeader("Authorization").getName() + ": " + method.getHeader("Authorization").getValue() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
// 업무별 고정 token
|
||||
String authToken = "";
|
||||
try {
|
||||
Properties authProp = PropManager.getInstance().getProperties(AUTH_TOKEN); //Authorization
|
||||
bizCode = vo.getAdapterGroupName().substring(1, 4);
|
||||
authToken = authProp.getProperty(bizCode, "");
|
||||
} catch (Exception e) {
|
||||
logger.debug("Property Group AUTH_TOKEN is not Exist !!");
|
||||
}
|
||||
|
||||
logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken);
|
||||
|
||||
if(!StringUtils.isBlank(authToken)) {
|
||||
setAuthHeaders(method, authToken);
|
||||
@@ -382,8 +421,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
// assignPostBody(dataObject, contentType, vo.getEncode(), method);
|
||||
//assignPostBody(dataContent, contentType, vo.getEncode(), method); // jwhong commnet
|
||||
// KJBank API 추적정보를 Header에 제공. jwhong
|
||||
String inboundToken= assignRequestKJHeaders(method, prop, tempProp );
|
||||
assignPostBody(dataContent, contentType, vo.getEncode(), method, vo.getAdapterName(), auth, inboundToken, currentDate);
|
||||
// String inboundToken= assignRequestKJHeaders(method, prop, tempProp );
|
||||
assignPostBody(dataObject, mimeType, charset, method, chunked);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -402,17 +441,19 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
|
||||
int status = -1;
|
||||
|
||||
Header[] responseHeaders = null;
|
||||
String responseString = null;
|
||||
try {
|
||||
byte[] responseMessage = null;
|
||||
byte[] responseMessageOri = null;
|
||||
Header[] responseHeaders;
|
||||
// byte[] responseMessageOri = null;
|
||||
String responseContentType = null;
|
||||
|
||||
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
|
||||
if (dataObject != null) {
|
||||
if (dataObject instanceof JSONObject) {
|
||||
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = ["
|
||||
+ dataObject.toJSONString() + "]");
|
||||
+ ((JSONObject)dataObject).toJSONString() + "]");
|
||||
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
|
||||
+ CommonLib.getDumpMessage(dataObject.toJSONString()));
|
||||
+ CommonLib.getDumpMessage(((JSONObject)dataObject).toJSONString()));
|
||||
} else {
|
||||
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData
|
||||
+ "]");
|
||||
@@ -427,6 +468,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
|
||||
"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
|
||||
}
|
||||
|
||||
TxFileLogger.logTxFile(tempProp, sendData, "[OUT_SEND]");
|
||||
|
||||
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||
HttpContext context = new BasicHttpContext();
|
||||
@@ -439,8 +482,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
}
|
||||
|
||||
// encrypt algorithm type 추출 from adapter property jwhong
|
||||
Properties properties = AdapterPropManager.getInstance().getProperties(vo.getAdapterName()); // jwhong
|
||||
String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong
|
||||
// Properties properties = AdapterPropManager.getInstance().getProperties(vo.getAdapterName()); // jwhong
|
||||
// String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong
|
||||
|
||||
try {
|
||||
if (logger.isDebug()) {
|
||||
@@ -465,18 +508,30 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
//responseMessage = EntityUtils.toByteArray(response.getEntity());
|
||||
// 역기서 responseMessage를 decryption 해야 하나 by jwhong
|
||||
|
||||
responseMessageOri = EntityUtils.toByteArray(response.getEntity());
|
||||
logger.debug("[responseMessageOri 바이트 배열 길이 ]" + responseMessageOri.length);
|
||||
responseMessage = EntityUtils.toByteArray(response.getEntity());
|
||||
logger.debug("[responseMessage 바이트 배열 길이 ]" + responseMessage.length);
|
||||
|
||||
responseMessage = doOutboundPostFilter(responseMessageOri, encryptAlgorithm, vo.getAdapterName(), accessToken); // jwhong
|
||||
// responseMessage = doOutboundPostFilter(responseMessageOri, encryptAlgorithm, vo.getAdapterName(), accessToken); // jwhong
|
||||
// 여기까지
|
||||
|
||||
responseHeaders = response.getHeaders();
|
||||
Header headerContentType = response.getHeader("Content-Type");
|
||||
if(headerContentType != null) {
|
||||
responseContentType = headerContentType.getValue();
|
||||
}
|
||||
|
||||
if(responseContentType == null)
|
||||
responseContentType = ContentType.APPLICATION_JSON.getMimeType();
|
||||
|
||||
tempProp.setProperty("RES_CONTENT_TYPE", responseContentType);
|
||||
|
||||
TxFileLogger.logTxFile(tempProp, new String(responseMessage, vo.getEncode()), "[OUT_RECV]");
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("[received status]" + status);
|
||||
logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + responseMessage + "]");
|
||||
}
|
||||
|
||||
}
|
||||
} catch (ConnectException e) {
|
||||
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
|
||||
@@ -502,16 +557,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
*/
|
||||
boolean needReissue = false;
|
||||
|
||||
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
|
||||
String responseString = new String(responseMessage, vo.getEncode());
|
||||
responseString = new String(responseMessage, vo.getEncode());
|
||||
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
|
||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp);
|
||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString);
|
||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status);
|
||||
|
||||
|
||||
if (isStatusInIgnore(status,errIgnore )) {
|
||||
// 이경우 정상으로 처리함
|
||||
} else if (status >= 200 && status <= 207) {
|
||||
@@ -580,10 +627,35 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
+ ") RETRY TOKEN = [" + newaccessToken + "]");
|
||||
}
|
||||
|
||||
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
|
||||
// executor 생성 (mclient, timeout은 기존 설정 그대로)
|
||||
DeadlineAwareRetryExecutor executor =
|
||||
new DeadlineAwareRetryExecutor((CloseableHttpClient)mclient,
|
||||
vo.getConnectionTimeout(),
|
||||
vo.getTimeout());
|
||||
|
||||
// Properties에서 RetryPolicy 로드
|
||||
RetryPolicy policy = RetryPolicy.from(prop); // 설정 로드 방식에 맞게
|
||||
|
||||
try (CloseableHttpResponse response = (CloseableHttpResponse) executor.execute(method, context, policy)) {
|
||||
// try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
|
||||
status = response.getCode();
|
||||
responseMessage = EntityUtils.toByteArray(response.getEntity());
|
||||
// 여기서도 decryption 해야 할듯 하나.. by jwhong
|
||||
logger.debug("[responseMessageOri 바이트 배열 길이 ]" + responseMessage.length);
|
||||
|
||||
// responseMessage = doOutboundPostFilter(responseMessageOri, encryptAlgorithm, vo.getAdapterName(), accessToken); // jwhong
|
||||
|
||||
responseHeaders = response.getHeaders();
|
||||
Header headerContentType = response.getHeader("Content-Type");
|
||||
if(headerContentType != null) {
|
||||
responseContentType = headerContentType.getValue();
|
||||
}
|
||||
|
||||
if(responseContentType == null)
|
||||
responseContentType = ContentType.APPLICATION_JSON.getMimeType();
|
||||
|
||||
tempProp.setProperty("RES_CONTENT_TYPE", responseContentType);
|
||||
|
||||
TxFileLogger.logTxFile(tempProp, new String(responseMessage, vo.getEncode()), "[OUT_RECV]");
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("[received status]" + status);
|
||||
@@ -627,12 +699,14 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
String jsonStr = new String(responseMessage, vo.getEncode());
|
||||
|
||||
if ( jsonStr.trim().startsWith("[") ) {
|
||||
jsonStr = KJB_ROOTLESS_ARRAY + jsonStr + "}";
|
||||
jsonStr = DJB_ROOTLESS_ARRAY + jsonStr + "}";
|
||||
responseMessage = jsonStr.getBytes(vo.getEncode());
|
||||
}
|
||||
|
||||
|
||||
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType,
|
||||
|
||||
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
|
||||
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
|
||||
|
||||
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), responseContentType,
|
||||
relayResponseHeaderKeys, status, responseHeaderProp);
|
||||
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
|
||||
if (vo.getTraceLevel() >= 3) {
|
||||
@@ -660,6 +734,10 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
}
|
||||
logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
|
||||
e);
|
||||
|
||||
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
|
||||
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
if (status != HttpStatus.SC_OK) {
|
||||
@@ -673,6 +751,15 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
}
|
||||
}
|
||||
|
||||
private void assignOutboundPropertyMap(Properties tempProp, int status, String responseString,
|
||||
Properties responseHeaderProp) {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
|
||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp);
|
||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString);
|
||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status);
|
||||
}
|
||||
|
||||
private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) {
|
||||
Properties headerProp = new Properties();
|
||||
if (responseHeaders == null || responseHeaders.length == 0) {
|
||||
@@ -699,57 +786,99 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
* @throws Exception
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private String assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName,
|
||||
String encode, String messageType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception {
|
||||
if (!MessageType.JSON.equals(messageType) || StringUtils.isBlank(headerGroupName)
|
||||
private Object assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName,
|
||||
String encode, String responseContentType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception {
|
||||
String messageType = MessageType.JSON;
|
||||
if (StringUtils.startsWith(responseContentType, ContentType.APPLICATION_XML.getMimeType())) {
|
||||
logger.debug("Content-Type xml received.");
|
||||
messageType = MessageType.XML;
|
||||
} else if (StringUtils.startsWith(responseContentType, ContentType.APPLICATION_JSON.getMimeType())) {
|
||||
logger.debug("Content-Type json received.");
|
||||
messageType = MessageType.JSON;
|
||||
} else {
|
||||
messageType = "ETC";
|
||||
}
|
||||
|
||||
// charset 추출, 없으면 파라미터 encdoe 이용
|
||||
ContentType oContentType = ContentType.parse(responseContentType);
|
||||
Charset contentTypeCharset = oContentType.getCharset();
|
||||
String charset = encode;
|
||||
if (contentTypeCharset != null)
|
||||
charset = contentTypeCharset.displayName();
|
||||
|
||||
if (!StringUtils.equalsAny(messageType, MessageType.JSON, MessageType.XML) || StringUtils.isBlank(headerGroupName)
|
||||
|| StringUtils.isBlank(relayHeaderKeys)) {
|
||||
return new String(responseMessage, encode);
|
||||
return new String(responseMessage, charset);
|
||||
}
|
||||
|
||||
JSONObject headerJson = new JSONObject();
|
||||
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
|
||||
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
|
||||
String key = (String)e.nextElement();
|
||||
headerJson.put(StringUtils.lowerCase(key), responseHeaderProp.getProperty(key));
|
||||
}
|
||||
} else {
|
||||
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
|
||||
for (String key : relayKeyArr) {
|
||||
String value = responseHeaderProp.getProperty(key);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
headerJson.put(StringUtils.lowerCase(key), value);
|
||||
if(StringUtils.equalsAny(messageType, MessageType.JSON)) {
|
||||
JSONObject headerJson = new JSONObject();
|
||||
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
|
||||
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
|
||||
String key = (String)e.nextElement();
|
||||
headerJson.put(StringUtils.lowerCase(key), responseHeaderProp.getProperty(key));
|
||||
}
|
||||
} else {
|
||||
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
|
||||
for (String key : relayKeyArr) {
|
||||
String value = responseHeaderProp.getProperty(key);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
headerJson.put(StringUtils.lowerCase(key), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headerJson.put(HTTP_STATUS, String.valueOf(status));
|
||||
|
||||
if (headerJson.size() <= 0) {
|
||||
return new String(responseMessage, encode);
|
||||
}
|
||||
|
||||
JSONObject message = null;
|
||||
if (responseMessage == null || responseMessage.length == 0) {
|
||||
message = new JSONObject();
|
||||
} else {
|
||||
message = parseJson(new String(responseMessage, encode));
|
||||
if (message == null) {
|
||||
message = new JSONObject();
|
||||
message.put("Malformed_Response_Message", new String(responseMessage, encode));
|
||||
|
||||
headerJson.put(HTTP_STATUS, String.valueOf(status));
|
||||
|
||||
if (headerJson.size() <= 0) {
|
||||
return new String(responseMessage, charset);
|
||||
}
|
||||
|
||||
JSONObject message = null;
|
||||
if (responseMessage == null || responseMessage.length == 0) {
|
||||
message = new JSONObject();
|
||||
} else {
|
||||
message = parseJson(new String(responseMessage, charset));
|
||||
if (message == null) {
|
||||
message = new JSONObject();
|
||||
message.put("Malformed_Response_Message", new String(responseMessage, charset));
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(headerGroupName) && message != null)
|
||||
message.put(headerGroupName, headerJson);
|
||||
|
||||
return message;
|
||||
} else if (StringUtils.equalsAny(messageType, MessageType.XML)) {
|
||||
Document doc = XMLUtils.convertXmlDocument(new String(responseMessage, charset));
|
||||
Element root = doc.getRootElement();
|
||||
Element headerGroupEle = root.addElement(headerGroupName);
|
||||
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
|
||||
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
|
||||
String key = (String)e.nextElement();
|
||||
headerGroupEle.addElement(StringUtils.lowerCase(key)).setText(responseHeaderProp.getProperty(key));
|
||||
}
|
||||
} else {
|
||||
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
|
||||
for (String key : relayKeyArr) {
|
||||
String value = responseHeaderProp.getProperty(key);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
headerGroupEle.addElement(StringUtils.lowerCase(key)).setText(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return doc.asXML();
|
||||
} else {
|
||||
return new String(responseMessage, charset);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(headerGroupName) && message != null)
|
||||
message.put(headerGroupName, headerJson);
|
||||
return message.toJSONString();
|
||||
}
|
||||
|
||||
private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
|
||||
protected void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
|
||||
method.setHeader("Authorization",
|
||||
String.format("%s", authorization));
|
||||
}
|
||||
|
||||
private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
|
||||
protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
|
||||
method.setHeader("Authorization",
|
||||
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
|
||||
}
|
||||
@@ -816,7 +945,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
return false;
|
||||
}
|
||||
|
||||
private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
|
||||
protected void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
|
||||
if (httpHeader != null) {
|
||||
if (httpHeader instanceof JSONObject) {
|
||||
JSONObject jsonObject = (JSONObject) httpHeader;
|
||||
@@ -870,7 +999,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
|
||||
}
|
||||
|
||||
private void assignFilterHeaders(HttpUriRequestBase method, Properties tempProp) {
|
||||
protected void assignFilterHeaders(HttpUriRequestBase method, Properties tempProp) {
|
||||
try {
|
||||
Properties filterHeaders = (Properties)tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders != null){
|
||||
@@ -963,45 +1092,64 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
return map;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method, String adapterName, String niceAuth, String inboundToken, Date currentDate) throws Exception {
|
||||
protected String assignPostBody(Object eaiBody, String mimeType, String charset, HttpUriRequestBase method,
|
||||
boolean chunked) throws Exception {
|
||||
if (eaiBody == null) {
|
||||
return;
|
||||
return "";
|
||||
}
|
||||
|
||||
if (eaiBody instanceof JSONObject) {
|
||||
JSONObject jsonObject = (JSONObject) eaiBody;
|
||||
if( jsonObject.containsKey("KJB_ROOTLESS_ARRAY") ) {
|
||||
eaiBody = jsonObject.get("KJB_ROOTLESS_ARRAY");
|
||||
if( jsonObject.containsKey("DJB_ROOTLESS_ARRAY") ) {
|
||||
eaiBody = jsonObject.get("DJB_ROOTLESS_ARRAY");
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) {
|
||||
if (StringUtils.contains(mimeType, "application/x-www-form-urlencoded")) {
|
||||
List<NameValuePair> params = new ArrayList<>();
|
||||
if (eaiBody instanceof JSONObject) {
|
||||
List<NameValuePair> params = new ArrayList<>();
|
||||
JSONObject jsonObject = (JSONObject) eaiBody;
|
||||
|
||||
for (Object key : jsonObject.keySet()) {
|
||||
|
||||
if( jsonObject.get(key) instanceof String ) {
|
||||
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8);
|
||||
method.setEntity(entity);
|
||||
} else if (eaiBody instanceof Document) {
|
||||
Document bodyDoc = (Document) eaiBody;
|
||||
Element root = bodyDoc.getRootElement();
|
||||
List<Element> childrenEle = XMLUtils.getChilds(root);
|
||||
for (Element child : childrenEle) {
|
||||
params.add(new BasicNameValuePair(child.getName(), child.getText()));
|
||||
}
|
||||
}
|
||||
ContentType contentTypeObj = ContentType.create(mimeType, charset);
|
||||
CustomUrlEncodedFormEntity entity = new CustomUrlEncodedFormEntity(params, contentTypeObj.getCharset(), chunked);
|
||||
method.setEntity(entity);
|
||||
return params.toString();
|
||||
} else {
|
||||
ContentType contentTypeObj = ContentType.create(contentType, charset);
|
||||
ContentType contentTypeObj = ContentType.create(mimeType, charset);
|
||||
|
||||
String eaiBodyMessage = doOutboundPreFilter(eaiBody, adapterName, niceAuth, inboundToken, currentDate); // jwhong
|
||||
StringEntity entity = new StringEntity(eaiBodyMessage, contentTypeObj);
|
||||
// 요청 본문을 StringEntity 객체로 생성
|
||||
//StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj);
|
||||
String body = "";
|
||||
if (eaiBody instanceof Document) {
|
||||
body = ((Document)eaiBody).asXML();
|
||||
} else if(eaiBody instanceof JSONObject) {
|
||||
body = ((JSONObject)eaiBody).toJSONString();
|
||||
} else {
|
||||
body = eaiBody.toString();
|
||||
}
|
||||
StringEntity entity = new StringEntity(body, contentTypeObj, chunked);
|
||||
|
||||
// 요청에 본문 추가
|
||||
method.setEntity(entity);
|
||||
return body;
|
||||
// ContentType contentTypeObj = ContentType.create(contentType, charset);
|
||||
//
|
||||
// String eaiBodyMessage = doOutboundPreFilter(eaiBody, adapterName, niceAuth, inboundToken, currentDate); // jwhong
|
||||
// StringEntity entity = new StringEntity(eaiBodyMessage, contentTypeObj);
|
||||
// // 요청 본문을 StringEntity 객체로 생성
|
||||
// //StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj);
|
||||
//
|
||||
// // 요청에 본문 추가
|
||||
// method.setEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1531,23 +1679,26 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
||||
uriVariables.add(fieldName);
|
||||
}
|
||||
if (MessageType.JSON.equals(messageType)) {
|
||||
JSONObject jsonObject;
|
||||
if (sendData instanceof JSONObject) {
|
||||
jsonObject = (JSONObject) sendData;
|
||||
} else {
|
||||
jsonObject = (JSONObject) JSONValue.parse((String) sendData);
|
||||
}
|
||||
for (Object tempObject : uriVariables) {
|
||||
String urlVaribleId = (String) tempObject;
|
||||
String uriVariable = null;
|
||||
if( jsonObject != null ) {
|
||||
uriVariable = (String) jsonObject.get(urlVaribleId);
|
||||
jsonObject.remove(urlVaribleId);
|
||||
}
|
||||
JsonNode jsonNode = JsonPathUtil.toTree(sendData);
|
||||
// JSONObject jsonObject;
|
||||
// if (sendData instanceof JSONObject) {
|
||||
// jsonObject = (JSONObject) sendData;
|
||||
//
|
||||
// } else {
|
||||
// jsonObject = (JSONObject) JSONValue.parse((String) sendData);
|
||||
// }
|
||||
for (String urlVaribleId : uriVariables) {
|
||||
String uriVariable = JsonPathUtil.getAt(jsonNode, urlVaribleId, "\\.");
|
||||
// String uriVariable = null;
|
||||
// if( jsonObject != null ) {
|
||||
// uriVariable = (String) jsonObject.get(urlVaribleId);
|
||||
// jsonObject.remove(urlVaribleId);
|
||||
// }
|
||||
|
||||
if (StringUtils.isBlank(uriVariable) && inboundPathVariables.containsKey(urlVaribleId)) {
|
||||
uriVariable = inboundPathVariables.get(urlVaribleId);
|
||||
}
|
||||
|
||||
urlVariableList.add(uriVariable);
|
||||
|
||||
}
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.eactive.eai.adapter.http.client.impl;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dom4j.Node;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
|
||||
import com.eactive.eai.adapter.http.client.impl.filter.HttpClient5AdapterFilterFactory;
|
||||
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
|
||||
|
||||
/**
|
||||
* 1. 기능 : HttpClient5AdapterServiceRest 호출 전후 Filter 적용할 수 있는 기능을 제공한다.<br>
|
||||
* 2. 처리 개요 : <br>
|
||||
* 3. 주의사항 <br>
|
||||
*
|
||||
* @author :
|
||||
* @version : v 1.0.0
|
||||
* @see : HttpClientAdapterServiceFactory.java,
|
||||
* HttpClientAdapterServiceSupport.java, HttpClient5AdapterServiceRest.java
|
||||
* @since :
|
||||
*
|
||||
*/
|
||||
public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterServiceRest
|
||||
implements HttpClientAdapterServiceKey {
|
||||
|
||||
// 요청전 수행할 필터(쉼표(,)로 구분)
|
||||
static final String PRE_FILTERS = "PRE_FILTERS";
|
||||
|
||||
// 요청후 수행할 필터(쉼표(,)로 구분)
|
||||
static final String POST_FILTERS = "POST_FILTERS";
|
||||
|
||||
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
|
||||
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
|
||||
|
||||
/**
|
||||
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
|
||||
* 3. 주의사항 <br>
|
||||
*
|
||||
* @param prop Http Adapter 속성 정보
|
||||
* @return 반환 된 Object
|
||||
* @exception Exception 수동 시스템 간 통신 중 발생
|
||||
*/
|
||||
public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
|
||||
|
||||
String adptGrpName = tempProp.getProperty(ADAPTER_GROUP_NAME);
|
||||
String adptName = tempProp.getProperty(ADAPTER_NAME);
|
||||
|
||||
data = doPreFilters(adptGrpName, adptName, prop, data, tempProp);
|
||||
|
||||
Object adapterResponse = super.execute(prop, data, tempProp);
|
||||
|
||||
adapterResponse = doPostFilters(adptGrpName, adptName, prop, adapterResponse, tempProp);
|
||||
if (adapterResponse instanceof JSONObject)
|
||||
adapterResponse = ((JSONObject) adapterResponse).toJSONString();
|
||||
|
||||
if (adapterResponse instanceof Node)
|
||||
adapterResponse = ((Node) adapterResponse).asXML();
|
||||
|
||||
return adapterResponse;
|
||||
}
|
||||
|
||||
protected Object doPreFilters(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
|
||||
// boolean isSetCommonFilterInAdapterProp = false;
|
||||
// 1. adapter에 설정된 필터 수행
|
||||
String preFiltersStr = prop.getProperty(PRE_FILTERS);
|
||||
if (StringUtils.isNotEmpty(preFiltersStr)) {
|
||||
String[] preFilters = StringUtils.split(preFiltersStr, ",");
|
||||
for (String filterName : preFilters) {
|
||||
if (StringUtils.isBlank(filterName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("HttpClient5AdapterServiceRestAddFilter] Processing Start [" + filterName + "]");
|
||||
HttpClientAdapterFilter adapterFilter = HttpClient5AdapterFilterFactory.createFilter(filterName.trim());
|
||||
|
||||
if (adapterFilter != null) {
|
||||
// if (adapterFilter instanceof IBKOutCommonFilter)
|
||||
// isSetCommonFilterInAdapterProp = true;
|
||||
|
||||
message = adapterFilter.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
|
||||
} else {
|
||||
throw new Exception("Failed get Filter Class: " + filterName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 2. IBKOutCommonFilter 필터 수행(adapter 필터에서 수행하지 않을 때만)
|
||||
// if (!isSetCommonFilterInAdapterProp) {
|
||||
// HttpClientAdapterFilter ibkFilter = HttpClient5AdapterFilterFactory
|
||||
// .createFilter(IBKOutCommonFilter.class.getName());
|
||||
// if (ibkFilter != null) {
|
||||
// message = ibkFilter.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
|
||||
// }
|
||||
// } else {
|
||||
// logger.debug("IBKOutCommonFilter isSetCommonFilterInAdapterProp 'POST_FILTERS'. already in adapter filters");
|
||||
// }
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
protected Object doPostFilters(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
|
||||
// boolean isSetCommonFilterInAdapterProp = false;
|
||||
// 1. adapter에 설정된 필터 수행
|
||||
String postFiltersStr = prop.getProperty(POST_FILTERS);
|
||||
if (StringUtils.isNotEmpty(postFiltersStr)) {
|
||||
String[] postFilters = StringUtils.split(postFiltersStr, ",");
|
||||
for (String filterName : postFilters) {
|
||||
if (StringUtils.isBlank(filterName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HttpClientAdapterFilter adapterFilter = HttpClient5AdapterFilterFactory.createFilter(filterName.trim());
|
||||
if (adapterFilter != null) {
|
||||
// if (adapterFilter instanceof IBKOutCommonFilter)
|
||||
// isSetCommonFilterInAdapterProp = true;
|
||||
message = adapterFilter.doPostFilter(adptGrpName, adptName, prop, message, tempProp);
|
||||
} else {
|
||||
throw new Exception("Failed get Filter Class: " + filterName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 2. IBKOutCommonFilter 필터 수행
|
||||
// if (!isSetCommonFilterInAdapterProp) {
|
||||
// HttpClientAdapterFilter ibkFilter = HttpClient5AdapterFilterFactory
|
||||
// .createFilter(IBKOutCommonFilter.class.getName());
|
||||
// if (ibkFilter != null) {
|
||||
// message = ibkFilter.doPostFilter(adptGrpName, adptName, prop, message, tempProp);
|
||||
// }
|
||||
// } else {
|
||||
// logger.debug("IBKOutCommonFilter isSetCommonFilterInAdapterProp 'PRE_FILTERS'. already in adapter filters");
|
||||
// }
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class AllHeaderFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
|
||||
public static final String HEADER_KEY_NAMES = "AllHeaderFilter.blackList";
|
||||
|
||||
|
||||
String[] HttpHeaderRelayBlackList = {
|
||||
"Content-Length",
|
||||
"Transfer-Encoding",
|
||||
"Host",
|
||||
"Authorization",
|
||||
"Accept",
|
||||
"Host",
|
||||
"Cookie",
|
||||
"Connection",
|
||||
"Keep-Alive",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"TE",
|
||||
"Trailer",
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
"Content-Type",
|
||||
"Content-Encoding",
|
||||
"Content-Language",
|
||||
"Content-Location",
|
||||
"Content-MD5",
|
||||
"Expect",
|
||||
};
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("doPreFilter Processing Start.");
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
|
||||
|
||||
String[] userSettingBlackList = propValue.split(",");
|
||||
|
||||
if( userSettingBlackList.length > 0 ) {
|
||||
HttpHeaderRelayBlackList = Stream
|
||||
.concat(Arrays.stream(HttpHeaderRelayBlackList), Arrays.stream(userSettingBlackList))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
|
||||
for(String keyName : inboundHeaderMap.keySet() ) {
|
||||
|
||||
if( StringUtils.equalsAnyIgnoreCase( keyName, HttpHeaderRelayBlackList ) ) {
|
||||
logger.debug("Skip Processing Key ["+keyName+"], value ["+inboundHeaderMap.get(keyName)+"] in HttpHeaderRelayBlackList");
|
||||
continue;
|
||||
}
|
||||
|
||||
filterHeaders.put(keyName, inboundHeaderMap.get(keyName));
|
||||
logger.debug("Processing Key ["+keyName+"], value ["+inboundHeaderMap.get(keyName)+"]");
|
||||
}
|
||||
|
||||
logger.debug("doPreFilter Processing End.");
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class AsyncReponseFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String X_TRACE_ID = "partnerTraceId";
|
||||
public static final String TRACE_ID = "traceId";
|
||||
|
||||
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
|
||||
public static final String HEADER_KEY_NAMES = "AsyncReponseFilter";
|
||||
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("AsyncReponseFilter] PreFilter Processing Start!!");
|
||||
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
|
||||
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
|
||||
|
||||
String[] headerKeyNames = propValue.split(",");
|
||||
|
||||
|
||||
headerKeyNames = Arrays.stream(headerKeyNames)
|
||||
.map(String::trim)
|
||||
.toArray(String[]::new);
|
||||
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(X_TRACE_ID)) {
|
||||
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)) {
|
||||
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+"]");
|
||||
|
||||
for (String keyName : inboundHeaderMap.keySet()) {
|
||||
|
||||
if (StringUtils.equalsAnyIgnoreCase(keyName, headerKeyNames)) {
|
||||
String value = inboundHeaderMap.get(keyName);
|
||||
filterHeaders.put(keyName, value);
|
||||
logger.debug("AsyncReponseFilter] Processing Key [" + keyName + "], value [" + value + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class HttpClient5AdapterFilterFactory {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
static String customBasePackage = "com.eactive.eai.custom.adapter.http.client.impl.filter";
|
||||
static String basePackage = "com.eactive.eai.adapter.http.client.impl.filter";
|
||||
private static ConcurrentHashMap<String, HttpClientAdapterFilter> h = new ConcurrentHashMap<>();
|
||||
|
||||
private HttpClient5AdapterFilterFactory() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static HttpClientAdapterFilter createFilter(String type) {
|
||||
if (h.containsKey(type)) {
|
||||
return h.get(type);
|
||||
}
|
||||
|
||||
HttpClientAdapterFilter filter = null;
|
||||
switch (HttpClient5AdapterFilterType.getValue(type)) {
|
||||
case SIMPLEFRAMEWORK:
|
||||
filter = new SimpleFrameworkFilter();
|
||||
break;
|
||||
case SIMPLEFRAMEWORKBODY:
|
||||
filter = new SimpleframeworkBodyFilter();
|
||||
break;
|
||||
case JBOBP:
|
||||
filter = new JBOBPFilter();
|
||||
break;
|
||||
case KFTCFACE:
|
||||
filter = new KFTCFaceFilter();
|
||||
break;
|
||||
case KFTCP2P:
|
||||
filter = new KFTCP2PFilter();
|
||||
break;
|
||||
case KAKAOBANK:
|
||||
filter = new KAKAOBankFilter();
|
||||
break;
|
||||
case NAVERFIN:
|
||||
filter = new NAVERFinFilter();
|
||||
break;
|
||||
case NICECREDIT:
|
||||
filter = new NICECreditFilter();
|
||||
break;
|
||||
case TRACEBODY:
|
||||
filter = new TraceBodyFilter();
|
||||
break;
|
||||
case TOSSBANK:
|
||||
filter = new TOSSBankFilter();
|
||||
break;
|
||||
case ASYNCRESPONSE:
|
||||
filter = new AsyncReponseFilter();
|
||||
break;
|
||||
case ALLHEADER:
|
||||
filter = new AllHeaderFilter();
|
||||
break;
|
||||
default:
|
||||
filter = classForName(type);
|
||||
if (filter == null) {
|
||||
filter = classForName(customBasePackage + "." + type);
|
||||
}
|
||||
if (filter == null) {
|
||||
filter = classForName(basePackage + "." + type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
h.put(type, filter);
|
||||
return filter;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClientAdapterFilter classForName(String type) {
|
||||
try {
|
||||
Class<?> cl = Class.forName(type);
|
||||
return (HttpClientAdapterFilter) cl.newInstance();
|
||||
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
|
||||
logger.error("Cannot create a HttpClientAdapterFilter. - {}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
public enum HttpClient5AdapterFilterType {
|
||||
SIMPLEFRAMEWORK,
|
||||
SIMPLEFRAMEWORKBODY,
|
||||
JBOBP,
|
||||
KFTCFACE,
|
||||
KFTCP2P,
|
||||
KAKAOBANK,
|
||||
NAVERFIN,
|
||||
NICECREDIT,
|
||||
TOSSBANK,
|
||||
TRACEBODY,
|
||||
ASYNCRESPONSE,
|
||||
ALLHEADER,
|
||||
UNKNOWN;
|
||||
|
||||
public static HttpClient5AdapterFilterType getValue(String type) {
|
||||
try {
|
||||
return valueOf(type.toUpperCase());
|
||||
} catch (NullPointerException e) {
|
||||
return UNKNOWN;
|
||||
} catch (Exception e) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public interface HttpClientAdapterFilter {
|
||||
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception;
|
||||
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class HttpClientAdapterFilterException extends RuntimeException {
|
||||
private static final long serialVersionUID = -1208983360831093751L;
|
||||
@Getter
|
||||
private final String code;
|
||||
@Getter
|
||||
private final Properties prop;
|
||||
@Getter
|
||||
private final Properties tempProp;
|
||||
|
||||
public HttpClientAdapterFilterException(String code, String msg, Properties prop, Properties tmepProp) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.prop = prop;
|
||||
this.tempProp = tmepProp;
|
||||
}
|
||||
|
||||
public HttpClientAdapterFilterException(String code, String msg, Properties prop, Properties tempProp, Throwable cause) {
|
||||
super(msg, cause);
|
||||
this.code = code;
|
||||
this.prop = prop;
|
||||
this.tempProp = tempProp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpClientAdapterFilterException [code=" + code + ", prop=" + prop + ", tempProp=" + tempProp + ", parent=" + super.toString() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class JBOBPFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String X_OBP_PARTNERCODE = "x-obp-partnercode";
|
||||
public static final String X_OBP_TXID = "x-obp-txid";
|
||||
public static final String X_OBP_TRUST_SYSTEM = "x-obp-trust-system";
|
||||
String systemTrustKey = "APIMGW-0001-QVBJTUdXLTAwMDE=";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("JBOBPFilter] PreFilter Processing Start!!");
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) systemTrustKey = propGroupVo.getProperty(X_OBP_TRUST_SYSTEM, systemTrustKey);
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
String obpPartnerCode = "000000-00";
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(X_OBP_PARTNERCODE)) {
|
||||
filterHeaders.put(X_OBP_PARTNERCODE, inboundHeaderMap.get(X_OBP_PARTNERCODE));
|
||||
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+inboundHeaderMap.get(X_OBP_PARTNERCODE)+"]");
|
||||
} else {
|
||||
filterHeaders.put(X_OBP_PARTNERCODE, obpPartnerCode);
|
||||
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+obpPartnerCode+"]");
|
||||
}
|
||||
} else {
|
||||
filterHeaders.put(X_OBP_PARTNERCODE, obpPartnerCode);
|
||||
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+obpPartnerCode+"]");
|
||||
}
|
||||
|
||||
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
|
||||
if (StringUtils.isNotBlank(uuid)) {
|
||||
filterHeaders.setProperty(X_OBP_TXID, uuid); // JB OBP Framework용
|
||||
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_TXID+"], value ["+uuid+"]");
|
||||
}
|
||||
if (StringUtils.isNotBlank(systemTrustKey)) {
|
||||
filterHeaders.setProperty(X_OBP_TRUST_SYSTEM, systemTrustKey); // JB OBP Framework용
|
||||
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_TRUST_SYSTEM+"], value ["+systemTrustKey+"]");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class KAKAOBankFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String PROP_PREFIX = "kakaobank";
|
||||
public static final String X_KKB_PARTNER_CODE = "X-KKB-PARTNER-CODE";
|
||||
public static final String X_KKB_API_NAME = "X-KKB-API-NAME";
|
||||
public static final String X_KKB_API_TX_ID = "X-KKB-API-TX-ID";
|
||||
public static final String X_KKB_TX_TIME = "X-KKB-TX-TIME";
|
||||
public static final String X_KKB_CMPR_MGMT_NO = "X-KKB-CMPR-MGMT-NO";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("KAKAOBankFilter] PreFilter Processing Start!!");
|
||||
Object returnMessage = message;
|
||||
String partnerCode = "KJB";
|
||||
String apiName = "status_change";
|
||||
String apiTxId = "0";
|
||||
String txTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
String cmprMgmtNo = "0";
|
||||
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) {
|
||||
partnerCode = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_PARTNER_CODE);
|
||||
apiName = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_API_NAME);
|
||||
apiTxId = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_API_TX_ID);
|
||||
cmprMgmtNo = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_CMPR_MGMT_NO);
|
||||
}
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(X_KKB_PARTNER_CODE)) {
|
||||
filterHeaders.put(X_KKB_PARTNER_CODE, inboundHeaderMap.get(X_KKB_PARTNER_CODE));
|
||||
} else {
|
||||
filterHeaders.put(X_KKB_PARTNER_CODE, partnerCode);
|
||||
}
|
||||
if (inboundHeaderMap.containsKey(X_KKB_API_NAME)) {
|
||||
filterHeaders.put(X_KKB_API_NAME, inboundHeaderMap.get(X_KKB_API_NAME));
|
||||
} else {
|
||||
filterHeaders.put(X_KKB_API_NAME, apiName);
|
||||
}
|
||||
if (inboundHeaderMap.containsKey(X_KKB_API_TX_ID)) {
|
||||
filterHeaders.put(X_KKB_API_TX_ID, inboundHeaderMap.get(X_KKB_API_TX_ID));
|
||||
} else {
|
||||
filterHeaders.put(X_KKB_API_TX_ID, apiTxId);
|
||||
}
|
||||
if (inboundHeaderMap.containsKey(X_KKB_TX_TIME)) {
|
||||
filterHeaders.put(X_KKB_TX_TIME, inboundHeaderMap.get(X_KKB_TX_TIME));
|
||||
} else {
|
||||
filterHeaders.put(X_KKB_TX_TIME, txTime);
|
||||
}
|
||||
if (inboundHeaderMap.containsKey(X_KKB_CMPR_MGMT_NO)) {
|
||||
filterHeaders.put(X_KKB_CMPR_MGMT_NO, inboundHeaderMap.get(X_KKB_CMPR_MGMT_NO));
|
||||
} else {
|
||||
filterHeaders.put(X_KKB_CMPR_MGMT_NO, cmprMgmtNo);
|
||||
}
|
||||
} else {
|
||||
filterHeaders.put(X_KKB_PARTNER_CODE, partnerCode);
|
||||
filterHeaders.put(X_KKB_API_NAME, apiName);
|
||||
filterHeaders.put(X_KKB_API_TX_ID, apiTxId);
|
||||
filterHeaders.put(X_KKB_TX_TIME, txTime);
|
||||
filterHeaders.put(X_KKB_CMPR_MGMT_NO, cmprMgmtNo);
|
||||
}
|
||||
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_PARTNER_CODE+"], value ["+filterHeaders.getProperty(X_KKB_PARTNER_CODE)+"]");
|
||||
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_API_NAME+"], value ["+filterHeaders.getProperty(X_KKB_API_NAME)+"]");
|
||||
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_API_TX_ID+"], value ["+filterHeaders.getProperty(X_KKB_API_TX_ID)+"]");
|
||||
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_TX_TIME+"], value ["+filterHeaders.getProperty(X_KKB_TX_TIME)+"]");
|
||||
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_CMPR_MGMT_NO+"], value ["+filterHeaders.getProperty(X_KKB_CMPR_MGMT_NO)+"]");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return returnMessage;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class KFTCFaceFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String PROP_PREFIX = "kftcface";
|
||||
public static final String H_CLIENT_ID = "Client-Id";
|
||||
public static final String B_ORG_CODE = "org_code";
|
||||
public static final String B_TRANSACTION_ID = "transaction_id";
|
||||
public static final String B_REQUEST_DATETIME = "request_datetime";
|
||||
public static String instId = null;
|
||||
|
||||
static {
|
||||
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
|
||||
instId = eaiServerManager.getInstId();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("KFTCFaceFilter] PreFilter Processing Start!!");
|
||||
JSONObject returnMessage = null;
|
||||
String clientId = "";
|
||||
String orgCode = "034"; // 광주은행 행코드
|
||||
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) {
|
||||
if (StringUtils.isEmpty(clientId))
|
||||
clientId = propGroupVo.getProperty(PROP_PREFIX + "." + H_CLIENT_ID);
|
||||
orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + B_ORG_CODE);
|
||||
}
|
||||
|
||||
String request_datetime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
String reqDate = new SimpleDateFormat("yyMMdd").format(new Date());
|
||||
String reqTime = new SimpleDateFormat("HHmmss").format(new Date());
|
||||
// 기관코드(3) + 요청일자(6) -- 금결원 표준화 항목, 고정 9자리.
|
||||
// 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 기관별 생성 거래고유번호(11자리)
|
||||
// 기관코드(3) + 요청일자(6) + 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 20자리.
|
||||
String transaction_id = orgCode + reqDate + reqTime + instId + RandomStringUtils.random(3, true, true).toUpperCase();
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(H_CLIENT_ID)) {
|
||||
filterHeaders.put(H_CLIENT_ID, inboundHeaderMap.get(H_CLIENT_ID));
|
||||
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+inboundHeaderMap.get(H_CLIENT_ID)+"]");
|
||||
} else {
|
||||
filterHeaders.put(H_CLIENT_ID, clientId);
|
||||
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+clientId+"]");
|
||||
}
|
||||
} else {
|
||||
filterHeaders.setProperty(H_CLIENT_ID, clientId);
|
||||
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+clientId+"]");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
AdapterManager adapterManager = AdapterManager.getInstance();
|
||||
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
|
||||
String encode = adptGrpVO.getMessageEncode();
|
||||
if (message == null) {
|
||||
returnMessage = new JSONObject();
|
||||
} else {
|
||||
if (message instanceof JSONObject) {
|
||||
returnMessage = (JSONObject) message;
|
||||
} else if (message instanceof String) {
|
||||
returnMessage = parseJson((String) message);
|
||||
} else if (message instanceof byte[]) {
|
||||
returnMessage = parseJson(new String((byte[]) message, encode));
|
||||
}
|
||||
}
|
||||
|
||||
returnMessage.put(B_ORG_CODE, orgCode);
|
||||
returnMessage.put(B_TRANSACTION_ID, transaction_id);
|
||||
returnMessage.put(B_REQUEST_DATETIME, request_datetime);
|
||||
|
||||
return returnMessage.toJSONString();
|
||||
}
|
||||
|
||||
private JSONObject parseJson(String message) throws Exception {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (JSONObject) JSONValue.parse(message);
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class KFTCP2PFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String PROP_PREFIX = "kftcp2p";
|
||||
public static final String H_ORG_CODE = "org_code";
|
||||
public static final String H_TRX_NO = "api_trx_no";
|
||||
public static final String H_TRX_DTM = "api_trx_dtm";
|
||||
public static String instId = null;
|
||||
|
||||
static {
|
||||
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
|
||||
instId = eaiServerManager.getInstId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("KFTCP2PFilter] PreFilter Processing Start!!");
|
||||
String orgCode = "D210400012"; // 광주은행 개발 기관코드(운영: K210800020)
|
||||
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) {
|
||||
orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + H_ORG_CODE);
|
||||
}
|
||||
|
||||
String apiTrxDtm = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
|
||||
String apiTrxTm = new SimpleDateFormat("HHmmss").format(new Date());
|
||||
// 기관코드(10) + 인스턴스ID(2) + 일시(6) + RandomString(2) -- 총 20자리
|
||||
String apiTrxNo = orgCode + instId + apiTrxTm + RandomStringUtils.random(2, true, true).toUpperCase();
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(H_TRX_NO)) {
|
||||
filterHeaders.put(H_TRX_NO, inboundHeaderMap.get(H_TRX_NO));
|
||||
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+inboundHeaderMap.get(H_TRX_NO)+"]");
|
||||
} else {
|
||||
filterHeaders.put(H_TRX_NO, apiTrxNo);
|
||||
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+apiTrxNo+"]");
|
||||
}
|
||||
if (inboundHeaderMap.containsKey(H_TRX_DTM)) {
|
||||
filterHeaders.put(H_TRX_DTM, inboundHeaderMap.get(H_TRX_DTM));
|
||||
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+inboundHeaderMap.get(H_TRX_DTM)+"]");
|
||||
} else {
|
||||
filterHeaders.put(H_TRX_DTM, apiTrxDtm);
|
||||
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+apiTrxDtm+"]");
|
||||
}
|
||||
} else {
|
||||
filterHeaders.put(H_TRX_NO, apiTrxNo);
|
||||
filterHeaders.put(H_TRX_DTM, apiTrxDtm);
|
||||
}
|
||||
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+filterHeaders.getProperty(H_TRX_NO)+"]");
|
||||
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+filterHeaders.getProperty(H_TRX_DTM)+"]");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class NAVERFinFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String PROP_PREFIX = "naverfin";
|
||||
public static final String X_PARTNER_ID = "X-Partner-Id";
|
||||
public static final String X_FINTECH_ID = "X-Fintech-Id";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("NAVERFinFilter] PreFilter Processing Start!!");
|
||||
String partnerID = "r0jtqwC9gAW5";
|
||||
String fintechId = "NF";
|
||||
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) {
|
||||
partnerID = propGroupVo.getProperty(PROP_PREFIX + "." + X_PARTNER_ID);
|
||||
fintechId = propGroupVo.getProperty(PROP_PREFIX + "." + X_FINTECH_ID);
|
||||
}
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(X_PARTNER_ID)) {
|
||||
filterHeaders.put(X_PARTNER_ID, inboundHeaderMap.get(X_PARTNER_ID));
|
||||
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+inboundHeaderMap.get(X_PARTNER_ID)+"]");
|
||||
} else {
|
||||
filterHeaders.put(X_PARTNER_ID, partnerID);
|
||||
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+partnerID+"]");
|
||||
}
|
||||
if (inboundHeaderMap.containsKey(X_FINTECH_ID)) {
|
||||
filterHeaders.put(X_FINTECH_ID, inboundHeaderMap.get(X_FINTECH_ID));
|
||||
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+inboundHeaderMap.get(X_FINTECH_ID)+"]");
|
||||
} else {
|
||||
filterHeaders.put(X_FINTECH_ID, fintechId);
|
||||
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+fintechId+"]");
|
||||
}
|
||||
} else {
|
||||
filterHeaders.put(X_PARTNER_ID, partnerID);
|
||||
filterHeaders.put(X_FINTECH_ID, fintechId);
|
||||
}
|
||||
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+filterHeaders.getProperty(X_PARTNER_ID)+"]");
|
||||
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+filterHeaders.getProperty(X_FINTECH_ID)+"]");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class NICECreditFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String PROP_PREFIX = "nicecredit";
|
||||
public static final String H_PRODUCTID = "ProductID";
|
||||
public static final String B_REQ_DTM = "req_dtm";
|
||||
public static final String B_GOODS_CLS = "goods_cls";
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
JSONObject returnMessage = null;
|
||||
String productId = "2303102120";
|
||||
String reqDtm = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
String goodsCls = "KJBAPI0001";
|
||||
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) {
|
||||
productId = propGroupVo.getProperty(PROP_PREFIX + "." + H_PRODUCTID);
|
||||
goodsCls = propGroupVo.getProperty(PROP_PREFIX + "." + B_GOODS_CLS);
|
||||
}
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
if (inboundHeaderMap != null && inboundHeaderMap.containsKey(H_PRODUCTID)) {
|
||||
for (Entry<String, String> e : inboundHeaderMap.entrySet()) {
|
||||
String key = (String) e.getKey();
|
||||
String value = (String) e.getValue();
|
||||
|
||||
if (StringUtils.equalsIgnoreCase(key, H_PRODUCTID)) {
|
||||
filterHeaders.setProperty(key, value);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Request Header :" + key + "=[" + value + "]");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filterHeaders.setProperty(H_PRODUCTID, productId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
AdapterManager adapterManager = AdapterManager.getInstance();
|
||||
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
|
||||
String encode = adptGrpVO.getMessageEncode();
|
||||
if (message == null) {
|
||||
returnMessage = new JSONObject();
|
||||
} else {
|
||||
if (message instanceof JSONObject) {
|
||||
returnMessage = (JSONObject) message;
|
||||
} else if (message instanceof String) {
|
||||
returnMessage = parseJson((String) message);
|
||||
} else if (message instanceof byte[]) {
|
||||
returnMessage = parseJson(new String((byte[]) message, encode));
|
||||
}
|
||||
}
|
||||
|
||||
returnMessage.put(B_REQ_DTM, reqDtm);
|
||||
returnMessage.put(B_GOODS_CLS, goodsCls);
|
||||
|
||||
return returnMessage.toJSONString();
|
||||
}
|
||||
|
||||
private JSONObject parseJson(String message) throws Exception {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (JSONObject) JSONValue.parse(message);
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
|
||||
|
||||
/**
|
||||
* HTTP 클라이언트 어댑터용 암복호화 필터 (송신 방향).
|
||||
*
|
||||
* doPreFilter : 외부 시스템으로 보내는 요청 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
|
||||
* doPostFilter : 외부 시스템에서 받은 응답 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
|
||||
*
|
||||
* DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
|
||||
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
|
||||
*
|
||||
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
|
||||
* AAD 관련 프로퍼티:
|
||||
* PROP_AAD_HEADER tempProp에서 AAD 값을 조회할 키 이름.
|
||||
* 미설정 또는 해당 키 없으면 aad=null → IV를 AAD 대체값으로 사용.
|
||||
*/
|
||||
public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientAdapterFilter {
|
||||
|
||||
/**
|
||||
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
|
||||
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
|
||||
* 서브클래스에서 오버라이드하여 tempProp으로부터 값을 추출할 수 있다.
|
||||
*/
|
||||
protected Map<String, String> buildRuntimeContext(Properties prop, Properties tempProp) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* CRYPTO_AAD_PROP 프로퍼티에 지정된 키로 tempProp에서 AAD 값을 조회한다.
|
||||
* 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
|
||||
*/
|
||||
protected byte[] buildAad(Properties prop, Properties tempProp) {
|
||||
String propKey = prop.getProperty(PROP_AAD_HEADER);
|
||||
if (StringUtils.isBlank(propKey) || tempProp == null) {
|
||||
return null;
|
||||
}
|
||||
String value = tempProp.getProperty(propKey);
|
||||
return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String body = toBodyString(message);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, tempProp);
|
||||
byte[] aad = buildAad(prop, tempProp);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return encryptField(body, moduleName, runtimeCtx, aad,
|
||||
getProp(tempProp, PROP_ENC_FROM_PATH, PATH_ROOT),
|
||||
getProp(tempProp, PROP_ENC_TO_PATH, PATH_ENCDATA));
|
||||
}
|
||||
return encryptBody(body, moduleName, runtimeCtx, aad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String body = toBodyString(message);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, tempProp);
|
||||
byte[] aad = buildAad(prop, tempProp);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return decryptField(body, moduleName, runtimeCtx, aad,
|
||||
getProp(tempProp, PROP_DEC_FROM_PATH, PATH_ENCDATA),
|
||||
getProp(tempProp, PROP_DEC_TO_PATH, PATH_ROOT));
|
||||
}
|
||||
return decryptBody(body, moduleName, runtimeCtx, aad);
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class SimpleFrameworkFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String CLIENTID = "clientId";
|
||||
public static final String TRACEID = "traceId";
|
||||
public static final String GUID = "guid";
|
||||
public static final String RECEIVEDTIMESTAMP = "receivedTimestamp";
|
||||
public static final String PARTNER_TRACE_ID = "partnerTraceId";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("SimpleFrameworkFilter] PreFilter Processing Start!!");
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
try {
|
||||
/* 업체정보 */
|
||||
String clientId = tempProp.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
|
||||
if (StringUtils.isEmpty(clientId)) clientId = "";
|
||||
filterHeaders.put(CLIENTID, clientId);
|
||||
logger.debug("SimpleFrameworkFilter] Processing Key ["+CLIENTID+"], value ["+clientId+"]");
|
||||
|
||||
/* APIM 거래추적자 */
|
||||
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
|
||||
filterHeaders.put(TRACEID, uuid);
|
||||
logger.debug("SimpleFrameworkFilter] Processing Key ["+TRACEID+"], value ["+uuid+"]");
|
||||
|
||||
/* GUID */
|
||||
String guid = tempProp.getProperty(TransactionContextKeys.GUID, "");
|
||||
filterHeaders.put(GUID, guid);
|
||||
logger.debug("SimpleFrameworkFilter] Processing Key ["+GUID+"], value ["+guid+"]");
|
||||
|
||||
/* 최초요청시간 */
|
||||
String receivedTimestamp = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME, "");
|
||||
filterHeaders.put(RECEIVEDTIMESTAMP, receivedTimestamp);
|
||||
logger.debug("SimpleFrameworkFilter] Processing Key ["+RECEIVEDTIMESTAMP+"], value ["+receivedTimestamp+"]");
|
||||
|
||||
String partnerTraceId = inboundHeaderMap.getOrDefault(PARTNER_TRACE_ID, "");
|
||||
filterHeaders.put(PARTNER_TRACE_ID, partnerTraceId);
|
||||
logger.debug("SimpleFrameworkFilter] Processing Key ["+PARTNER_TRACE_ID+"], value ["+partnerTraceId+"]");
|
||||
|
||||
String x_loan_token = inboundHeaderMap.getOrDefault(TransactionContextKeys.X_LOAN_TOKEN, "");
|
||||
filterHeaders.put(TransactionContextKeys.X_LOAN_TOKEN, x_loan_token);
|
||||
logger.debug("SimpleFrameworkFilter] Processing Key ["+TransactionContextKeys.X_LOAN_TOKEN+"], value ["+x_loan_token+"]");
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.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);
|
||||
if( bizJsonStr != null ) {
|
||||
bizJsonStr.put("GUID", tempProp.getOrDefault(TransactionContextKeys.GUID, "") );
|
||||
}
|
||||
}
|
||||
|
||||
return bizJsonStr != null ? bizJsonStr.toJSONString() : returnMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
|
||||
public class TOSSBankFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String KJB_HEADER = "KJB_Header";
|
||||
public static final String PROP_PREFIX = "tossbank";
|
||||
public static final String X_TOSSBANK_LOAN_TRACE_ID = "X-TOSSBANK-LOAN-TRACE-ID";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
logger.debug("TOSSBankFilter] PreFilter Processing Start!!");
|
||||
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
|
||||
|
||||
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
|
||||
if (propGroupVo != null) {
|
||||
}
|
||||
|
||||
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
|
||||
if (filterHeaders == null) {
|
||||
filterHeaders = new Properties();
|
||||
tempProp.put("FILTERHEADER", filterHeaders);
|
||||
}
|
||||
try {
|
||||
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
|
||||
|
||||
if (!inboundHeaderMap.isEmpty()) {
|
||||
if (inboundHeaderMap.containsKey(X_TOSSBANK_LOAN_TRACE_ID)) {
|
||||
filterHeaders.put(X_TOSSBANK_LOAN_TRACE_ID, inboundHeaderMap.get(X_TOSSBANK_LOAN_TRACE_ID));
|
||||
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+inboundHeaderMap.get(X_TOSSBANK_LOAN_TRACE_ID)+"]");
|
||||
} else {
|
||||
filterHeaders.put(X_TOSSBANK_LOAN_TRACE_ID, traceId);
|
||||
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+traceId+"]");
|
||||
}
|
||||
} else {
|
||||
/* APIM 거래추적자 */
|
||||
filterHeaders.setProperty(X_TOSSBANK_LOAN_TRACE_ID, traceId);
|
||||
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+traceId+"]");
|
||||
}
|
||||
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+filterHeaders.getProperty(X_TOSSBANK_LOAN_TRACE_ID)+"]");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public Map<String, String> getInboundHeaderProp(Properties prop) {
|
||||
Properties header = new Properties();
|
||||
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
try {
|
||||
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
|
||||
if (headerObj instanceof Properties) { //tomcat
|
||||
header = (Properties) headerObj;
|
||||
for (String name : header.stringPropertyNames()) {
|
||||
inboundHeaderMap.put(name, header.getProperty(name));
|
||||
}
|
||||
} else if (headerObj instanceof String) { //weblogic
|
||||
String str = (String) headerObj;
|
||||
str = str.substring(1, str.length() - 1);
|
||||
// key=value 쌍 분리
|
||||
String[] entries = str.split(",");
|
||||
for (String entry : entries) {
|
||||
String[] kv = entry.split("=", 2);
|
||||
if (kv.length == 2) {
|
||||
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inboundHeaderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.eactive.eai.adapter.http.client.impl.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.nimbusds.jose.shaded.gson.JsonObject;
|
||||
|
||||
public class TraceBodyFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String BIZ_HEADER = "header";
|
||||
public static final String GUID = "guid";
|
||||
public static final String TRACEID = "traceId";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
|
||||
throws Exception {
|
||||
|
||||
JSONObject bizJsonStr = null;
|
||||
|
||||
if (message instanceof String) {
|
||||
|
||||
bizJsonStr = (JSONObject) JSONValue.parse((String) message);
|
||||
|
||||
if ( bizJsonStr == null ) return message; //json string이 아님.
|
||||
|
||||
if (bizJsonStr.containsKey(BIZ_HEADER)) {
|
||||
JSONObject bizHeader = (JSONObject) bizJsonStr.get(BIZ_HEADER);
|
||||
|
||||
putBizHeaderData(bizHeader, tempProp);
|
||||
|
||||
}else {
|
||||
|
||||
JSONObject bizHeader = new JSONObject();
|
||||
bizJsonStr.put(BIZ_HEADER, bizHeader);
|
||||
putBizHeaderData(bizHeader, tempProp);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bizJsonStr != null ? bizJsonStr.toJSONString() : message;
|
||||
}
|
||||
|
||||
static private void putBizHeaderData(JSONObject bizHeader, Properties tempProp) {
|
||||
|
||||
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
|
||||
|
||||
String guid = tempProp.getProperty(TransactionContextKeys.GUID, "");
|
||||
|
||||
bizHeader.put(GUID, guid);
|
||||
logger.debug("Processing Key [" + GUID + "], value [" + guid + "]");
|
||||
|
||||
bizHeader.put(TRACEID, traceId);
|
||||
logger.debug("Processing Key [" + TRACEID + "], value [" + traceId + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
|
||||
Properties tempProp) throws Exception {
|
||||
// TODO Auto-generated method stub
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
@@ -13,10 +14,13 @@ import com.eactive.eai.adapter.RequestDispatcher;
|
||||
import com.eactive.eai.adapter.http.HttpStatusException;
|
||||
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.common.util.TxSiftContext;
|
||||
import com.eactive.eai.common.util.UUIDGenerator;
|
||||
|
||||
public abstract class HttpAdapterServiceSupport implements HttpAdapterService, HttpAdapterServiceKey {
|
||||
private static final String LOG_PREFIX = "HttpAdapterServiceSupport] ";
|
||||
@@ -27,53 +31,67 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
||||
|
||||
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
String transactionId = request.getHeader(HttpClientAdapterServiceKey.TRANSACTION_ID);
|
||||
if (StringUtils.isNotBlank(transactionId)) {
|
||||
prop.put(HttpClientAdapterServiceKey.TRANSACTION_ID, transactionId);
|
||||
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||
boolean bMDCput = false;
|
||||
if(uuid == null) {
|
||||
uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
|
||||
prop.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
|
||||
}
|
||||
|
||||
bMDCput = TxSiftContext.begin(uuid);
|
||||
|
||||
try {
|
||||
String clientId = request.getHeader(HEADER_NAME_CLIENT_ID);
|
||||
if (StringUtils.isNotBlank(clientId)) {
|
||||
prop.put(PROPERTIES_NAME_CLIENT_ID, clientId);
|
||||
String transactionId = request.getHeader(HttpClientAdapterServiceKey.TRANSACTION_ID);
|
||||
if (StringUtils.isNotBlank(transactionId)) {
|
||||
prop.put(HttpClientAdapterServiceKey.TRANSACTION_ID, transactionId);
|
||||
}
|
||||
|
||||
String instanceId = request.getHeader(HttpClientAdapterServiceKey.INSTANCE_ID);
|
||||
if (StringUtils.isNotBlank(instanceId)) {
|
||||
prop.put(HttpClientAdapterServiceKey.INSTANCE_ID, instanceId);
|
||||
try {
|
||||
String clientId = request.getHeader(HEADER_NAME_CLIENT_ID);
|
||||
if (StringUtils.isNotBlank(clientId)) {
|
||||
prop.put(PROPERTIES_NAME_CLIENT_ID, clientId);
|
||||
}
|
||||
|
||||
String instanceId = request.getHeader(HttpClientAdapterServiceKey.INSTANCE_ID);
|
||||
if (StringUtils.isNotBlank(instanceId)) {
|
||||
prop.put(HttpClientAdapterServiceKey.INSTANCE_ID, instanceId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn(LOG_PREFIX + "Failed to read request headers: " + e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
Object obj = null;
|
||||
try {
|
||||
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
|
||||
obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
|
||||
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
|
||||
} catch (HttpStatusException e) { // inbound error
|
||||
logger.warn(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||
throw e;
|
||||
} catch (JwtAuthException e) { // filter error
|
||||
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
|
||||
"RECEAIIRP202", e);
|
||||
throw e;
|
||||
} catch (Exception e) { // filter error
|
||||
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
|
||||
"RECEAIIRP201", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (obj == null) {
|
||||
return null;
|
||||
} else if (obj instanceof byte[]) {
|
||||
return (byte[]) obj;
|
||||
} else if (obj instanceof String) {
|
||||
return (String) obj;
|
||||
} else {
|
||||
throw new Exception("RECEAIAHA001");
|
||||
|
||||
Object obj = null;
|
||||
try {
|
||||
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
|
||||
obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
|
||||
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
|
||||
} catch (HttpStatusException e) { // inbound error
|
||||
logger.warn(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||
throw e;
|
||||
} catch (JwtAuthException e) { // filter error
|
||||
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
|
||||
"RECEAIIRP202", e);
|
||||
throw e;
|
||||
} catch (Exception e) { // filter error
|
||||
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
|
||||
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
|
||||
"RECEAIIRP201", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (obj == null) {
|
||||
return null;
|
||||
} else if (obj instanceof byte[]) {
|
||||
return (byte[]) obj;
|
||||
} else if (obj instanceof String) {
|
||||
return (String) obj;
|
||||
} else {
|
||||
throw new Exception("RECEAIAHA001");
|
||||
}
|
||||
} finally {
|
||||
if(bMDCput)
|
||||
TxSiftContext.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +103,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
||||
if (!"HTC".equals(group.getType())) {
|
||||
String allowIp = prop.getProperty(ALLOW_IP,"");
|
||||
if (StringUtils.isNotBlank(allowIp)) {
|
||||
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
|
||||
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
|
||||
if (adapterFilter != null) {
|
||||
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
|
||||
}
|
||||
@@ -103,7 +121,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
||||
continue;
|
||||
}
|
||||
|
||||
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
|
||||
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
|
||||
if (adapterFilter != null) {
|
||||
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
|
||||
} else {
|
||||
@@ -126,7 +144,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
|
||||
continue;
|
||||
}
|
||||
|
||||
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
|
||||
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
|
||||
if (adapterFilter != null) {
|
||||
resultMessage = adapterFilter.doPostFilter(adptGrpName, adptName, resultMessage, prop, request,
|
||||
response);
|
||||
|
||||
@@ -93,17 +93,23 @@ public class ApiAuthFilter implements HttpAdapterFilter {
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
|
||||
if (StringUtils.isBlank(apiId)) {
|
||||
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Can not find apiId(apiCode) info");
|
||||
String apiId = prop.getProperty("FINAL_STD_MESSAGE_KEY");
|
||||
String eaiSvcCd = prop.getProperty("API_SERVICE_CODE");
|
||||
if(StringUtils.isEmpty(eaiSvcCd)) {
|
||||
if(StringUtils.isEmpty(apiId))
|
||||
apiId = assignApiId(adptGrpName, adptName, message, prop, request);
|
||||
|
||||
if (StringUtils.isBlank(apiId)) {
|
||||
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Can not find apiId(apiCode) info");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: 중복되는 표준메시지 조회로 리소드 낭비가 있을 수 있으므로 체크 자체를 추후 RequestProcessor혹은 RestProcessor로 이동 필요 해 보임
|
||||
* ApiRequestProcessor을 만드는것도 괜찮을 수 있을 것 같으나 관리가 안될 것 같음
|
||||
*/
|
||||
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
|
||||
eaiSvcCd = StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: 중복되는 표준메시지 조회로 리소드 낭비가 있을 수 있으므로 체크 자체를 추후 RequestProcessor혹은 RestProcessor로 이동 필요 해 보임
|
||||
* ApiRequestProcessor을 만드는것도 괜찮을 수 있을 것 같으나 관리가 안될 것 같음
|
||||
*/
|
||||
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
|
||||
String eaiSvcCd = StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
|
||||
// if (StringUtils.isNotEmpty(eaiSvcCd) && !StringUtils.equals(eaiSvcCd, ))
|
||||
// eaiSvcCd = prop.getProperty("API_SERVICE_CODE");
|
||||
EAIMessage eaiMessage = EAIMessageManager.getInstance().getEAIMessage(eaiSvcCd);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
import com.eactive.eai.common.message.EAIMessageManager;
|
||||
import com.eactive.eai.common.stdmessage.STDMessageManager;
|
||||
import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.common.util.MessageUtil;
|
||||
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.StandardMessage;
|
||||
import com.eactive.eai.message.manager.StandardMessageManager;
|
||||
import com.eactive.eai.message.service.InterfaceMapper;
|
||||
|
||||
/**
|
||||
* 요청 url를 기반으로 interface id 찾기
|
||||
* tx prop에 STD_MESSAGE_KEY, FINAL_STD_MESSAGE_KEY, INTERFACE_ID, EAI_MESSAGE 세팅
|
||||
*/
|
||||
public class ApiKeyExtractFilter implements HttpAdapterFilter {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String STD_MESSAGE_KEY = "STD_MESSAGE_KEY";
|
||||
public static final String FINAL_STD_MESSAGE_KEY = "FINAL_STD_MESSAGE_KEY";
|
||||
public static final String API_SERVICE_CODE = "API_SERVICE_CODE";
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
String actionName = prop.getProperty(Processor.REQUEST_ACTION);
|
||||
RequestAction action = ActionFactory.createAction(actionName);
|
||||
action.setAdapterInfo(adptGrpName, adptName, prop);
|
||||
String[] keys = action.perform(message);
|
||||
String requestPath = keys[0];
|
||||
|
||||
prop.setProperty(STD_MESSAGE_KEY, requestPath);// action class 통해서 생성
|
||||
|
||||
// PathVariable 지원 추가
|
||||
String ruledPath = getMatchedKey(requestPath);
|
||||
if(ruledPath == null)
|
||||
throw new IllegalAccessException("path not found - " + requestPath);
|
||||
prop.setProperty(FINAL_STD_MESSAGE_KEY, ruledPath);// StandardMessageUtil.getMatchedKey(), url
|
||||
// pathvariable도 처리
|
||||
|
||||
String apiId = prop.getProperty(ApiKeyExtractFilter.FINAL_STD_MESSAGE_KEY);
|
||||
if (StringUtils.isBlank(apiId)) {
|
||||
throw new JwtAuthException(MessageUtil.ERROR_CODE_AUTH_FAIL, "Can not find apiId(apiCode) info");
|
||||
}
|
||||
|
||||
String apiSvcCode = this.getEaiSvcCode(apiId);
|
||||
prop.put(API_SERVICE_CODE, apiSvcCode);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private String getEaiSvcCode(String apiId) throws Exception {
|
||||
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
|
||||
return StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 서비스에서 실제 요청된 path를 설정된 PathVariable 형태의 key를 찾는다
|
||||
* ex) /api/bank/account/12345 --> /api/bank/account/{accountNo}
|
||||
* @param key
|
||||
* @param actionName
|
||||
* @return
|
||||
*/
|
||||
public static String getMatchedKey(String key) {
|
||||
STDMessageManager stdMessageManager = STDMessageManager.getInstance();
|
||||
String[] keys = stdMessageManager.getAllSTDMessageKeys();
|
||||
for (String keyStr : keys) {
|
||||
if (StringUtils.equals(key, keyStr)) {
|
||||
return keyStr;
|
||||
}
|
||||
}
|
||||
|
||||
return stdMessageManager.getMatchedPathVariable(key);
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.security.CryptoModuleService;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.util.JsonPathUtil;
|
||||
|
||||
/**
|
||||
* HTTP 어댑터 요청/응답 암복호화 필터 기반 클래스.
|
||||
*
|
||||
* 서브클래스는 {@link #buildRuntimeContext}를 구현하여
|
||||
* DYNAMIC 키 도출에 필요한 컨텍스트 맵을 제공한다.
|
||||
* STATIC 키 모듈을 사용하는 경우 빈 Map을 반환하면 된다.
|
||||
* (CryptoModuleManager가 KEY_SOURCE_TYPE=STATIC이면 runtimeContext를 무시하고 고정 키를 사용한다.)
|
||||
*
|
||||
* 어댑터 프로퍼티 키:
|
||||
* crypto.module.name 필수. DB에 등록된 암호화 모듈명.
|
||||
* crypto.scope BODY | FIELD (기본 BODY)
|
||||
* BODY : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
|
||||
* FIELD : 특정 JSON 경로의 값만 암복호화
|
||||
* crypto.dec.enabled Y | N (기본 Y). doPreFilter(요청) 복호화 수행 여부.
|
||||
* crypto.enc.enabled Y | N (기본 N). doPostFilter(응답) 암호화 수행 여부.
|
||||
*
|
||||
* FIELD 범위 전용:
|
||||
* crypto.dec.from.path 복호화 대상 JSON 경로 (예: /encryptedData)
|
||||
* crypto.dec.to.path 복호화 결과 반영 경로. "/" = 전체 body 교체.
|
||||
* crypto.enc.from.path 암호화 대상 JSON 경로. "/" = 전체 body 암호화.
|
||||
* crypto.enc.to.path 암호화 결과(Base64)를 넣을 JSON 경로 (예: /encryptedData)
|
||||
*/
|
||||
public abstract class CryptoFilter implements HttpAdapterFilter {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String PROP_MODULE_NAME = "crypto.module.name";
|
||||
public static final String PROP_SCOPE = "crypto.scope";
|
||||
public static final String PROP_DEC_ENABLED = "crypto.dec.enabled";
|
||||
public static final String PROP_ENC_ENABLED = "crypto.enc.enabled";
|
||||
public static final String PROP_DEC_FROM_PATH = "crypto.dec.from.path";
|
||||
public static final String PROP_DEC_TO_PATH = "crypto.dec.to.path";
|
||||
public static final String PROP_ENC_FROM_PATH = "crypto.enc.from.path";
|
||||
public static final String PROP_ENC_TO_PATH = "crypto.enc.to.path";
|
||||
|
||||
protected static final String SCOPE_BODY = "BODY";
|
||||
protected static final String SCOPE_FIELD = "FIELD";
|
||||
protected static final String PATH_ROOT = "/";
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 추상 메서드
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* DYNAMIC 키 도출에 필요한 runtimeContext 맵을 구성한다.
|
||||
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
|
||||
*/
|
||||
protected abstract Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// doPreFilter: 요청 복호화
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
if (!isEnabled(prop, PROP_DEC_ENABLED, "Y")) {
|
||||
return message;
|
||||
}
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = prop.getProperty(PROP_SCOPE, SCOPE_BODY).toUpperCase();
|
||||
String body = toBodyString(message);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return decryptField(body, moduleName, runtimeCtx,
|
||||
required(prop, PROP_DEC_FROM_PATH),
|
||||
prop.getProperty(PROP_DEC_TO_PATH, PATH_ROOT));
|
||||
}
|
||||
return decryptBody(body, moduleName, runtimeCtx);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// doPostFilter: 응답 암호화
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
if (!isEnabled(prop, PROP_ENC_ENABLED, "N")) {
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = prop.getProperty(PROP_SCOPE, SCOPE_BODY).toUpperCase();
|
||||
String body = toBodyString(resultMessage);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return encryptField(body, moduleName, runtimeCtx,
|
||||
prop.getProperty(PROP_ENC_FROM_PATH, PATH_ROOT),
|
||||
required(prop, PROP_ENC_TO_PATH));
|
||||
}
|
||||
return encryptBody(body, moduleName, runtimeCtx);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 복호화 구현
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private String decryptBody(String body, String moduleName, Map<String, String> runtimeCtx) throws Exception {
|
||||
byte[] cipherBytes = Base64.getDecoder().decode(body.trim());
|
||||
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, cipherBytes);
|
||||
return new String(plainBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* fromPath의 Base64 값을 복호화하여 toPath에 반영.
|
||||
* toPath = "/" → 복호화된 텍스트로 body 전체 교체.
|
||||
*/
|
||||
private String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||
String fromPath, String toPath) throws Exception {
|
||||
|
||||
String encBase64 = JsonPathUtil.getValueAtPath(body, fromPath);
|
||||
if (StringUtils.isBlank(encBase64)) {
|
||||
logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
|
||||
return body;
|
||||
}
|
||||
|
||||
byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
|
||||
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, cipherBytes);
|
||||
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (PATH_ROOT.equals(toPath)) {
|
||||
return plainText;
|
||||
}
|
||||
return JsonPathUtil.setValueAtPath(body, toPath, plainText, false);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 암호화 구현
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx) throws Exception {
|
||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx,
|
||||
body.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(cipherBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* fromPath 값을 암호화하여 toPath(Base64)에 반영.
|
||||
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑.
|
||||
*/
|
||||
private String encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||
String fromPath, String toPath) throws Exception {
|
||||
|
||||
String plainText;
|
||||
if (PATH_ROOT.equals(fromPath)) {
|
||||
plainText = body;
|
||||
} else {
|
||||
plainText = JsonPathUtil.getValueAtPath(body, fromPath);
|
||||
if (StringUtils.isBlank(plainText)) {
|
||||
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx,
|
||||
plainText.getBytes(StandardCharsets.UTF_8));
|
||||
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
|
||||
|
||||
if (PATH_ROOT.equals(fromPath)) {
|
||||
String fieldName = toPath.replaceFirst("^/", "");
|
||||
return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
|
||||
}
|
||||
return JsonPathUtil.setValueAtPath(body, toPath, encBase64, false);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 유틸
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
|
||||
* <ul>
|
||||
* <li>{@code String} — 그대로 반환</li>
|
||||
* <li>{@code byte[]} — UTF-8 디코딩</li>
|
||||
* <li>{@code JSONObject} / 기타 — {@code toString()} 호출
|
||||
* (json-simple JSONObject는 toString()이 toJSONString()을 위임함)</li>
|
||||
* </ul>
|
||||
*/
|
||||
protected String toBodyString(Object message) {
|
||||
if (message instanceof String) {
|
||||
return (String) message;
|
||||
}
|
||||
if (message instanceof byte[]) {
|
||||
return new String((byte[]) message, StandardCharsets.UTF_8);
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
private boolean isEnabled(Properties prop, String key, String defaultVal) {
|
||||
return !"N".equalsIgnoreCase(prop.getProperty(key, defaultVal));
|
||||
}
|
||||
|
||||
protected String required(Properties prop, String key) {
|
||||
String v = prop.getProperty(key);
|
||||
if (StringUtils.isBlank(v)) {
|
||||
throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -28,7 +28,7 @@ import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter {
|
||||
|
||||
static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : ";
|
||||
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
|
||||
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
@@ -63,8 +63,8 @@ public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter {
|
||||
String chkBody = inboundBody;
|
||||
String clientSecret = assignClientSecret(prop, request);
|
||||
|
||||
if (chkBody.startsWith(KJB_ROOTLESS_ARRAY)) {
|
||||
chkBody = chkBody.substring(KJB_ROOTLESS_ARRAY.length());
|
||||
if (chkBody.startsWith(DJB_ROOTLESS_ARRAY)) {
|
||||
chkBody = chkBody.substring(DJB_ROOTLESS_ARRAY.length());
|
||||
|
||||
if (chkBody.endsWith("}")) {
|
||||
chkBody = chkBody.substring(0, chkBody.length() - 1);
|
||||
@@ -161,8 +161,8 @@ public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter {
|
||||
|
||||
Object root = JSONValue.parse(outboundBody);
|
||||
|
||||
if (root instanceof JSONObject && ((JSONObject) root).containsKey("KJB_ROOTLESS_ARRAY")) {
|
||||
JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("KJB_ROOTLESS_ARRAY");
|
||||
if (root instanceof JSONObject && ((JSONObject) root).containsKey("DJB_ROOTLESS_ARRAY")) {
|
||||
JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("DJB_ROOTLESS_ARRAY");
|
||||
outboundBody = jsonArray.toJSONString();
|
||||
}
|
||||
|
||||
|
||||
+24
-10
@@ -6,13 +6,10 @@ import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class HttpAdapterFilterFactory {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
static String basePackage = "com.eactive.eai.adapter.http.dynamic.filter";
|
||||
static String customBasePackage = "com.eactive.eai.custom.adapter.http.dynamic.filter";
|
||||
static private ConcurrentHashMap<String, HttpAdapterFilter> h = new ConcurrentHashMap<String, HttpAdapterFilter>();
|
||||
|
||||
public HttpAdapterFilterFactory() {
|
||||
super();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static HttpAdapterFilter createFilter(String type) {
|
||||
if (h.containsKey(type)) {
|
||||
return (HttpAdapterFilter) h.get(type);
|
||||
@@ -32,12 +29,19 @@ public class HttpAdapterFilterFactory {
|
||||
case ADAPTERALLOWIPLISTFILTER:
|
||||
filter = new AdapterAllowIPListFilter();
|
||||
break;
|
||||
case HMAC_SHA256:
|
||||
filter = new HmacSha256VerifyFilterKjb();
|
||||
break;
|
||||
case REFLECTALLHEADER:
|
||||
filter = new ReflectAllHeaderFilter();
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
Class cl = Class.forName(type);
|
||||
filter = (HttpAdapterFilter) cl.newInstance();
|
||||
} catch (Exception e) {
|
||||
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
|
||||
filter = classForName(type);
|
||||
if (filter == null) {
|
||||
filter = classForName(basePackage + "." + type);
|
||||
}
|
||||
if (filter == null) {
|
||||
filter = classForName(customBasePackage + "." + type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -49,4 +53,14 @@ public class HttpAdapterFilterFactory {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpAdapterFilter classForName(String type) {
|
||||
try {
|
||||
Class<?> cl = Class.forName(type);
|
||||
return (HttpAdapterFilter) cl.newInstance();
|
||||
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
|
||||
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class HttpAdapterFilterFactoryKjb extends HttpAdapterFilterFactory {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
static String basePackage = "com.eactive.eai.custom.adapter.http.dynamic.filter";
|
||||
static private ConcurrentHashMap<String, HttpAdapterFilter> h = new ConcurrentHashMap<String, HttpAdapterFilter>();
|
||||
|
||||
public HttpAdapterFilterFactoryKjb() {
|
||||
super();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static HttpAdapterFilter createFilter(String type) {
|
||||
if (h.containsKey(type)) {
|
||||
return (HttpAdapterFilter) h.get(type);
|
||||
}
|
||||
|
||||
HttpAdapterFilter filter = null;
|
||||
switch (HttpAdapterFilterType.getValue(type)) {
|
||||
case APIAUTHFILTER:
|
||||
filter = new ApiAuthFilter();
|
||||
break;
|
||||
case JWTAUTHFILTER:
|
||||
filter = new JwtAuthFilter();
|
||||
break;
|
||||
case IPWHITELISTFILTER:
|
||||
filter = new IpWhiteListFilter();
|
||||
break;
|
||||
case ADAPTERALLOWIPLISTFILTER:
|
||||
filter = new AdapterAllowIPListFilter();
|
||||
break;
|
||||
case HMAC_SHA256:
|
||||
filter = new HmacSha256VerifyFilterKjb();
|
||||
break;
|
||||
case REFLECTALLHEADER:
|
||||
filter = new ReflectAllHeaderFilter();
|
||||
break;
|
||||
default:
|
||||
filter = classForName(type);
|
||||
if (filter == null) {
|
||||
filter = classForName(basePackage + "." + type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (filter != null) {
|
||||
h.put(type, filter);
|
||||
return filter;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpAdapterFilter classForName(String type) {
|
||||
try {
|
||||
Class<?> cl = Class.forName(type);
|
||||
return (HttpAdapterFilter) cl.newInstance();
|
||||
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
|
||||
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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;
|
||||
|
||||
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
|
||||
|
||||
/**
|
||||
* HTTP 서버 어댑터용 암복호화 필터 (수신 방향).
|
||||
*
|
||||
* doPreFilter : 수신 요청 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
|
||||
* doPostFilter : 송신 응답 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
|
||||
*
|
||||
* DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
|
||||
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
|
||||
*
|
||||
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
|
||||
*/
|
||||
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {
|
||||
|
||||
/**
|
||||
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
|
||||
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
|
||||
* 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 수 있다.
|
||||
*/
|
||||
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* CRYPTO_AAD_HEADER 프로퍼티에 지정된 헤더값을 UTF-8 바이트로 반환한다.
|
||||
* 미설정 또는 헤더값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
|
||||
*/
|
||||
protected byte[] buildAad(Properties prop, HttpServletRequest request) {
|
||||
String headerName = prop.getProperty(PROP_AAD_HEADER);
|
||||
if (StringUtils.isBlank(headerName) || request == null) {
|
||||
return null;
|
||||
}
|
||||
String headerValue = request.getHeader(headerName);
|
||||
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String body = toBodyString(message);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
|
||||
byte[] aad = buildAad(prop, request);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return decryptField(body, moduleName, runtimeCtx, aad,
|
||||
getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
|
||||
getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
|
||||
}
|
||||
return decryptBody(body, moduleName, runtimeCtx, aad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
String moduleName = required(prop, PROP_MODULE_NAME);
|
||||
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
|
||||
String body = toBodyString(resultMessage);
|
||||
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
|
||||
byte[] aad = buildAad(prop, request);
|
||||
|
||||
if (SCOPE_FIELD.equals(scope)) {
|
||||
return encryptField(body, moduleName, runtimeCtx, aad,
|
||||
getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
|
||||
getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
|
||||
}
|
||||
return encryptBody(body, moduleName, runtimeCtx, aad);
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class JsonToSetStatusFilter implements HttpAdapterFilter {
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
JsonNode rootNode = parseJson(adptGrpName, message);
|
||||
|
||||
int httpStatus = 200;
|
||||
|
||||
if (rootNode.has("apiRsltCd") && !rootNode.get("apiRsltCd").asText().isEmpty()) {
|
||||
try {
|
||||
String statusParam = rootNode.get("apiRsltCd").asText();
|
||||
httpStatus = Integer.parseInt(statusParam);
|
||||
} catch (NumberFormatException e) {
|
||||
httpStatus = 400; // 잘못된 입력일 경우 400 Bad Request 반환
|
||||
}
|
||||
// HTTP 상태 코드 설정
|
||||
response.setStatus(httpStatus);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
|
||||
int httpStatus = 200;
|
||||
|
||||
if (rootNode.has("apiRsltCd") && !rootNode.get("apiRsltCd").asText().isEmpty()) {
|
||||
try {
|
||||
String statusParam = rootNode.get("apiRsltCd").asText();
|
||||
httpStatus = Integer.parseInt(statusParam);
|
||||
} catch (NumberFormatException e) {
|
||||
httpStatus = 400; // 잘못된 입력일 경우 400 Bad Request 반환
|
||||
}
|
||||
// HTTP 상태 코드 설정
|
||||
response.setStatus(httpStatus);
|
||||
}
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException, JsonProcessingException {
|
||||
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
|
||||
String orgMessageString;
|
||||
if(message instanceof byte[]) {
|
||||
orgMessageString = new String((byte[])message, charset);
|
||||
} else {
|
||||
orgMessageString = (String) message;
|
||||
}
|
||||
|
||||
return mapper.readTree(orgMessageString);
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class JsonToStdConverterFilter implements HttpAdapterFilter {
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
JsonNode rootNode = parseJson(adptGrpName, message);
|
||||
|
||||
if (rootNode.has("header_part")) {
|
||||
JsonNode headerPart = rootNode.path("header_part");
|
||||
if (!headerPart.has("eaiIntfId") || headerPart.get("eaiIntfId").asText().isEmpty()) {
|
||||
// /api/v1/public/getUserInfo.svc
|
||||
String inboundUri = prop.getProperty(HttpAdapterServiceKey.INBOUND_EXTURI);
|
||||
String apiPath = prop.getProperty(HttpAdapterServiceKey.API_PATH) + "/";
|
||||
|
||||
// getUserInfo.svc
|
||||
String eaiIntfId = org.apache.commons.lang.StringUtils.removeStart(inboundUri, apiPath);
|
||||
|
||||
// header_part 아래 eaiIntfId가 추가 또는 수정
|
||||
if (headerPart instanceof ObjectNode) {
|
||||
((ObjectNode) headerPart).put("eaiIntfId", eaiIntfId);
|
||||
// 요청 응답 구분코드 강제 설정 -> API명 찾아갈때 필요
|
||||
if (!headerPart.has("reqRspnsDscd")) {
|
||||
((ObjectNode) headerPart).put("reqRspnsDscd", "S");
|
||||
}
|
||||
|
||||
// GUID 강제 설정
|
||||
String orgnlTx = "";
|
||||
if (headerPart.has("orgnlTx") && !headerPart.get("orgnlTx").asText().isEmpty()) {
|
||||
orgnlTx = headerPart.get("orgnlTx").asText();
|
||||
}
|
||||
if (!headerPart.has("tlgrWrtnDt")) {
|
||||
((ObjectNode) headerPart).put("tlgrWrtnDt", orgnlTx);
|
||||
}
|
||||
if (!headerPart.has("tlgrCrtnSysNm")) {
|
||||
((ObjectNode) headerPart).put("tlgrCrtnSysNm", "");
|
||||
}
|
||||
if (!headerPart.has("tlgrSrlNo")) {
|
||||
((ObjectNode) headerPart).put("tlgrSrlNo", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return message;
|
||||
}
|
||||
return rootNode.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
|
||||
|
||||
if (rootNode.has("header_part")) {
|
||||
JsonNode headerPart = rootNode.path("header_part");
|
||||
if (!headerPart.has("eaiIntfId") || headerPart.get("eaiIntfId").asText().isEmpty()) {
|
||||
// /api/v1/public/getUserInfo.svc
|
||||
String inboundUri = prop.getProperty(HttpAdapterServiceKey.INBOUND_EXTURI);
|
||||
String apiPath = prop.getProperty(HttpAdapterServiceKey.API_PATH) + "/";
|
||||
|
||||
// getUserInfo.svc
|
||||
String eaiIntfId = org.apache.commons.lang.StringUtils.removeStart(inboundUri, apiPath);
|
||||
|
||||
// header_part 아래 eaiIntfId가 추가 또는 수정
|
||||
if (headerPart instanceof ObjectNode) {
|
||||
((ObjectNode) headerPart).put("eaiIntfId", eaiIntfId);
|
||||
// 요청 응답 구분코드 강제 설정 -> API명 찾아갈때 필요
|
||||
((ObjectNode) headerPart).put("reqRspnsDscd", "S");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return resultMessage;
|
||||
}
|
||||
return rootNode.toString();
|
||||
}
|
||||
|
||||
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException, JsonProcessingException {
|
||||
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
|
||||
String orgMessageString;
|
||||
if(message instanceof byte[]) {
|
||||
orgMessageString = new String((byte[])message, charset);
|
||||
} else {
|
||||
orgMessageString = (String) message;
|
||||
}
|
||||
|
||||
return mapper.readTree(orgMessageString);
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
public class KbankEaiJsonParseFilter implements HttpAdapterFilter {
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
JsonNode rootNode = parseJson(adptGrpName, message);
|
||||
|
||||
ObjectNode replacedJson = mapper.createObjectNode();
|
||||
for(Iterator<String> it = rootNode.fieldNames(); it.hasNext();) {
|
||||
String fieldName = it.next();
|
||||
String value = rootNode.get(fieldName).asText();
|
||||
JsonNode jsonNode = mapper.readTree(value);
|
||||
replacedJson.set(fieldName, jsonNode);
|
||||
}
|
||||
|
||||
return replacedJson.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
|
||||
|
||||
ObjectNode replacedJson = mapper.createObjectNode();
|
||||
for(Iterator<String> it = rootNode.fieldNames(); it.hasNext();) {
|
||||
String fieldName = it.next();
|
||||
JsonNode childNode = rootNode.get(fieldName);
|
||||
String childString = mapper.writeValueAsString(childNode);
|
||||
replacedJson.put(fieldName, childString);
|
||||
}
|
||||
|
||||
return replacedJson.toString();
|
||||
}
|
||||
|
||||
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException, JsonProcessingException {
|
||||
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
|
||||
String orgMessageString;
|
||||
if(message instanceof byte[]) {
|
||||
orgMessageString = new String((byte[])message, charset);
|
||||
} else {
|
||||
orgMessageString = (String) message;
|
||||
}
|
||||
|
||||
return mapper.readTree(orgMessageString);
|
||||
}
|
||||
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Properties;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.Mac;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.commons.net.util.Base64;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.common.server.Keys;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class KbankHmacSha256VerifyFilter implements HttpAdapterFilter {
|
||||
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
private static final String SERVICE_CODE_UNKNOWN = "00";
|
||||
private static final int TIMESTAMP_EXPIRATION_SECONDS = 60*10*1000;
|
||||
private static final String GROUP_NAME = "HMAC_INFO";
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
try {
|
||||
|
||||
String hmacSignature = getHeaderCaseInsensitive(request, "hmac_signature");
|
||||
String hmacTimestamp = getHeaderCaseInsensitive(request, "hmac_timestamp");
|
||||
|
||||
if(StringUtils.isBlank(hmacSignature) || StringUtils.isBlank(hmacTimestamp)) {
|
||||
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Can not find hmac info");
|
||||
}
|
||||
|
||||
long inputTime = new SimpleDateFormat("yyyyMMddHHmmss").parse(hmacTimestamp).getTime();
|
||||
long currentTime = new Date().getTime();
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
||||
|
||||
logger.debug("### inputTime :"+inputTime);
|
||||
logger.debug("### currentTime :"+currentTime);
|
||||
logger.debug("### timeStamp :"+timeStamp);
|
||||
|
||||
String systemMode = System.getProperty(Keys.EAI_SYSTEMMODE);
|
||||
|
||||
if(currentTime - inputTime > TIMESTAMP_EXPIRATION_SECONDS) {
|
||||
|
||||
logger.debug("### systemMode:" + systemMode);
|
||||
logger.debug("### currentTime - inputTime:" + (currentTime - inputTime));
|
||||
|
||||
if("P".equals(systemMode)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN,"00"), "Signature verifying failed");
|
||||
}
|
||||
}
|
||||
|
||||
String sMessage = (String)message;
|
||||
|
||||
sMessage = sMessage.replace(" ","");
|
||||
sMessage = sMessage.replace("\n","");
|
||||
sMessage = sMessage.replace("\r","");
|
||||
|
||||
sMessage = sMessage + hmacTimestamp;
|
||||
|
||||
logger.info("### sMessage:"+sMessage);
|
||||
|
||||
String alCoId = "AL2021012901001";
|
||||
|
||||
JsonNode rootNode = parseJson(adptGrpName, message);
|
||||
|
||||
for(Iterator<String> it = rootNode.fieldNames(); it.hasNext();){
|
||||
String fieldName = it.next();
|
||||
if(fieldName.equals("ALCO_ID")) alCoId = rootNode.get(fieldName).asText();
|
||||
}
|
||||
|
||||
logger.info("### alCoId:"+alCoId);
|
||||
|
||||
Properties hmacProp = PropManager.getInstance().getProperties(GROUP_NAME);
|
||||
|
||||
String hmacKey = hmacProp.getProperty(alCoId+"-hmack-key","");
|
||||
String hmacKek = hmacProp.getProperty(alCoId+"-hmack-kek","");
|
||||
String hmacIvForKey = hmacProp.getProperty(alCoId+"-hmack-iv-for-key","");
|
||||
|
||||
logger.debug("### hmacKey:"+hmacKey);
|
||||
logger.debug("### hmacIvForKey:"+hmacIvForKey);
|
||||
logger.debug("### hmacKek:"+hmacKek);
|
||||
|
||||
//kbank.payment.hmac-key
|
||||
int hmacKeyLen = hmacKey.length();
|
||||
byte[] hmacKeyData = new byte[hmacKeyLen/2];
|
||||
for(int i=0;i<hmacKeyLen;i+=2){
|
||||
hmacKeyData[i/2] = (byte)((Character.digit(hmacKey.charAt(i), 16) << 4) + Character.digit(hmacKey.charAt(i+1), 16));
|
||||
}
|
||||
|
||||
//kbank.payment.hmac-kek
|
||||
int hmacKekLen = hmacKek.length();
|
||||
byte[] hmacKekData = new byte[hmacKekLen/2];
|
||||
for(int i=0;i<hmacKekLen;i+=2){
|
||||
hmacKekData[i/2] = (byte)((Character.digit(hmacKek.charAt(i),16) << 4) + Character.digit(hmacKek.charAt(i+1), 16));
|
||||
}
|
||||
|
||||
//kbank.payment.hmac-iv-for-key
|
||||
int hmacIvForKeyLen = hmacIvForKey.length();
|
||||
byte[] hmacIvForKeyData = new byte[hmacIvForKeyLen/2];
|
||||
for(int i=0;i<hmacIvForKeyLen;i+=2){
|
||||
hmacIvForKeyData[i/2] = (byte)((Character.digit(hmacIvForKey.charAt(i),16) << 4) + Character.digit(hmacIvForKey.charAt(i+1), 16));
|
||||
}
|
||||
|
||||
byte[] decrypedHmacKey = null;
|
||||
|
||||
try{
|
||||
Cipher cp = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cp.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hmacIvForKeyData,"AES"), new IvParameterSpec(hmacIvForKeyData));
|
||||
decrypedHmacKey = cp.doFinal(hmacKeyData);
|
||||
} catch (Exception e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed");
|
||||
}
|
||||
|
||||
SecretKeySpec secretKey = new SecretKeySpec(decrypedHmacKey, "HmacSHA256");
|
||||
String madeSignature = "";
|
||||
|
||||
logger.info("### secretKey:"+secretKey);
|
||||
|
||||
try{
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(secretKey);
|
||||
madeSignature= Base64.encodeBase64String(mac.doFinal(sMessage.getBytes()));
|
||||
} catch (Exception e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed");
|
||||
}
|
||||
|
||||
logger.info("### madeSignature:"+madeSignature);
|
||||
|
||||
if(!StringUtils.equals(hmacSignature, madeSignature)){
|
||||
logger.info("### hmacSignature:"+hmacSignature);
|
||||
logger.info("### madeSignature:"+madeSignature);
|
||||
|
||||
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed" );
|
||||
}
|
||||
|
||||
} catch (JwtAuthException e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed(Unknown)" );
|
||||
}
|
||||
|
||||
logger.info("### pass: "+"success");
|
||||
|
||||
return message;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 대소문자 구분 없이 HTTP 헤더 값을 가져오는 메소드
|
||||
* @param request HTTP 요청 객체
|
||||
* @param headerName 찾고자 하는 헤더 이름
|
||||
* @return 헤더 값 또는 null
|
||||
*/
|
||||
public static String getHeaderCaseInsensitive(HttpServletRequest request, String headerName) {
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String header = headerNames.nextElement();
|
||||
if (header != null && header.equalsIgnoreCase(headerName)) {
|
||||
return request.getHeader(header);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException,JsonProcessingException {
|
||||
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
|
||||
String orgMessageString;
|
||||
|
||||
if(message instanceof byte[]) {
|
||||
orgMessageString = new String((byte[])message, charset);
|
||||
} else {
|
||||
orgMessageString = (String) message;
|
||||
}
|
||||
|
||||
return mapper.readTree(orgMessageString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.codec.digest.HmacAlgorithms;
|
||||
import org.apache.commons.codec.digest.HmacUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.authserver.service.OAuth2Manager;
|
||||
import com.eactive.eai.authserver.vo.ClientVO;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class SnapHmacSha512VerifyFilter implements HttpAdapterFilter {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
private static final String SERVICE_CODE_UNKNOWN = "00";
|
||||
private static final int X_TIMESTAMP_EXPIRATION_SECONDS = 60 * 5;
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
// @formatter:off
|
||||
/*
|
||||
* HMAC_SHA512 (clientSecret, stringToSign)
|
||||
*
|
||||
* stringToSign = HTTPMethod:EndpointUrl:AccessToken
|
||||
* :Lowercase(HexEncode(SHA-256(minify(RequestBody)))):TimeStamp
|
||||
*/
|
||||
try {
|
||||
String xSignature = request.getHeader("X-SIGNATURE");
|
||||
if (StringUtils.isBlank(xSignature)) {
|
||||
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Can not find X-SIGNATURE");
|
||||
}
|
||||
|
||||
String clientSecret = assignClientSecret(prop, request);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(request.getMethod())
|
||||
.append(":")
|
||||
.append(getEndpointUrl(request))
|
||||
.append(":")
|
||||
.append(SnapSimpleOauth2Filter.extractToken(request))
|
||||
.append(":")
|
||||
.append(getRequestBody((String)message))
|
||||
.append(":")
|
||||
.append(getTimeStamp(request));
|
||||
// @formatter:on
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug(sb.toString());
|
||||
}
|
||||
|
||||
String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, clientSecret).hmacHex(sb.toString());
|
||||
|
||||
if (!StringUtils.equals(xSignature, hmac)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Signature verifying failed");
|
||||
}
|
||||
} catch (JwtAuthException e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Signature verifying failed(unkown)");
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private String assignClientSecret(Properties prop, HttpServletRequest request) throws Exception {
|
||||
String clientId = assignClientId(prop, request);
|
||||
if (StringUtils.isBlank(clientId)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Can not find clientId info");
|
||||
}
|
||||
|
||||
ClientVO client = (ClientVO) OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId);
|
||||
if (client == null) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Can not find client info");
|
||||
}
|
||||
|
||||
return client.getClientSecret();
|
||||
}
|
||||
|
||||
private String assignClientId(Properties prop, HttpServletRequest request) {
|
||||
String clientId = null;
|
||||
try {
|
||||
// 이전 filter에서 셋팅한 clientId 확보
|
||||
clientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
|
||||
|
||||
// 이전 filter에서 셋팅한 값이 없다면 header 정보에서 확보
|
||||
if (StringUtils.isBlank(clientId)) {
|
||||
clientId = request.getHeader(HttpAdapterServiceSupport.HEADER_NAME_CLIENT_ID);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
private String getEndpointUrl(HttpServletRequest request) {
|
||||
String servletPath = request.getServletPath();
|
||||
String pathInfo = request.getPathInfo();
|
||||
String queryString = request.getQueryString();
|
||||
|
||||
StringBuilder url = new StringBuilder();
|
||||
url.append(servletPath);
|
||||
|
||||
if (pathInfo != null) {
|
||||
url.append(pathInfo);
|
||||
}
|
||||
|
||||
if (queryString != null) {
|
||||
url.append("?").append(queryString);
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
private String getRequestBody(String message) throws Exception {
|
||||
if (StringUtils.isEmpty(message)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Lowercase(HexEncode(SHA-256(minify(RequestBody))))
|
||||
String body = minifyJson(message);
|
||||
try {
|
||||
body = DigestUtils.sha256Hex(body);
|
||||
} catch (Exception e) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"SHA-256 digest error");
|
||||
}
|
||||
return body.toLowerCase();
|
||||
}
|
||||
|
||||
private String minifyJson(String json) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = objectMapper.readValue(json, JsonNode.class);
|
||||
return jsonNode.toString();
|
||||
} catch (Exception e) {
|
||||
// json이 아닐경우
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private String getTimeStamp(HttpServletRequest request) throws Exception {
|
||||
String timeStamp = request.getHeader("X-TIMESTAMP");
|
||||
if (StringUtils.isBlank(timeStamp)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Can not find X-TIMESTAMP");
|
||||
}
|
||||
|
||||
// 2020-12-23T09:10:11+07:00(현재시간이랑 비교로직 추가-보안)
|
||||
ZonedDateTime xTimeStamp = ZonedDateTime.parse(timeStamp, DateTimeFormatter.ISO_DATE_TIME);
|
||||
xTimeStamp = xTimeStamp.plusSeconds(X_TIMESTAMP_EXPIRATION_SECONDS);
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
if (xTimeStamp.isBefore(now.withZoneSameInstant(xTimeStamp.getZone()))) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Too old X-TIMESTAMP");
|
||||
}
|
||||
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String str = "POST:/api/test/aaa/type02/555:"
|
||||
+ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJzbmFwIl0sImV4cCI6MTY3NjUyNDI1MSwianRpIjoiNzI0MWMyZGUtZjJhNi00NTFmLWExMjQtZGM3NmI0ZGY1OThjIiwiY2xpZW50X2lkIjoic1NHSkR3bEpzZWw1V2VYb2R6ZDNKM01RMjBLWUNacEwifQ.Obe8t8IwuQ3P7i4yCSASYze-9DLmVwXe0g_gBYlwv_Jzc7V8-ooTwp6SVnaNsM6pp1GV-9lxMpAzQO_CRHQq_hdMeiPI_CXHh3gETvjQThn57QGEGdCNp_lUEVYtMPUxi5u3X6Of07o0_OB83WI7rA1zD0DaP8V67pgfq-jMRe_3IXztOYu_nwMgREbGvs9tqcsjxppRKkEHcK1S8Eq4HzaLwjwyHJAhIlTba27yd4T8ELC7IpFphJBfEPF_t0ZpYW15lOrg5pHPjy1xpTV0Kgipog5wycTZlFUeCWWjfxTrpVmt_1XlEOlZH9X6xAefWrH93_fpbt3OcxwP4u9KhA"
|
||||
+ ":424058b889b9d860d13b79d90df52ddcbefba2fc9d27607e999c7c3c4d61d9c8:" + "2023-02-16T09:47:07+07:00";
|
||||
|
||||
String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_512,
|
||||
"o3Hre3voTrHbLueDrHC0LYM5effHQZILI8t23htbD12TKD5QJUMONqFqv4494iQS46bzHS0xW1fBC5LHo3D0SzhZvQcPUaJZw0iQ79NnzYFUCTrEsoFJRL300dp3Ql4y")
|
||||
.hmacHex(str);
|
||||
|
||||
// System.out.println(hmac);
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.text.ParseException;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthFilter;
|
||||
import com.eactive.eai.authserver.service.OAuth2Manager;
|
||||
import com.eactive.eai.common.property.PropGroupVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
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.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||
import com.nimbusds.jose.util.IOUtils;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
public class SnapJwtAuthFilter implements HttpAdapterFilter {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String PROP_GROUP_AUTH_SERVER = "OAuthServer";
|
||||
public static final String PROP_KEYSTORE_PATH = "certification.publicKeyPath";
|
||||
|
||||
private static final String SERVICE_CODE_UNKNOWN = "00";
|
||||
|
||||
public static final String PAYLOAD_PARAM_NAME_SCOPE = "scope";
|
||||
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id";
|
||||
|
||||
private JWSVerifier jwsVerifier;
|
||||
|
||||
public SnapJwtAuthFilter() {
|
||||
super();
|
||||
|
||||
PropGroupVO vo = PropManager.getInstance().getPropGroupVO(PROP_GROUP_AUTH_SERVER);
|
||||
String publicKeyPath = "/certificate/elink-oauth-dev.pub";
|
||||
if (vo != null) {
|
||||
publicKeyPath = vo.getProperty(PROP_KEYSTORE_PATH);
|
||||
} else {
|
||||
logger.warn("The properties has not been set.[" + PROP_GROUP_AUTH_SERVER + "]");
|
||||
}
|
||||
|
||||
Resource resource = new ClassPathResource(publicKeyPath);
|
||||
String publicKey = null;
|
||||
try {
|
||||
publicKey = IOUtils.readInputStreamToString(resource.getInputStream());
|
||||
publicKey = StringUtils.replace(publicKey, "-----BEGIN PUBLIC KEY-----", "");
|
||||
publicKey = StringUtils.replace(publicKey, "-----END PUBLIC KEY-----", "");
|
||||
publicKey = StringUtils.remove(publicKey, "\r");
|
||||
publicKey = StringUtils.remove(publicKey, "\n");
|
||||
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
jwsVerifier = new RSASSAVerifier((RSAPublicKey) keyFactory.generatePublic(keySpec));
|
||||
} catch (final IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
|
||||
if (StringUtils.isBlank(apiId)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Can not find apiId(apiCode) info");
|
||||
}
|
||||
|
||||
try {
|
||||
String token = JwtAuthFilter.extractJWTToken(request);
|
||||
SignedJWT signedJWT = SignedJWT.parse(token);
|
||||
|
||||
if (new Date().after(signedJWT.getJWTClaimsSet().getExpirationTime())) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
"Invalid Token - expired (B2B)");
|
||||
}
|
||||
|
||||
if (!signedJWT.verify(jwsVerifier)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
"Invalid Token (B2B)");
|
||||
}
|
||||
|
||||
OAuth2Manager manager = OAuth2Manager.getInstance();
|
||||
HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
|
||||
if (!verifyScope(scopeSet, signedJWT)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
|
||||
}
|
||||
|
||||
// header 값으로 전송된 clientId와 토큰 소유 clientId check
|
||||
if (!verifyClientId(prop, signedJWT)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"client_id not matched(header/token)");
|
||||
}
|
||||
} catch (JwtAuthException e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
"Invalid Token (B2B)");
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private boolean verifyClientId(Properties prop, SignedJWT signedJWT) {
|
||||
try {
|
||||
// 이전 filter에서 셋팅한 clientId 확보
|
||||
String headerClientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
|
||||
String tokenClientId = (String) signedJWT.getJWTClaimsSet().getClaim(PAYLOAD_PARAM_NAME_CLIENT_ID);
|
||||
|
||||
// 이전 filter에서 셋팅한 값이 없다면 token 정보에서 확보
|
||||
if (StringUtils.isBlank(headerClientId)) {
|
||||
if (StringUtils.isNotBlank(tokenClientId)) {
|
||||
prop.put(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, tokenClientId);
|
||||
}
|
||||
} else { // header로 전달된 clientId가 있을때만 비교
|
||||
if (!headerClientId.equals(tokenClientId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
private boolean verifyScope(HashSet<String> scopeSet, SignedJWT signedJWT) throws ParseException {
|
||||
if (scopeSet == null || scopeSet.isEmpty()) {
|
||||
return true; // If no scope is specified in the api, all are allowed
|
||||
}
|
||||
|
||||
String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE);
|
||||
if (scopeArr == null || scopeArr.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String scope : scopeArr) {
|
||||
if (scopeSet.contains(scope)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String extractJWTToken(HttpServletRequest request) throws JwtAuthException {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (StringUtils.isBlank(authorization)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"No have header info: Authorization");
|
||||
}
|
||||
|
||||
String[] components = authorization.split("\\s");
|
||||
|
||||
if (components.length != 2) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Malformat [Authorization] content");
|
||||
}
|
||||
|
||||
if (!StringUtils.equalsIgnoreCase(components[0], "Bearer")) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"[Bearer] is needed");
|
||||
}
|
||||
|
||||
return components[1].trim();
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter.custom;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
|
||||
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
|
||||
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||
import com.eactive.eai.authserver.service.OAuth2Manager;
|
||||
import com.eactive.eai.authserver.util.BeanUtils;
|
||||
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;
|
||||
|
||||
public class SnapSimpleOauth2Filter implements HttpAdapterFilter {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
private static final String SERVICE_CODE_UNKNOWN = "00";
|
||||
|
||||
private TokenStore tokenStore;
|
||||
|
||||
public SnapSimpleOauth2Filter() {
|
||||
super();
|
||||
tokenStore = BeanUtils.getBean("tokenStore", TokenStore.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
try {
|
||||
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
|
||||
if (StringUtils.isBlank(apiId)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Can not find apiId(apiCode) info");
|
||||
}
|
||||
|
||||
String tokenStr = extractToken(request);
|
||||
|
||||
OAuth2AccessToken token = tokenStore.readAccessToken(tokenStr);
|
||||
if (token == null) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "03"),
|
||||
"Token Not Found (B2B)");
|
||||
}
|
||||
|
||||
if (token.isExpired()) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
"Invalid Token - expired (B2B)");
|
||||
}
|
||||
|
||||
OAuth2Authentication authentication = tokenStore.readAuthentication(token);
|
||||
if (!authentication.isAuthenticated()) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
"Invalid Token (B2B)");
|
||||
}
|
||||
|
||||
// check scope
|
||||
OAuth2Manager manager = OAuth2Manager.getInstance();
|
||||
HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
|
||||
if (!verifyScope(scopeSet, authentication)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
|
||||
}
|
||||
|
||||
// header 값으로 전송된 clientId와 토큰 소유 clientId check
|
||||
if (!verifyClientId(prop, authentication)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"client_id not matched(header/token)");
|
||||
}
|
||||
} catch (JwtAuthException e) {
|
||||
logger.debug(e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
|
||||
"Invalid Token (B2B)");
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private boolean verifyScope(HashSet<String> scopeSet, OAuth2Authentication auth) {
|
||||
if (scopeSet == null || scopeSet.isEmpty()) {
|
||||
return true; // If no scope is specified in the api, all are allowed
|
||||
}
|
||||
|
||||
Set<String> scopesInToken = auth.getOAuth2Request().getScope();
|
||||
|
||||
if (scopesInToken == null || scopesInToken.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String scope : scopesInToken) {
|
||||
if (scopeSet.contains(scope)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean verifyClientId(Properties prop, Authentication authentication) {
|
||||
try {
|
||||
// 이전 filter에서 셋팅한 clientId 확보
|
||||
String headerClientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
|
||||
String tokenClientId = ((OAuth2Authentication) authentication).getOAuth2Request().getClientId();
|
||||
|
||||
// 이전 filter에서 셋팅한 값이 없다면 token 정보에서 확보
|
||||
if (StringUtils.isBlank(headerClientId)) {
|
||||
if (StringUtils.isNotBlank(tokenClientId)) {
|
||||
prop.put(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, tokenClientId);
|
||||
}
|
||||
} else { // header로 전달된 clientId가 있을때만 비교
|
||||
if (!headerClientId.equals(tokenClientId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static String extractToken(HttpServletRequest request) throws JwtAuthException {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (StringUtils.isBlank(authorization)) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"No have header info: Authorization");
|
||||
}
|
||||
|
||||
String[] components = authorization.split("\\s");
|
||||
|
||||
if (components.length != 2) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"Malformat [Authorization] content");
|
||||
}
|
||||
|
||||
if (!StringUtils.equalsIgnoreCase(components[0], "Bearer")) {
|
||||
throw new JwtAuthException(
|
||||
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
|
||||
"[Bearer] is needed");
|
||||
}
|
||||
|
||||
return components[1].trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return resultMessage;
|
||||
}
|
||||
}
|
||||
+51
-29
@@ -21,16 +21,19 @@ import com.eactive.eai.inbound.processor.Processor;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.StopWatch;
|
||||
import org.apache.hc.core5.http.ContentType;
|
||||
import org.apache.mina.common.ByteBuffer;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
import org.springframework.jms.support.converter.MessageType;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
@@ -194,7 +197,7 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
|
||||
Object root = JSONValue.parse(body);
|
||||
if( root instanceof JSONArray ) {
|
||||
JSONObject wrappedObject = new JSONObject();
|
||||
wrappedObject.put("KJB_ROOTLESS_ARRAY", root);
|
||||
wrappedObject.put("DJB_ROOTLESS_ARRAY", root);
|
||||
requestBytes = wrappedObject.toJSONString().getBytes(encode);
|
||||
}
|
||||
|
||||
@@ -212,6 +215,7 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
|
||||
|
||||
stopWatch.stop();
|
||||
|
||||
byte[] responseBytes = null;
|
||||
String responseData = "";
|
||||
if (RESPONSE_TYPE_ASYNC.equals(responseType)){
|
||||
if (stopWatch.getTime() > slowTranTime){
|
||||
@@ -222,17 +226,22 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
|
||||
|
||||
if ( result == null) {
|
||||
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
|
||||
responseBytes = responseData.getBytes();
|
||||
} else if (result instanceof byte[]) {
|
||||
responseData = new String((byte[]) result, encode);
|
||||
responseBytes = (byte[]) result;
|
||||
} else if (result instanceof String) {
|
||||
responseData = (String) result;
|
||||
if ( StringUtils.isBlank( responseData ) ) {
|
||||
responseBytes = ((String)result).getBytes();
|
||||
if ( StringUtils.isBlank((String)result ) ) {
|
||||
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
|
||||
responseBytes = responseData.getBytes();
|
||||
}
|
||||
}
|
||||
|
||||
response.setCharacterEncoding(encode);
|
||||
response.getWriter().print(responseData);
|
||||
// response.getWriter().print(responseData);
|
||||
response.getOutputStream().write(responseBytes);
|
||||
|
||||
if (traceLevel >= 3){
|
||||
HttpMemoryLogger.txlog(adptGrpName+adptName, "SEND "+"["+responseData+"]"+
|
||||
((responseData == null)?"":CommonLib.getDumpMessage(responseData))
|
||||
@@ -240,45 +249,58 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
|
||||
}
|
||||
} else {
|
||||
if(requestBytes != null) {
|
||||
if (result instanceof byte[]) {
|
||||
if ( result == null) {
|
||||
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
|
||||
responseBytes = responseData.getBytes();
|
||||
} else if (result instanceof byte[]) {
|
||||
responseData = new String((byte[]) result, encode);
|
||||
responseBytes = (byte[]) result;
|
||||
} else if (result instanceof String) {
|
||||
responseData = (String) result;
|
||||
responseBytes = ((String)result).getBytes();
|
||||
if ( StringUtils.isBlank((String)result ) ) {
|
||||
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
|
||||
responseBytes = responseData.getBytes();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
logger.info("종료 >> encode = [" + encode + "]");
|
||||
|
||||
if (addData.length() > 0){
|
||||
responseData = addData + responseData;
|
||||
responseBytes = responseData.getBytes();
|
||||
}
|
||||
|
||||
try {
|
||||
Object root = JSONValue.parse(responseData);
|
||||
if( root instanceof JSONObject ) {
|
||||
JSONObject wrappedObject = (JSONObject) root;
|
||||
responseData = wrappedObject.get("KJB_ROOTLESS_ARRAY").toString();
|
||||
}
|
||||
|
||||
}catch (Exception e) {
|
||||
// ignore json이 아니기에 해줄것이 없음.
|
||||
}
|
||||
// try {
|
||||
// Object root = JSONValue.parse(responseData);
|
||||
// if( root instanceof JSONObject ) {
|
||||
// JSONObject wrappedObject = (JSONObject) root;
|
||||
// responseData = wrappedObject.get("DJB_ROOTLESS_ARRAY").toString();
|
||||
// }
|
||||
//
|
||||
// }catch (Exception e) {
|
||||
// // ignore json이 아니기에 해줄것이 없음.
|
||||
// }
|
||||
|
||||
// 300응답에서 받은 Header정보 사용하기 위해 읽어옴.
|
||||
Map<String, Object> responsePropertyMap = (Map<String, Object>) prop.get(OUTBOUND_PROPERTY_MAP);
|
||||
if(responsePropertyMap != null){
|
||||
Properties responseHeaders = (Properties)responsePropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
|
||||
Map<String, String> responseHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
// Properties 내용을 모두 복사
|
||||
for (String name : responseHeaders.stringPropertyNames()) {
|
||||
responseHeaderMap.put(name, responseHeaders.getProperty(name));
|
||||
}
|
||||
String contentTytpe = responseHeaderMap.get("Content-Type");
|
||||
response.setContentType(contentTytpe); // 300응답에서 받은 Content-Type을 그대로 적용.
|
||||
}
|
||||
// Map<String, Object> responsePropertyMap = (Map<String, Object>) prop.get(OUTBOUND_PROPERTY_MAP);
|
||||
// if(responsePropertyMap != null){
|
||||
// Properties responseHeaders = (Properties)responsePropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
|
||||
// Map<String, String> responseHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
// // Properties 내용을 모두 복사
|
||||
// for (String name : responseHeaders.stringPropertyNames()) {
|
||||
// responseHeaderMap.put(name, responseHeaders.getProperty(name));
|
||||
// }
|
||||
// String contentTytpe = responseHeaderMap.get("Content-Type");
|
||||
// response.setContentType(contentTytpe); // 300응답에서 받은 Content-Type을 그대로 적용.
|
||||
// }
|
||||
|
||||
// UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
|
||||
response.setCharacterEncoding(encode);
|
||||
response.getWriter().print(responseData);
|
||||
// response.getWriter().print(responseData);
|
||||
String resContentType = request.getContentType();
|
||||
response.setContentType(resContentType);
|
||||
response.getOutputStream().write(responseBytes);
|
||||
|
||||
if (logger.isDebug()){
|
||||
logger.debug("HttpAdapter] SEND ("+adptGrpName+") = [" + responseData + "]");
|
||||
logger.debug("HttpAdapter] SEND ("+adptGrpName+") = " +
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.eactive.eai.adapter.http.filter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.security.CryptoModuleService;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.util.JsonPathUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* InCryptoFilter / OutCryptoFilter 공통 기반 클래스.
|
||||
*
|
||||
* BODY/FIELD 범위 암복호화 로직, 프로퍼티 상수, 유틸리티 메서드를 제공한다.
|
||||
* HTTP 서버 어댑터용은 {@link InCryptoFilter}, 클라이언트 어댑터용은 {@link OutCryptoFilter} 참조.
|
||||
*
|
||||
* 프로퍼티 키:
|
||||
* CRYPTO_MODULE_NAME 필수. DB에 등록된 암호화 모듈명.
|
||||
* CRYPTO_SCOPE BODY | FIELD (기본값: FIELD)
|
||||
* BODY : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
|
||||
* FIELD : 특정 JSON 경로의 값만 암복호화
|
||||
*
|
||||
* FIELD 범위 전용:
|
||||
* CRYPTO_DEC_FROM_PATH 복호화 대상 JSON 경로 (기본값: /encrypted_data)
|
||||
* CRYPTO_DEC_TO_PATH 복호화 결과 반영 경로. "/" = 전체 body 교체.
|
||||
* CRYPTO_ENC_FROM_PATH 암호화 대상 JSON 경로. "/" = 전체 body 암호화.
|
||||
* CRYPTO_ENC_TO_PATH 암호화 결과(Base64)를 넣을 JSON 경로 (기본값: /encrypted_data)
|
||||
*
|
||||
* GCM AAD 전용 (선택):
|
||||
* CRYPTO_AAD_HEADER AAD로 사용할 HTTP 요청 헤더명 (InCryptoFilter 전용).
|
||||
* 미설정 또는 헤더값 없으면 aad=null → IV를 AAD 대체값으로 사용.
|
||||
*/
|
||||
public abstract class AbstractCryptoFilter {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String PROP_MODULE_NAME = "CRYPTO_MODULE_NAME";
|
||||
public static final String PROP_SCOPE = "CRYPTO_SCOPE";
|
||||
public static final String PROP_DEC_FROM_PATH = "CRYPTO_DEC_FROM_PATH";
|
||||
public static final String PROP_DEC_TO_PATH = "CRYPTO_DEC_TO_PATH";
|
||||
public static final String PROP_ENC_FROM_PATH = "CRYPTO_ENC_FROM_PATH";
|
||||
public static final String PROP_ENC_TO_PATH = "CRYPTO_ENC_TO_PATH";
|
||||
public static final String PROP_AAD_HEADER = "CRYPTO_AAD_HEADER";
|
||||
|
||||
public static final String SCOPE_BODY = "BODY";
|
||||
public static final String SCOPE_FIELD = "FIELD";
|
||||
public static final String PATH_ROOT = "/";
|
||||
public static final String PATH_ENCDATA = "/encrypted_data";
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 복호화 구현
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
protected String decryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||
byte[] aad) throws Exception {
|
||||
byte[] cipherBytes = Base64.getDecoder().decode(body.trim());
|
||||
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
|
||||
return new String(plainBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
|
||||
* toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
|
||||
*/
|
||||
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||
byte[] aad, String fromPath, String toPath) throws Exception {
|
||||
JsonNode root = JsonPathUtil.toTree(body);
|
||||
String encBase64 = JsonPathUtil.getAt(root, fromPath);
|
||||
if (StringUtils.isBlank(encBase64)) {
|
||||
logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
|
||||
return body;
|
||||
}
|
||||
byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
|
||||
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
|
||||
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
|
||||
if (PATH_ROOT.equals(toPath)) {
|
||||
return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
|
||||
}
|
||||
JsonPathUtil.setAt(root, toPath, plainText);
|
||||
return JsonPathUtil.fromTree(root);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 암호화 구현
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
protected String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||
byte[] aad) throws Exception {
|
||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
||||
body.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(cipherBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* fromPath 값을 암호화하여 toPath(Base64)에 반영. JSON 파싱 1회.
|
||||
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
|
||||
*/
|
||||
protected String encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
|
||||
byte[] aad, String fromPath, String toPath) throws Exception {
|
||||
if (PATH_ROOT.equals(fromPath)) {
|
||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
||||
body.getBytes(StandardCharsets.UTF_8));
|
||||
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
|
||||
String fieldName = toPath.replaceFirst("^/", "");
|
||||
return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
|
||||
}
|
||||
JsonNode root = JsonPathUtil.toTree(body);
|
||||
String plainText = JsonPathUtil.getAt(root, fromPath);
|
||||
if (StringUtils.isBlank(plainText)) {
|
||||
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
|
||||
return body;
|
||||
}
|
||||
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
|
||||
plainText.getBytes(StandardCharsets.UTF_8));
|
||||
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
|
||||
JsonPathUtil.setAt(root, toPath, encBase64);
|
||||
return JsonPathUtil.fromTree(root);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 유틸
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
|
||||
*/
|
||||
protected String toBodyString(Object message) {
|
||||
if (message instanceof String) {
|
||||
return (String) message;
|
||||
}
|
||||
if (message instanceof byte[]) {
|
||||
return new String((byte[]) message, StandardCharsets.UTF_8);
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
protected String required(Properties prop, String key) {
|
||||
String v = prop.getProperty(key);
|
||||
if (StringUtils.isBlank(v)) {
|
||||
throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
protected String getProp(Properties prop, String key, String defaultVal) {
|
||||
String v = prop.getProperty(key);
|
||||
return StringUtils.isBlank(v) ? defaultVal : v;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.eactive.eai.agent.encryption;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
@@ -11,6 +12,8 @@ import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.server.Keys;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
|
||||
import com.eactive.ext.kjb.safedb.Utils;
|
||||
|
||||
@Component
|
||||
public class EncryptionManager implements Lifecycle {
|
||||
@@ -18,6 +21,8 @@ public class EncryptionManager implements Lifecycle {
|
||||
private static final String GROUP_NAME = "ENCRYPTION";
|
||||
|
||||
private String encryptYN = "";
|
||||
private String dbEncryptSolutionName = "DAMO";
|
||||
|
||||
private boolean started;
|
||||
private LifecycleSupport lifecycle = new LifecycleSupport(this);
|
||||
|
||||
@@ -55,8 +60,15 @@ public class EncryptionManager implements Lifecycle {
|
||||
encryptYN = "N";
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("EncryptionManager] init :: encryption YN [ " + encryptYN + " ]");
|
||||
dbEncryptSolutionName = PropManager.getInstance().getProperty(GROUP_NAME, "dbEncryptSolutionName");
|
||||
if (dbEncryptSolutionName == null) {
|
||||
dbEncryptSolutionName = "DAMO";
|
||||
} else {
|
||||
if (dbEncryptSolutionName.trim().length() == 0) {
|
||||
dbEncryptSolutionName = "DAMO";
|
||||
}
|
||||
}
|
||||
logger.warn("EncryptionManager] init :: encryption YN [ " + encryptYN + " ], dbEncryptSolutionName [ " + dbEncryptSolutionName + " ]");
|
||||
}
|
||||
|
||||
public synchronized void reload() throws Exception {
|
||||
@@ -99,4 +111,101 @@ public class EncryptionManager implements Lifecycle {
|
||||
public String getEncryptYN() {
|
||||
return encryptYN;
|
||||
}
|
||||
|
||||
public String getDBEncryptSolutionName() {
|
||||
return dbEncryptSolutionName;
|
||||
}
|
||||
|
||||
public boolean isEncrypt() {
|
||||
return "Y".equalsIgnoreCase(encryptYN);
|
||||
}
|
||||
|
||||
public String encryptDBData(String plainText) {
|
||||
if(isEncrypt()) {
|
||||
if ("DAMO".equals(dbEncryptSolutionName)) {
|
||||
com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
|
||||
return damoManager.encrypt(plainText);
|
||||
} else if ("SAFEDB".equals(dbEncryptSolutionName)) {
|
||||
String originalAttribute = plainText;
|
||||
if (StringUtils.isNotEmpty(plainText)) {
|
||||
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
|
||||
plainText = wrapper.encryptNotRnnoString(plainText);
|
||||
|
||||
if (logger.isInfo()) {
|
||||
// originalAttribute 홀수 글자 마스킹
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < originalAttribute.length(); i++) {
|
||||
if (i % 2 == 0) {
|
||||
sb.append(originalAttribute.charAt(i));
|
||||
} else {
|
||||
sb.append("*");
|
||||
}
|
||||
}
|
||||
|
||||
originalAttribute = sb.toString();
|
||||
// System.out.println("DB 암호화 #2 : {"+ originalAttribute +"} ->
|
||||
// {"+attribute+"}");
|
||||
// logger.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute);
|
||||
}
|
||||
}
|
||||
return plainText;
|
||||
} else {
|
||||
return plainText;
|
||||
}
|
||||
} else {
|
||||
return plainText;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String decryptDBData(String dbData) {
|
||||
if (StringUtils.isNotEmpty(dbData)) {
|
||||
if (this.isEncrypted(dbData)) {
|
||||
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
|
||||
try {
|
||||
dbData = safeDBWrapper.decryptNotRnno(dbData);
|
||||
} catch (Exception e) {
|
||||
// 복호화 실패시 원본 반환
|
||||
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
|
||||
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
|
||||
return dbData;
|
||||
}
|
||||
} else {
|
||||
logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
|
||||
}
|
||||
}
|
||||
// logger.debug("DB 복호화 #2 : {} -> {}", dbDataOriginal, dbData);
|
||||
return dbData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 암호화 여부 확인(base64 인코딩 여부로 판단, 또는 0-9, - 로 구성된 경우 암호화 되지 않은 걸로 판단(연락처))
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
private boolean isEncrypted(String data) {
|
||||
if (StringUtils.isEmpty(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처)
|
||||
if (data.matches("^[0-9-]+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = data.trim();
|
||||
|
||||
// Base64 형식 체크
|
||||
if (!Utils.isBase64(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Base64 길이 체크 (4의 배수)
|
||||
if (data.length() % 4 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.eactive.eai.agent.inflow;
|
||||
|
||||
import com.eactive.eai.agent.command.Command;
|
||||
import com.eactive.eai.agent.command.CommandException;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class ReloadInflowClientControlCommand extends Command {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReloadInflowClientControlCommand() {
|
||||
super("ReloadInflowClientControlCommand");
|
||||
}
|
||||
|
||||
public Object execute() throws CommandException {
|
||||
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
if (logger.isInfo())
|
||||
logger.info(this.name + " is executed");
|
||||
|
||||
if (!(args instanceof String)) {
|
||||
String rspErrorCode = "RECEAIMCM001";
|
||||
String msg = makeException(rspErrorCode, null);
|
||||
if (logger.isError())
|
||||
logger.error(msg);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
|
||||
String keyName = (String) args;
|
||||
try {
|
||||
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
|
||||
if (!(base instanceof ClientDualInflowControlManager)) {
|
||||
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
|
||||
|
||||
if (keyName != null) {
|
||||
if ("ALL".equals(keyName)) {
|
||||
manager.reloadClient();
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] all rule Reload.");
|
||||
} else {
|
||||
manager.reloadClient(keyName);
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] " + keyName + " Reload.");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
String rspErrorCode = "RECEAIMCM002";
|
||||
String msg = makeException(rspErrorCode, e);
|
||||
if (logger.isError())
|
||||
logger.error(msg, e);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.eactive.eai.agent.inflow;
|
||||
|
||||
import com.eactive.eai.agent.command.Command;
|
||||
import com.eactive.eai.agent.command.CommandException;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class RemoveInflowClientControlCommand extends Command {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RemoveInflowClientControlCommand() {
|
||||
super("RemoveInflowClientControlCommand");
|
||||
}
|
||||
|
||||
public Object execute() throws CommandException {
|
||||
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
if (logger.isInfo())
|
||||
logger.info(this.name + " is executed");
|
||||
|
||||
if (!(args instanceof String)) {
|
||||
String rspErrorCode = "RECEAIMCM001";
|
||||
String msg = makeException(rspErrorCode, null);
|
||||
if (logger.isError())
|
||||
logger.error(msg);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
|
||||
String key = (String) args;
|
||||
try {
|
||||
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
|
||||
if (!(base instanceof ClientDualInflowControlManager)) {
|
||||
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
|
||||
manager.removeClient(key);
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] " + key + " removed.");
|
||||
} catch (Exception e) {
|
||||
String rspErrorCode = "RECEAIMCM002";
|
||||
String msg = makeException(rspErrorCode, e);
|
||||
if (logger.isError())
|
||||
logger.error(msg, e);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.eactive.eai.authserver.jwt;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* PS256(RSA-PSS) JWT를 발급·검증하는 JwtAccessTokenConverter 확장 클래스.
|
||||
*
|
||||
* spring-security-jwt 1.1.1은 JwtAlgorithms에 PS256을 등록하지 않아
|
||||
* JwtHelper.encode/decodeAndVerify가 PS256 헤더를 처리하지 못한다.
|
||||
* 따라서 encode/decode를 직접 구현하여 JwtHelper를 완전히 우회한다.
|
||||
*/
|
||||
public class PssJwtAccessTokenConverter extends JwtAccessTokenConverter {
|
||||
|
||||
/** PS256 JWT 헤더 {"alg":"PS256","typ":"JWT"} 의 Base64URL 인코딩 (고정값) */
|
||||
private static final String ENCODED_HEADER = Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString("{\"alg\":\"PS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
private final RsaPssSigner pssSigner;
|
||||
private final RsaPssVerifier pssVerifier;
|
||||
private final String publicKeyPem;
|
||||
private final ObjectMapper jackson = new ObjectMapper();
|
||||
|
||||
public PssJwtAccessTokenConverter(RSAPrivateKey privateKey, RSAPublicKey publicKey) {
|
||||
this.pssSigner = new RsaPssSigner(privateKey);
|
||||
this.pssVerifier = new RsaPssVerifier(publicKey);
|
||||
this.publicKeyPem = "-----BEGIN PUBLIC KEY-----\n"
|
||||
+ Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(publicKey.getEncoded())
|
||||
+ "\n-----END PUBLIC KEY-----";
|
||||
// 부모 setVerifierKey — afterPropertiesSet() 내부에서 참조하는 verifierKey 필드 초기화
|
||||
setVerifierKey(this.publicKeyPem);
|
||||
// getKey() (/oauth/token_key 엔드포인트)가 동작하도록 부모의 verifier 필드를 주입
|
||||
try {
|
||||
Field f = JwtAccessTokenConverter.class.getDeclaredField("verifier");
|
||||
f.setAccessible(true);
|
||||
f.set(this, pssVerifier);
|
||||
} catch (Exception ignored) { }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// 부모 afterPropertiesSet()은 signingKey 기반 초기화(JwtHelper 의존)를 시도하므로 건너뜀
|
||||
}
|
||||
|
||||
/**
|
||||
* PS256 JWT 발급. JwtHelper.encode를 사용하지 않고 직접 구성한다.
|
||||
* signingInput = Base64URL(header) + "." + Base64URL(payload)
|
||||
* JWT = signingInput + "." + Base64URL(PSS_sign(signingInput))
|
||||
*/
|
||||
@Override
|
||||
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
|
||||
Map<String, ?> claims = getAccessTokenConverter().convertAccessToken(accessToken, authentication);
|
||||
String payload;
|
||||
try {
|
||||
payload = jackson.writeValueAsString(claims);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Access token을 JSON으로 변환 실패", e);
|
||||
}
|
||||
return buildJwt(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* PS256 JWT 검증 및 클레임 추출. JwtHelper.decodeAndVerify를 사용하지 않고 직접 파싱한다.
|
||||
*/
|
||||
@Override
|
||||
protected Map<String, Object> decode(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
throw new IllegalArgumentException("잘못된 JWT 형식");
|
||||
}
|
||||
byte[] signingInput = (parts[0] + "." + parts[1]).getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] sig = Base64.getUrlDecoder().decode(padBase64Url(parts[2]));
|
||||
pssVerifier.verify(signingInput, sig); // 실패 시 InvalidSignatureException
|
||||
|
||||
byte[] payloadBytes = Base64.getUrlDecoder().decode(padBase64Url(parts[1]));
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> result = jackson.readValue(payloadBytes, Map.class);
|
||||
// JwtAccessTokenConverter 원본 동작 유지: exp 필드 Integer → Long 변환
|
||||
if (result.containsKey(EXP) && result.get(EXP) instanceof Integer) {
|
||||
result.put(EXP, ((Integer) result.get(EXP)).longValue());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("JWT 페이로드 파싱 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** /oauth/token_key 엔드포인트 응답 — alg와 PEM 공개키 반환 */
|
||||
@Override
|
||||
public Map<String, String> getKey() {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
result.put("alg", "PS256");
|
||||
result.put("value", publicKeyPem);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String buildJwt(String payload) {
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString(payload.getBytes(StandardCharsets.UTF_8));
|
||||
String signingInput = ENCODED_HEADER + "." + encodedPayload;
|
||||
byte[] sig = pssSigner.sign(signingInput.getBytes(StandardCharsets.US_ASCII));
|
||||
return signingInput + "." + Base64.getUrlEncoder().withoutPadding().encodeToString(sig);
|
||||
}
|
||||
|
||||
private static String padBase64Url(String s) {
|
||||
int mod = s.length() % 4;
|
||||
if (mod == 2) return s + "==";
|
||||
if (mod == 3) return s + "=";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.eactive.eai.authserver.jwt;
|
||||
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
|
||||
import org.springframework.security.jwt.crypto.sign.Signer;
|
||||
|
||||
/**
|
||||
* PS256 (RSA-PSS + SHA-256) 서명 구현체.
|
||||
* RFC 7518 §3.5 파라미터: Hash=SHA-256, MGF=MGF1/SHA-256, saltLen=32, trailer=0xBC
|
||||
* JDK 11+ 내장 RSASSA-PSS 사용. JDK 8 환경은 BouncyCastle 프로바이더 등록이 필요하다.
|
||||
*
|
||||
* JwtHelper를 우회하는 PssJwtAccessTokenConverter와 함께 사용한다.
|
||||
* (spring-security-jwt 1.1.1은 JwtAlgorithms에 PS256을 등록하지 않아 JwtHelper 직접 사용 불가)
|
||||
*/
|
||||
public class RsaPssSigner implements Signer {
|
||||
|
||||
private static final PSSParameterSpec PSS_SPEC =
|
||||
new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
|
||||
|
||||
private final RSAPrivateKey privateKey;
|
||||
|
||||
public RsaPssSigner(RSAPrivateKey privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(byte[] bytes) {
|
||||
try {
|
||||
Signature sig = Signature.getInstance("RSASSA-PSS");
|
||||
sig.setParameter(PSS_SPEC);
|
||||
sig.initSign(privateKey);
|
||||
sig.update(bytes);
|
||||
return sig.sign();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("RSA-PSS 서명 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String algorithm() {
|
||||
return "PS256";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.eactive.eai.authserver.jwt;
|
||||
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
|
||||
import org.springframework.security.jwt.crypto.sign.InvalidSignatureException;
|
||||
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
|
||||
|
||||
/**
|
||||
* PS256 (RSA-PSS + SHA-256) 서명 검증 구현체.
|
||||
* RFC 7518 §3.5 파라미터: Hash=SHA-256, MGF=MGF1/SHA-256, saltLen=32, trailer=0xBC
|
||||
*/
|
||||
public class RsaPssVerifier implements SignatureVerifier {
|
||||
|
||||
private static final PSSParameterSpec PSS_SPEC =
|
||||
new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
|
||||
|
||||
private final RSAPublicKey publicKey;
|
||||
|
||||
public RsaPssVerifier(RSAPublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(byte[] content, byte[] signature) {
|
||||
try {
|
||||
Signature sig = Signature.getInstance("RSASSA-PSS");
|
||||
sig.setParameter(PSS_SPEC);
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(content);
|
||||
if (!sig.verify(signature)) {
|
||||
throw new InvalidSignatureException("RSA-PSS 서명 검증 실패");
|
||||
}
|
||||
} catch (InvalidSignatureException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new InvalidSignatureException("RSA-PSS 서명 검증 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String algorithm() {
|
||||
return "PS256";
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,21 @@ import com.eactive.eai.common.lifecycle.Lifecycle;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleListener;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleSupport;
|
||||
import com.eactive.eai.common.session.SessionManager;
|
||||
import com.eactive.eai.common.session.SessionManagerForIgnite;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.mchange.v1.cachedstore.CachedStore.Manager;
|
||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 1. 기능 : Access 토큰 정보를 DB로 부터 로딩하고 만료시 재발급 정보를 메모리에 관리하는 Manager 클래스
|
||||
@@ -55,13 +61,8 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
/**
|
||||
* 기동 여부
|
||||
*/
|
||||
private boolean started;
|
||||
|
||||
/**
|
||||
* 토큰 정보를 저장하는 collection
|
||||
*/
|
||||
private HashMap<String, AccessTokenVO> tokens;
|
||||
|
||||
private boolean started;
|
||||
|
||||
/**
|
||||
* 인증 정보를 저장하는 Map
|
||||
*/
|
||||
@@ -78,7 +79,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
private static final int TOKEN_SCHEDULER_THREAD_POOL_SIZE = 30;
|
||||
|
||||
public AccessTokenManagerByDB() {
|
||||
tokens = new HashMap<>();
|
||||
scheduledTasks = new ConcurrentHashMap<>();
|
||||
outboundOAuthCredentialVos = new HashMap<>();
|
||||
runningTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
@@ -117,7 +117,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
|
||||
|
||||
try {
|
||||
tokens.clear();
|
||||
init();
|
||||
} catch(Exception e) {
|
||||
throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201"));
|
||||
@@ -168,7 +167,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
logger.error("Scheduler shutdown interrupted", e);
|
||||
}
|
||||
|
||||
this.tokens = null;
|
||||
started = false;
|
||||
|
||||
if (logger.isWarn()){
|
||||
@@ -240,27 +238,34 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
try {
|
||||
logger.debug("Executing token issuance for adapter group: {}", adapterGroupName);
|
||||
|
||||
// 현재 토큰 가져오기
|
||||
AccessTokenVO accessToken = tokens.get(adapterGroupName);
|
||||
SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, new Function<AccessTokenVO, AccessTokenVO>() {
|
||||
|
||||
// 다음 스케줄 시간 계산
|
||||
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
|
||||
// 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
|
||||
if (accessToken == null) {
|
||||
issueToken(credential, false);
|
||||
} else if(accessToken.getExpiration() != null) {
|
||||
// 토큰이 있고 만료시간이 설정 된 경우
|
||||
if(accessToken.getExpiration().before(new Date(intervalTime))){
|
||||
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
|
||||
issueToken(credential, true);
|
||||
@Override
|
||||
public AccessTokenVO apply(AccessTokenVO accessToken) {
|
||||
|
||||
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
|
||||
// // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
|
||||
if (accessToken == null) {
|
||||
return issueToken(credential);
|
||||
} else if(accessToken.getExpiration() != null) {
|
||||
// 토큰이 있고 만료시간이 설정 된 경우
|
||||
if(accessToken.getExpiration().before(new Date(intervalTime))){
|
||||
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
|
||||
return issueToken(credential);
|
||||
} else {
|
||||
// 토큰이 아직 유효한 경우
|
||||
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
|
||||
return accessToken;
|
||||
}
|
||||
} else {
|
||||
// 토큰이 아직 유효한 경우
|
||||
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
|
||||
//토큰이 있지만 만료 시간이 없는 경우
|
||||
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
|
||||
return accessToken;
|
||||
}
|
||||
} else {
|
||||
//토큰이 있지만 만료 시간이 없는 경우
|
||||
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
logger.debug("Token issuance completed for adapter group: {}", adapterGroupName);
|
||||
} catch (Exception e) {
|
||||
@@ -282,19 +287,24 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
}
|
||||
}
|
||||
|
||||
private void issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo, boolean isTokenExpiredWithinInterval) throws Exception {
|
||||
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
|
||||
boolean isExpired = true;
|
||||
private AccessTokenVO issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo) {
|
||||
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
|
||||
AccessTokenVO accessToken = null;
|
||||
try {
|
||||
|
||||
// boolean isExpired = true;
|
||||
|
||||
// if (tokens.containsKey(adapterGroupName)) {
|
||||
// AccessTokenVO accessToken = tokens.get(adapterGroupName);
|
||||
// isExpired = accessToken.isExpired();
|
||||
// }
|
||||
// logger.debug("Token status for adapter group: {}, expired: {}, isTokenExpiredWithinInterval: {}", adapterGroupName, isExpired, isTokenExpiredWithinInterval);
|
||||
|
||||
if (tokens.containsKey(adapterGroupName)) {
|
||||
AccessTokenVO accessToken = tokens.get(adapterGroupName);
|
||||
isExpired = accessToken.isExpired();
|
||||
}
|
||||
logger.debug("Token status for adapter group: {}, expired: {}, isTokenExpiredWithinInterval: {}", adapterGroupName, isExpired, isTokenExpiredWithinInterval);
|
||||
|
||||
if (isExpired || isTokenExpiredWithinInterval) {
|
||||
// if (isExpired || isTokenExpiredWithinInterval) {
|
||||
|
||||
|
||||
logger.info("Issuing new token for adapter group: {}", adapterGroupName);
|
||||
tokens.remove(adapterGroupName);
|
||||
// tokens.remove(adapterGroupName);
|
||||
|
||||
AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
||||
|
||||
@@ -306,16 +316,23 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
||||
if(service != null) {
|
||||
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
|
||||
AccessTokenVO accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
|
||||
this.tokens.put(adapterGroupName, accessToken);
|
||||
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
|
||||
logger.info("New token issued successfully for adapter group: {}", adapterGroupName);
|
||||
return accessToken;
|
||||
|
||||
} else {
|
||||
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
|
||||
}
|
||||
} else {
|
||||
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
|
||||
}
|
||||
}
|
||||
|
||||
}catch (Exception e) {
|
||||
logger.error("Task execution failed for adapter group: {}", adapterGroupName, e);
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
|
||||
}
|
||||
|
||||
public void stopToken(String adapterGroupName) {
|
||||
@@ -324,7 +341,7 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
task.cancel(true);
|
||||
logger.warn("Token task stopped for adapter group: {}", adapterGroupName);
|
||||
}
|
||||
tokens.remove(adapterGroupName);
|
||||
SessionManager.getInstance().removeOutboundAccessToken(adapterGroupName);
|
||||
logger.debug("Token removed for adapter group: {}", adapterGroupName);
|
||||
}
|
||||
|
||||
@@ -382,15 +399,28 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
* @return Access 토큰 정보
|
||||
**/
|
||||
public AccessTokenVO getAccessTokenVO(String adapterGroupName) {
|
||||
AccessTokenVO accessToken = null;
|
||||
|
||||
try {
|
||||
accessToken = tokens.get(adapterGroupName);
|
||||
} catch(Exception e) {
|
||||
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
|
||||
}
|
||||
AccessTokenVO accessToken = null;
|
||||
|
||||
return accessToken;
|
||||
try {
|
||||
accessToken = SessionManager.getInstance().getOutboundAccessToken(adapterGroupName,
|
||||
new Function<AccessTokenVO, AccessTokenVO>() {
|
||||
|
||||
@Override
|
||||
public AccessTokenVO apply(AccessTokenVO t) {
|
||||
|
||||
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos
|
||||
.get(adapterGroupName);
|
||||
|
||||
return issueToken(outboundOAuthCredentialVo);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
if (logger.isError())
|
||||
logger.error("토큰을 찾을 수 없습니다. [" + adapterGroupName + "] ", e);
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,9 +433,9 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
**/
|
||||
public String getAccessToken(String adapterGroupName) {
|
||||
String accessToken = null;
|
||||
|
||||
|
||||
try {
|
||||
accessToken = tokens.get(adapterGroupName).getAccessToken();
|
||||
accessToken = getAccessTokenVO(adapterGroupName).getAccessToken();
|
||||
} catch(Exception e) {
|
||||
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
|
||||
}
|
||||
@@ -435,27 +465,29 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
||||
* 3. 주의사항
|
||||
*
|
||||
**/
|
||||
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties, String oldToken) throws Exception {
|
||||
|
||||
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
|
||||
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties,
|
||||
String oldToken) throws Exception {
|
||||
|
||||
// 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다.
|
||||
if (accessToken == null || accessToken.isExpired()
|
||||
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
|
||||
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
|
||||
|
||||
// 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다.
|
||||
if (accessToken == null || accessToken.isExpired()
|
||||
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
|
||||
|
||||
if (isOAuthCredentialRegistered(adapterGroupName) == false) {
|
||||
throw new Exception(
|
||||
"There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
|
||||
}
|
||||
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
|
||||
|
||||
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
||||
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
|
||||
if (service != null) {
|
||||
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties,
|
||||
outboundOAuthCredentialVo);
|
||||
}
|
||||
}
|
||||
|
||||
if(isOAuthCredentialRegistered(adapterGroupName) == false){
|
||||
throw new Exception("There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
|
||||
}
|
||||
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
|
||||
|
||||
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
||||
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
|
||||
if(service != null) {
|
||||
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
|
||||
}
|
||||
tokens.put(adapterGroupName, accessToken);
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -336,7 +336,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
|
||||
switch (event.getEventType()) {
|
||||
case ERROR :
|
||||
TypedCircuitBreakerManager.logger
|
||||
.error("CirbuitBreaker ERROR event occurred. CircuitBreakerEvent={}" + event);
|
||||
.error("CircuitBreaker ERROR event occurred. CircuitBreakerEvent={}" + event);
|
||||
break;
|
||||
case STATE_TRANSITION :
|
||||
if (event instanceof CircuitBreakerOnStateTransitionEvent) {
|
||||
@@ -345,7 +345,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
|
||||
CircuitBreaker.State toState = stateTransition.getToState();
|
||||
CircuitBreaker.State fromState = stateTransition.getFromState();
|
||||
TypedCircuitBreakerManager.logger.error(
|
||||
"CirbuitBreaker STATE_TRANSITION event occurred. {} : {} -> {}. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
|
||||
"CircuitBreaker STATE_TRANSITION event occurred. {} : {} -> {}. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
|
||||
new Object[]{event.getCircuitBreakerName(), fromState, toState, event, this.vo});
|
||||
// if (!toState.equals(State.OPEN) && !toState.equals(State.HALF_OPEN)
|
||||
// && !toState.equals(State.CLOSED)) {
|
||||
@@ -367,7 +367,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
|
||||
|
||||
} else {
|
||||
TypedCircuitBreakerManager.logger.error(
|
||||
"CirbuitBreaker STATE_TRANSITION event occurred. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
|
||||
"CircuitBreaker STATE_TRANSITION event occurred. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
|
||||
new Object[]{event, this.vo});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,10 @@ import org.json.simple.JSONValue;
|
||||
|
||||
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.handler.AdapterErrorMessageHandler;
|
||||
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandlerFactory;
|
||||
import com.eactive.eai.common.EAIKeys;
|
||||
import com.eactive.eai.common.logger.EAILogSender;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
@@ -749,6 +753,53 @@ public class ExceptionHandler {
|
||||
String serviceType, String psvItfTp, String charset) {
|
||||
String error = null;
|
||||
EAIMessage resultEAIMessage = null;
|
||||
|
||||
// AdapterErrorMessageHandler(TemplateAdapterErrorMsgHandler) 설정이 있으면 우선 호출
|
||||
try {
|
||||
AdapterGroupVO inboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(sngSysItfTp);
|
||||
if (inboundAdapterGroupVO != null) {
|
||||
AdapterVO inboundAdapterVO = inboundAdapterGroupVO.nextAdapterVO();
|
||||
if (inboundAdapterVO != null) {
|
||||
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
|
||||
inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
|
||||
if (StringUtils.isNotBlank(errorHandlerClass)) {
|
||||
if (logger.isInfo())
|
||||
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
|
||||
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
|
||||
if (handler != null) {
|
||||
// 표준전문에 에러코드/에러메시지 세팅
|
||||
try {
|
||||
StandardMessageManager standardManager = StandardMessageManager.getInstance();
|
||||
standardManager.getMessageCoordinator().coordinateSetStandardMessageError(
|
||||
eaiMessage.getStandardMessage(), eaiMessage.getMapper(), errCode, errMsg);
|
||||
} catch (Exception e) {
|
||||
if (logger.isWarn())
|
||||
logger.warn("ExceptionHandler] errorSyncSend coordinateSetStandardMessageError 처리 실패: " + e.getMessage());
|
||||
}
|
||||
|
||||
Object responseObj = handler.generateNonStandardInternalErrorResponseMessage(
|
||||
sngSysItfTp, inboundAdapterVO.getName(), null, tmpMsg, eaiMessage);
|
||||
if (responseObj != null) {
|
||||
resultEAIMessage = eaiMessage;
|
||||
try {
|
||||
resultEAIMessage.getStandardMessage().setBizData(responseObj, charset);
|
||||
} catch (Exception e) {
|
||||
logger.error("setBizData error", e);
|
||||
}
|
||||
resultEAIMessage.getMapper().setResponseType(
|
||||
resultEAIMessage.getStandardMessage(), STDMessageKeys.RESPONSE_TYPE_CODE_E);
|
||||
resultEAIMessage.setRspErrCd(EAIMessageKeys.EAI_SUCCESS_CODE, false);
|
||||
return resultEAIMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger.isWarn())
|
||||
logger.warn("ExceptionHandler] errorSyncSend AdapterErrorMessageHandler 처리 실패: " + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
if (transClassify) {
|
||||
if (logger.isInfo()) {
|
||||
|
||||
@@ -7,7 +7,12 @@ import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.security.KeyStore;
|
||||
import java.security.Provider;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -24,13 +29,17 @@ import com.eactive.eai.common.util.Logger;
|
||||
* SafeNet ProtectServer HSM 연동 관리자 (JDK 8 / SunPKCS11)
|
||||
*
|
||||
* PropManager 그룹 "HSM" 에서 읽는 키:
|
||||
* PKCS11_CONFIG - pkcs11.cfg 파일 내용 (name/library/slot 설정을 DB에 직접 저장)
|
||||
* PKCS11_CONFIG - pkcs11.cfg 파일 내용
|
||||
* PIN - HSM 슬롯 PIN
|
||||
*
|
||||
* PKCS11_CONFIG 값 예시 (개행은 \n 으로 입력):
|
||||
* name = ProtectServer
|
||||
* library = /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so
|
||||
* slot = 0
|
||||
*
|
||||
* name = SoftHSM
|
||||
* library = C:/SoftHSM2/lib/softhsm2-x64.dll
|
||||
* slotListIndex = 0
|
||||
*/
|
||||
@Component
|
||||
public class HsmManager implements Lifecycle {
|
||||
@@ -99,7 +108,20 @@ public class HsmManager implements Lifecycle {
|
||||
StringBuilder aliasList = new StringBuilder();
|
||||
while (aliases.hasMoreElements()) {
|
||||
if (aliasList.length() > 0) aliasList.append(", ");
|
||||
aliasList.append(aliases.nextElement());
|
||||
|
||||
String alias = aliases.nextElement();
|
||||
aliasList.append(alias);
|
||||
|
||||
java.security.Key key = keyStore.getKey(alias, null);
|
||||
if (key instanceof SecretKey) {
|
||||
SecretKey secretKey = (SecretKey) key;
|
||||
if(secretKey.getEncoded() != null) {
|
||||
String encBase64 = Base64.getEncoder().encodeToString(secretKey.getEncoded());
|
||||
logger.debug("HsmManager] HSM - {} : [{}]", alias, encBase64);
|
||||
} else {
|
||||
logger.debug("HsmManager] HSM - secretKey null {} : [{}]", alias, secretKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.warn("HsmManager] HSM 키 목록: [" + aliasList + "]");
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
|
||||
/**
|
||||
* DualBucket에 클라이언트 유량제어 메서드를 추가한 인터페이스.
|
||||
* {@link ClientDualInflowControlManager}가 구현한다.
|
||||
*/
|
||||
public interface ClientDualBucket extends DualBucket {
|
||||
|
||||
/**
|
||||
* 클라이언트 유량제어 체크 — 차단 원인 포함.
|
||||
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
|
||||
*/
|
||||
String isClientPassDetail(String clientId);
|
||||
|
||||
/**
|
||||
* 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용.
|
||||
* @return 등록된 설정이 없으면 null
|
||||
*/
|
||||
InflowTargetVO getClientInflowThreshold(String clientId);
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
import com.eactive.eai.common.inflow.InflowType;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
/**
|
||||
* 어댑터/인터페이스 이중 버킷(DualInflowControlManager)에 클라이언트 유량제어를 추가한 관리자.
|
||||
*
|
||||
* <p>적용 방법: INFLOW.properties에 아래 한 줄 추가.
|
||||
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager</pre>
|
||||
*/
|
||||
@Component
|
||||
public class ClientDualInflowControlManager extends DualInflowControlManager implements ClientDualBucket {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
public static synchronized Bucket getBucket() {
|
||||
return ApplicationContextProvider.getContext().getBean(ClientDualInflowControlManager.class);
|
||||
}
|
||||
|
||||
private volatile Map<String, DualCustomBucket> clientBucketMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> clientMetricsMap = new ConcurrentHashMap<>();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void start() throws LifecycleException {
|
||||
super.start();
|
||||
try {
|
||||
initClients();
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException("ClientDualInflowControlManager init failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 초기화 / 리로드
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void initClients() throws Exception {
|
||||
this.clientBucketMap = loadBucketMap(InflowType.CLIENT);
|
||||
if (logger.isInfo()) logger.info("ClientDualInflowControlManager] initClients: " + clientBucketMap.size() + " buckets");
|
||||
}
|
||||
|
||||
public synchronized void reloadClient() throws Exception {
|
||||
initClients();
|
||||
clientMetricsMap.clear();
|
||||
}
|
||||
|
||||
public synchronized void reloadClient(String clientId) throws Exception {
|
||||
reloadBucketMap(clientBucketMap, InflowType.CLIENT, clientId);
|
||||
TargetMetrics m = clientMetricsMap.get(clientId);
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
public synchronized void removeClient(String clientId) {
|
||||
clientBucketMap.remove(clientId);
|
||||
clientMetricsMap.remove(clientId);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 클라이언트 메트릭 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); }
|
||||
public Map<String, TargetMetrics> getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); }
|
||||
public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); }
|
||||
public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ClientDualBucket — 클라이언트 유량제어
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public String isClientPassDetail(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
if (b == null) return DualCustomBucket.RESULT_PASS;
|
||||
String result = b.tryConsume();
|
||||
recordMetrics(clientMetricsMap, clientId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getClientInflowThreshold(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
return (b == null) ? null : b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 모니터링용 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public DualCustomBucket getClientBucket(String clientId) {
|
||||
return clientBucketMap.get(clientId);
|
||||
}
|
||||
|
||||
public Map<String, DualCustomBucket> getClientBucketMap() {
|
||||
return clientBucketMap;
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,9 @@ public class EAIFileLogger
|
||||
* @exception DAOException
|
||||
**/
|
||||
public void setLog(EAIMessage message, Properties prop) throws Exception{
|
||||
|
||||
|
||||
if(!tranLogger.isError())
|
||||
return;
|
||||
|
||||
// 업무데이터 size 체크하여 업무데이터1,2에 insert하고
|
||||
// addBwkDataSub(message) 호출 여부 결정
|
||||
@@ -517,27 +519,28 @@ public class EAIFileLogger
|
||||
|
||||
//전장표 데이터부 JSON Array로 저장
|
||||
//암호화
|
||||
Charset hexaCharset = Charset.forName("euc-kr");
|
||||
if("Y".equalsIgnoreCase(EncryptionManager.getInstance().getEncryptYN()) ){
|
||||
try{
|
||||
biz = HexaConverter.bytesToHexa(bizMsgStr.getBytes(hexaCharset));
|
||||
}catch(Exception e){
|
||||
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
|
||||
}
|
||||
try{
|
||||
header = HexaConverter.bytesToHexa(header.getBytes(hexaCharset));
|
||||
}catch(Exception e){
|
||||
logger.error("EAIFileLogger] header encryption Error - " + e.getMessage());
|
||||
}
|
||||
try{
|
||||
mdclData = HexaConverter.bytesToHexa(mdclData.getBytes(hexaCharset));
|
||||
}catch(Exception e){
|
||||
logger.error("EAIFileLogger] mdclData encryption Error - " + e.getMessage());
|
||||
}
|
||||
|
||||
}else {
|
||||
biz = bizMsgStr;
|
||||
}
|
||||
biz = EncryptionManager.getInstance().encryptDBData(bizMsgStr);
|
||||
// Charset hexaCharset = Charset.forName("euc-kr");
|
||||
// if("Y".equalsIgnoreCase(EncryptionManager.getInstance().getEncryptYN()) ){
|
||||
// try{
|
||||
// biz = HexaConverter.bytesToHexa(bizMsgStr.getBytes(hexaCharset));
|
||||
// }catch(Exception e){
|
||||
// logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
|
||||
// }
|
||||
// try{
|
||||
// header = HexaConverter.bytesToHexa(header.getBytes(hexaCharset));
|
||||
// }catch(Exception e){
|
||||
// logger.error("EAIFileLogger] header encryption Error - " + e.getMessage());
|
||||
// }
|
||||
// try{
|
||||
// mdclData = HexaConverter.bytesToHexa(mdclData.getBytes(hexaCharset));
|
||||
// }catch(Exception e){
|
||||
// logger.error("EAIFileLogger] mdclData encryption Error - " + e.getMessage());
|
||||
// }
|
||||
//
|
||||
// }else {
|
||||
// biz = bizMsgStr;
|
||||
// }
|
||||
|
||||
//임시코드 시작
|
||||
String logType = PropManager.getInstance().getProperty("BIZ_LOG_TYPE");
|
||||
|
||||
@@ -3,8 +3,6 @@ package com.eactive.eai.common.logger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.authserver.service.OAuth2Manager;
|
||||
import com.eactive.eai.authserver.vo.ClientVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -14,13 +12,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.agent.encryption.EncryptionManager;
|
||||
import com.eactive.eai.authserver.service.OAuth2Manager;
|
||||
import com.eactive.eai.authserver.vo.ClientVO;
|
||||
import com.eactive.eai.common.dao.DAOException;
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.logger.EAILogLogger;
|
||||
import com.eactive.eai.common.logger.StatAdapterLogLogger;
|
||||
import com.eactive.eai.common.logger.StatLogLogger;
|
||||
import com.eactive.eai.common.logger.StatTranLogLogger;
|
||||
import com.eactive.eai.common.logger.SubMessageLogLogger;
|
||||
import com.eactive.eai.common.logger.mapper.StatAdapterLogMapper;
|
||||
import com.eactive.eai.common.logger.mapper.StatLogMapper;
|
||||
import com.eactive.eai.common.logger.mapper.StatTranLogMapper;
|
||||
@@ -30,7 +25,6 @@ import com.eactive.eai.common.message.MessageType;
|
||||
import com.eactive.eai.common.message.ServiceMessage;
|
||||
import com.eactive.eai.common.monitor.StatMonitorLogVO;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.server.EAIServerVO;
|
||||
import com.eactive.eai.common.server.loader.EAIServerLoader;
|
||||
@@ -55,11 +49,10 @@ import com.eactive.eai.transformer.layout.Item;
|
||||
import com.eactive.eai.transformer.layout.Layout;
|
||||
import com.eactive.eai.transformer.transform.Transform;
|
||||
import com.eactive.eai.transformer.transform.TransformManager;
|
||||
import com.eactive.eai.util.HexaConverter;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
// safeDB
|
||||
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
|
||||
import com.eactive.ext.kjb.safedb.Utils;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
|
||||
|
||||
@Service
|
||||
@@ -478,20 +471,12 @@ public class EAILogDAO {
|
||||
}
|
||||
*/
|
||||
|
||||
// jwhong safeDB 암호화
|
||||
|
||||
if ("Y".equalsIgnoreCase(EncryptionManager.getInstance().getEncryptYN())) {
|
||||
try {
|
||||
biz = EncryptSafeDb(bizMsgStr);
|
||||
} catch (Exception e) {
|
||||
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
|
||||
biz = bizMsgStr;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
biz = EncryptionManager.getInstance().encryptDBData(bizMsgStr);
|
||||
} catch (Exception e) {
|
||||
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
|
||||
biz = bizMsgStr;
|
||||
}
|
||||
|
||||
// jwhong safeDB end
|
||||
}
|
||||
|
||||
// 임시코드 시작
|
||||
String logType = PropManager.getInstance().getProperty("BIZ_LOG_TYPE");
|
||||
@@ -860,88 +845,6 @@ public class EAILogDAO {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private String EncryptSafeDb(String attribute) {
|
||||
String originalAttribute = attribute;
|
||||
if (StringUtils.isNotEmpty(attribute)) {
|
||||
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
|
||||
attribute = wrapper.encryptNotRnnoString(attribute);
|
||||
|
||||
if (logger.isInfo()) {
|
||||
// originalAttribute 홀수 글자 마스킹
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < originalAttribute.length(); i++) {
|
||||
if (i % 2 == 0) {
|
||||
sb.append(originalAttribute.charAt(i));
|
||||
} else {
|
||||
sb.append("*");
|
||||
}
|
||||
}
|
||||
|
||||
originalAttribute = sb.toString();
|
||||
// System.out.println("DB 암호화 #2 : {"+ originalAttribute +"} -> {"+attribute+"}");
|
||||
//logger.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute);
|
||||
}
|
||||
}
|
||||
return attribute;
|
||||
|
||||
}
|
||||
|
||||
public String DecryptSafeDb(String dbData) {
|
||||
|
||||
String dbDataOriginal = dbData;
|
||||
if (StringUtils.isNotEmpty(dbData)) {
|
||||
if (this.isEncrypted(dbData)) {
|
||||
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
|
||||
try {
|
||||
dbData = safeDBWrapper.decryptNotRnno(dbData);
|
||||
} catch (Exception e) {
|
||||
// 복호화 실패시 원본 반환
|
||||
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
|
||||
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
|
||||
return dbData;
|
||||
}
|
||||
} else {
|
||||
logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
|
||||
}
|
||||
}
|
||||
|
||||
//logger.debug("DB 복호화 #2 : {} -> {}", dbDataOriginal, dbData);
|
||||
|
||||
return dbData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 암호화 여부 확인(base64 인코딩 여부로 판단, 또는 0-9, - 로 구성된 경우 암호화 되지 않은 걸로 판단(연락처))
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
private boolean isEncrypted(String data) {
|
||||
if (StringUtils.isEmpty(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처)
|
||||
if (data.matches("^[0-9-]+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = data.trim();
|
||||
|
||||
// Base64 형식 체크
|
||||
if (!Utils.isBase64(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Base64 길이 체크 (4의 배수)
|
||||
if (data.length() % 4 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.eactive.eai.common.message;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.eactive.eai.message.manager.StandardMessageManager;
|
||||
@@ -109,7 +109,8 @@ public class EAIMessage implements EAIMessageKeys, Serializable, Comparable<EAIM
|
||||
private String authkeyfieldname;
|
||||
|
||||
private String authCheckdYn;
|
||||
|
||||
|
||||
private Properties callProp;
|
||||
|
||||
public EAIMessage() {
|
||||
this.svcMsgs = new ArrayList<>();
|
||||
@@ -664,6 +665,14 @@ public class EAIMessage implements EAIMessageKeys, Serializable, Comparable<EAIM
|
||||
public Map<String, Object> getTransactionContextTransfer() {
|
||||
return transactionContextTransfer;
|
||||
}
|
||||
|
||||
public Properties getCallProp() {
|
||||
return callProp;
|
||||
}
|
||||
|
||||
public void setCallProp(Properties callProp) {
|
||||
this.callProp = callProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* [비동기 전달 전용] 컨텍스트 전달용 Map 설정
|
||||
|
||||
@@ -64,7 +64,8 @@ public class IFRouter
|
||||
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(service.getPsvSysItfTp());
|
||||
if( EAIMessageKeys.TRANTYPE_TAS.equals(eaiMessage.getTranType())
|
||||
// RESTAdapter는 교체하지 않는다
|
||||
&& !StringUtils.equals(adptGrpVO.getType(), "RST")) {
|
||||
&& !StringUtils.equals(adptGrpVO.getType(), "RST")
|
||||
&& !StringUtils.equals(adptGrpVO.getType(), "HTT")) {
|
||||
// isLocal = true;
|
||||
// if (logger.isInfo()) logger.info(guidLogPrefix + " TRANTYPE TAS [" + eaiMessage.getTranType() + "]");
|
||||
PropManager manager = PropManager.getInstance();
|
||||
|
||||
@@ -144,8 +144,12 @@ public class CryptoModuleManager implements Lifecycle {
|
||||
dynamicKeyCache.put(cacheKey, new CachedDerivedKey(derivedKey, ttl));
|
||||
}
|
||||
}
|
||||
|
||||
byte[] iv = vo.getIvHex() != null ? DatatypeConverter.parseHexBinary(vo.getIvHex()) : null;
|
||||
byte[] iv = null;
|
||||
if(derivedKey.getIv() != null) {
|
||||
iv = derivedKey.getIv();
|
||||
} else if (vo.getIvHex() != null){
|
||||
iv = DatatypeConverter.parseHexBinary(vo.getIvHex());
|
||||
}
|
||||
return buildExtension(vo, iv, derivedKey.getEncKey(), derivedKey.getDecKey());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,19 @@ public class DerivedKey {
|
||||
|
||||
private final byte[] encKey;
|
||||
private final byte[] decKey;
|
||||
private final byte[] iv;
|
||||
|
||||
|
||||
public DerivedKey(byte[] encKey, byte[] decKey) {
|
||||
this.encKey = encKey;
|
||||
this.decKey = decKey != null ? decKey : encKey;
|
||||
this.iv = null;
|
||||
}
|
||||
|
||||
public DerivedKey(byte[] encKey, byte[] decKey, byte[] iv) {
|
||||
this.encKey = encKey;
|
||||
this.decKey = decKey != null ? decKey : encKey;
|
||||
this.iv = iv;
|
||||
}
|
||||
|
||||
public byte[] getEncKey() {
|
||||
@@ -17,4 +26,8 @@ public class DerivedKey {
|
||||
public byte[] getDecKey() {
|
||||
return decKey;
|
||||
}
|
||||
|
||||
public byte[] getIv() {
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ package com.eactive.eai.common.security.keyderiv;
|
||||
import java.util.Map;
|
||||
|
||||
public interface KeyDerivationStrategy {
|
||||
|
||||
public static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
|
||||
public static final String PARAM_HSM_IV_ALIAS = "hsmIvAlias";
|
||||
public static final String PARAM_CONTEXT_KEY = "contextKey";
|
||||
|
||||
|
||||
/**
|
||||
* 키를 도출한다.
|
||||
*
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.eactive.eai.common.security.keyderiv.strategy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||
import com.eactive.eai.common.hsm.HsmException;
|
||||
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
|
||||
/**
|
||||
* HSM 마스터키를 이용하는 전략 클래스들의 기본 클래스
|
||||
*/
|
||||
public abstract class BaseHsmKeyDerivationStrategy implements KeyDerivationStrategy {
|
||||
|
||||
protected byte[] getHsmKey(Map<String, String> params) throws HsmException {
|
||||
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
|
||||
return getHsmKey(hsmKeyAlias);
|
||||
}
|
||||
|
||||
protected byte[] getHsmKey(String hsmKeyAlias) throws HsmException {
|
||||
SecretKey masterKey = hsmCryptoService().getSecretKey(hsmKeyAlias);
|
||||
return masterKey.getEncoded();
|
||||
}
|
||||
|
||||
protected byte[] getHsmIv(Map<String, String> params) throws HsmException {
|
||||
String hsmKeyAlias = required(params, PARAM_HSM_IV_ALIAS);
|
||||
return getHsmKey(hsmKeyAlias);
|
||||
}
|
||||
|
||||
private String required(Map<String, String> params, String key) {
|
||||
String value = params.get(key);
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("key_deriv_params 필수값 누락: " + key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private HsmCryptoService hsmCryptoService() {
|
||||
return ApplicationContextProvider.getContext().getBean(HsmCryptoService.class);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.eactive.eai.common.security.keyderiv.strategy;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
|
||||
/**
|
||||
* HSM 마스터키 + 런타임 컨텍스트값을 연결(concatenate)한 뒤 SHA-256 해싱으로 키를 도출하는 전략.
|
||||
* SHA-256 출력은 항상 32바이트(AES-256)이므로 별도 keyLength 파라미터가 불필요하다.
|
||||
*
|
||||
* key_deriv_params (JSON) 예시:
|
||||
* {
|
||||
* "hsmKeyAlias" : "MASTER_KEY_AES",
|
||||
* "contextKey" : "X-Api-Group-Seq" -- runtimeContext에서 꺼낼 키 이름
|
||||
* }
|
||||
*/
|
||||
public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy {
|
||||
|
||||
@Override
|
||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
||||
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
|
||||
String contextKey = required(params, PARAM_CONTEXT_KEY);
|
||||
|
||||
SecretKey masterKey = hsmCryptoService().getSecretKey(hsmKeyAlias);
|
||||
byte[] masterKeyBytes = masterKey.getEncoded();
|
||||
byte[] contextBytes = runtimeContext.getOrDefault(contextKey, "")
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] combined = new byte[masterKeyBytes.length + contextBytes.length];
|
||||
System.arraycopy(masterKeyBytes, 0, combined, 0, masterKeyBytes.length);
|
||||
System.arraycopy(contextBytes, 0, combined, masterKeyBytes.length, contextBytes.length);
|
||||
|
||||
byte[] derived = MessageDigest.getInstance("SHA-256").digest(combined);
|
||||
return new DerivedKey(derived, derived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
||||
String contextKey = params.getOrDefault(PARAM_CONTEXT_KEY, "");
|
||||
String contextValue = runtimeContext.getOrDefault(contextKey, "");
|
||||
return cryptoName + ":" + contextValue;
|
||||
}
|
||||
|
||||
private String required(Map<String, String> params, String key) {
|
||||
String value = params.get(key);
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("key_deriv_params 필수값 누락: " + key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private HsmCryptoService hsmCryptoService() {
|
||||
return ApplicationContextProvider.getContext().getBean(HsmCryptoService.class);
|
||||
}
|
||||
}
|
||||
-2
@@ -26,8 +26,6 @@ public class HsmContextXorKeyDerivationStrategy implements KeyDerivationStrategy
|
||||
/** DB key_deriv_strategy 컬럼에 사용하는 FQCN 참조용 상수. */
|
||||
public static final String STRATEGY_CLASS = HsmContextXorKeyDerivationStrategy.class.getName();
|
||||
|
||||
private static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
|
||||
private static final String PARAM_CONTEXT_KEY = "contextKey";
|
||||
private static final String PARAM_OFFSET = "offset";
|
||||
private static final String PARAM_LENGTH = "length";
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.eactive.eai.common.security.keyderiv.strategy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
|
||||
/**
|
||||
* HSM 마스터키를 사용하는 전략
|
||||
*
|
||||
* key_deriv_params (JSON) 예시: { "hsmKeyAlias" : "MASTER_KEY_AES", "hsmIvAlias" : "MASTER_IV_AES"}
|
||||
*/
|
||||
public class HsmKeyAndIvDerivationStrategy extends BaseHsmKeyDerivationStrategy {
|
||||
|
||||
@Override
|
||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
||||
byte[] masterKeyBytes = super.getHsmKey(params);
|
||||
byte[] iv = super.getHsmIv(params);
|
||||
return new DerivedKey(masterKeyBytes, masterKeyBytes, iv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
||||
return cryptoName;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.eactive.eai.common.security.keyderiv.strategy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
|
||||
/**
|
||||
* HSM 마스터키를 사용하는 전략
|
||||
*
|
||||
* key_deriv_params (JSON) 예시: { "hsmKeyAlias" : "MASTER_KEY_AES"}
|
||||
*/
|
||||
public class HsmKeyDerivationStrategy extends BaseHsmKeyDerivationStrategy {
|
||||
|
||||
@Override
|
||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
||||
byte[] masterKeyBytes = super.getHsmKey(params);
|
||||
return new DerivedKey(masterKeyBytes, masterKeyBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
||||
return cryptoName;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.eactive.eai.common.security.keyderiv.strategy;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Map;
|
||||
|
||||
import com.eactive.eai.common.security.keyderiv.DerivedKey;
|
||||
|
||||
/**
|
||||
* HSM 마스터키를 SHA-256 해싱으로 키를 도출하는 전략.
|
||||
* SHA-256 출력은 항상 32바이트(AES-256)이므로 별도 keyLength 파라미터가 불필요하다.
|
||||
*
|
||||
* key_deriv_params (JSON) 예시:
|
||||
* {
|
||||
* "hsmKeyAlias" : "MASTER_KEY_AES"
|
||||
* }
|
||||
*/
|
||||
public class HsmSha256KeyDerivationStrategy extends BaseHsmKeyDerivationStrategy {
|
||||
|
||||
@Override
|
||||
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
|
||||
byte[] masterKeyBytes = super.getHsmKey(params);
|
||||
byte[] derived = MessageDigest.getInstance("SHA-256").digest(masterKeyBytes);
|
||||
return new DerivedKey(derived, derived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
|
||||
return cryptoName;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.eactive.eai.common.session;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.ignite.IgniteCache;
|
||||
|
||||
import com.eactive.eai.authserver.service.BearerTokenInfo;
|
||||
import com.eactive.eai.common.lifecycle.Lifecycle;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||
@@ -11,6 +16,7 @@ import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.server.EAIServerVO;
|
||||
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||
|
||||
/**
|
||||
* 1. 기능 :
|
||||
@@ -60,6 +66,7 @@ public abstract class SessionManager implements Lifecycle {
|
||||
public static final String WEBSOCKER_CAHCE_NAME = "webSocketTimeout";
|
||||
public static final String TERMINAL_CAHCE_NAME = "terminalSession";
|
||||
public static final String CAGATEWAY_TOKEN_CACHE_NAME = "CAGatewayToken";
|
||||
public static final String OUTBOUND_ACCESS_TOKEN_CACHE_NAME = "OutBoundAccessToken";
|
||||
|
||||
public static final String SESSION_MANAGER_GROUPNAME = "RMIInfo";
|
||||
public static final String SESSION_MANAGER_CACHETYPE = "cacheType";
|
||||
@@ -204,6 +211,13 @@ public abstract class SessionManager implements Lifecycle {
|
||||
|
||||
public abstract void removeCAToken(String key);
|
||||
|
||||
//Outbound Access Token Cache
|
||||
public abstract AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> getNewToken);
|
||||
|
||||
public abstract void removeOutboundAccessToken(String key);
|
||||
|
||||
public abstract void clearOutboundAccessToken();
|
||||
|
||||
|
||||
// HTTP Cache
|
||||
public abstract SessionVO getHttpLogin(String key);
|
||||
@@ -245,4 +259,5 @@ public abstract class SessionManager implements Lifecycle {
|
||||
public abstract void closeManager();
|
||||
|
||||
public abstract String getCacheStatus();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.eactive.eai.common.session;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.eactive.eai.authserver.service.BearerTokenInfo;
|
||||
import com.eactive.eai.common.ehcache.CustomRMICacheReplicatorFactory;
|
||||
@@ -14,6 +16,7 @@ import com.eactive.eai.common.sessioninfo.TerminalInfoDAO;
|
||||
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.util.HealthCheckManager;
|
||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
@@ -937,4 +940,19 @@ public class SessionManagerForEhcache extends SessionManager {
|
||||
public void removeCAToken(String key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> getNewToken) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOutboundAccessToken(String key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearOutboundAccessToken() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.cache.Cache;
|
||||
import javax.cache.configuration.Factory;
|
||||
@@ -54,6 +58,7 @@ import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.worker.Future;
|
||||
import com.eactive.eai.common.worker.ResponseMap;
|
||||
import com.eactive.eai.util.HealthCheckManager;
|
||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
@@ -66,6 +71,8 @@ import ch.qos.logback.classic.Level;
|
||||
//------------------------------------------------------------------------------------
|
||||
|
||||
public class SessionManagerForIgnite extends SessionManager {
|
||||
|
||||
private static final String DISTRIBUTED_TOKEN_LOCK = "DISTRIBUTED_TOKEN_LOCK";
|
||||
// evictMaster Instance - single socket 관련
|
||||
private static IgniteCache<String, String> evictMasterCache = null;
|
||||
// Socket Session cache 정보
|
||||
@@ -76,8 +83,11 @@ public class SessionManagerForIgnite extends SessionManager {
|
||||
private static IgniteCache<String, CacheStoreObject> cacheStore = null;
|
||||
// CA Gateway Token 관리용
|
||||
private static IgniteCache<String, BearerTokenInfo> cacheCAToken = null;
|
||||
// OutboundAccessToekn 관리용
|
||||
private static IgniteCache<String, AccessTokenVO> cacheOutBoundAccessToken = null;
|
||||
// webSocket Login cache 정보
|
||||
private static IgniteCache<String, SessionVO> cacheLogin = null; // key = loginId
|
||||
|
||||
private static IgniteCache<String, String> cacheConvertUserId = null; // 단말ID 를 userID로 변경
|
||||
// HTTP Polling 에 대한 timeout 관리용
|
||||
private static IgniteCache<String, SessionVO> httpLogin = null; // key == uuid
|
||||
@@ -392,6 +402,7 @@ public class SessionManagerForIgnite extends SessionManager {
|
||||
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, USERID_CAHCE_NAME, 20000, false));
|
||||
caches.add(initCache(config, webSocketTimeout, bootstrapAsynchronously, WEBSOCKER_CAHCE_NAME, 20000, false));
|
||||
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, TERMINAL_CAHCE_NAME, 20000, false));
|
||||
caches.add(initCache(config, 0, bootstrapAsynchronously, OUTBOUND_ACCESS_TOKEN_CACHE_NAME, 1000, false, true)); // transactionMode true
|
||||
caches.add(initCache(config, 0, bootstrapAsynchronously, CAGATEWAY_TOKEN_CACHE_NAME, 1000, false));
|
||||
|
||||
config.setCacheConfiguration(caches.stream().toArray(CacheConfiguration[]::new));
|
||||
@@ -421,6 +432,7 @@ public class SessionManagerForIgnite extends SessionManager {
|
||||
cacheTopic = manager.cache(TOPIC_CAHCE_NAME);
|
||||
cacheStore = manager.cache(STORE_CAHCE_NAME);
|
||||
cacheCAToken = manager.cache(CAGATEWAY_TOKEN_CACHE_NAME);
|
||||
cacheOutBoundAccessToken = manager.cache(OUTBOUND_ACCESS_TOKEN_CACHE_NAME);
|
||||
|
||||
cacheLogin = manager.cache(LOGIN_CAHCE_NAME);
|
||||
httpLogin = manager.cache(HTTP_CAHCE_NAME);
|
||||
@@ -428,49 +440,56 @@ public class SessionManagerForIgnite extends SessionManager {
|
||||
cacheWebSocketTimeout = manager.cache(WEBSOCKER_CAHCE_NAME);
|
||||
cacheTerminal = manager.cache(TERMINAL_CAHCE_NAME);
|
||||
}
|
||||
|
||||
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs,
|
||||
String bootstrapAsynchronously, String name, int maxsize, boolean useCacheWriteModeAsync,
|
||||
boolean transactionMode) {
|
||||
if (logger.isDebug()) {
|
||||
logger.debug(logPrefix + " create " + name + " cache expiryDurationSecs=" + expiryDurationSecs);
|
||||
}
|
||||
// near-cache
|
||||
NearCacheConfiguration nearConfiguration = new NearCacheConfiguration();
|
||||
LruEvictionPolicy nearEvictionPolicy = new LruEvictionPolicy();
|
||||
nearEvictionPolicy.setMaxSize(maxsize);
|
||||
nearConfiguration.setNearEvictionPolicy(nearEvictionPolicy);
|
||||
|
||||
CacheConfiguration cacheConfig = new CacheConfiguration(name);
|
||||
|
||||
cacheConfig.setCacheMode(CacheMode.PARTITIONED);
|
||||
|
||||
// ATOMIC,TRANSACTIONAL
|
||||
if (transactionMode) {
|
||||
cacheConfig.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
|
||||
} else {
|
||||
cacheConfig.setAtomicityMode(CacheAtomicityMode.ATOMIC);
|
||||
}
|
||||
|
||||
// Set the number of backups (replicas) for each partition : default(0)
|
||||
cacheConfig.setBackups(1);
|
||||
// Enable reading from backups to improve read consistency : default(true)
|
||||
cacheConfig.setReadFromBackup(true);
|
||||
|
||||
// FULL_SYNC/FULL_ASYNC/PRIMARY_SYNC(default)
|
||||
if (useCacheWriteModeAsync) {
|
||||
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_ASYNC);
|
||||
} else {
|
||||
// cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
|
||||
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
|
||||
}
|
||||
|
||||
if (expiryDurationSecs > 0) {
|
||||
cacheConfig.setExpiryPolicyFactory(
|
||||
ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, expiryDurationSecs)));
|
||||
} else {
|
||||
cacheConfig.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(Duration.ETERNAL));
|
||||
}
|
||||
cacheConfig.setNearConfiguration(nearConfiguration);
|
||||
return cacheConfig;
|
||||
}
|
||||
|
||||
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs, String bootstrapAsynchronously,
|
||||
String name, int maxsize, boolean useCacheWriteModeAsync) {
|
||||
if (logger.isDebug()) {
|
||||
logger.debug(logPrefix + " create " + name + " cache expiryDurationSecs=" + expiryDurationSecs);
|
||||
}
|
||||
// near-cache
|
||||
NearCacheConfiguration nearConfiguration = new NearCacheConfiguration();
|
||||
LruEvictionPolicy nearEvictionPolicy = new LruEvictionPolicy();
|
||||
nearEvictionPolicy.setMaxSize(maxsize);
|
||||
nearConfiguration.setNearEvictionPolicy(nearEvictionPolicy);
|
||||
|
||||
CacheConfiguration cacheConfig = new CacheConfiguration(name);
|
||||
// REPLICATED,PARTITIONED(default)
|
||||
// cacheConfig.setCacheMode(CacheMode.REPLICATED);
|
||||
cacheConfig.setCacheMode(CacheMode.PARTITIONED);
|
||||
|
||||
// ATOMIC,TRANSACTIONAL
|
||||
cacheConfig.setAtomicityMode(CacheAtomicityMode.ATOMIC);
|
||||
|
||||
// Set the number of backups (replicas) for each partition : default(0)
|
||||
cacheConfig.setBackups(1);
|
||||
// Enable reading from backups to improve read consistency : default(true)
|
||||
cacheConfig.setReadFromBackup(true);
|
||||
|
||||
// FULL_SYNC/FULL_ASYNC/PRIMARY_SYNC(default)
|
||||
if (useCacheWriteModeAsync) {
|
||||
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_ASYNC);
|
||||
} else {
|
||||
// cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
|
||||
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
|
||||
}
|
||||
|
||||
if(expiryDurationSecs > 0) {
|
||||
cacheConfig
|
||||
.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, expiryDurationSecs)));
|
||||
}
|
||||
else {
|
||||
cacheConfig
|
||||
.setExpiryPolicyFactory( ModifiedExpiryPolicy.factoryOf(Duration.ETERNAL) );
|
||||
}
|
||||
cacheConfig.setNearConfiguration(nearConfiguration);
|
||||
return cacheConfig;
|
||||
return initCache(config, expiryDurationSecs, bootstrapAsynchronously, name, maxsize, useCacheWriteModeAsync, false);
|
||||
}
|
||||
|
||||
// WebSocket Login 관련
|
||||
@@ -868,5 +887,114 @@ public class SessionManagerForIgnite extends SessionManager {
|
||||
cacheCAToken.remove(key);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void sleepQuietly(long millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {
|
||||
// 인터럽트 상태 복구 (중요)
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> tokenSupplier) {
|
||||
// 1. 먼저 캐시에서 조회 (락 없이 빠르게)
|
||||
AccessTokenVO token = cacheOutBoundAccessToken.get(key);
|
||||
|
||||
// 2. 만료되었거나 없다면 락 획득 시도
|
||||
if (token == null || token.isExpired()) {
|
||||
Lock lock = cacheOutBoundAccessToken.lock(DISTRIBUTED_TOKEN_LOCK); // Ignite 분산 락
|
||||
|
||||
boolean acquired = false;
|
||||
|
||||
try {
|
||||
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock");
|
||||
acquired = lock.tryLock(60, TimeUnit.SECONDS);
|
||||
|
||||
if ( acquired ) {
|
||||
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock success=>"+acquired);
|
||||
// 3. 락 획득 후 다시 확인 (그 사이 다른 인스턴스가 갱신했을 수 있음)
|
||||
token = cacheOutBoundAccessToken.get(key);
|
||||
|
||||
logger.debug(String.format("getting token in ignite=%s", token));
|
||||
|
||||
if (token == null || token.isExpired()) {
|
||||
// 4. 실제로 비어있을 때만 외부 API 호출 (I/O 발생)
|
||||
token = tokenSupplier.apply(token);
|
||||
|
||||
logger.debug(String.format("getting token is null or expired borrow new token =%s", token));
|
||||
|
||||
cacheOutBoundAccessToken.put(key, token);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock fail=>"+acquired);
|
||||
}
|
||||
}catch(Throwable e) {
|
||||
logger.error("occuring exception in getOutboundAccessToken", e);
|
||||
}finally {
|
||||
|
||||
try {
|
||||
if( acquired ) {
|
||||
lock.unlock();
|
||||
logger.debug("calling func getOutboundAccessToken = distributed ignite unlock");
|
||||
}
|
||||
}catch(Throwable e) {
|
||||
logger.error("occuring exception in getOutboundAccessToken unlock fail.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeOutboundAccessToken(String key) {
|
||||
|
||||
Lock lock = cacheOutBoundAccessToken.lock(DISTRIBUTED_TOKEN_LOCK); // Ignite 분산 락
|
||||
|
||||
boolean acquired = false;
|
||||
try {
|
||||
|
||||
logger.debug("calling func removeOutboundAccessToken = distributed ignite trylock");
|
||||
acquired = lock.tryLock(60, TimeUnit.SECONDS);
|
||||
|
||||
if( acquired ) {
|
||||
if (logger.isDebug()) {
|
||||
|
||||
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock success=>"
|
||||
+ acquired);
|
||||
|
||||
logger.debug("calling func removeOutboundAccessToken Remove OutBoundAccessToken key - " + key);
|
||||
}
|
||||
cacheOutBoundAccessToken.remove(key);
|
||||
}else {
|
||||
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock fail=>"+acquired);
|
||||
}
|
||||
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.error("occuring exception in removeOutboundAccessToken", e);
|
||||
} finally {
|
||||
|
||||
try {
|
||||
if (acquired) {
|
||||
lock.unlock();
|
||||
logger.debug("calling func removeOutboundAccessToken = distributed ignite unlock");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("occuring exception in removeOutboundAccessToken unlock fail.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearOutboundAccessToken() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,9 +49,10 @@ public class STDMessageDAO extends BaseDAO {
|
||||
for (StandardMessageInfo info : list) {
|
||||
vo = standardManager.getStandardMessage();
|
||||
vo.setServiceKey(info.getBzwksvckeyname());
|
||||
vo.setEaiSvcCd(info.getEaisvcname());
|
||||
// TODO
|
||||
// to be defined...
|
||||
mapper.setInterfaceId(vo, info.getEaitranname());
|
||||
// mapper.setInterfaceId(vo, info.getEaitranname());
|
||||
mapper.setSendRecvDivision(vo, info.getEaisendrecv()); // S | R
|
||||
mapper.setInExDivision(vo, info.getEaidirection()); // 1 | 2
|
||||
List<StandardMessageItem> items = info.getStandardMessageItems();
|
||||
|
||||
@@ -72,6 +72,9 @@ public class HttpAdapterExtraLogUtil {
|
||||
}
|
||||
httpAdapterExtraLogVo.setHeaderList(headerVoList);
|
||||
httpAdapterExtraLogVo.setHttpStatus(httpStatus);
|
||||
|
||||
logger.debug("[{}] Insert HTTP Log. - {}", guid, httpAdapterExtraLogVo);
|
||||
|
||||
if(ElinkConfig.isUseAsyncLogging()) {
|
||||
AsyncHttpLoggingPoolManager.getInstance().publish(httpAdapterExtraLogVo);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
package com.eactive.eai.common.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
public class JSONUtils {
|
||||
|
||||
private JSONUtils() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static Object parseJsonGeneric(Object jsonData) {
|
||||
if (jsonData == null) {
|
||||
return new JSONObject();
|
||||
}
|
||||
if (jsonData instanceof JSONObject) {
|
||||
return (JSONObject) jsonData;
|
||||
} else if (jsonData instanceof String) {
|
||||
if (StringUtils.isBlank((String) jsonData)) {
|
||||
return new JSONObject();
|
||||
}
|
||||
try {
|
||||
JSONParser parser = new JSONParser();
|
||||
Object parsedObj = parser.parse((String) jsonData);
|
||||
return parsedObj;
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} else
|
||||
throw new IllegalArgumentException("not support json type. " + jsonData.getClass());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static JSONObject parseJson(Object message) {
|
||||
if (message == null) {
|
||||
return new JSONObject();
|
||||
}
|
||||
if (message instanceof JSONObject)
|
||||
return (JSONObject) message;
|
||||
else if (message instanceof String)
|
||||
try {
|
||||
return (JSONObject) JSONValue.parseWithException((String) message);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException("json parsing exception : " + message, e);
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("not support json type. " + message.getClass());
|
||||
}
|
||||
|
||||
public static JSONArray parseJsonArray(Object message) {
|
||||
if (message == null) {
|
||||
return new JSONArray();
|
||||
}
|
||||
if (message instanceof JSONArray)
|
||||
return (JSONArray) message;
|
||||
else if (message instanceof String)
|
||||
try {
|
||||
return (JSONArray) JSONValue.parseWithException((String) message);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException("json parsing exception : " + message, e);
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("not support json type. " + message.getClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Set<Map.Entry<String, Object>> getField(JSONObject json) {
|
||||
return json.entrySet();
|
||||
}
|
||||
|
||||
public static JSONObject getChildJson(JSONObject json, String childName) {
|
||||
return (JSONObject)json.get(childName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void setChild(JSONObject json, String childName, Object value) {
|
||||
json.put(childName, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void setChildWithPath(JSONObject json, String path, Object value) {
|
||||
String[] keys = path.split("\\.");
|
||||
JSONObject current = json;
|
||||
for(int i = 0; i < keys.length - 1; i++) {
|
||||
current = (JSONObject) current.get(keys[i]);
|
||||
if(current == null)
|
||||
return;
|
||||
}
|
||||
current.put(keys[keys.length - 1], value);
|
||||
}
|
||||
|
||||
/**
|
||||
* json array의 String 값의 ',' 구분자
|
||||
* @param jsonArray
|
||||
* @return
|
||||
*/
|
||||
public static String toString(JSONArray jsonArray) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
for(Object ele : jsonArray ) {
|
||||
if(ele instanceof String || ele instanceof Number) {
|
||||
if(first)
|
||||
first = false;
|
||||
else
|
||||
sb.append(",");
|
||||
sb.append(ele);
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("not support type - " + ele.getClass().getName());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String toString2(JSONArray jsonArray) {
|
||||
String strJsonArray = jsonArray.toJSONString();
|
||||
strJsonArray = replace(strJsonArray);
|
||||
|
||||
return strJsonArray;
|
||||
}
|
||||
|
||||
private static String replace(String strJsonArray) {
|
||||
strJsonArray = StringUtils.replace(strJsonArray, "\"", "");
|
||||
strJsonArray = StringUtils.replace(strJsonArray, "[", "");
|
||||
strJsonArray = StringUtils.replace(strJsonArray, "]", "");
|
||||
return strJsonArray;
|
||||
}
|
||||
|
||||
public static JSONArray getJSONArray(JSONObject jsonObject, String path) {
|
||||
if(jsonObject == null || path == null || path.isEmpty())
|
||||
return null;
|
||||
|
||||
String[] keys = path.split("\\.");
|
||||
JSONObject current = jsonObject;
|
||||
for(int i = 0; i < keys.length - 1; i++) {
|
||||
current = (JSONObject) current.get(keys[i]);
|
||||
if(current == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return (JSONArray)current.get(keys[keys.length - 1]);
|
||||
}
|
||||
|
||||
public static String getStringWithPath(JSONObject jsonObject, String path) {
|
||||
if(jsonObject == null || path == null || path.isEmpty())
|
||||
return null;
|
||||
|
||||
String[] keys = path.split("\\.");
|
||||
JSONObject current = jsonObject;
|
||||
for(int i = 0; i < keys.length - 1; i++) {
|
||||
current = (JSONObject) current.get(keys[i]);
|
||||
if(current == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String)current.get(keys[keys.length - 1]);
|
||||
}
|
||||
|
||||
public static String removeStringWithPath(JSONObject jsonObject, String path) {
|
||||
if(jsonObject == null || path == null || path.isEmpty())
|
||||
return null;
|
||||
|
||||
String[] keys = path.split("\\.");
|
||||
JSONObject current = jsonObject;
|
||||
for(int i = 0; i < keys.length - 1; i++) {
|
||||
current = (JSONObject) current.get(keys[i]);
|
||||
if(current == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String)current.remove(keys[keys.length - 1]);
|
||||
}
|
||||
|
||||
public static void removeEmptyWithPath(JSONObject jsonObject, String path) {
|
||||
if(jsonObject == null || path == null || path.isEmpty())
|
||||
return;
|
||||
|
||||
String[] keys = path.split("\\.");
|
||||
JSONObject current = jsonObject;
|
||||
for(int i = 0; i < keys.length - 1; i++) {
|
||||
current = (JSONObject) current.get(keys[i]);
|
||||
if(current == null)
|
||||
return;
|
||||
}
|
||||
String value = (String) current.get(keys[keys.length - 1]);
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
current.remove(keys[keys.length - 1]);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected static com.eactive.eai.common.util.Logger logger = com.eactive.eai.common.util.Logger
|
||||
.getLogger(com.eactive.eai.common.util.Logger.LOGGER_DEFAULT);
|
||||
|
||||
public static void removeEmptyWithPath2(JSONObject jsonObject, String path) {
|
||||
if (jsonObject == null || path == null || path.isEmpty())
|
||||
return;
|
||||
|
||||
String[] keys = path.split("\\.");
|
||||
Object current = jsonObject;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
String key = keys[i];
|
||||
if (current instanceof JSONObject) {
|
||||
JSONObject currentObj = (JSONObject) current;
|
||||
if (currentObj.get(key) == null) {
|
||||
if (i == keys.length - 1) {
|
||||
// 마지막
|
||||
currentObj.remove(key);
|
||||
return;
|
||||
} else {
|
||||
logger.debug("경로를 찾을 수 없음. {} of {}", path, key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i == keys.length - 1) {
|
||||
if (currentObj.get(key) instanceof String && StringUtils.isEmpty((String)currentObj.get(key))) {
|
||||
// 마지막
|
||||
currentObj.remove(key);
|
||||
return;
|
||||
} else {
|
||||
logger.debug("경로 값이 빈값이 아님. {} of {}'s value [{}]", path, key, currentObj.get(key));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
current = currentObj.get(key);
|
||||
}
|
||||
}
|
||||
} else if (current instanceof JSONArray) {
|
||||
JSONArray currentArray = (JSONArray) current;
|
||||
for (int j = 0; j < currentArray.size(); j++) {
|
||||
Object arrayElement = currentArray.get(j);
|
||||
if (arrayElement instanceof JSONObject) {
|
||||
List<String> lastPath = new ArrayList<>();
|
||||
for (int k = i; k < keys.length; k++)
|
||||
lastPath.add(keys[k]);
|
||||
|
||||
removeEmptyWithPath2((JSONObject) arrayElement, String.join(".", lastPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeZeroWithPath2(JSONObject jsonObject, String path) {
|
||||
if (jsonObject == null || path == null || path.isEmpty())
|
||||
return;
|
||||
|
||||
String[] keys = path.split("\\.");
|
||||
Object current = jsonObject;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
String key = keys[i];
|
||||
if (current instanceof JSONObject) {
|
||||
JSONObject currentObj = (JSONObject) current;
|
||||
if (currentObj.get(key) == null) {
|
||||
if (i == keys.length - 1) {
|
||||
logger.debug("경로 값이 null. {} of {}", path, key);
|
||||
return;
|
||||
} else {
|
||||
logger.debug("경로를 찾을 수 없음. {} of {}", path, key);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i == keys.length - 1) {
|
||||
if (isZero(key, currentObj)) {
|
||||
// 마지막
|
||||
currentObj.remove(key);
|
||||
return;
|
||||
} else {
|
||||
logger.debug("경로 값이 0이 아님. {} of {}'s value [{}]", path, key, currentObj.get(key));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
current = currentObj.get(key);
|
||||
}
|
||||
}
|
||||
} else if (current instanceof JSONArray) {
|
||||
JSONArray currentArray = (JSONArray) current;
|
||||
for (int j = 0; j < currentArray.size(); j++) {
|
||||
Object arrayElement = currentArray.get(j);
|
||||
if (arrayElement instanceof JSONObject) {
|
||||
List<String> lastPath = new ArrayList<>();
|
||||
for (int k = i; k < keys.length; k++)
|
||||
lastPath.add(keys[k]);
|
||||
|
||||
removeZeroWithPath2((JSONObject) arrayElement, String.join(".", lastPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern numberPattern = Pattern.compile("[+-]?\\d*(\\.\\d+)?");
|
||||
|
||||
private static boolean isZero(String key, JSONObject currentObj) {
|
||||
Object value = currentObj.get(key);
|
||||
if (value instanceof Number && ((Number) value).doubleValue() == 0) {
|
||||
return true;
|
||||
} else if (value instanceof String && StringUtils.isNotEmpty((String) value)
|
||||
&& numberPattern.matcher((String) value).matches()) {
|
||||
try {
|
||||
return Double.parseDouble((String) value) == 0.0;
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("숫자 형식이 아님. [{}]", value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* (문자열)'"aaa", "bbb", "ccc"' -> (json array)["aaa", "bbb", "ccc"]
|
||||
* @param doublequotedSeperatedStr
|
||||
* @return
|
||||
*/
|
||||
public static JSONArray createJSONArray(String doublequotedSeperatedStr) {
|
||||
return (JSONArray) JSONValue.parse("[" + doublequotedSeperatedStr + "]");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONArray createJSONArray(String[] strArray ) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for(String str : strArray) {
|
||||
jsonArray.add(str);
|
||||
}
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONArray createJSONArray(List<String> strList ) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for(String str : strList) {
|
||||
jsonArray.add(str);
|
||||
}
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject replaceSpacesWith(JSONObject json, String plus) {
|
||||
if (json == null || plus == null || plus.isEmpty())
|
||||
return json;
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
for (Object key : json.keySet()) {
|
||||
Object value = json.get(key);
|
||||
|
||||
if(value instanceof String) {
|
||||
result.put(key, ((String)value).replace(" ", plus));
|
||||
} else if (value instanceof JSONObject) {
|
||||
result.put(key, replaceSpacesWith((JSONObject)value, plus));
|
||||
} else if ( value instanceof JSONArray) {
|
||||
result.put(key, replaceSpacesInArrayWith((JSONArray)value, plus));
|
||||
} else {
|
||||
result.put(key, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONArray replaceSpacesInArrayWith(JSONArray array, String plus) {
|
||||
JSONArray result = new JSONArray();
|
||||
for(int i =0; i < array.size(); i++) {
|
||||
Object value = array.get(i);
|
||||
|
||||
if(value instanceof String) {
|
||||
result.add(((String)value).replace(" ", plus));
|
||||
} else if(value instanceof JSONObject) {
|
||||
result.add(replaceSpacesWith((JSONObject)value, plus));
|
||||
} else if(value instanceof JSONArray) {
|
||||
result.add(replaceSpacesInArrayWith((JSONArray)value, plus));
|
||||
} else {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject stripTrailingZeros(JSONObject json, String exceptionHeaderName) {
|
||||
if (json == null)
|
||||
return json;
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
for (Object key : json.keySet()) {
|
||||
Object value = json.get(key);
|
||||
|
||||
if(value instanceof Number) {
|
||||
BigDecimal bd = new BigDecimal(value.toString());
|
||||
BigDecimal stripped = bd.stripTrailingZeros();
|
||||
if(stripped.scale() <= 0)
|
||||
result.put(key, stripped.intValue());
|
||||
else
|
||||
result.put(key, stripped);
|
||||
|
||||
} else if (value instanceof JSONObject) {
|
||||
result.put(key, stripTrailingZeros((JSONObject)value, exceptionHeaderName));
|
||||
} else if ( value instanceof JSONArray) {
|
||||
result.put(key, stripTrailingZerosInArrayWith((JSONArray)value, exceptionHeaderName));
|
||||
} else {
|
||||
result.put(key, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONArray stripTrailingZerosInArrayWith(JSONArray array, String exceptionHeaderName) {
|
||||
JSONArray result = new JSONArray();
|
||||
for(int i =0; i < array.size(); i++) {
|
||||
Object value = array.get(i);
|
||||
|
||||
if(value instanceof Number) {
|
||||
BigDecimal bd = new BigDecimal(value.toString());
|
||||
BigDecimal stripped = bd.stripTrailingZeros();
|
||||
if(stripped.scale() <= 0)
|
||||
result.add(stripped.intValue());
|
||||
else
|
||||
result.add(stripped);
|
||||
} else if(value instanceof JSONObject) {
|
||||
result.add(stripTrailingZeros((JSONObject)value, exceptionHeaderName));
|
||||
} else if(value instanceof JSONArray) {
|
||||
result.add(stripTrailingZerosInArrayWith((JSONArray)value, exceptionHeaderName));
|
||||
} else {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -415,6 +415,10 @@ public final class MessageUtil {
|
||||
if ("RECEAIIRP072".equals(oldErrCode)) { // 유량제어 초과 에러
|
||||
return STDMessageErrorKeys.RATELIMIT_ERROR_CODE_VALUE;
|
||||
}
|
||||
if ("RECEAIOHP201".equals(oldErrCode)) { // CIRCUITBREAKER 초과 에러
|
||||
return STDMessageErrorKeys.RATELIMIT_ERROR_CODE_VALUE;
|
||||
}
|
||||
|
||||
if (EAIServiceMonitor.getInstance().isTimeOutCodes(oldErrCode)){ //TIMEOUT일경우
|
||||
return STDMessageErrorKeys.TIMEOUT_ERROR_CODE_VALUE;
|
||||
}else if (EAIServiceMonitor.getInstance().isBizErrorCodes(oldErrCode)){ //업무오류일경우
|
||||
@@ -448,6 +452,9 @@ public final class MessageUtil {
|
||||
if ("RECEAIIRP072".equals(oldErrCode)) { // 유량제어 초과 에러
|
||||
return STDMessageErrorKeys.RATELIMIT_ERROR_MSG_VALUE;
|
||||
}
|
||||
if ("RECEAIOHP201".equals(oldErrCode)) { // CIRCUITBREAKER 초과 에러
|
||||
return STDMessageErrorKeys.RATELIMIT_ERROR_MSG_VALUE;
|
||||
}
|
||||
if (EAIServiceMonitor.getInstance().isTimeOutCodes(oldErrCode) ){ //TIMEOUT일경우
|
||||
return STDMessageErrorKeys.TIMEOUT_ERROR_MSG_VALUE;
|
||||
}else if (EAIServiceMonitor.getInstance().isBizErrorCodes(oldErrCode)){ //업무오류일경우
|
||||
@@ -455,7 +462,27 @@ public final class MessageUtil {
|
||||
}else{
|
||||
return STDMessageErrorKeys.SYSTEM_ERROR_MSG_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTobeDesc(String oldErrCode){
|
||||
if ("".equals(oldErrCode)){
|
||||
return STDMessageErrorKeys.SYSTEM_ERROR_DESC_VALUE;
|
||||
}
|
||||
if ("RECEAIIRP020,RECEAIIRP120,RECEAIIRP110".indexOf(oldErrCode) >= 0 ){//유효값 오류
|
||||
return STDMessageErrorKeys.VALID_VALUE_ERROR_DESC_VALUE;
|
||||
}
|
||||
if ("RECEAIIRP072".equals(oldErrCode)) { // 유량제어 초과 에러
|
||||
return STDMessageErrorKeys.RATELIMIT_ERROR_DESC_VALUE;
|
||||
}
|
||||
if ("RECEAIOHP201".equals(oldErrCode)) { // CIRCUITBREAKER 초과 에러
|
||||
return STDMessageErrorKeys.RATELIMIT_ERROR_DESC_VALUE;
|
||||
}
|
||||
if (EAIServiceMonitor.getInstance().isTimeOutCodes(oldErrCode) ){ //TIMEOUT일경우
|
||||
return STDMessageErrorKeys.TIMEOUT_ERROR_DESC_VALUE;
|
||||
}else{
|
||||
return STDMessageErrorKeys.SYSTEM_ERROR_DESC_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO json tunning 요소 다시 확인해봐야됨(불필요한 파싱)
|
||||
public static String addJsonRootItem(String jsonString, String rootItemName) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.eactive.eai.common.util;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public class TxFileLogger {
|
||||
|
||||
public static void logTxFile(Properties transactionProp, Object message, String prefix) {
|
||||
Logger siftLogger = Logger.getLogger(Logger.LOGGER_SIFT);
|
||||
if (siftLogger.isInfoEnabled()) {
|
||||
siftLogger.info(prefix + message);
|
||||
if (siftLogger.isDebugEnabled())
|
||||
siftLogger.debug(CommonLib.getDumpMessage(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.eactive.eai.common.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
public class TxSiftContext {
|
||||
|
||||
/**
|
||||
* 거래 시작 시 SIFT discriminator를 MDC에 등록한다.
|
||||
* SIFT가 비활성화 상태이면 아무 작업도 하지 않는다.
|
||||
*/
|
||||
public static boolean begin(String discriminatingValue) {
|
||||
if (!Logger.isSiftEnabled()) return false;
|
||||
if(MDC.get(Logger.DISCRIMINATOR) == null) {
|
||||
MDC.put(Logger.DISCRIMINATOR, discriminatingValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 종료 시 MDC에 등록된 SIFT appender를 닫고 MDC를 정리한다.
|
||||
* begin() 에서 MDC가 설정되지 않았으면 아무 작업도 하지 않는다.
|
||||
*/
|
||||
public static void end() {
|
||||
String discriminatingValue = MDC.get(Logger.DISCRIMINATOR);
|
||||
if (StringUtils.isBlank(discriminatingValue)) return;
|
||||
Logger.closeSiftAppender(discriminatingValue);
|
||||
MDC.remove(Logger.DISCRIMINATOR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.eactive.eai.common.util;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.DocumentHelper;
|
||||
import org.dom4j.Element;
|
||||
import org.dom4j.io.SAXReader;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class XMLUtils {
|
||||
|
||||
private XMLUtils() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static Document convertXmlDocument(Object message) throws DocumentException {
|
||||
if (message == null)
|
||||
return DocumentHelper.createDocument();
|
||||
else if (message instanceof Document)
|
||||
return (Document) message;
|
||||
else if (message instanceof String) {
|
||||
SAXReader builder = new SAXReader();
|
||||
return builder.read(new StringReader((String)message));
|
||||
} else
|
||||
throw new IllegalArgumentException("not support xml type. " + message.getClass());
|
||||
}
|
||||
|
||||
public static List<Element> getChilds(Element parent) {
|
||||
List<Element> elements = new ArrayList<>();
|
||||
Iterator<Element> iterator = parent.elementIterator();
|
||||
while(iterator.hasNext()) {
|
||||
Element child = iterator.next();
|
||||
elements.add(child);
|
||||
elements.addAll(getChilds(child));
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
public static List<Element> getChildsLevel1(Element parent) {
|
||||
List<Element> elements = new ArrayList<>();
|
||||
Iterator<Element> iterator = parent.elementIterator();
|
||||
while(iterator.hasNext()) {
|
||||
Element child = iterator.next();
|
||||
elements.add(child);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object elementToJSONObject(Element element) {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
List<Element> children = element.elements();
|
||||
if(children.isEmpty()) {
|
||||
return element.getText();
|
||||
}
|
||||
for(Element child : children) {
|
||||
String childName = child.getName();
|
||||
if(json.containsKey(childName)) {
|
||||
Object existing = json.get(childName);
|
||||
if(existing instanceof JSONArray) {
|
||||
((JSONArray) existing).add(elementToJSONObject(child));
|
||||
} else {
|
||||
JSONArray list = new JSONArray();
|
||||
list.add(existing);
|
||||
list.add(elementToJSONObject(child));
|
||||
json.put(childName, list);
|
||||
}
|
||||
} else {
|
||||
json.put(childName, elementToJSONObject(child));
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public static Element getElement(Element parent, String path) {
|
||||
String[] keys = path.split("\\.");
|
||||
Element current = parent;
|
||||
for(String key : keys) {
|
||||
current = current.element(key);
|
||||
}
|
||||
return (Element) current.elements().get(0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.eactive.eai.agent.encryption.EncryptionManager;
|
||||
import com.eactive.eai.common.dao.BaseDAO;
|
||||
import com.eactive.eai.common.dao.DAOException;
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.logger.EAIDBLogControl;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.common.util.ScpDbAgentUtil;
|
||||
@@ -28,38 +29,6 @@ public class InboundErrorInfoDAO extends BaseDAO {
|
||||
private InboundErrorInfoMapper inboundErrorInfoMapper;
|
||||
|
||||
public void addInboundErrorInfo(InboundErrorInfoVO vo) throws DAOException {
|
||||
EncryptionManager manager = EncryptionManager.getInstance();
|
||||
String bzwkDataCtnt = vo.getBwkDataTxt();
|
||||
|
||||
// 업무데이터를 암호화할 경우 길이가 늘어나는 문제가 있으므로 double check 함
|
||||
if ("Y".equalsIgnoreCase(manager.getEncryptYN())) {
|
||||
if (bzwkDataCtnt.length() > 2000) {
|
||||
bzwkDataCtnt = StringUtil.chunkString(bzwkDataCtnt, 2000);
|
||||
}
|
||||
|
||||
String encbzwkDataCtnt = null;
|
||||
try {
|
||||
encbzwkDataCtnt = ScpDbAgentUtil.ScpEncB64(
|
||||
PropManager.getInstance().getProperties(ScpDbAgentUtil.EXT_MODULE), bzwkDataCtnt, "EUC-KR");
|
||||
} catch (Exception e) {
|
||||
logger.error("addInboundErrorInfo] scpdb_agent encryption Error - " + e.getMessage());
|
||||
}
|
||||
|
||||
// double check
|
||||
if (encbzwkDataCtnt != null && encbzwkDataCtnt.length() > 4000) {
|
||||
bzwkDataCtnt = StringUtil.chunkString(bzwkDataCtnt, 500);
|
||||
try {
|
||||
encbzwkDataCtnt = ScpDbAgentUtil.ScpEncB64(
|
||||
PropManager.getInstance().getProperties(ScpDbAgentUtil.EXT_MODULE), bzwkDataCtnt, "EUC-KR");
|
||||
bzwkDataCtnt = encbzwkDataCtnt;
|
||||
} catch (Exception e) {
|
||||
logger.error("addInboundErrorInfo] scpdb_agent encryption Error - " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bzwkDataCtnt = StringUtil.chunkString(bzwkDataCtnt, 4000);
|
||||
}
|
||||
|
||||
try {
|
||||
vo.setErrTm((vo.getErrTm()).substring(0, 16));
|
||||
vo.setErrTxt(StringUtil.chunkString(vo.getErrTxt(), 4000));
|
||||
@@ -74,6 +43,17 @@ public class InboundErrorInfoDAO extends BaseDAO {
|
||||
}
|
||||
vo.setBzwkSvcKeyName(bizKey);
|
||||
|
||||
String bzwkDataCtnt = vo.getBwkDataTxt();
|
||||
// db 암호화
|
||||
bzwkDataCtnt = EncryptionManager.getInstance().encryptDBData(bzwkDataCtnt);
|
||||
vo.setBwkDataTxt(bzwkDataCtnt);
|
||||
|
||||
if (!EAIDBLogControl.isEnable()) {
|
||||
logger.warn("DB logging is disabled.");
|
||||
logger.error(vo.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
InboundErrorInfo entity = inboundErrorInfoMapper.toEntity(vo);
|
||||
inboundErrorInfoLoader.save(entity);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -4,9 +4,6 @@ import java.util.Calendar;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.context.ElinkTransactionContext;
|
||||
import com.eactive.eai.common.util.*;
|
||||
@@ -25,7 +22,6 @@ import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.common.bizkey.BizKeyManager;
|
||||
import com.eactive.eai.common.bizkey.BizKeyVO;
|
||||
import com.eactive.eai.common.dao.DAOFactory;
|
||||
import com.eactive.eai.common.errorcode.ErrorCodeHandler;
|
||||
import com.eactive.eai.common.exception.ExceptionHandler;
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
@@ -59,9 +55,6 @@ import com.eactive.eai.message.service.InterfaceMapper;
|
||||
import com.eactive.eai.util.SimHeaderUtil;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
//import org.apache.commons.lang3.StringUtils;
|
||||
//import org.springframework.http.HttpStatus;
|
||||
|
||||
@@ -129,8 +122,8 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
String testMasterYn = adptGrpVO.getTestMasterYn();
|
||||
|
||||
// UUID 생성 : UUID에서 - 없는 32자리
|
||||
String uuid = "";
|
||||
uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
|
||||
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
|
||||
uuid = uuid == null ? UUIDGenerator.getUUID().toString().replaceAll("-", "") : uuid;
|
||||
// UUID 생성 : UUID = server구분4자리 + UUID
|
||||
/*
|
||||
String uuid = "";
|
||||
@@ -464,7 +457,9 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
logUnknownMessage(vo,orgMessage);
|
||||
return getInboundErrorResponse(prop, standardMessage, mapper, vo.getRspErrorCode(), vo.getRspErrorMsg(), inboundCharset);
|
||||
}
|
||||
|
||||
|
||||
eaiMsg.setCallProp(prop);
|
||||
|
||||
// set standardMessage & mapper to EAIMessage
|
||||
eaiMsg.setStandardMessage(standardMessage);
|
||||
eaiMsg.setMapper(mapper);
|
||||
@@ -475,7 +470,9 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
if(svrLogLevel >= 1) {
|
||||
if (esbLogger.isInfo()) esbLogger.info("RequestProcessor] started... - EAI Server Log Level is INFO");
|
||||
}
|
||||
|
||||
|
||||
afterFoundEaiMessage(eaiMsg, vo, prop);
|
||||
|
||||
// 3-1. 수신메시지와 초기값을 EAIMessage에 설정한다.
|
||||
// 메시지 수신시각 설정
|
||||
eaiMsg.setMsgRcvTm(vo.getErrTm());
|
||||
@@ -956,10 +953,7 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
// 거래통제 대상 거래일 경우 '1'로 설정한다.
|
||||
eaiMsg.setSvcTmEn(EAIMessageKeys.YES_FLAG);
|
||||
vo.setRspErrorCode(EAIMessageKeys.EAI_BLOCKED_CODE);
|
||||
vo.setRspErrorMsg(ExceptionUtil.make(vo.getRspErrorCode(), new String[]{vo.getEaiSvcCd()}));
|
||||
|
||||
// eaiMsg.setRspErrCd(vo.getRspErrorCode());
|
||||
// eaiMsg.setRspErrMsg(vo.getRspErrorMsg());
|
||||
vo.setRspErrorMsg(buildRestrictErrorMsg(eaiMsg, prop));
|
||||
eaiMsg.setRspErr(vo.getRspErrorCode(),vo.getRspErrorMsg());
|
||||
|
||||
// 거래통제 로그에 로그생성
|
||||
@@ -1033,9 +1027,8 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
Bucket bucket = InflowControlUtil.getBucket();
|
||||
|
||||
String blockedType = "";
|
||||
// Client 유량제어 체크 — clientId가 있는 경우에만
|
||||
String clientBlockedType = checkClientInflow(bucket, clientId);
|
||||
if (clientBlockedType != null) {
|
||||
String customBlockedType = checkCustomInflow(bucket, eaiMsg, prop);
|
||||
if (customBlockedType != null) {
|
||||
blockedType += "C";
|
||||
}
|
||||
// Adapter 유량제어 체크
|
||||
@@ -1066,7 +1059,7 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
InflowTargetVO inflowTargetVO = null;
|
||||
InflowGroupVO inflowGroupVO = null;
|
||||
if (blockedType.startsWith("C")) {
|
||||
inflowTargetVO = resolveClientInflowThreshold(bucket, clientId);
|
||||
inflowTargetVO = resolveCustomInflowThreshold(bucket, eaiMsg, prop);
|
||||
} else if (blockedType.startsWith("A")) {
|
||||
inflowTargetVO = bucket.getAdapterInflowThreashold(vo.getAdapterGroupName());
|
||||
} else if (blockedType.contains("G")) {
|
||||
@@ -1089,7 +1082,7 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
}
|
||||
} else if (inflowTargetVO != null) {
|
||||
String targetBlockedType;
|
||||
if (blockedType.startsWith("C")) targetBlockedType = clientBlockedType;
|
||||
if (blockedType.startsWith("C")) targetBlockedType = customBlockedType;
|
||||
else if (blockedType.startsWith("A")) targetBlockedType = adapterBlockedType;
|
||||
else targetBlockedType = interfaceBlockedType;
|
||||
inflowErrorMsg = buildInflowTargetErrorMsg(inflowTargetVO, targetBlockedType);
|
||||
@@ -1257,9 +1250,10 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
if(svrLogLevel >= 1 && esbLogger.isInfo()) {
|
||||
esbLogger.info(guidLogPrefix + " : FlowControl ROUTING_TYPE [" + prop.getProperty(RouteKeys.ROUTING_TYPE) + "]");
|
||||
}
|
||||
|
||||
|
||||
beforeRoute(eaiMsg, prop);
|
||||
retEaiMsg = FlowRouter.process(eaiMsg, prop);
|
||||
|
||||
retEaiMsg.setCallProp(prop);
|
||||
// 응답메시지에 Error Code기 설정되어 있을 경우
|
||||
// ExceptionHandler를 통해 응답메시지를 생성한다.
|
||||
if(retEaiMsg != null && !MessageUtil.checkRspErrCd(retEaiMsg.getRspErrCd())) {
|
||||
@@ -1285,8 +1279,9 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
throw new RequestProcessorException(vo.getRspErrorMsg());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : StandardMessage -> SubMessage
|
||||
afterRoute(eaiMsg, retEaiMsg, prop);
|
||||
|
||||
// TODO : StandardMessage -> SubMessage
|
||||
if(Keys.IF_SUBSTANDARD.equals(vo.getStdMsgTypeCode())) {
|
||||
StandardMessage stdMessage = retEaiMsg.getStandardMessage();
|
||||
try {
|
||||
@@ -1398,12 +1393,15 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
}
|
||||
}
|
||||
|
||||
// 클라이언트 유량제어 체크 훅 — 기본 구현은 체크 없음, DJErpRequestProcessor가 오버라이드
|
||||
protected String checkClientInflow(Bucket bucket, String clientId) {
|
||||
protected void afterFoundEaiMessage(EAIMessage eaiMsg, ProcessVO vo, Properties prop) {
|
||||
}
|
||||
|
||||
|
||||
protected String checkCustomInflow(Bucket bucket, EAIMessage eaiMsg, Properties prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected InflowTargetVO resolveClientInflowThreshold(Bucket bucket, String clientId) {
|
||||
protected InflowTargetVO resolveCustomInflowThreshold(Bucket bucket, EAIMessage eaiMsg, Properties prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1420,6 +1418,14 @@ public class RequestProcessor extends RequestProcessorSupport {
|
||||
return String.format("API 호출 한도 초과 [%s]", inflowTargetVO.getName());
|
||||
}
|
||||
|
||||
protected String buildRestrictErrorMsg(EAIMessage eaiMsg, Properties prop) {
|
||||
return ExceptionUtil.make(EAIMessageKeys.EAI_BLOCKED_CODE, new String[]{eaiMsg.getEAISvcCd()});
|
||||
}
|
||||
|
||||
protected void beforeRoute(EAIMessage eaiMsg, Properties prop) {}
|
||||
|
||||
protected void afterRoute(EAIMessage reqMsg, EAIMessage retMsg, Properties prop) {}
|
||||
|
||||
protected String getInboundErrorResponse(Properties prop, StandardMessage standardMessage, InterfaceMapper mapper
|
||||
, String errCode, String errMsg, String charset) {
|
||||
String errorResponse = null;
|
||||
|
||||
@@ -18,6 +18,9 @@ import com.eactive.eai.message.filter.MessageFilter;
|
||||
public class StandardMessage extends StandardItem {
|
||||
private static final long serialVersionUID = 8250470942348479859L;
|
||||
static Logger logger = LoggerFactory.getLogger(StandardMessage.class);
|
||||
|
||||
String eaiSvcCd = null;
|
||||
|
||||
int readPosition = 0;
|
||||
|
||||
private String serviceKey = null;
|
||||
@@ -30,6 +33,14 @@ public class StandardMessage extends StandardItem {
|
||||
|
||||
private MessageFilter flatFilter = null;
|
||||
|
||||
public String getEaiSvcCd() {
|
||||
return eaiSvcCd;
|
||||
}
|
||||
|
||||
public void setEaiSvcCd(String eaiSvcCd) {
|
||||
this.eaiSvcCd = eaiSvcCd;
|
||||
}
|
||||
|
||||
public String getBizDataCharset() {
|
||||
return bizDataCharset;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public class StandardMessageUtil {
|
||||
// API 서비스일경우 PathVariable 지원 추가
|
||||
// ex) /api/account/{no}
|
||||
if (StringUtils.equalsAny(vo.getActionName(), REST_URL_ACTION0, REST_URL_ACTION1, REST_URL_ACTION2)) {
|
||||
logger.debug("getSTDMessageForPathVariable call");
|
||||
standardMessage = stdMessageManager.getSTDMessageForPathVariable(vo.getSelectedKey());
|
||||
}
|
||||
|
||||
@@ -61,6 +62,7 @@ public class StandardMessageUtil {
|
||||
|
||||
standardMessage.setBizData(message, charset);
|
||||
standardManager.getMessageCoordinator().coordinateAfterCreateMessageByRule(standardMessage, vo, prop);
|
||||
logger.debug("coordinateAfterCreateMessageByRule called");
|
||||
return standardMessage;
|
||||
}
|
||||
|
||||
@@ -282,7 +284,7 @@ public class StandardMessageUtil {
|
||||
if (item.getLevel() == curItem.getLevel() + 1) {
|
||||
if (curItem.getType() == StandardType.GRID) {
|
||||
curItem.addArrayItem(0,
|
||||
setRecursiveItems(message, itemList, i, getFullPath(parentPath, curItem.getName())));
|
||||
setRecursiveItems(message, itemList, i, getFullPath(parentPath, curItem.getName() + "[0]")));
|
||||
} else {
|
||||
curItem.addItem(
|
||||
setRecursiveItems(message, itemList, i, getFullPath(parentPath, curItem.getName())));
|
||||
|
||||
@@ -334,6 +334,8 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
|
||||
}
|
||||
if(logger.isDebug()) logger.debug("mapperClass : {}", mapper.getClass().getCanonicalName());
|
||||
|
||||
standardMessage.setBizDataPath(mapper.getPath(InterfaceMapper.BIZ_DATA));
|
||||
|
||||
String versionMapperClass = config.getProperty(VERSION_MAPPER);
|
||||
if(StringUtils.isEmpty(versionMapperClass)) {
|
||||
versionMapperClass = "com.eactive.eai.message.mapper.DefaultMessageMapper";
|
||||
|
||||
@@ -18,7 +18,7 @@ public class DefaultInterfaceMapper implements InterfaceMapper {
|
||||
itemPathMap = map;
|
||||
}
|
||||
|
||||
protected String getPath(String key) {
|
||||
public String getPath(String key) {
|
||||
String path = itemPathMap.get(key);
|
||||
if(StringUtils.isBlank(path)) {
|
||||
logger.debug("message-mapping not defined key = {}", key);
|
||||
|
||||
@@ -34,7 +34,11 @@ public interface InterfaceMapper extends Serializable {
|
||||
String RESPONSE_TYPE = "RESPONSE_TYPE";
|
||||
String SYNC_ASYNC_TYPE = "SYNC_ASYNC_TYPE";
|
||||
|
||||
String BIZ_DATA = "BIZ_DATA";
|
||||
|
||||
public void initPathMap(HashMap<String, String> map);
|
||||
|
||||
public String getPath(String key);
|
||||
//------------------------------------------------------------
|
||||
// 표준전문과 Mapping 되는 항목을 모두 정의해야 함.
|
||||
//------------------------------------------------------------
|
||||
|
||||
@@ -671,6 +671,8 @@ public abstract class DefaultProcess extends Process {
|
||||
StandardMessage resStandardMessage = this.resEaiMsg.getStandardMessage();
|
||||
InterfaceMapper mapper = this.resEaiMsg.getMapper();
|
||||
String responseType = mapper.getResponseType(resStandardMessage);
|
||||
|
||||
// apigw내 오류 없이, 표준헤더내 오류설정된 경우 응답 처리
|
||||
if(!STDMessageKeys.RESPONSE_TYPE_CODE_N.equals(responseType)){
|
||||
String inboudnAdapterGroupName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
|
||||
String inboudnAdapterName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_NAME);
|
||||
@@ -682,11 +684,10 @@ public abstract class DefaultProcess extends Process {
|
||||
AdapterVO inboundAdapterVO = AdapterManager.getInstance().getAdapterVO(inboudnAdapterGroupName, inboudnAdapterName);
|
||||
String errorResponseHandlerClass = AdapterPropManager.getInstance().getProperty(inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
|
||||
if(StringUtils.isBlank(errorResponseHandlerClass)) {
|
||||
this.resEaiMsg.setRspErrCd("RECEAIINA001", false);
|
||||
String errorCode = mapper.getErrorCode(resStandardMessage);
|
||||
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
|
||||
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("Message.Msg_Body.OUTPUT_MSG_DESC"));
|
||||
this.resEaiMsg.setRspErrMsg(String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
|
||||
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("MSG.MAIN_MSG.outp_msg_desc"));
|
||||
this.resEaiMsg.setRspErr("RECEAIINA001", String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -694,10 +695,12 @@ public abstract class DefaultProcess extends Process {
|
||||
|
||||
AdapterErrorMessageHandler adapterErrorMessageHandler = AdapterErrorMessageHandlerFactory.createHandler(errorResponseHandlerClass);
|
||||
|
||||
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, resStandardMessage);
|
||||
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, this.resEaiMsg);
|
||||
if(responseObj != null) {
|
||||
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
|
||||
}
|
||||
|
||||
this.resEaiMsg.setRspErrCd(EAIMessageKeys.BWK_FAILMSG_CODE, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,8 +776,9 @@ public abstract class DefaultProcess extends Process {
|
||||
if(this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP) instanceof Map) {
|
||||
Map<String, Object> map = (Map<String, Object>) this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP);
|
||||
this.resObject = map.get(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE);
|
||||
this.callProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
|
||||
}
|
||||
standardMessage.setBizData(this.resObject, this.inboundCharset);
|
||||
standardMessage.setBizData(this.resObject, this.inboundCharset);
|
||||
try {
|
||||
//mapper.nextGuidSeq(standardMessage);
|
||||
mapper.setSendRecvDivision(standardMessage, STDMessageKeys.SEND_RECV_CD_RECV);
|
||||
|
||||
@@ -4,11 +4,17 @@ import java.net.SocketTimeoutException;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.circuitBreaker.CircuitBreakerManager;
|
||||
import com.eactive.eai.common.circuitBreaker.type.TypedCircuitBreakerManager;
|
||||
import com.eactive.eai.util.PropertiesUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||
@@ -99,7 +105,32 @@ public class HTTPProcess extends DefaultProcess {
|
||||
this.timeout = iTimeoutValue * 1000;
|
||||
this.tempProp.put(INTERFACE_TIME_OUT, "" + this.timeout);
|
||||
try {
|
||||
this.resObject = HttpSender.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp);
|
||||
// API별 이용
|
||||
String apiId = reqEaiMsg.getEAISvcCd();
|
||||
CircuitBreaker circuitBreaker = TypedCircuitBreakerManager.getInstance().getCircuitBreaker(apiId);
|
||||
|
||||
// 디폴트 이용
|
||||
if(circuitBreaker == null) {
|
||||
String circuitBreakerUseYn = PropManager.getInstance().getProperty("CircuitBreaker", "useYn");
|
||||
if ("Y".equals(circuitBreakerUseYn))
|
||||
circuitBreaker = CircuitBreakerManager.getInstance().getCircuitBreaker(apiId);
|
||||
}
|
||||
|
||||
if (circuitBreaker != null) {
|
||||
// ObjectMapper 인스턴스 생성
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 서킷 브레이커의 메트릭 정보와 상태 정보를 직접 Node로 변환
|
||||
ObjectNode combinedNode = objectMapper.createObjectNode();
|
||||
combinedNode.put("state", circuitBreaker.getState().toString());
|
||||
combinedNode.set("metrics", objectMapper.valueToTree(circuitBreaker.getMetrics()));
|
||||
|
||||
logger.info("CircuitBreaker state " + combinedNode.toString());
|
||||
this.resObject = circuitBreaker.executeCallable(() -> HttpSender
|
||||
.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp));
|
||||
} else {
|
||||
this.resObject = HttpSender.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp);
|
||||
}
|
||||
// 응답코드에 대한 처리
|
||||
this.resEaiMsg = setResponseForOutbound(this.resEaiMsg);
|
||||
if (!com.eactive.eai.adapter.Keys.IF_STANDARD.equals(this.adptrMsgPtrnCd)) {
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
package com.eactive.eai.outbound;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.circuitBreaker.CircuitBreakerManager;
|
||||
import com.eactive.eai.common.circuitBreaker.type.TypedCircuitBreakerManager;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.eactive.eai.util.PropertiesUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.initech.core.util.PropertiesManager;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||
import com.eactive.eai.adapter.AdapterManager;
|
||||
import com.eactive.eai.adapter.AdapterPropManager;
|
||||
import com.eactive.eai.adapter.AdapterVO;
|
||||
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
|
||||
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandlerFactory;
|
||||
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.HttpAdapterServiceSupport;
|
||||
import com.eactive.eai.common.EAIKeys;
|
||||
import com.eactive.eai.common.TransactionContextKeys;
|
||||
import com.eactive.eai.common.circuitBreaker.CircuitBreakerManager;
|
||||
import com.eactive.eai.common.circuitBreaker.type.TypedCircuitBreakerManager;
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.header.HeaderAction;
|
||||
import com.eactive.eai.common.header.HeaderActionFactory;
|
||||
@@ -43,14 +39,16 @@ import com.eactive.eai.common.worker.ResponseMap;
|
||||
import com.eactive.eai.control.HttpSender;
|
||||
import com.eactive.eai.control.LogSender;
|
||||
import com.eactive.eai.control.Transform;
|
||||
import com.eactive.eai.custom.alarm.AlarmStateManager;
|
||||
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
|
||||
import com.eactive.eai.custom.alarm.event.AlarmEvent;
|
||||
import com.eactive.eai.custom.alarm.key.AlarmKey;
|
||||
import com.eactive.eai.custom.alarm.key.CoreAlarmKey;
|
||||
import com.eactive.eai.env.ElinkConfig;
|
||||
import com.eactive.eai.inbound.processor.Processor;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
import com.eactive.eai.util.PropertiesUtil;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
|
||||
public class RESTProcess extends HTTPProcess {
|
||||
static final long serialVersionUID = 1L;
|
||||
@@ -148,6 +146,16 @@ public class RESTProcess extends HTTPProcess {
|
||||
logger.error(guidLogPrefix + rspErrorMsg, hse);
|
||||
this.resEaiMsg.setRspErr(rspErrorCode,rspErrorMsg);
|
||||
throw new RuntimeException(rspErrorCode);
|
||||
|
||||
} catch (CallNotPermittedException e) {
|
||||
String[] msgArgs = new String[1];
|
||||
msgArgs[0] = this.reqEaiMsg.getEAISvcCd();
|
||||
String rspErrorCode = "RECEAIOHP201";
|
||||
String rspErrorMsg = ExceptionUtil.make(e, rspErrorCode, msgArgs);
|
||||
if (logger.isError())
|
||||
logger.error(guidLogPrefix + rspErrorMsg, e);
|
||||
this.resEaiMsg.setRspErr(rspErrorCode,rspErrorMsg);
|
||||
throw new RuntimeException(rspErrorCode);
|
||||
} catch (Exception e) {
|
||||
String[] msgArgs = new String[1];
|
||||
msgArgs[0] = this.reqEaiMsg.getEAISvcCd();
|
||||
@@ -522,6 +530,7 @@ public class RESTProcess extends HTTPProcess {
|
||||
|
||||
String url = StringUtils.removeEnd(simAddr, "/") + "/mockapi/" + this.reqEaiMsg.getEAISvcCd();
|
||||
this.outboundProp.put(HttpClientAdapterServiceKey.URL, url);
|
||||
this.tempProp.put("tranType", reqEaiMsg.getTranType());
|
||||
this.tempProp.remove(HttpAdapterServiceKey.INBOUND_REWRITE_PATH);
|
||||
logger.debug("simUrl=" + url);
|
||||
}
|
||||
@@ -681,5 +690,44 @@ public class RESTProcess extends HTTPProcess {
|
||||
public void storeMessage() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
public void setOutboundErrorMessage() throws Exception {
|
||||
this.logPssSno = TranLogUtil.getResLogSeq(this.svcPssTp, this.svcPssSeq, this.isComp);
|
||||
|
||||
boolean setResObject = false;
|
||||
AdapterGroupVO outboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
||||
if (outboundAdapterGroupVO != null) {
|
||||
AdapterVO outboundAdapterVo = null;
|
||||
if(adapterName != null)
|
||||
outboundAdapterVo = outboundAdapterGroupVO.getAdapterVO(adapterName);
|
||||
else
|
||||
outboundAdapterVo = outboundAdapterGroupVO.nextAdapterVO();
|
||||
|
||||
if (outboundAdapterVo != null) {
|
||||
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
|
||||
outboundAdapterVo.getPropGroupName(), "ERR_MSG_HANDLER");
|
||||
if (StringUtils.isNotBlank(errorHandlerClass)) {
|
||||
if (logger.isInfo())
|
||||
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
|
||||
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
|
||||
this.resObject = handler.generateOutboundErrorResponseMessage(adapterGroupName, adapterGroupName,
|
||||
callProp, this.reqObject, this.resEaiMsg);
|
||||
setResObject = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!setResObject) {
|
||||
this.resObject = "";
|
||||
}
|
||||
StandardMessage standardMessage = this.resEaiMsg.getStandardMessage();
|
||||
standardMessage.setBizData(this.resObject, this.inboundCharset);
|
||||
|
||||
// 에러에 대한 응답메시지
|
||||
if(this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP) instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP);
|
||||
this.callProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.eactive.eai.util;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
@@ -10,34 +12,70 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
// 해당 필드에 대한 get/set 을 수행
|
||||
public class JsonPathUtil {
|
||||
private JsonPathUtil() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// JsonNode 기반 단일 파싱 API — 연속 get/set 시 파싱 횟수 절감
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/** JSON 문자열을 JsonNode 트리로 파싱. */
|
||||
public static JsonNode toTree(Object json) throws Exception {
|
||||
if(json == null)
|
||||
return null;
|
||||
else if(json instanceof JsonNode)
|
||||
return (JsonNode)json;
|
||||
else if(json instanceof String)
|
||||
return objectMapper.readTree((String)json);
|
||||
else if(json instanceof JSONObject)
|
||||
return objectMapper.readTree(((JSONObject)json).toJSONString());
|
||||
else
|
||||
return objectMapper.readTree(json.toString());
|
||||
}
|
||||
|
||||
/** 이미 파싱된 트리에서 경로의 값을 추출. */
|
||||
public static String getAt(JsonNode root, String path) {
|
||||
JsonNode node = getNodeAtPath(root, path, "/");
|
||||
return node != null ? node.asText() : null;
|
||||
}
|
||||
|
||||
/** 이미 파싱된 트리에서 경로의 값을 추출. */
|
||||
public static String getAt(JsonNode root, String path, String delimeter) {
|
||||
JsonNode node = getNodeAtPath(root, path, delimeter);
|
||||
return node != null ? node.asText() : null;
|
||||
}
|
||||
|
||||
/** 이미 파싱된 트리의 경로에 값을 설정 (in-place). */
|
||||
public static void setAt(JsonNode root, String path, String value) {
|
||||
JsonNode parent = getParentNode(root, getParentPath(path));
|
||||
if (parent == null) return;
|
||||
String key = getFieldName(path);
|
||||
if (parent.isArray()) {
|
||||
((ArrayNode) parent).set(Integer.parseInt(key), objectMapper.convertValue(value, JsonNode.class));
|
||||
} else if (parent.isObject()) {
|
||||
((ObjectNode) parent).put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/** JsonNode 트리를 JSON 문자열로 직렬화. */
|
||||
public static String fromTree(JsonNode root) throws Exception {
|
||||
return objectMapper.writeValueAsString(root);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// 문자열 기반 API (내부적으로 위 메서드에 위임)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// 특정 경로의 값을 설정하는 함수
|
||||
public static String setValueAtPath(String jsonString, String path, String newValue, boolean isPretty) {
|
||||
try {
|
||||
// JSON 문자열을 JsonNode 객체로 파싱
|
||||
JsonNode rootNode = objectMapper.readTree(jsonString);
|
||||
|
||||
// 경로에 해당하는 부모 노드를 가져옴
|
||||
JsonNode parentNode = getParentNode(rootNode, getParentPath(path));
|
||||
|
||||
// 경로의 마지막 노드 이름 또는 배열 인덱스 추출
|
||||
String fieldNameOrIndex = getFieldName(path);
|
||||
|
||||
// 배열 또는 객체에서 값을 설정
|
||||
if (parentNode.isArray()) {
|
||||
((ArrayNode) parentNode).set(Integer.parseInt(fieldNameOrIndex), objectMapper.convertValue(newValue, JsonNode.class));
|
||||
} else if (parentNode.isObject()) {
|
||||
((ObjectNode) parentNode).put(fieldNameOrIndex, newValue);
|
||||
}
|
||||
|
||||
// 수정된 JSON 문자열 반환
|
||||
setAt(rootNode, path, newValue);
|
||||
if(isPretty)
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
|
||||
else
|
||||
else
|
||||
return objectMapper.writeValueAsString(rootNode);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -48,13 +86,7 @@ public class JsonPathUtil {
|
||||
// 특정 경로의 값을 가져오는 함수
|
||||
public static String getValueAtPath(String jsonString, String path) {
|
||||
try {
|
||||
// JSON 문자열을 JsonNode 객체로 파싱
|
||||
JsonNode rootNode = objectMapper.readTree(jsonString);
|
||||
|
||||
// 경로에 해당하는 노드의 값을 반환
|
||||
JsonNode resultNode = getNodeAtPath(rootNode, path);
|
||||
return resultNode != null ? resultNode.asText() : null;
|
||||
|
||||
return getAt(objectMapper.readTree(jsonString), path);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
@@ -75,12 +107,12 @@ public class JsonPathUtil {
|
||||
|
||||
// 특정 경로에 해당하는 부모 노드를 가져오는 유틸리티 함수
|
||||
private static JsonNode getParentNode(JsonNode rootNode, String parentPath) {
|
||||
return getNodeAtPath(rootNode, parentPath);
|
||||
return getNodeAtPath(rootNode, parentPath, "/");
|
||||
}
|
||||
|
||||
// 특정 경로에 해당하는 노드를 가져오는 유틸리티 함수
|
||||
private static JsonNode getNodeAtPath(JsonNode rootNode, String path) {
|
||||
String[] tokens = path.split("/");
|
||||
private static JsonNode getNodeAtPath(JsonNode rootNode, String path, String delimeter) {
|
||||
String[] tokens = path.split(delimeter);
|
||||
JsonNode currentNode = rootNode;
|
||||
|
||||
for (String token : tokens) {
|
||||
@@ -101,6 +133,71 @@ public class JsonPathUtil {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미 파싱된 bodyNode에서 removeFromPath 필드를 제거하고 mergeJson을 병합.
|
||||
* body의 재파싱 없이 호출 가능하여 파싱 횟수를 1회 절감.
|
||||
*
|
||||
* body와 mergeJson이 모두 JSON 객체인 경우에만 병합을 수행한다.
|
||||
* 어느 한쪽이 JSON 객체가 아니면 mergeJson을 그대로 반환한다 (전체 교체 fallback).
|
||||
*
|
||||
* @param bodyNode 이미 파싱된 원본 JsonNode
|
||||
* @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
|
||||
* @param mergeJson 병합할 JSON 문자열
|
||||
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
|
||||
*/
|
||||
public static String mergeAtRoot(JsonNode bodyNode, String removeFromPath, String mergeJson) {
|
||||
try {
|
||||
JsonNode mergeNode = objectMapper.readTree(mergeJson);
|
||||
|
||||
if (!bodyNode.isObject() || !mergeNode.isObject()) {
|
||||
return mergeJson;
|
||||
}
|
||||
|
||||
ObjectNode result = (ObjectNode) bodyNode.deepCopy();
|
||||
|
||||
// removeFromPath 필드 제거
|
||||
int lastSlash = removeFromPath.lastIndexOf('/');
|
||||
String fieldName = removeFromPath.substring(lastSlash + 1);
|
||||
if (lastSlash == 0) {
|
||||
result.remove(fieldName);
|
||||
} else {
|
||||
JsonNode parent = getNodeAtPath(result, removeFromPath.substring(0, lastSlash), "/");
|
||||
if (parent instanceof ObjectNode) {
|
||||
((ObjectNode) parent).remove(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
// mergeJson 필드를 body에 병합 (기존 필드 덮어쓰기)
|
||||
result.setAll((ObjectNode) mergeNode);
|
||||
return objectMapper.writeValueAsString(result);
|
||||
} catch (Exception e) {
|
||||
return mergeJson;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* body에서 removeFromPath 필드를 제거한 뒤, mergeJson의 필드를 body에 병합하여 반환한다.
|
||||
* (문자열 기반 overload — 내부적으로 body를 1회 파싱하여 위 메서드에 위임)
|
||||
*
|
||||
* 사용 예 (toPath="/" 복호화):
|
||||
* body = {"encryptedData":"...","requestId":"REQ001"}
|
||||
* removeFrom = /encryptedData
|
||||
* mergeJson = {"userId":"U001","name":"test"}
|
||||
* 결과 = {"requestId":"REQ001","userId":"U001","name":"test"}
|
||||
*
|
||||
* @param body 원본 JSON 문자열
|
||||
* @param removeFromPath 제거할 필드의 경로 (예: /encryptedData)
|
||||
* @param mergeJson 병합할 JSON 문자열
|
||||
* @return 병합된 JSON 문자열, 병합 불가 시 mergeJson 그대로 반환
|
||||
*/
|
||||
public static String mergeAtRoot(String body, String removeFromPath, String mergeJson) {
|
||||
try {
|
||||
return mergeAtRoot(objectMapper.readTree(body), removeFromPath, mergeJson);
|
||||
} catch (Exception e) {
|
||||
return mergeJson;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String jsonString = "{ \"person\": { \"name\": \"John\", \"age\": 30, \"groups\": [ { \"id\": 1, \"name\": \"Group A\" }, { \"id\": 2, \"name\": \"Group B\" }, { \"id\": 3, \"name\": \"Group C\" } ] } }";
|
||||
|
||||
|
||||
+1386
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user