1 Commits

Author SHA1 Message Date
curry772 0d41170b67 config: local reposilite 환경으로 변경 2026-04-03 17:46:57 +09:00
159 changed files with 1501 additions and 19761 deletions
+2 -6
View File
@@ -82,14 +82,12 @@ dependencies {
api 'com.nimbusds:nimbus-jose-jwt:9.24.3'
api 'org.bouncycastle:bcprov-jdk15on:1.70'
api "io.undertow:undertow-servlet:${undertowVersion}"
api 'org.java-websocket:Java-WebSocket:1.3.9'
api 'javax.cache:cache-api:1.1.1'
api 'org.apache.ignite:ignite-slf4j:2.14.0'
api 'org.apache.ignite:ignite-kubernetes:2.14.0'
api 'org.apache.ignite:ignite-slf4j:2.16.0'
api 'org.apache.ignite:ignite-kubernetes:2.16.0'
// ignite 2.17 - JDK 11 이상 필요
// api 'org.apache.ignite:ignite-slf4j:2.17.0'
// api 'org.apache.ignite:ignite-kubernetes:2.17.0'
@@ -135,8 +133,6 @@ 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.
+29
View File
@@ -0,0 +1,29 @@
#-----------------------------------------------------------------------------
# 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 #-----------------------------------------------------------------------------
2 # comment start with #
3 # level; // 0 : root ~ n
4 # type; // 0: Message, 1: Field, 2:Group 3, Grid, 4 : Field Array, 9 : BIZ DATA
5 # size // 0 : variable, > 0 fixed
6 # fieldType; // 0: ELEMENT, 1: ATTRIBUTE,
7 # length;
8 # dataType; // 1: String, 2: Number,11: ZZ String, 12: LL Number
9 #-----------------------------------------------------------------------------
10 # name ,level ,type ,size ,fieldType ,length ,dataType ,,refPath, refValue,value
11 #-----------------------------------------------------------------------------
12 ElinkHeader ,0,0,1,0, 0,0,,,
13 Header ,1,2,1,1, 0,0,,,
14 StndCicsTrncd ,2,1,1,1,10,0,,,
15 StndIntnlStndTelgmLen,2,1,1,1,10,11,,,0
16 StndTranBaseYmd ,2,1,1,1,10,0,,,
17 Common ,1,2,0,0, 0,0,Header.StndCicsTrncd,JI6H,null
18 TranInfo ,2,2,1,0, 0,0,,,
19 StndCicsTrncd ,3,1,1,0, 3,0,,,
20 StndTelgmRecvTranCd ,3,1,1,0,10,0,,,
21 StndPrcssRtdTranCd ,3,1,1,0,10,0,,,
22 Array ,1,3,0,0, 3,0,,,
23 Item1 ,2,1,1,0,10,0,,,JI6H
24 Group ,2,3,1,0, 1,0,,,
25 gitem1 ,3,1,1,0,10,1,,,726
26 gitem2 ,3,1,1,0,10,0,,,20220905
27 FArray ,1,4,0,0,10,0,,,
28 bizData ,1,9,1,0, 0,0,,,
29 zzData ,1,1,1,0, 2,10,,,ZZ
+41
View File
@@ -0,0 +1,41 @@
#-----------------------------------------------------------------------------
# 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 #-----------------------------------------------------------------------------
2 # comment start with #
3 # level; // 0 : root ~ n
4 # type; // 0: Message, 1: Field, 2:Group 3, Grid, 4 : Field Array, 9 : BIZ DATA
5 # size // 0 : variable, > 0 fixed
6 # fieldType; // 0: ELEMENT, 1: ATTRIBUTE,
7 # length;
8 # dataType; // 1: String, 2: Number,11: ZZ String, 12: LL Number
9 #-----------------------------------------------------------------------------
10 # name ,level ,type ,size ,fieldType ,length ,dataType ,,refPath, refValue,value
11 #-----------------------------------------------------------------------------
12 # name ,level ,type ,size ,fieldType ,length ,dataType ,refPath, refValue, value
13 STD_HNCPHEADER_REQ ,0 ,1 ,1 ,1 ,0 ,1 , , ,,표준헤더
14 msg_len ,1 ,2 ,1 ,1 ,6 ,12 , , ,199,전문길이
15 global_id ,1 ,2 ,1 ,1 ,29 ,1 , , ,GIDNO00000000001111,Global아이디
16 service_id ,1 ,2 ,1 ,1 ,10 ,1 , , ,ELINKSVC01,거래코드
17 service_cmt ,1 ,2 ,1 ,1 ,100 ,1 , , ,ELINK TEST Service,거래코드설명
18 service_tn ,1 ,2 ,1 ,1 ,1 ,1 , , ,1,거래구분
19 Session_id ,1 ,2 ,1 ,1 ,100 ,1 , , ,SESSIONID001,Session아이디
20 encrypt_yn ,1 ,2 ,1 ,1 ,2 ,1 , , ,NO,전문암호화여부
21 http_url ,1 ,2 ,1 ,1 ,100 ,1 , , ,,HTTPURL정보
22 screen_id ,1 ,2 ,1 ,1 ,13 ,1 , , ,SCREEN0000001,화면ID
23 scr_no ,1 ,2 ,1 ,1 ,30 ,1 , , ,000000000100000000020000000003,화면번호
24 scr_dsc ,1 ,2 ,1 ,1 ,100 ,1 , , ,,화면명
25 usr_id ,1 ,2 ,1 ,1 ,10 ,1 , , ,USER000001,사용자ID
26 ip_addr ,1 ,2 ,1 ,1 ,47 ,1 , , ,,IPAddress
27 ip_mac ,1 ,2 ,1 ,1 ,12 ,1 , , ,,ClientMAC Address
28 com_code ,1 ,2 ,1 ,1 ,4 ,1 , , ,EACT,그룹사코드
29 head_code ,1 ,2 ,1 ,1 ,7 ,1 , , ,EACT001,본부코드
30 dept_cd ,1 ,2 ,1 ,1 ,7 ,1 , , ,ELINK01,부서코드
31 conn_tp ,1 ,2 ,1 ,1 ,2 ,1 , , ,CH,요청채널구분
32 Inter_chn ,1 ,2 ,1 ,1 ,2 ,1 , , ,EA,시스템구분
33 recv_tm ,1 ,2 ,1 ,1 ,17 ,1 , , ,20220928000000000,전문수신시간
34 send_tm ,1 ,2 ,1 ,1 ,17 ,1 , , ,,전문송신시간
35 return_gubun ,1 ,2 ,1 ,1 ,1 ,1 , , ,S,전문응답구분
36 return_value ,1 ,2 ,1 ,1 ,1 ,1 , , ,,처리결과
37 msg_id ,1 ,2 ,1 ,1 ,7 ,1 , , ,,오류코드
38 msg_nm ,1 ,2 ,1 ,1 ,250 ,1 , , ,,오류메시지
39 filler ,1 ,2 ,1 ,1 ,125 ,1 , , ,,Filler
40 bizData ,1 ,9 ,0 ,1 ,0 ,1 , , ,,업무데이터
41 zzData ,1 ,2 ,0 ,1 ,2 ,11 , , ,,ZZ플래그
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,20 @@
# 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
@@ -0,0 +1,28 @@
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
@@ -0,0 +1,26 @@
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
@@ -0,0 +1,4 @@
service_id=header.if_id
global_id=header.guid
service_tn=header.req_type
bizData=bizData
@@ -6,7 +6,6 @@ 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;
@@ -164,9 +163,6 @@ 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,50 +2,9 @@ package com.eactive.eai.adapter.handler;
import java.util.Properties;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.message.StandardMessage;
public interface AdapterErrorMessageHandler {
/**
* 비표준 -> 표준 거래
* 아웃바운드 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;
}
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName, Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception;
}
@@ -2,25 +2,13 @@ package com.eactive.eai.adapter.handler;
import java.util.Properties;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.message.StandardMessage;
public class DefaultAdapterErrorMessageHandler implements AdapterErrorMessageHandler {
@Override
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
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 {
Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception {
return null;
}
@@ -1,533 +0,0 @@
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.FilterCryptoException;
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 인코딩된 엔티티를 복원 (예: &#35; → #)
template = template.replace("&#35;", "#")
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&quot;", "\"");
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 FilterCryptoException) {
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e, 550);
} else 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;
}
}
@@ -1,126 +0,0 @@
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);
}
}
}
@@ -41,12 +41,6 @@ public class HttpStatusException extends Exception {
this.status = status;
}
public HttpStatusException(String msg, String code, int status, Throwable cause) {
super(msg, cause);
this.setCode(code);
this.status = status;
}
public int getStatus() {
return status;
}
@@ -40,9 +40,7 @@ import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.Keys;
import com.eactive.eai.adapter.http.secure.HttpClient5SSLContextFactory;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.message.EAIMessageKeys;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.HttpAdapterExtraLogUtil;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.httpouttlsinfo.HttpOutTlsInfoVO;
@@ -55,8 +53,6 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
private static boolean testMode = TestModeChecker.isTestMode();
private static boolean httpHeaderLogMode = HttpAdapterExtraLogUtil.isHttpHeaderMode();
public synchronized void close() {
if(connectionManager != null) {
connectionManager.close();
@@ -370,20 +366,8 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
}
else {
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
SSLConnectionSocketFactory sslSocketFactory = null;
if(testMode) {
// Hostname verifier 비활성화 (테스트용)
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
, NoopHostnameVerifier.INSTANCE
);
}
else {
sslSocketFactory = new SSLConnectionSocketFactory(
sslContext
);
}
cmBuilder.setSSLSocketFactory(sslSocketFactory);
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
cmBuilder.setSSLSocketFactory(csf);
}
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException
@@ -400,11 +384,6 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
HttpRequestInterceptor requestLogInterceptor = (request, entity, context) -> {
if (!httpHeaderLogMode) {
logger.info("httpHeader logging off - use.http.header.log");
return;
}
try {
String uuid = (String) context.getAttribute(TransactionContextKeys.TRANSACTION_UUID);
String contextAdapterGroupName = (String) context.getAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
@@ -424,12 +403,7 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
};
HttpResponseInterceptor responseLogInterceptor = (response, entity, context) -> {
if (!httpHeaderLogMode) {
logger.info("httpHeader logging off - use.http.header.log");
return;
}
try{
try{
HttpRequest request = (HttpRequest)context.getAttribute("http.request");
String url = request.getUri().toString();
String uuid = (String) context.getAttribute(TransactionContextKeys.TRANSACTION_UUID);
@@ -568,10 +542,4 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
}
public abstract Object execute(Properties prop, Object message, Properties tempProp) throws Exception;
protected boolean isTas(Properties tempProp) {
String tranType = tempProp.getProperty("tranType");
return EAIServerManager.getInstance().isTASEnabledEAIServer()
&& StringUtils.equals(tranType, EAIMessageKeys.TRANTYPE_TAS);
}
}
@@ -1,24 +0,0 @@
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);
}
}
@@ -7,8 +7,6 @@ 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;
@@ -43,11 +41,14 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y");
String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL");
if(useForwardProxy && !isTas(tempProp)) {
if(useForwardProxy) {
java.net.URL url = new java.net.URL(forwardProxyUrl);
// 프로토콜, 호스트, 포트 추출
HttpHost proxy = new HttpHost(url.getProtocol(), url.getHost(), url.getPort());
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
HttpHost proxy = new HttpHost(protocol,host, port);
requestConfigBuilder.setProxy(proxy);
}
@@ -120,9 +121,7 @@ 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);
@@ -151,8 +150,7 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
}
stopWatch.stop();
TxFileLogger.logTxFile(tempProp, responseString, "[OUT_RECV]");
if (status >= 400 && status < 500) {
String errMsg = String.format(
@@ -177,8 +175,7 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
+ CommonLib.getDumpMessage(responseMessage));
}
// return responseMessage;
return responseString;
return responseMessage;
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
if (vo.getTraceLevel() >= 3) {
@@ -6,13 +6,11 @@ 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;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +34,7 @@ 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,11 +74,6 @@ 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;
@@ -121,7 +115,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id"; // jwhong
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : ";
private boolean useAdapterToken;
private boolean encryptResponseApply;
@@ -143,6 +137,17 @@ 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");
@@ -188,56 +193,42 @@ 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;
Object sendData = "";
String sendData = "";
if (data instanceof String) {
sendData = data;
sendData = (String) 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());
Object dataObject = null;
JSONObject dataObject = null;
Object httpHeader = null;
//Object dataContent = null;
Object dataContent = null;
if (MessageType.JSON.equals(messageType)) {
Object parsed = JSONUtils.parseJsonGeneric(sendData);
Object parsed = parseJsonGeneric(sendData);
if(parsed instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) parsed;
dataObject = jsonObject;
} else if(parsed instanceof JSONArray) {
// not support
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;
}
}
} else if (MessageType.XML.equals(messageType)) {
Document doc = XMLUtils.convertXmlDocument(sendData);
dataObject = doc;
}
// if (MessageType.JSON.equals(messageType)) {
@@ -254,29 +245,10 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
Map<String, String> inboundPathVariables = (Map<String, String>) tempProp.get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES);
// interface 에 설정된게 우선한다.
String uri = null;
if(this.isTas(tempProp)) {
uri = vo.getUrl();
if (dataObject == null) {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
} else {
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;
}
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
}
logger.debug("HttpClientAdapterServiceRest] dataObject2 = [" + dataObject + "]");
@@ -322,11 +294,14 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
if(useForwardProxy && !isTas(tempProp)) {
if(useForwardProxy) {
java.net.URL url = new java.net.URL(forwardProxyUrl);
// 프로토콜, 호스트, 포트 추출
HttpHost proxy = new HttpHost(url.getProtocol(), url.getHost(), url.getPort());
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
HttpHost proxy = new HttpHost(protocol,host, port);
requestConfigBuilder.setProxy(proxy);
}
@@ -376,18 +351,6 @@ 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);
@@ -421,8 +384,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(dataObject, mimeType, charset, method, chunked);
String inboundToken= assignRequestKJHeaders(method, prop, tempProp );
assignPostBody(dataContent, contentType, vo.getEncode(), method, vo.getAdapterName(), auth, inboundToken, currentDate);
break;
default:
break;
@@ -441,19 +404,17 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
int status = -1;
Header[] responseHeaders = null;
String responseString = null;
try {
byte[] responseMessage = null;
// byte[] responseMessageOri = null;
String responseContentType = null;
byte[] responseMessageOri = null;
Header[] responseHeaders;
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
if (dataObject instanceof JSONObject) {
if (dataObject != null) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = ["
+ ((JSONObject)dataObject).toJSONString() + "]");
+ dataObject.toJSONString() + "]");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(((JSONObject)dataObject).toJSONString()));
+ CommonLib.getDumpMessage(dataObject.toJSONString()));
} else {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData
+ "]");
@@ -468,8 +429,6 @@ 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();
@@ -482,8 +441,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()) {
@@ -508,30 +467,18 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
//responseMessage = EntityUtils.toByteArray(response.getEntity());
// 역기서 responseMessage를 decryption 해야 하나 by jwhong
responseMessage = EntityUtils.toByteArray(response.getEntity());
logger.debug("[responseMessage 바이트 배열 길이 ]" + responseMessage.length);
responseMessageOri = EntityUtils.toByteArray(response.getEntity());
logger.debug("[responseMessageOri 바이트 배열 길이 ]" + responseMessageOri.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())) {
@@ -557,8 +504,16 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
*/
boolean needReissue = false;
responseString = new String(responseMessage, vo.getEncode());
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
String 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) {
@@ -627,35 +582,10 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
+ ") RETRY TOKEN = [" + newaccessToken + "]");
}
// 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)) {
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
status = response.getCode();
responseMessage = EntityUtils.toByteArray(response.getEntity());
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]");
// 여기서도 decryption 해야 할듯 하나.. by jwhong
if (logger.isDebug()) {
logger.debug("[received status]" + status);
@@ -699,14 +629,12 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
String jsonStr = new String(responseMessage, vo.getEncode());
if ( jsonStr.trim().startsWith("[") ) {
jsonStr = DJB_ROOTLESS_ARRAY + jsonStr + "}";
jsonStr = KJB_ROOTLESS_ARRAY + jsonStr + "}";
responseMessage = jsonStr.getBytes(vo.getEncode());
}
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), responseContentType,
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType,
relayResponseHeaderKeys, status, responseHeaderProp);
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
if (vo.getTraceLevel() >= 3) {
@@ -734,10 +662,6 @@ 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) {
@@ -751,15 +675,6 @@ 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) {
@@ -786,99 +701,50 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
* @throws Exception
*/
@SuppressWarnings("unchecked")
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)
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)
|| StringUtils.isBlank(relayHeaderKeys)) {
return new String(responseMessage, charset);
return new String(responseMessage, encode);
}
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);
}
}
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
JSONObject headerJson = new JSONObject();
for (String key : relayKeyArr) {
String value = responseHeaderProp.getProperty(key);
if (StringUtils.isNotBlank(value)) {
headerJson.put(key, value);
}
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);
}
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));
}
}
if (StringUtils.isNotBlank(headerGroupName) && message != null)
message.put(headerGroupName, headerJson);
return message.toJSONString();
}
protected void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
method.setHeader("Authorization",
String.format("%s", authorization));
}
protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
method.setHeader("Authorization",
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
}
@@ -945,7 +811,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
return false;
}
protected void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
if (httpHeader != null) {
if (httpHeader instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) httpHeader;
@@ -999,7 +865,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
}
protected void assignFilterHeaders(HttpUriRequestBase method, Properties tempProp) {
private void assignFilterHeaders(HttpUriRequestBase method, Properties tempProp) {
try {
Properties filterHeaders = (Properties)tempProp.get("FILTERHEADER");
if (filterHeaders != null){
@@ -1092,64 +958,45 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
return map;
}
protected String assignPostBody(Object eaiBody, String mimeType, String charset, HttpUriRequestBase method,
boolean chunked) throws Exception {
@SuppressWarnings("deprecation")
private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method, String adapterName, String niceAuth, String inboundToken, Date currentDate) throws Exception {
if (eaiBody == null) {
return "";
return;
}
if (eaiBody instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) eaiBody;
if( jsonObject.containsKey("DJB_ROOTLESS_ARRAY") ) {
eaiBody = jsonObject.get("DJB_ROOTLESS_ARRAY");
if( jsonObject.containsKey("KJB_ROOTLESS_ARRAY") ) {
eaiBody = jsonObject.get("KJB_ROOTLESS_ARRAY");
}
}
if (StringUtils.contains(mimeType, "application/x-www-form-urlencoded")) {
List<NameValuePair> params = new ArrayList<>();
if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) {
if (eaiBody instanceof JSONObject) {
List<NameValuePair> params = new ArrayList<>();
JSONObject jsonObject = (JSONObject) eaiBody;
for (Object key : jsonObject.keySet()) {
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
}
} 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(mimeType, charset);
// 요청 본문을 StringEntity 객체로 생성
String body = "";
if (eaiBody instanceof Document) {
body = ((Document)eaiBody).asXML();
} else if(eaiBody instanceof JSONObject) {
body = ((JSONObject)eaiBody).toJSONString();
} else {
body = eaiBody.toString();
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);
}
StringEntity entity = new StringEntity(body, contentTypeObj, chunked);
} else {
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);
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);
}
}
@@ -1679,26 +1526,23 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
uriVariables.add(fieldName);
}
if (MessageType.JSON.equals(messageType)) {
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);
// }
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);
}
if (StringUtils.isBlank(uriVariable) && inboundPathVariables.containsKey(urlVaribleId)) {
uriVariable = inboundPathVariables.get(urlVaribleId);
}
urlVariableList.add(uriVariable);
}
@@ -1,142 +0,0 @@
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;
}
}
@@ -1,121 +0,0 @@
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;
}
}
@@ -1,118 +0,0 @@
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;
}
}
@@ -1,90 +0,0 @@
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;
}
}
}
@@ -1,27 +0,0 @@
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;
}
}
}
@@ -1,12 +0,0 @@
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;
}
@@ -1,35 +0,0 @@
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() + "]";
}
}
@@ -1,106 +0,0 @@
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;
}
}
@@ -1,135 +0,0 @@
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;
}
}
@@ -1,154 +0,0 @@
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;
}
}
@@ -1,122 +0,0 @@
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;
}
}
@@ -1,107 +0,0 @@
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;
}
}
@@ -1,138 +0,0 @@
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;
}
}
@@ -1,85 +0,0 @@
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);
}
}
@@ -1,109 +0,0 @@
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;
}
}
@@ -1,28 +0,0 @@
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;
}
}
@@ -1,97 +0,0 @@
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;
}
}
@@ -1,71 +0,0 @@
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;
}
}
@@ -1,897 +0,0 @@
package com.eactive.eai.adapter.http.client.impl.kjb;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.commons.codec.binary.Base64;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIBuilder;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
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;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.HttpMethodType;
import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.CommonLib;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.openbanking.eai.common.token.AccessTokenManager;
import com.openbanking.eai.common.token.AccessTokenVO;
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
import com.eactive.eai.common.property.PropManager;
public class HttpClient5AdapterServiceBase64 extends HttpClient5AdapterServiceSupport
implements HttpClientAdapterServiceKey {
public static final String TYPE_VARIABLE_URL_REQUEST = "variableUrlRequest";
public static final String TYPE_SIMPLE_REQUEST = "simpleRequest";
public static final String REST_OPTION = "REST_OPTION";
//public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_GROUP = "HEADER_GROUP";
public static final String HTTP_STATUS = "HTTP_STATUS";
public static final String HEADER_KEYS = "HEADER_KEYS";
public static final String AUTH_TOKEN = "AUTH_TOKEN";
/**
* 1. 기능 : REST API 통신에 사용 <br>
* 2. 처리 개요 : - 속성 정보를 설정 하고 수동 시스템 서비스를 호출 한다. <br>
* 3. 주의사항 <br>
*
* @param prop Http Adapter 속성 정보
* @return 반환 된 Object
* @exception Exception 수동 시스템 간 통신 중 발생
**/
@SuppressWarnings({ "unchecked" })
public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
HttpClientAdapterVO vo = super.setting(prop, tempProp);
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
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, "");
boolean useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y");
boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y");
String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL");
// ex) $.dataHeader.GW_RSLT_CD
String tokenErrorCodeKey = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_KEY");
String tokenErrorCodeValues = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_VALUES");
String tokenErrorHttpStatusCode = prop.getProperty("ADAPTER_TOKEN_ERROR_HTTP_STATUS_CODE"); // 200 or 400번대
// 레이아웃 메시지 타입 REST URL 추출 시 활용. Default JSON
String messageType = prop.getProperty("MESSAGE_TYPE", MessageType.JSON);
String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST");
/**
* @formatter:off
* TSEAIHE02.RESTOPTION 정보 => JSON 형태로 구성
* - type : simpleRequest, variableUrlRequest
* - extraPath : 어댑터 프로퍼티 URL에 추가될 HTTP REST URL
* - method : get, delete, post, put
* - contentType: application/json(default), application/x-www-form-urlencoded
* - adapterTokenUseYn: Y, N(default)
* ex)
* {"type":"simpleRequest","extraPath":"v2.0/accout/balance","method":"post"}
* @formatter:on
*/
String restOptionData = tempProp.getProperty(REST_OPTION);
JSONObject restOptionObject = parseJson(restOptionData);
if (restOptionObject == null) {
throw new Exception("OptionData parsing result is NULL");
}
String rmethod = (String) restOptionObject.getOrDefault("method", inboundMethod);
String contentType = (String) restOptionObject.getOrDefault("contentType", "application/json");
String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn");
if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다.
useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y");
}
HttpClient mclient = this.client;
String sendData = "";
if (data instanceof String) {
sendData = (String) data;
} else if (data instanceof byte[]) {
sendData = new String((byte[]) data, vo.getEncode());
}
////mclient.getParams().setContentCharset(vo.getEncode());
JSONObject dataObject = null;
Object httpHeader = null;
Object dataContent = null;
if (MessageType.JSON.equals(messageType)) {
Object parsed = 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;
}
}
}
logger.debug("HttpClientAdapterServiceRest] dataObject1 = [" + dataObject + "]");
logger.debug("HttpClientAdapterServiceRest] dataContent = [" + dataContent + "]");
// interface 에 설정된게 우선한다.
String uri = null;
if (dataObject == null) {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData);
} else {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject);
}
// ////if (vo.getConnectionTimeout() != 0) {
// mclient.getHttpConnectionManager().getParams().setConnectionTimeout(vo.getConnectionTimeout());
// }
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") URL=[" + vo.getUrl() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") PARAMETER_NAME=[" + vo.getParameterName() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") ENCODE=[" + vo.getEncode() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") RESPONSE_TYPE=[" + vo.getResponseType() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") URL_ENCODE_YN=[" + vo.getUrlEncodeYn() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") CONNECTION_TIMEOUT=[" + vo.getConnectionTimeout() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") REST_OPTION=[" + restOptionData + "] ");
}
HttpUriRequestBase method = null;
// 메소드 타입에 따라 HttpRequestBase 인스턴스 생성
switch (HttpMethodType.getValue(rmethod)) {
case GET:
method = new HttpGet(uri);
break;
case DELETE:
method = new HttpDelete(uri);
break;
case POST:
method = new HttpPost(uri);
break;
case PUT:
method = new HttpPut(uri);
break;
default:
method = new HttpGet(uri);
break;
}
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
if(useForwardProxy) {
java.net.URL url = new java.net.URL(forwardProxyUrl);
// 프로토콜, 호스트, 포트 추출
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
HttpHost proxy = new HttpHost(protocol,host, port);
requestConfigBuilder.setProxy(proxy);
}
configureHttpClient(requestConfigBuilder, vo, method);
switch (HttpMethodType.getValue(rmethod)) {
case GET:
case DELETE:
HashMap<String, String> h = getParameters(dataObject);
URIBuilder uriBuilder = new URIBuilder(uri);
for (Map.Entry<String, String> entry : h.entrySet()) {
uriBuilder.addParameter(entry.getKey(), entry.getValue());
}
URI fullUri = uriBuilder.build();
if (method instanceof HttpGet) {
((HttpGet) method).setUri(fullUri);
} else if (method instanceof HttpDelete) {
((HttpDelete) method).setUri(fullUri);
}
if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] (get Method) QueryString = [" + fullUri.getQuery() + "]");
}
break;
case PUT:
case POST:
//assignPostBody(dataObject, contentType, vo.getEncode(), method);
assignPostBody(dataContent, contentType, vo.getEncode(), method);
break;
default:
break;
}
if (logger.isDebugEnabled()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") method.getParams()=["
+ method.getEntity() + "] ");
}
OAuth2AccessTokenVO accessToken = null;
if (useAdapterToken) {
AccessTokenManagerByDB tokenManager = AccessTokenManagerByDB.getInstance();
accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName());
// 토큰이 없거나 만료 됐으면 재발급
if (accessToken == null || accessToken.isExpired()) {
String oldToken = accessToken == null ? null : accessToken.getAccessToken();
accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop,
oldToken);
}
if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") TOKEN = ["
+ accessToken + "]");
}
setAuthHeaders(method, accessToken);
if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName()
+ ") RequestHeader [Authorization=" + method.getHeader("Authorization") + "]");
}
}
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
if(!StringUtils.isBlank(authToken)) {
setAuthHeaders(method, authToken);
}
// 전달 header 셋팅
// Adapter 레벨 header 보다 data로 넘어온 httpHeader를 우선 적용(header명이 같다면 덮어씀)
assignRequestHeaders(method, httpHeader);
int status = -1;
try {
byte[] responseMessage = null;
Header[] responseHeaders;
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
if (dataContent != null) { //dataObject
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = ["
+ dataContent.toString() + "]"); //dataObject.toJSONString()
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(dataContent.toString())); //dataObject.toJSONString()
} else {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData
+ "]");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(sendData));
}
}
StopWatch stopWatch = new StopWatch();
stopWatch.start();
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
}
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
HttpContext context = new BasicHttpContext();
context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid);
context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName());
context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName());
Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
if (logProcessNo != null && logProcessNo > 0) {
context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo);
}
try {
if (logger.isDebug()) {
logger.debug("[method getName]" + method.getMethod());
logger.debug("[method getEntity]" + method.getEntity());
logger.debug("[method getRequestHeaders]" + method.getHeaders().toString());
logger.debug("[method getURI]" + method.getPath());
}
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method, context)) {
status = response.getCode();
if (status >= 200 && status <= 207) {
status = 200;
}
// if (status == 404) {
// status = 200;
// }
// responseMessage = EntityUtils.toByteArray(response.getEntity());
responseHeaders = response.getHeaders();
// 2025/08/06 Response body를 Base64로 Encode 해서 Json으로 만들어서 전달하도록 수정
HttpEntity entity = response.getEntity();
if (entity != null) {
byte[] entityBytes = EntityUtils.toByteArray(response.getEntity());
String b64 = Base64.encodeBase64String(entityBytes);
JSONObject resultJson = new JSONObject();
resultJson.put("base64_file_data", b64);
responseMessage = resultJson.toString().getBytes();
} else {
responseMessage = EntityUtils.toByteArray(response.getEntity());
}
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())) {
logger.debug("HttpClientAdapterServiceRest] responseType =" + vo.getResponseType());
}
throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage());
}
stopWatch.stop();
/**
* @formatter:off
* OAuth 토큰 응답 체크(Adapter properties로 설정)
* 응답코드에 따라 유효한 토큰 확인(토큰 재발급 여부 확인)
* API 서비스 마다 정책이 다름(보통 400대에서 체크하나 200에서도 체크할 수 있음)
*
* TOKEN_ERROR_CODE_KEY: 응답 json error code key
* __ex) $.dataHeader.resultCode
* TOKEN_ERROR_CODE_VALUES: 토큰 재발급이 필요한 응답 error codes(ex: O0001,O0002,O0003)
* TOKEN_ERROR_HTTP_STATUS_CODE: 보통 400대에서 체크하나 API 제공자에 따라 200에서도 체클할수 있음
* ex) TOKEN_ERROR_HTTP_STATUS_CODE = 200
* @formatter:on
*/
boolean needReissue = false;
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
String 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 (status >= 200 && status <= 207) {
if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) {
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
vo.getEncode());
}
} else if (status == 302) {
if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) {
relayResponseHeaderKeys += ",Location";
}
} else {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseString);
// logger.error(errMsg);
if (status >= 400 && status < 500) {
if (useAdapterToken) {
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
vo.getEncode());
}
// if (!needReissue) {
// throw new Exception(errMsg);
// }
} else {
throw new Exception(errMsg);
}
}
// ----------------------------------------------------------
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] responseType =" + vo.getResponseType());
logger.debug("HttpClientAdapterServiceRest] RECV " + vo.getEncode() + "(" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]");
// logger.debug("HttpClientAdapterServiceRest] RECV MS949 (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "MS949") + "]");
// logger.debug("HttpClientAdapterServiceRest] RECV euc-kr (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "euc-kr") + "]");
logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = " + CommonLib.getDumpMessage(responseMessage));
}
if (useAdapterToken) {
if (responseMessage == null) {
throw new Exception("responseMessage is NULL, HttpStatus=" + status);
}
// OAuth 토큰 재요청 코드 확인
if (needReissue) {
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] retry access token response code = ["
+ vo.getResponseType() + "] message = [" + new String(responseMessage, vo.getEncode())
+ "]");
}
// 토큰 재발급
AccessTokenManager tokenManager = AccessTokenManager.getInstance();
String oldToken = accessToken == null ? null : accessToken.getAccessToken();
OAuth2AccessTokenVO newaccessToken = (OAuth2AccessTokenVO) tokenManager
.retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken);
method.setHeader("Authorization", newaccessToken.getAuthorization());
setAuthHeaders(method, newaccessToken);
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName()
+ ") RETRY TOKEN = [" + newaccessToken + "]");
}
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
status = response.getCode();
responseMessage = EntityUtils.toByteArray(response.getEntity());
if (logger.isDebug()) {
logger.debug("[received status]" + status);
logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]");
}
}catch (IOException e){
throw new Exception("retry excuteMethod Exceptioin = " + e.getMessage());
}
if (status == 302) {
if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) {
relayResponseHeaderKeys += ",Location";
}
} else {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseMessage);
throw new Exception(errMsg);
}
// ----------------------------------------------------------
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] RETRY responseType =" + vo.getResponseType());
logger.debug("HttpClientAdapterServiceRest] RETRY RECV " + vo.getEncode() + "("
+ vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode())
+ "]");
logger.debug("HttpClientAdapterServiceRest] RETRY RECV (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(responseMessage));
}
// ----------------------------------------------------------
}
}
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"RECV [" + new String(responseMessage, vo.getEncode()) + "]"
+ CommonLib.getDumpMessage(responseMessage));
}
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType,
relayResponseHeaderKeys, status, responseHeaderProp);
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
"HttpClientAdapterServiceRest] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : "
+ ste.toString(),
ste);
}
logger.error("HttpClientAdapterServiceRest] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : "
+ ste.toString(), ste);
throw ste;
} catch (ConnectException ce) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
"HttpClientAdapterServiceRest] Connection Exception (" + vo.getAdapterGroupName() + ") : "
+ ce.toString(),
ce);
}
logger.error("HttpClientAdapterServiceRest] Connection Exception (" + vo.getAdapterGroupName() + ") : "
+ ce.toString(), ce);
throw ce;
} catch (Exception e) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), e.toString(), e);
}
logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
e);
throw e;
} finally {
if (status != HttpStatus.SC_OK) {
String[] msgArgs = new String[1];
msgArgs[0] = String.valueOf(status);
String resMsg = ExceptionUtil.make("RDCEAIAHA013", msgArgs);
if (logger.isDebug()) {
logger.debug(resMsg);
}
}
}
}
private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) {
Properties headerProp = new Properties();
if (responseHeaders == null || responseHeaders.length == 0) {
return headerProp;
}
for (Header header : responseHeaders) {
headerProp.put(header.getName(), header.getValue());
}
return headerProp;
}
/**
* 현재 1단계 레이아웃에만 적용되도록 구현됨.
*
* @param method
* @param responseMessage
* @param headerGroupName
* @param encode
* @param messageType
* @param relayHeaderKeys
* @return
* @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)
|| StringUtils.isBlank(relayHeaderKeys)) {
return new String(responseMessage, encode);
}
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
JSONObject headerJson = new JSONObject();
for (String key : relayKeyArr) {
String value = responseHeaderProp.getProperty(key);
if (StringUtils.isNotBlank(value)) {
headerJson.put(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));
}
}
if (StringUtils.isNotBlank(headerGroupName) && message != null)
message.put(headerGroupName, headerJson);
logger.info("------------- message --------------- : {} ", message);
return message.toJSONString();
}
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
method.setHeader("Authorization",
String.format("%s", authorization));
}
private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
method.setHeader("Authorization",
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
}
private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues,
String encode) {
if (responseMessage == null) {
return false;
}
if (StringUtils.isBlank(tokenErrorCodeKey)) {
return false;
}
if (StringUtils.isBlank(tokenErrorCodeValues)) {
return false;
}
try {
DocumentContext jsonContext = JsonPath.parse(new String(responseMessage, encode));
String responseCode = jsonContext.read(tokenErrorCodeKey);
if (StringUtils.isBlank(responseCode)) {
return false;
}
String[] arr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenErrorCodeValues, ",");
return ArrayUtils.contains(arr, responseCode);
} catch (Exception e) {
logger.error("HttpClientAdapterServiceRest] checkTokenRetry error=" + e.getMessage());
}
return false;
}
private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader) {
if (httpHeader == null) {
return;
}
if (httpHeader instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) httpHeader;
for (Object key : jsonObject.keySet()) {
Object obj = jsonObject.get(key);
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
continue;
}
method.setHeader((String) key, (String) obj);
if (logger.isDebugEnabled()) {
logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]");
}
}
}
}
@SuppressWarnings("deprecation")
private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method) {
if (eaiBody == null) {
return;
}
if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) {
if (eaiBody instanceof JSONObject) {
List<NameValuePair> params = new ArrayList<>();
JSONObject jsonObject = (JSONObject) eaiBody;
for (Object key : jsonObject.keySet()) {
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8);
method.setEntity(entity);
}
} else {
ContentType contentTypeObj = ContentType.create(contentType, charset);
// 요청 본문을 StringEntity 객체로 생성
StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj);
// 요청에 본문 추가
method.setEntity(entity);
}
}
private HashMap<String, String> getParameters(Object message) throws Exception {
HashMap<String, String> result = new HashMap<String, String>();
if (message != null) {
if (message instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) message;
for (Object key : jsonObject.keySet()) {
Object obj = jsonObject.get(key);
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
continue;
}
result.put((String) key, getStringValue(obj));
}
} else {
String[] messages = ((String) message).split("&");
String[] data = null;
for (int i = 0; i < messages.length; i++) {
data = messages[i].split("=", 2);
if (data.length == 2) {
result.put(data[0], data[1]);
}
}
}
}
return result;
}
public String getStringValue(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof String) {
return (String) obj;
} else if (obj instanceof Long) {
return Long.toString((Long) obj);
} else if (obj instanceof BigDecimal) {
return ((BigDecimal) obj).toPlainString();
} else {
return (String) obj;
}
}
public static Object parseJsonGeneric(String jsonData) {
if (StringUtils.isBlank(jsonData)) {
return null;
}
try {
JSONParser parser = new JSONParser();
Object parsedObj = parser.parse(jsonData);
return parsedObj;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
@SuppressWarnings("deprecation")
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
private Document convertXmlDocument(String message) throws Exception {
SAXReader builder = new SAXReader();
Document document = builder.read(new StringReader(message));
return document;
}
@SuppressWarnings("deprecation")
private String changeUrl(String messageType, String url, String restOption, Object sendData) {
if ((MessageType.JSON.equals(messageType) || MessageType.XML.equals(messageType))) {
try {
if (StringUtils.isBlank(restOption)) {
return url;
}
List<String> urlVariableList = new ArrayList<String>();
JSONObject restOptionObject = parseJson(restOption);
if (restOptionObject == null)
throw new Exception("restOption is NULL");
String type = (String) restOptionObject.get("type");
String requestExtraPath = (String) restOptionObject.get("extraPath");
url = getUrl(url, requestExtraPath);
if (TYPE_VARIABLE_URL_REQUEST.equals(type)) {
Pattern p = Pattern.compile("\\{(.*?)\\}");
Matcher m = p.matcher(requestExtraPath);
List<String> uriVariables = new ArrayList<>();
while(m.find()) {
String fieldName = m.group(1);
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 = (String) jsonObject.get(urlVaribleId);
urlVariableList.add(uriVariable);
jsonObject.remove(urlVaribleId);
}
} else if (MessageType.XML.equals(messageType)) {
Document doc;
if (sendData instanceof Document) {
doc = (Document) sendData;
} else {
doc = convertXmlDocument((String) sendData);
}
for (Object tempObject : uriVariables) {
String urlVaribleId = (String) tempObject;
Element element = (Element) doc.selectSingleNode("//" + urlVaribleId);
urlVariableList.add(element.getText());
if (doc.getRootElement() == element) {
doc = null;
} else {
element.getParent().remove(element);
}
}
}
if (urlVariableList.size() > 0) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build();
Object[] urls = urlVariableList.toArray();
url = uriComponents.expand(urls).toUriString();
logger.debug("HttpClientAdapterServiceRest] after extand url=[" + url + "] ");
}
}
} catch (Exception e) {
logger.error("Failed to change url", e);
}
}
return url;
}
private String getUrl(String baseUrl, String extraPath) {
if (StringUtils.isBlank(extraPath)) {
return baseUrl;
}
if (StringUtils.contains(extraPath, "http://") || StringUtils.contains(extraPath, "https://")) {
return extraPath;
}
String targetUrl = baseUrl;
if (!targetUrl.endsWith("/")) {
targetUrl += "/";
}
if (extraPath.startsWith("/")) {
targetUrl += extraPath.substring(1);
} else {
targetUrl += extraPath;
}
logger.debug("HttpClientAdapterServiceRest] after concatenate url=[" + targetUrl + "] ");
return targetUrl;
}
}
@@ -1,680 +0,0 @@
package com.eactive.eai.adapter.http.client.impl.kjb;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.HttpMethodType;
import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
import com.eactive.eai.adapter.http.client.impl.HttpClientAdapterServiceRest;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.impl.HttpAdapterServiceBypass;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.util.CommonLib;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.openbanking.eai.common.token.AccessTokenManager;
import com.openbanking.eai.common.token.AccessTokenVO;
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
public class HttpClient5AdapterServiceBypass extends HttpClient5AdapterServiceSupport
implements HttpClientAdapterServiceKey {
public static final String TYPE_BYPASS_REQUEST = "bypassRequest";
public static final String REST_OPTION = "REST_OPTION";
protected boolean doSendUrlFragment = true;
protected boolean doHandleCompression = false;
protected boolean doForwardIP = false;
@SuppressWarnings({ "unchecked" })
public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
HttpClientAdapterVO vo = super.setting(prop, tempProp);
boolean useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y");
// ex) $.dataHeader.GW_RSLT_CD
String tokenErrorCodeKey = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_KEY");
String tokenErrorCodeValues = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_VALUES");
String tokenErrorHttpStatusCode = prop.getProperty("ADAPTER_TOKEN_ERROR_HTTP_STATUS_CODE"); // 200 or 400번대
/**
* @formatter:off
* TSEAIHE02.RESTOPTION 정보 => JSON 형태로 구성
* - type : simpleRequest, variableUrlRequest
* - extraPath : 어댑터 프로퍼티 URL에 추가될 HTTP REST URL
* - method : get, delete, post, put
* - contentType : application/json, application/x-www-form-urlencoded
* - adapterTokenUseYn: Y, N(default)
* ex)
* {"type":"variableUrlRequest","extraPath":"/aaa/{userId}","uriVariables":["userId"]}
* @formatter:on
*/
String restOptionData = tempProp.getProperty(REST_OPTION, "{}");
JSONObject restOptionObject = parseJson(restOptionData);
String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn");
if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다.
useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y");
}
String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST");
String inboundRewritePath = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REWRITE_PATH, "");
String inboundQueryString = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_QUERY_STRING, "");
Properties inboundHeaders = (Properties) tempProp.get(HttpAdapterServiceKey.INBOUND_HEADER);
Map<String, String> inboundPathVariables = (Map<String, String>) tempProp
.get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES);
String restMethod = (String) restOptionObject.get("method");
if (StringUtils.isBlank(restMethod)) {
restMethod = inboundMethod;
}
String url = getRewriteUrl(vo, inboundRewritePath, inboundQueryString, restOptionObject, inboundPathVariables);
if (logger.isDebug()) {
logger.debug("HttpClient5AdapterServiceBypass] url = [" + url + "]");
}
HttpClient mclient = this.client;
HttpUriRequestBase method = generateMethod(restMethod, url);
assignRequestHeaders(method, inboundHeaders, tempProp);
if (hasBody(data, method)) {
byte[] bodyBytes;
if (data instanceof byte[]) {
bodyBytes = (byte[]) data;
} else if (data instanceof String) {
bodyBytes = ((String) data).getBytes(vo.getEncode());
} else {
bodyBytes = new byte[0];
}
method.setEntity(new ByteArrayEntity(bodyBytes, null));
}
String contentType = (String) restOptionObject.get("contentType");
if (StringUtils.isBlank(contentType) && inboundHeaders != null) {
contentType = getIgnoreCaseProp(inboundHeaders, HttpHeaders.CONTENT_TYPE);
contentType = StringUtils.substringBefore(contentType, ";");
}
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
configureHttpClient(requestConfigBuilder, vo, method);
OAuth2AccessTokenVO accessToken = null;
if (useAdapterToken) {
AccessTokenManager tokenManager = AccessTokenManager.getInstance();
accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName());
// 토큰이 없거나 만료 됐으면 재발급
if (accessToken == null || accessToken.isExpired()) {
String oldToken = accessToken == null ? null : accessToken.getAccessToken();
accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop,
oldToken);
}
if (logger.isDebug()) {
logger.debug("HttpClient5AdapterServiceBypass] SEND (" + vo.getAdapterGroupName() + ") TOKEN = ["
+ accessToken + "]");
}
setAuthHeaders(method, accessToken);
if (logger.isDebug()) {
logger.debug("HttpClient5AdapterServiceBypass] SEND (" + vo.getAdapterGroupName()
+ ") RequestHeader [Authorization=" + method.getFirstHeader("Authorization") + "]");
}
}
// 로깅 인터셉터에 전달할 컨텍스트 정보 설정
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
HttpContext context = new BasicHttpContext();
context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid);
context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName());
context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName());
Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
if (logProcessNo != null && logProcessNo > 0) {
context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo);
}
int status = -1;
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
byte[] responseMessage = null;
Header[] responseHeaders = null;
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"SEND [Bypass Request..]" + CommonLib.getDumpMessage(data));
}
try {
if (logger.isDebug()) {
logger.debug("[method getName]" + method.getMethod());
logger.debug("[method getRequestHeaders]" + java.util.Arrays.toString(method.getHeaders()));
logger.debug("[method getURI]" + method.getPath());
}
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method, context)) {
status = response.getCode();
responseHeaders = response.getHeaders();
responseMessage = EntityUtils.toByteArray(response.getEntity());
if (logger.isDebug()) {
logger.debug("[received status]" + status);
}
}
} catch (ConnectException e) {
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClient5AdapterServiceBypass] responseType =" + vo.getResponseType());
logger.debug("HttpClient5AdapterServiceBypass] RECV (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(responseMessage));
}
throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage());
}
stopWatch.stop();
/**
* @formatter:off
* OAuth 토큰 응답 체크(Adapter properties로 설정)
* 응답코드에 따라 유효한 토큰 확인(토큰 재발급 여부 확인)
* API 서비스 마다 정책이 다름(보통 400대에서 체크하나 200에서도 체크할 수 있음)
*
* TOKEN_ERROR_CODE_KEY: 응답 json error code key
* __ex) $.dataHeader.resultCode
* TOKEN_ERROR_CODE_VALUES: 토큰 재발급이 필요한 응답 error codes(ex: O0001,O0002,O0003)
* TOKEN_ERROR_HTTP_STATUS_CODE: 보통 400대에서 체크하나 API 제공자에 따라 200에서도 체클할수 있음
* ex) TOKEN_ERROR_HTTP_STATUS_CODE = 200
* @formatter:on
*/
boolean needReissue = false;
if (status == 200) {
if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) {
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
vo.getEncode());
}
} else if (status >= 400 && status < 500) {
if (useAdapterToken) {
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
vo.getEncode());
}
if (!needReissue) {
// body 값이 없을때만 exception 처리하고, body가 있으면 bypass 한다.
if (responseMessage == null || responseMessage.length == 0) {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), url, restMethod, responseMessage);
throw new Exception(errMsg);
}
}
} else if (status != 200) {
responseMessage = responseMessage != null ? responseMessage : new byte[0];
// body 값이 없을때만 exception 처리하고, body가 있으면 bypass 한다.
if (responseMessage.length == 0) {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), url, restMethod, responseMessage);
throw new Exception(errMsg);
}
}
if (useAdapterToken) {
if (responseMessage == null) {
throw new Exception("responseMessage is NULL, HttpStatus=" + status);
}
// OAuth 토큰 재요청 코드 확인
if (needReissue) {
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClient5AdapterServiceBypass] retry access token response code = ["
+ vo.getResponseType() + "] message = [" + new String(responseMessage, vo.getEncode())
+ "]");
}
// 토큰 재발급
AccessTokenManager tokenManager = AccessTokenManager.getInstance();
String oldToken = accessToken == null ? null : accessToken.getAccessToken();
OAuth2AccessTokenVO newAccessToken = (OAuth2AccessTokenVO) tokenManager
.retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken);
method.setHeader("Authorization", newAccessToken.getAuthorization());
setAuthHeaders(method, newAccessToken);
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClient5AdapterServiceBypass] SEND (" + vo.getAdapterGroupName()
+ ") RETRY TOKEN = [" + newAccessToken + "]");
}
try (CloseableHttpResponse retryResponse = (CloseableHttpResponse) mclient.execute(method, context)) {
status = retryResponse.getCode();
responseHeaders = retryResponse.getHeaders();
responseMessage = EntityUtils.toByteArray(retryResponse.getEntity());
} catch (IOException e) {
throw new Exception("retry excuteMethod Exception = " + e.getMessage());
}
if (status != 200) {
responseMessage = responseMessage != null ? responseMessage : new byte[0];
if (responseMessage.length == 0) {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), url, restMethod,
responseMessage);
throw new Exception(errMsg);
}
}
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClient5AdapterServiceBypass] RETRY responseType =" + vo.getResponseType());
logger.debug("HttpClient5AdapterServiceBypass] RETRY RECV (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(responseMessage));
}
}
}
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"RECV [Bypass Request..]" + CommonLib.getDumpMessage(responseMessage));
}
// 응답 Content-Type charset 기반 인코딩 결정, 없으면 어댑터 encoding 사용
String responseEncode = vo.getEncode();
if (responseHeaders != null) {
for (Header header : responseHeaders) {
if (StringUtils.equalsIgnoreCase(header.getName(), HttpHeaders.CONTENT_TYPE)) {
try {
MediaType mediaType = MediaType.parseMediaType(header.getValue());
if (mediaType.getCharset() != null) {
responseEncode = mediaType.getCharset().name();
}
} catch (Exception ignored) {}
break;
}
}
}
assignRelayDataToInbound(tempProp, responseHeaders, status);
return responseMessage != null ? new String(responseMessage, responseEncode) : "";
} catch (SocketTimeoutException ste) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
"HttpClient5AdapterServiceBypass] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : "
+ ste.toString(),
ste);
}
logger.error("HttpClient5AdapterServiceBypass] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : "
+ ste.toString(), ste);
throw ste;
} catch (ConnectException ce) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
"HttpClient5AdapterServiceBypass] Connection Exception (" + vo.getAdapterGroupName() + ") : "
+ ce.toString(),
ce);
}
logger.error("HttpClient5AdapterServiceBypass] Connection Exception (" + vo.getAdapterGroupName() + ") : "
+ ce.toString(), ce);
throw ce;
} catch (Exception e) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), e.toString(), e);
}
logger.error(
"HttpClient5AdapterServiceBypass] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
e);
throw e;
} finally {
if (status != HttpStatus.SC_OK) {
String[] msgArgs = new String[1];
msgArgs[0] = String.valueOf(status);
String resMsg = ExceptionUtil.make("RDCEAIAHA013", msgArgs);
if (logger.isDebug()) {
logger.debug(resMsg);
}
}
}
}
private boolean hasBody(Object data, HttpUriRequestBase method) {
if (data == null) {
return false;
}
// GET, DELETE는 표준 HTTP에서 body를 지원하지 않음
return method instanceof HttpPost || method instanceof HttpPut;
}
private HttpUriRequestBase generateMethod(String restMethod, String url) {
switch (HttpMethodType.getValue(restMethod)) {
case GET:
return new HttpGet(url);
case DELETE:
return new HttpDelete(url);
case POST:
return new HttpPost(url);
case PUT:
return new HttpPut(url);
default:
return new HttpPost(url);
}
}
private String getRewriteUrl(HttpClientAdapterVO vo, String inboundRewritePath, String queryString,
JSONObject restOptionObject, Map<String, String> inboundPathVariables) {
String fragment = null;
if (StringUtils.isNotBlank(queryString)) {
int fragIdx = queryString.indexOf('#');
if (fragIdx >= 0) {
fragment = queryString.substring(fragIdx + 1);
queryString = queryString.substring(0, fragIdx);
}
}
String restExtraPath = (String) restOptionObject.get("extraPath");
if (StringUtils.isBlank(restExtraPath)) {
StringBuilder uri = new StringBuilder(500);
String baseUrl = StringUtils.removeEnd(vo.getUrl(), "/");
String rewritePath = inboundRewritePath != null && !inboundRewritePath.startsWith("/")
? "/" + inboundRewritePath : inboundRewritePath;
uri.append(baseUrl).append(StringUtils.defaultString(rewritePath));
if (StringUtils.isNotBlank(queryString)) {
uri.append('?');
uri.append(encodeUriQuery(queryString, false));
}
if (doSendUrlFragment && fragment != null) {
uri.append('#');
uri.append(encodeUriQuery(fragment, false));
}
return uri.toString();
} else {
String type = (String) restOptionObject.get("type");
String url = getUrl(vo.getUrl(), restExtraPath);
if (StringUtils.equalsIgnoreCase(type, HttpClientAdapterServiceRest.TYPE_VARIABLE_URL_REQUEST)) {
inboundPathVariables = mergeInboundPathVariables(inboundPathVariables, queryString);
List<String> urlVariableValueList = new ArrayList<>();
JSONArray uriVariables = (JSONArray) restOptionObject.get("uriVariables");
if (uriVariables != null && !uriVariables.isEmpty() && inboundPathVariables != null
&& !inboundPathVariables.isEmpty()) {
for (Object tempObject : uriVariables) {
String urlVaribleId = (String) tempObject;
String uriVariableValue = inboundPathVariables.get(urlVaribleId);
urlVariableValueList.add(uriVariableValue);
}
if (!urlVariableValueList.isEmpty()) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build();
Object[] urls = urlVariableValueList.toArray();
url = uriComponents.expand(urls).toUriString();
}
}
} else {
if (StringUtils.isNotBlank(queryString)) {
url += "?" + encodeUriQuery(queryString, false);
}
if (doSendUrlFragment && fragment != null) {
url += "#" + encodeUriQuery(fragment, false);
}
}
return url;
}
}
private Map<String, String> mergeInboundPathVariables(Map<String, String> inboundPathVariables,
String queryString) {
if (StringUtils.isBlank(queryString)) {
return inboundPathVariables;
}
if (inboundPathVariables == null) {
inboundPathVariables = new LinkedHashMap<>();
}
String[] pairs = queryString.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
inboundPathVariables.put(pair.substring(0, idx), pair.substring(idx + 1));
}
return inboundPathVariables;
}
private String getUrl(String baseUrl, String extraPath) {
if (StringUtils.isBlank(extraPath)) {
return baseUrl;
}
if (StringUtils.contains(extraPath, "http://") || StringUtils.contains(extraPath, "https://")) {
return extraPath;
}
String targetUrl = baseUrl;
if (!targetUrl.endsWith("/")) {
targetUrl += "/";
}
if (extraPath.startsWith("/")) {
targetUrl += extraPath.substring(1);
} else {
targetUrl += extraPath;
}
return targetUrl;
}
protected CharSequence encodeUriQuery(CharSequence in, boolean encodePercent) {
StringBuilder outBuf = null;
Formatter formatter = null;
for (int i = 0; i < in.length(); i++) {
char c = in.charAt(i);
boolean escape = true;
if (c < 128) {
if (asciiQueryChars.get(c) && !(encodePercent && c == '%')) {
escape = false;
}
} else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) {
escape = false;
}
if (!escape) {
if (outBuf != null)
outBuf.append(c);
} else {
if (outBuf == null) {
outBuf = new StringBuilder(in.length() + 5 * 3);
outBuf.append(in, 0, i);
formatter = new Formatter(outBuf);
}
formatter.format("%%%02X", (int) c);
}
}
return outBuf != null ? outBuf : in;
}
protected static final BitSet asciiQueryChars;
static {
char[] c_unreserved = "_-!.~'()*".toCharArray();
char[] c_punct = ",;:$&+=".toCharArray();
char[] c_reserved = "/@".toCharArray();
asciiQueryChars = new BitSet(128);
for (char c = 'a'; c <= 'z'; c++)
asciiQueryChars.set(c);
for (char c = 'A'; c <= 'Z'; c++)
asciiQueryChars.set(c);
for (char c = '0'; c <= '9'; c++)
asciiQueryChars.set(c);
for (char c : c_unreserved)
asciiQueryChars.set(c);
for (char c : c_punct)
asciiQueryChars.set(c);
for (char c : c_reserved)
asciiQueryChars.set(c);
asciiQueryChars.set('%');
}
private void assignRelayDataToInbound(Properties prop, Header[] responseHeaders, int status) {
Properties headerProp = new Properties();
if (responseHeaders != null) {
for (Header header : responseHeaders) {
String name = header.getName();
String value = header.getValue();
String existing = headerProp.getProperty(name);
if (existing != null) {
// 동일 이름의 헤더가 여러 개인 경우 콤마로 합침 (RFC 7230)
headerProp.put(name, existing + ", " + value);
} else {
headerProp.put(name, value);
}
}
}
headerProp.put(HttpAdapterServiceBypass.HTTP_STATUS, String.valueOf(status));
Map<String, Object> map = new HashMap<String, Object>();
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, headerProp);
prop.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
}
private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
method.setHeader("Authorization",
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
}
private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues,
String encode) {
if (responseMessage == null) {
return false;
}
if (StringUtils.isBlank(tokenErrorCodeKey)) {
return false;
}
if (StringUtils.isBlank(tokenErrorCodeValues)) {
return false;
}
try {
DocumentContext jsonContext = JsonPath.parse(new String(responseMessage, encode));
String responseCode = jsonContext.read(tokenErrorCodeKey);
if (StringUtils.isBlank(responseCode)) {
return false;
}
String[] arr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenErrorCodeValues, ",");
return ArrayUtils.contains(arr, responseCode);
} catch (Exception e) {
logger.error("HttpClient5AdapterServiceBypass] checkTokenRetry error=" + e.getMessage());
}
return false;
}
private void assignRequestHeaders(HttpUriRequestBase method, Properties headerProp, Properties inProp) {
if (headerProp == null) {
return;
}
for (Entry<Object, Object> e : headerProp.entrySet()) {
String key = (String) e.getKey();
String value = (String) e.getValue();
if (StringUtils.equalsAnyIgnoreCase(key, HttpAdapterServiceBypass.HOP_BY_HOP_HEADERS)
|| StringUtils.equalsIgnoreCase(key, HttpHeaders.CONTENT_LENGTH)) {
continue;
}
if (doHandleCompression && StringUtils.equalsIgnoreCase(key, HttpHeaders.ACCEPT_ENCODING)) {
continue;
}
method.addHeader(key, value);
if (logger.isDebugEnabled()) {
logger.debug("Request Header :" + key + "=[" + value + "]");
}
}
if (doForwardIP) {
String forHeaderName = "X-Forwarded-For";
String forHeader = inProp.getProperty(HttpAdapterServiceKey.INBOUND_REMOTE_ADDR);
if (StringUtils.isNotBlank(forHeader)) {
String existingForHeader = headerProp.getProperty(forHeaderName);
if (existingForHeader != null) {
forHeader = existingForHeader + ", " + forHeader;
}
method.addHeader(forHeaderName, forHeader);
}
String protoHeaderName = "X-Forwarded-Proto";
String protoHeader = inProp.getProperty(HttpAdapterServiceKey.INBOUND_SCHEME);
if (StringUtils.isNotBlank(protoHeader)) {
method.addHeader(protoHeaderName, protoHeader);
}
}
}
@SuppressWarnings("deprecation")
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
public static final String getIgnoreCaseProp(Properties prop, String key) {
if (prop == null) {
return null;
}
for (Entry<Object, Object> e : prop.entrySet()) {
if (StringUtils.equalsIgnoreCase(key, (String) e.getKey())) {
return (String) e.getValue();
}
}
return null;
}
}
@@ -1,888 +0,0 @@
package com.eactive.eai.adapter.http.client.impl.kjb;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIBuilder;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
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;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.HttpMethodType;
import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.authoutbound.AccessTokenManagerByDB;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.CommonLib;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.openbanking.eai.common.token.AccessTokenManager;
import com.openbanking.eai.common.token.AccessTokenVO;
import com.openbanking.eai.common.token.OAuth2AccessTokenVO;
import com.eactive.eai.common.property.PropManager;
public class HttpClient5AdapterServiceKbank extends HttpClient5AdapterServiceSupport
implements HttpClientAdapterServiceKey {
public static final String TYPE_VARIABLE_URL_REQUEST = "variableUrlRequest";
public static final String TYPE_SIMPLE_REQUEST = "simpleRequest";
public static final String REST_OPTION = "REST_OPTION";
//public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_GROUP = "HEADER_GROUP";
public static final String HTTP_STATUS = "HTTP_STATUS";
public static final String HEADER_KEYS = "HEADER_KEYS";
public static final String AUTH_TOKEN = "AUTH_TOKEN";
/**
* 1. 기능 : REST API 통신에 사용 <br>
* 2. 처리 개요 : - 속성 정보를 설정 하고 수동 시스템 서비스를 호출 한다. <br>
* 3. 주의사항 <br>
*
* @param prop Http Adapter 속성 정보
* @return 반환 된 Object
* @exception Exception 수동 시스템 간 통신 중 발생
**/
@SuppressWarnings({ "unchecked" })
public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
HttpClientAdapterVO vo = super.setting(prop, tempProp);
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
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, "");
boolean useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y");
boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y");
String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL");
// ex) $.dataHeader.GW_RSLT_CD
String tokenErrorCodeKey = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_KEY");
String tokenErrorCodeValues = prop.getProperty("ADAPTER_TOKEN_ERROR_CODE_VALUES");
String tokenErrorHttpStatusCode = prop.getProperty("ADAPTER_TOKEN_ERROR_HTTP_STATUS_CODE"); // 200 or 400번대
// 레이아웃 메시지 타입 REST URL 추출 시 활용. Default JSON
String messageType = prop.getProperty("MESSAGE_TYPE", MessageType.JSON);
String inboundMethod = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_METHOD, "POST");
/**
* @formatter:off
* TSEAIHE02.RESTOPTION 정보 => JSON 형태로 구성
* - type : simpleRequest, variableUrlRequest
* - extraPath : 어댑터 프로퍼티 URL에 추가될 HTTP REST URL
* - method : get, delete, post, put
* - contentType: application/json(default), application/x-www-form-urlencoded
* - adapterTokenUseYn: Y, N(default)
* ex)
* {"type":"simpleRequest","extraPath":"v2.0/accout/balance","method":"post"}
* @formatter:on
*/
String restOptionData = tempProp.getProperty(REST_OPTION);
JSONObject restOptionObject = parseJson(restOptionData);
if (restOptionObject == null) {
throw new Exception("OptionData parsing result is NULL");
}
String rmethod = (String) restOptionObject.getOrDefault("method", inboundMethod);
String contentType = (String) restOptionObject.getOrDefault("contentType", "application/json");
String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn");
if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다.
useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y");
}
HttpClient mclient = this.client;
String sendData = "";
if (data instanceof String) {
sendData = (String) data;
} else if (data instanceof byte[]) {
sendData = new String((byte[]) data, vo.getEncode());
}
////mclient.getParams().setContentCharset(vo.getEncode());
JSONObject dataObject = null;
Object httpHeader = null;
Object dataContent = null;
if (MessageType.JSON.equals(messageType)) {
Object parsed = 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;
}
}
}
logger.debug("HttpClientAdapterServiceRest] dataObject1 = [" + dataObject + "]");
logger.debug("HttpClientAdapterServiceRest] dataContent = [" + dataContent + "]");
// interface 에 설정된게 우선한다.
String uri = null;
if (dataObject == null) {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData);
} else {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject);
}
// ////if (vo.getConnectionTimeout() != 0) {
// mclient.getHttpConnectionManager().getParams().setConnectionTimeout(vo.getConnectionTimeout());
// }
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") URL=[" + vo.getUrl() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") PARAMETER_NAME=[" + vo.getParameterName() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") ENCODE=[" + vo.getEncode() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") RESPONSE_TYPE=[" + vo.getResponseType() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") URL_ENCODE_YN=[" + vo.getUrlEncodeYn() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") CONNECTION_TIMEOUT=[" + vo.getConnectionTimeout() + "] ");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") REST_OPTION=[" + restOptionData + "] ");
}
HttpUriRequestBase method = null;
// 메소드 타입에 따라 HttpRequestBase 인스턴스 생성
switch (HttpMethodType.getValue(rmethod)) {
case GET:
method = new HttpGet(uri);
break;
case DELETE:
method = new HttpDelete(uri);
break;
case POST:
method = new HttpPost(uri);
break;
case PUT:
method = new HttpPut(uri);
break;
default:
method = new HttpGet(uri);
break;
}
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
if(useForwardProxy) {
java.net.URL url = new java.net.URL(forwardProxyUrl);
// 프로토콜, 호스트, 포트 추출
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
HttpHost proxy = new HttpHost(protocol,host, port);
requestConfigBuilder.setProxy(proxy);
}
configureHttpClient(requestConfigBuilder, vo, method);
switch (HttpMethodType.getValue(rmethod)) {
case GET:
case DELETE:
HashMap<String, String> h = getParameters(dataObject);
URIBuilder uriBuilder = new URIBuilder(uri);
for (Map.Entry<String, String> entry : h.entrySet()) {
uriBuilder.addParameter(entry.getKey(), entry.getValue());
}
URI fullUri = uriBuilder.build();
if (method instanceof HttpGet) {
((HttpGet) method).setUri(fullUri);
} else if (method instanceof HttpDelete) {
((HttpDelete) method).setUri(fullUri);
}
if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] (get Method) QueryString = [" + fullUri.getQuery() + "]");
}
break;
case PUT:
case POST:
//assignPostBody(dataObject, contentType, vo.getEncode(), method);
assignPostBody(dataContent, contentType, vo.getEncode(), method);
break;
default:
break;
}
if (logger.isDebugEnabled()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") method.getParams()=["
+ method.getEntity() + "] ");
}
OAuth2AccessTokenVO accessToken = null;
if (useAdapterToken) {
AccessTokenManagerByDB tokenManager = AccessTokenManagerByDB.getInstance();
accessToken = (OAuth2AccessTokenVO) tokenManager.getAccessTokenVO(vo.getAdapterGroupName());
// 토큰이 없거나 만료 됐으면 재발급
if (accessToken == null || accessToken.isExpired()) {
String oldToken = accessToken == null ? null : accessToken.getAccessToken();
accessToken = (OAuth2AccessTokenVO) tokenManager.retryAccessTokenVO(vo.getAdapterGroupName(), prop,
oldToken);
}
if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") TOKEN = ["
+ accessToken + "]");
}
setAuthHeaders(method, accessToken);
if (logger.isDebug()) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName()
+ ") RequestHeader [Authorization=" + method.getHeader("Authorization") + "]");
}
}
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
if(!StringUtils.isBlank(authToken)) {
setAuthHeaders(method, authToken);
}
// 전달 header 셋팅
// Adapter 레벨 header 보다 data로 넘어온 httpHeader를 우선 적용(header명이 같다면 덮어씀)
assignRequestHeaders(method, httpHeader);
int status = -1;
try {
byte[] responseMessage = null;
Header[] responseHeaders;
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
if (dataContent != null) { //dataObject
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = ["
+ dataContent.toString() + "]"); //dataObject.toJSONString()
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(dataContent.toString())); //dataObject.toJSONString()
} else {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData
+ "]");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(sendData));
}
}
StopWatch stopWatch = new StopWatch();
stopWatch.start();
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
}
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
HttpContext context = new BasicHttpContext();
context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid);
context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName());
context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName());
Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
if (logProcessNo != null && logProcessNo > 0) {
context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo);
}
try {
if (logger.isDebug()) {
logger.debug("[method getName]" + method.getMethod());
logger.debug("[method getEntity]" + method.getEntity());
logger.debug("[method getRequestHeaders]" + method.getHeaders().toString());
logger.debug("[method getURI]" + method.getPath());
}
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method, context)) {
status = response.getCode();
if (status >= 200 && status <= 207) {
status = 200;
}
// if (status == 404) {
// status = 200;
// }
responseMessage = EntityUtils.toByteArray(response.getEntity());
responseHeaders = response.getHeaders();
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())) {
logger.debug("HttpClientAdapterServiceRest] responseType =" + vo.getResponseType());
}
throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage());
}
stopWatch.stop();
/**
* @formatter:off
* OAuth 토큰 응답 체크(Adapter properties로 설정)
* 응답코드에 따라 유효한 토큰 확인(토큰 재발급 여부 확인)
* API 서비스 마다 정책이 다름(보통 400대에서 체크하나 200에서도 체크할 수 있음)
*
* TOKEN_ERROR_CODE_KEY: 응답 json error code key
* __ex) $.dataHeader.resultCode
* TOKEN_ERROR_CODE_VALUES: 토큰 재발급이 필요한 응답 error codes(ex: O0001,O0002,O0003)
* TOKEN_ERROR_HTTP_STATUS_CODE: 보통 400대에서 체크하나 API 제공자에 따라 200에서도 체클할수 있음
* ex) TOKEN_ERROR_HTTP_STATUS_CODE = 200
* @formatter:on
*/
boolean needReissue = false;
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
String 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 (status >= 200 && status <= 207) {
if (useAdapterToken && StringUtils.equals(tokenErrorHttpStatusCode, "200")) {
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
vo.getEncode());
}
} else if (status == 302) {
if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) {
relayResponseHeaderKeys += ",Location";
}
} else {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseString);
// logger.error(errMsg);
if (status >= 400 && status < 500) {
if (useAdapterToken) {
needReissue = checkTokenRetry(responseMessage, tokenErrorCodeKey, tokenErrorCodeValues,
vo.getEncode());
}
// if (!needReissue) {
// throw new Exception(errMsg);
// }
} else {
throw new Exception(errMsg);
}
}
// ----------------------------------------------------------
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] responseType =" + vo.getResponseType());
logger.debug("HttpClientAdapterServiceRest] RECV " + vo.getEncode() + "(" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]");
// logger.debug("HttpClientAdapterServiceRest] RECV MS949 (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "MS949") + "]");
// logger.debug("HttpClientAdapterServiceRest] RECV euc-kr (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, "euc-kr") + "]");
logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = " + CommonLib.getDumpMessage(responseMessage));
}
if (useAdapterToken) {
if (responseMessage == null) {
throw new Exception("responseMessage is NULL, HttpStatus=" + status);
}
// OAuth 토큰 재요청 코드 확인
if (needReissue) {
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] retry access token response code = ["
+ vo.getResponseType() + "] message = [" + new String(responseMessage, vo.getEncode())
+ "]");
}
// 토큰 재발급
AccessTokenManager tokenManager = AccessTokenManager.getInstance();
String oldToken = accessToken == null ? null : accessToken.getAccessToken();
OAuth2AccessTokenVO newaccessToken = (OAuth2AccessTokenVO) tokenManager
.retryAccessTokenVO(vo.getAdapterGroupName(), prop, oldToken);
method.setHeader("Authorization", newaccessToken.getAuthorization());
setAuthHeaders(method, newaccessToken);
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName()
+ ") RETRY TOKEN = [" + newaccessToken + "]");
}
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
status = response.getCode();
responseMessage = EntityUtils.toByteArray(response.getEntity());
if (logger.isDebug()) {
logger.debug("[received status]" + status);
logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode()) + "]");
}
}catch (IOException e){
throw new Exception("retry excuteMethod Exceptioin = " + e.getMessage());
}
if (status == 302) {
if (!StringUtils.contains(relayResponseHeaderKeys, "Location")) {
relayResponseHeaderKeys += ",Location";
}
} else {
String errMsg = String.format(
"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s response Data=%s",
status, vo.getAdapterGroupName(), vo.getAdapterName(), uri, rmethod, responseMessage);
throw new Exception(errMsg);
}
// ----------------------------------------------------------
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
logger.debug("HttpClientAdapterServiceRest] RETRY responseType =" + vo.getResponseType());
logger.debug("HttpClientAdapterServiceRest] RETRY RECV " + vo.getEncode() + "("
+ vo.getAdapterGroupName() + ") = [" + new String(responseMessage, vo.getEncode())
+ "]");
logger.debug("HttpClientAdapterServiceRest] RETRY RECV (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(responseMessage));
}
// ----------------------------------------------------------
}
}
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"RECV [" + new String(responseMessage, vo.getEncode()) + "]"
+ CommonLib.getDumpMessage(responseMessage));
}
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType,
relayResponseHeaderKeys, status, responseHeaderProp);
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
"HttpClientAdapterServiceRest] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : "
+ ste.toString(),
ste);
}
logger.error("HttpClientAdapterServiceRest] SocketTimeoutException (" + vo.getAdapterGroupName() + ") : "
+ ste.toString(), ste);
throw ste;
} catch (ConnectException ce) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
"HttpClientAdapterServiceRest] Connection Exception (" + vo.getAdapterGroupName() + ") : "
+ ce.toString(),
ce);
}
logger.error("HttpClientAdapterServiceRest] Connection Exception (" + vo.getAdapterGroupName() + ") : "
+ ce.toString(), ce);
throw ce;
} catch (Exception e) {
if (vo.getTraceLevel() >= 3) {
HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(), e.toString(), e);
}
logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
e);
throw e;
} finally {
if (status != HttpStatus.SC_OK) {
String[] msgArgs = new String[1];
msgArgs[0] = String.valueOf(status);
String resMsg = ExceptionUtil.make("RDCEAIAHA013", msgArgs);
if (logger.isDebug()) {
logger.debug(resMsg);
}
}
}
}
private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) {
Properties headerProp = new Properties();
if (responseHeaders == null || responseHeaders.length == 0) {
return headerProp;
}
for (Header header : responseHeaders) {
headerProp.put(header.getName(), header.getValue());
}
return headerProp;
}
/**
* 현재 1단계 레이아웃에만 적용되도록 구현됨.
*
* @param method
* @param responseMessage
* @param headerGroupName
* @param encode
* @param messageType
* @param relayHeaderKeys
* @return
* @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)
|| StringUtils.isBlank(relayHeaderKeys)) {
return new String(responseMessage, encode);
}
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
JSONObject headerJson = new JSONObject();
for (String key : relayKeyArr) {
String value = responseHeaderProp.getProperty(key);
if (StringUtils.isNotBlank(value)) {
headerJson.put(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));
}
}
if (StringUtils.isNotBlank(headerGroupName) && message != null)
message.put(headerGroupName, headerJson);
logger.info("------------- message --------------- : {} ", message);
return message.toJSONString();
}
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
method.setHeader("Authorization",
String.format("%s", authorization));
}
private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
method.setHeader("Authorization",
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
}
private boolean checkTokenRetry(byte[] responseMessage, String tokenErrorCodeKey, String tokenErrorCodeValues,
String encode) {
if (responseMessage == null) {
return false;
}
if (StringUtils.isBlank(tokenErrorCodeKey)) {
return false;
}
if (StringUtils.isBlank(tokenErrorCodeValues)) {
return false;
}
try {
DocumentContext jsonContext = JsonPath.parse(new String(responseMessage, encode));
String responseCode = jsonContext.read(tokenErrorCodeKey);
if (StringUtils.isBlank(responseCode)) {
return false;
}
String[] arr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenErrorCodeValues, ",");
return ArrayUtils.contains(arr, responseCode);
} catch (Exception e) {
logger.error("HttpClientAdapterServiceRest] checkTokenRetry error=" + e.getMessage());
}
return false;
}
private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader) {
if (httpHeader == null) {
return;
}
if (httpHeader instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) httpHeader;
for (Object key : jsonObject.keySet()) {
Object obj = jsonObject.get(key);
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
continue;
}
method.setHeader((String) key, (String) obj);
if (logger.isDebugEnabled()) {
logger.debug("Request Header :" + (String) key + "=[" + (String) obj + "]");
}
}
}
}
@SuppressWarnings("deprecation")
private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method) {
if (eaiBody == null) {
return;
}
if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) {
if (eaiBody instanceof JSONObject) {
List<NameValuePair> params = new ArrayList<>();
JSONObject jsonObject = (JSONObject) eaiBody;
for (Object key : jsonObject.keySet()) {
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8);
method.setEntity(entity);
}
} else {
ContentType contentTypeObj = ContentType.create(contentType, charset);
// 요청 본문을 StringEntity 객체로 생성
StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj);
// 요청에 본문 추가
method.setEntity(entity);
}
}
private HashMap<String, String> getParameters(Object message) throws Exception {
HashMap<String, String> result = new HashMap<String, String>();
if (message != null) {
if (message instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) message;
for (Object key : jsonObject.keySet()) {
Object obj = jsonObject.get(key);
if ((obj instanceof JSONObject) || (obj instanceof JSONArray)) {
continue;
}
result.put((String) key, getStringValue(obj));
}
} else {
String[] messages = ((String) message).split("&");
String[] data = null;
for (int i = 0; i < messages.length; i++) {
data = messages[i].split("=", 2);
if (data.length == 2) {
result.put(data[0], data[1]);
}
}
}
}
return result;
}
public String getStringValue(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof String) {
return (String) obj;
} else if (obj instanceof Long) {
return Long.toString((Long) obj);
} else if (obj instanceof BigDecimal) {
return ((BigDecimal) obj).toPlainString();
} else {
return (String) obj;
}
}
public static Object parseJsonGeneric(String jsonData) {
if (StringUtils.isBlank(jsonData)) {
return null;
}
try {
JSONParser parser = new JSONParser();
Object parsedObj = parser.parse(jsonData);
return parsedObj;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
@SuppressWarnings("deprecation")
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
private Document convertXmlDocument(String message) throws Exception {
SAXReader builder = new SAXReader();
Document document = builder.read(new StringReader(message));
return document;
}
@SuppressWarnings("deprecation")
private String changeUrl(String messageType, String url, String restOption, Object sendData) {
if ((MessageType.JSON.equals(messageType) || MessageType.XML.equals(messageType))) {
try {
if (StringUtils.isBlank(restOption)) {
return url;
}
List<String> urlVariableList = new ArrayList<String>();
JSONObject restOptionObject = parseJson(restOption);
if (restOptionObject == null)
throw new Exception("restOption is NULL");
String type = (String) restOptionObject.get("type");
String requestExtraPath = (String) restOptionObject.get("extraPath");
url = getUrl(url, requestExtraPath);
if (TYPE_VARIABLE_URL_REQUEST.equals(type)) {
Pattern p = Pattern.compile("\\{(.*?)\\}");
Matcher m = p.matcher(requestExtraPath);
List<String> uriVariables = new ArrayList<>();
while(m.find()) {
String fieldName = m.group(1);
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 = (String) jsonObject.get(urlVaribleId);
urlVariableList.add(uriVariable);
jsonObject.remove(urlVaribleId);
}
} else if (MessageType.XML.equals(messageType)) {
Document doc;
if (sendData instanceof Document) {
doc = (Document) sendData;
} else {
doc = convertXmlDocument((String) sendData);
}
for (Object tempObject : uriVariables) {
String urlVaribleId = (String) tempObject;
Element element = (Element) doc.selectSingleNode("//" + urlVaribleId);
urlVariableList.add(element.getText());
if (doc.getRootElement() == element) {
doc = null;
} else {
element.getParent().remove(element);
}
}
}
if (urlVariableList.size() > 0) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(url).build();
Object[] urls = urlVariableList.toArray();
url = uriComponents.expand(urls).toUriString();
logger.debug("HttpClientAdapterServiceRest] after extand url=[" + url + "] ");
}
}
} catch (Exception e) {
logger.error("Failed to change url", e);
}
}
return url;
}
private String getUrl(String baseUrl, String extraPath) {
if (StringUtils.isBlank(extraPath)) {
return baseUrl;
}
if (StringUtils.contains(extraPath, "http://") || StringUtils.contains(extraPath, "https://")) {
return extraPath;
}
String targetUrl = baseUrl;
if (!targetUrl.endsWith("/")) {
targetUrl += "/";
}
if (extraPath.startsWith("/")) {
targetUrl += extraPath.substring(1);
} else {
targetUrl += extraPath;
}
logger.debug("HttpClientAdapterServiceRest] after concatenate url=[" + targetUrl + "] ");
return targetUrl;
}
}
@@ -6,92 +6,51 @@ 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;
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.HttpAdapterFilterFactory;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb;
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] ";
protected long slowTranTime = 2000L;
public static final String HEADER_NAME_CLIENT_ID = "x-elink-client-id";
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
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);
}
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());
String instanceId = request.getHeader(HttpClientAdapterServiceKey.INSTANCE_ID);
if (StringUtils.isNotBlank(instanceId)) {
prop.put(HttpClientAdapterServiceKey.INSTANCE_ID, instanceId);
}
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();
} catch (Exception e) {
}
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
Object obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
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");
}
}
@@ -103,7 +62,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
if (!"HTC".equals(group.getType())) {
String allowIp = prop.getProperty(ALLOW_IP,"");
if (StringUtils.isNotBlank(allowIp)) {
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
if (adapterFilter != null) {
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
}
@@ -121,7 +80,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
continue;
}
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
if (adapterFilter != null) {
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
} else {
@@ -144,7 +103,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
continue;
}
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
if (adapterFilter != null) {
resultMessage = adapterFilter.doPostFilter(adptGrpName, adptName, resultMessage, prop, request,
response);
@@ -1,149 +0,0 @@
package com.eactive.eai.adapter.http.dynamic;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
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.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.InboundErrorLogger;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.inbound.error.InboundErrorInfoVO;
import com.eactive.eai.inbound.error.InboundErrorKeys;
import com.eactive.eai.util.HexaConverter;
public class UnkownMessageLogUtils {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private UnkownMessageLogUtils() {
throw new IllegalStateException("Utility class");
}
public static void logUnkownMessage(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response, String errCode, Exception e) {
try {
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
String serverName = eaiServerManager.getLocalServerName();
String txId = prop.getProperty(HttpClientAdapterServiceKey.TRANSACTION_ID);
if(StringUtils.isEmpty(txId)) {
StringBuffer sb = new StringBuffer();
String instanceid1 = serverName.substring(0,2);
String instanceid2 = serverName.substring(serverName.length()-2, serverName.length());
String instid = instanceid1 + instanceid2;
txId = instid + UUIDGenerator.getUUID();
}
String clientId = prop.getProperty("clientId");
String hexClientId = clientId != null ? HexaConverter.bytesToHexa(clientId.getBytes()) : "";
String errorMsg = ExceptionUtil.make(e, errCode);
StringBuffer sb = new StringBuffer();
sb.append(errorMsg).append("\n").append("Remote Addr : ").append(request.getRemoteAddr()).append("\n").append("Request URI : ")
.append(request.getRequestURI()).append("\n").append("clientId=").append(clientId).append(",hexClientId=").append(hexClientId).append(",txProp=").append(prop).append("\n").append("Exception : ").append(e.getMessage());
UnkownMessageLogUtils.logUnknownMessage(txId, adptGrpName, adptName,
"", "", false, errCode, sb.toString(), System.currentTimeMillis(),
serverName, InboundErrorKeys.IN_UNKNOWN, message);
} catch (Exception ex) {
logger.warn("unkown log fail.", ex);
}
}
// public static void logUnkownMessage(String uuid, String adapterGroupName, String adapterName, String errCode,
// String errMsg, Object message) {
// UnkownMessageLogUtils.logUnknownMessage(uuid, adapterGroupName, adapterName,
// "", "", false, errCode, errMsg, System.currentTimeMillis(),
// EAIServerManager.getInstance().getInstId(), InboundErrorKeys.IN_UNKNOWN, message);
// }
private static void logUnknownMessage(String uuid, String adapterGroupName, String adapterName,
String bzwkSvcKeyName, String eaiSvcCd, boolean isTasStarted, String errCode, String errMsg, long errTm,
String svcInstNm, String errDstCd, Object message) {
String msg = null;
if(errDstCd == null || errDstCd.length() ==0) {
errDstCd = "ND";
}
if(isTasStarted) {
errDstCd = InboundErrorKeys.TAS_START;
}
InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO();
errorInfoVO.setEaiSvcSno(uuid); // EAI서비스일련번호
errorInfoVO.setAdptBwkGrpNm(adapterGroupName); // 어댑터업무그룹명
errorInfoVO.setEaiSvcCd(eaiSvcCd);
errorInfoVO.setErrCd(errCode); // 에러코드
errorInfoVO.setErrTxt(errMsg); // 에러내용
errorInfoVO.setErrTm(DatetimeUtil.getCurrentTime(errTm)); // 에러발생시각
errorInfoVO.setErrDstcd(errDstCd);
errorInfoVO.setBzwkSvcKeyName(bzwkSvcKeyName);
if(message == null) {
msg = "null";
}
else {
if(message instanceof byte[]) {
AdapterManager adapterManager = AdapterManager.getInstance();
AdapterGroupVO srcAdapterGrpVO = adapterManager.getAdapterGroupVO(adapterGroupName);
// TAS 거래일 경우 NULL일 있음
if(srcAdapterGrpVO == null) {
msg = new String((byte[])message);
}
else {
// String srcAdapterMsgType = srcAdapterGrpVO.getMessageType();
// if( MessageUtil.isUTF8(srcAdapterMsgType) ) {
// try {
// msg = new String((byte[])message, UJSONMessage.encode);
// } catch (UnsupportedEncodingException e) {
// logger.warn("msg encode error", e);
// msg = new String((byte[])message); }
// }
// else {
// msg = new String((byte[])message);
// }
String charset = StringUtils.defaultIfBlank(srcAdapterGrpVO.getMessageEncode(), Charset.defaultCharset().name());
try {
msg = new String((byte[])message, charset);
} catch (UnsupportedEncodingException e) {
logger.warn("msg encode error", e);
msg = new String((byte[])message);
}
}
}
else if(message instanceof String) {
msg = (String)message;
}
else {
msg = "invaild message ["+message.getClass().getName()+"] "+message.toString();
}
}
errorInfoVO.setBwkDataTxt(msg); // 업무데이터내용
errorInfoVO.setEaiSvrInstNm(svcInstNm); // EAI서버인스턴스명
InboundErrorLogger.error(errorInfoVO);
if (logger.isError()) {
logger.error("======================================================================" );
logger.error(" RequestDispatcher] GET UNKNOWN MESSAGE");
logger.error(" ADAPTER GROUP NAME [" + adapterGroupName + "]");
logger.error(" EAISVCNAME [" + eaiSvcCd + "]");
logger.error(" ADAPTER NAME [" + adapterName + "]");
logger.error("======================================================================" );
}
}
}
@@ -1,18 +1,20 @@
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 org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.IpUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AdapterAllowIPListFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
@@ -28,9 +30,9 @@ public class AdapterAllowIPListFilter implements HttpAdapterFilter {
}
if (!IpUtil.isMatchIp(allowedIps, requestIp)) {
throw new FilterException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
throw new HttpStatusException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
}
} catch (FilterException e) {
} catch (HttpStatusException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
@@ -8,12 +8,14 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.IpUtil;
public class AdapterBlockIpListFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
@@ -26,9 +28,9 @@ public class AdapterBlockIpListFilter implements HttpAdapterFilter {
}
if (IpUtil.isMatchIp(blockIps, requestIp)) {
throw new FilterException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
throw new HttpStatusException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
}
} catch (FilterException e) {
} catch (HttpStatusException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
@@ -44,9 +46,9 @@ public class AdapterBlockIpListFilter implements HttpAdapterFilter {
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
}
if (ipAddress.indexOf(",") >= 0) {
if(ipAddress.indexOf(",") >= 0) {
ipAddress = org.springframework.util.StringUtils.tokenizeToStringArray(ipAddress, ",")[0];
}
}
return ipAddress;
}
@@ -93,25 +93,18 @@ 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 = 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);
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
if (StringUtils.isBlank(apiId)) {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Can not find apiId(apiCode) info");
}
// if (StringUtils.isNotEmpty(eaiSvcCd) && !StringUtils.equals(eaiSvcCd, ))
// eaiSvcCd = prop.getProperty("API_SERVICE_CODE");
/**
* TODO: 중복되는 표준메시지 조회로 리소드 낭비가 있을 있으므로 체크 자체를 추후 RequestProcessor혹은 RestProcessor로 이동 필요 보임
* ApiRequestProcessor을 만드는것도 괜찮을 있을 같으나 관리가 안될 같음
*/
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
String eaiSvcCd = StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
if(!StringUtils.equals(eaiSvcCd, prop.getProperty("API_SERVICE_CODE"))) eaiSvcCd = prop.getProperty("API_SERVICE_CODE");
EAIMessage eaiMessage = EAIMessageManager.getInstance().getEAIMessage(eaiSvcCd);
if(StringUtils.isBlank(eaiMessage.getAuthType())){
@@ -312,8 +305,7 @@ public class ApiAuthFilter implements HttpAdapterFilter {
}
public static String extractBearerToken(HttpServletRequest request) throws JwtAuthException {
String tokenHeaderName = PropManager.getInstance().getProperty("ApiConfig", "token.header.name", "Authorization");
String authorization = request.getHeader(tokenHeaderName);
String authorization = request.getHeader("Authorization");
if (StringUtils.isBlank(authorization)) {
// QueryString으로 전달된 access_token 파라미터 확인
String tokenParamName = PropManager.getInstance().getProperty("ApiConfig", "token.param.name", "access_token");
@@ -321,13 +313,13 @@ public class ApiAuthFilter implements HttpAdapterFilter {
if (StringUtils.isNotBlank(queryToken)) {
return queryToken.trim();
}
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "No have header info: " + tokenHeaderName);
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "No have header info: Authorization");
}
String[] components = authorization.trim().split("\\s+", 2);
String[] components = authorization.split("\\s");
if (components.length != 2) {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Malformat [" + tokenHeaderName + "] content");
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Malformat [Authorization] content");
}
if (!StringUtils.equalsIgnoreCase(components[0], "Bearer")) {
@@ -1,95 +0,0 @@
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,18 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
@SuppressWarnings("serial")
public class FilterCryptoException extends FilterException {
public FilterCryptoException(String msg) {
super(msg);
}
public FilterCryptoException(String msg, int status) {
super(msg, status);
}
public FilterCryptoException(String msg, String code, int status) {
super(msg, code, status);
}
public FilterCryptoException(String msg, String code, int status, Throwable cause) {
super(msg, code, status, cause);
}
}
@@ -1,21 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import com.eactive.eai.adapter.http.HttpStatusException;
@SuppressWarnings("serial")
public class FilterException extends HttpStatusException {
public FilterException(String msg) {
super(msg);
}
public FilterException(String msg, int status) {
super(msg, status);
}
public FilterException(String msg, String code, int status) {
super(msg, code, status);
}
public FilterException(String msg, String code, int status, Throwable cause) {
super(msg, code, status, cause);
}
}
@@ -28,7 +28,7 @@ import com.eactive.eai.common.util.Logger;
public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter {
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_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(DJB_ROOTLESS_ARRAY)) {
chkBody = chkBody.substring(DJB_ROOTLESS_ARRAY.length());
if (chkBody.startsWith(KJB_ROOTLESS_ARRAY)) {
chkBody = chkBody.substring(KJB_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("DJB_ROOTLESS_ARRAY")) {
JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("DJB_ROOTLESS_ARRAY");
if (root instanceof JSONObject && ((JSONObject) root).containsKey("KJB_ROOTLESS_ARRAY")) {
JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("KJB_ROOTLESS_ARRAY");
outboundBody = jsonArray.toJSONString();
}
@@ -6,10 +6,13 @@ 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);
@@ -29,19 +32,12 @@ public class HttpAdapterFilterFactory {
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);
}
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
try {
Class cl = Class.forName(type);
filter = (HttpAdapterFilter) cl.newInstance();
} catch (Exception e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
}
break;
}
@@ -53,14 +49,4 @@ 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;
}
}
}
@@ -0,0 +1,67 @@
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;
}
}
}
@@ -1,85 +0,0 @@
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);
}
}
@@ -1,71 +0,0 @@
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);
}
}
@@ -1,110 +0,0 @@
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);
}
}
@@ -1,68 +0,0 @@
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);
}
}
@@ -1,215 +0,0 @@
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;
}
}
@@ -1,205 +0,0 @@
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);
}
}
@@ -1,237 +0,0 @@
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();
}
}
@@ -1,205 +0,0 @@
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;
}
}
@@ -45,7 +45,7 @@ public class HttpAdapterServiceBypass extends HttpAdapterServiceSupport {
public static final String HTTP_STATUS = "HTTP_STATUS";
public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";
public static final String[] HOP_BY_HOP_HEADERS = new String[] { "Connection", "Keep-Alive", "Proxy-Authenticate",
"Proxy-Authorization", "TE", "Trailers", "Trailer", "Transfer-Encoding", "Upgrade" };
"Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" };
protected boolean doPreserveCookies = false;
protected boolean doPreserveCookiePath = false;
@@ -21,19 +21,16 @@ 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;
@@ -197,7 +194,7 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
Object root = JSONValue.parse(body);
if( root instanceof JSONArray ) {
JSONObject wrappedObject = new JSONObject();
wrappedObject.put("DJB_ROOTLESS_ARRAY", root);
wrappedObject.put("KJB_ROOTLESS_ARRAY", root);
requestBytes = wrappedObject.toJSONString().getBytes(encode);
}
@@ -215,7 +212,6 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
stopWatch.stop();
byte[] responseBytes = null;
String responseData = "";
if (RESPONSE_TYPE_ASYNC.equals(responseType)){
if (stopWatch.getTime() > slowTranTime){
@@ -226,22 +222,17 @@ 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) {
responseBytes = ((String)result).getBytes();
if ( StringUtils.isBlank((String)result ) ) {
responseData = (String) result;
if ( StringUtils.isBlank( responseData ) ) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
responseBytes = responseData.getBytes();
}
}
response.setCharacterEncoding(encode);
// response.getWriter().print(responseData);
response.getOutputStream().write(responseBytes);
response.getWriter().print(responseData);
if (traceLevel >= 3){
HttpMemoryLogger.txlog(adptGrpName+adptName, "SEND "+"["+responseData+"]"+
((responseData == null)?"":CommonLib.getDumpMessage(responseData))
@@ -249,58 +240,45 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
}
} else {
if(requestBytes != null) {
if ( result == null) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
responseBytes = responseData.getBytes();
} else if (result instanceof byte[]) {
if (result instanceof byte[]) {
responseData = new String((byte[]) result, encode);
responseBytes = (byte[]) result;
} else if (result instanceof String) {
responseBytes = ((String)result).getBytes();
if ( StringUtils.isBlank((String)result ) ) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
responseBytes = responseData.getBytes();
}
responseData = (String) result;
}
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("DJB_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("KJB_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);
String resContentType = request.getContentType();
response.setContentType(resContentType);
response.getOutputStream().write(responseBytes);
response.getWriter().print(responseData);
if (logger.isDebug()){
logger.debug("HttpAdapter] SEND ("+adptGrpName+") = [" + responseData + "]");
logger.debug("HttpAdapter] SEND ("+adptGrpName+") = " +
@@ -1,153 +0,0 @@
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,6 +1,5 @@
package com.eactive.eai.agent.encryption;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.eactive.eai.common.exception.ExceptionUtil;
@@ -12,8 +11,6 @@ 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 {
@@ -21,8 +18,6 @@ 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);
@@ -60,15 +55,8 @@ public class EncryptionManager implements Lifecycle {
encryptYN = "N";
}
}
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 + " ]");
logger.warn("EncryptionManager] init :: encryption YN [ " + encryptYN + " ]");
}
public synchronized void reload() throws Exception {
@@ -111,101 +99,4 @@ 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;
}
}
@@ -2,8 +2,7 @@ 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.InflowControlManager;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowAdapterControlCommand extends Command {
@@ -28,7 +27,7 @@ public class ReloadInflowAdapterControlCommand extends Command {
String keyName = (String) args;
try {
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
InflowControlManager manager = InflowControlManager.getInstance();
if (keyName != null) {
if ("ALL".equals(keyName)) {
@@ -2,8 +2,7 @@ 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.InflowControlManager;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowGroupControlCommand extends Command {
@@ -29,7 +28,7 @@ public class ReloadInflowGroupControlCommand extends Command {
String keyName = (String) args;
try {
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
InflowControlManager manager = InflowControlManager.getInstance();
if (keyName != null) {
if ("ALL".equals(keyName)) {
@@ -2,8 +2,7 @@ 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.InflowControlManager;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowInterfaceControlCommand extends Command {
@@ -29,7 +28,7 @@ public class ReloadInflowInterfaceControlCommand extends Command {
String keyName = (String) args;
try {
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
InflowControlManager manager = InflowControlManager.getInstance();
if (keyName != null) {
if ("ALL".equals(keyName)) {
@@ -2,8 +2,7 @@ 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.InflowControlManager;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowAdapterControlCommand extends Command {
@@ -27,7 +26,7 @@ public class RemoveInflowAdapterControlCommand extends Command {
String key = (String) args;
try {
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
InflowControlManager manager = InflowControlManager.getInstance();
manager.removeAdapter(key);
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
@@ -2,8 +2,7 @@ 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.InflowControlManager;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowGroupControlCommand extends Command {
@@ -29,7 +28,7 @@ public class RemoveInflowGroupControlCommand extends Command {
String key = (String) args;
try {
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
InflowControlManager manager = InflowControlManager.getInstance();
manager.removeGroup(key);
if (logger.isWarn())
logger.warn(this.name + "] group " + key + " removed.");
@@ -2,8 +2,7 @@ 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.InflowControlManager;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowInterfaceControlCommand extends Command {
@@ -26,7 +25,7 @@ public class RemoveInflowInterfaceControlCommand extends Command {
String key = (String) args;
try {
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
InflowControlManager manager = InflowControlManager.getInstance();
manager.removeInterface(key);
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
@@ -1,28 +0,0 @@
package com.eactive.eai.agent.security;
import com.eactive.eai.agent.command.Command;
import com.eactive.eai.agent.command.CommandException;
import com.eactive.eai.common.security.CryptoModuleManager;
import com.eactive.eai.common.util.Logger;
public class ReloadCryptoModuleCommand extends Command {
private static final long serialVersionUID = 1032982004418318100L;
@Override
public Object execute() throws CommandException {
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
try {
String cryptoId = (String) args;
CryptoModuleManager.getInstance().reload(cryptoId);
return "success";
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
String msg = this.makeException(rspErrorCode, e);
if (logger.isError()) {
logger.error(msg, e);
}
throw new CommandException(e.getMessage());
}
}
}
@@ -1,123 +0,0 @@
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;
}
}
@@ -1,46 +0,0 @@
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";
}
}
@@ -1,47 +0,0 @@
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,21 +13,15 @@ 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 클래스
@@ -61,8 +55,13 @@ public class AccessTokenManagerByDB implements Lifecycle {
/**
* 기동 여부
*/
private boolean started;
private boolean started;
/**
* 토큰 정보를 저장하는 collection
*/
private HashMap<String, AccessTokenVO> tokens;
/**
* 인증 정보를 저장하는 Map
*/
@@ -79,6 +78,7 @@ 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,6 +117,7 @@ public class AccessTokenManagerByDB implements Lifecycle {
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
tokens.clear();
init();
} catch(Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201"));
@@ -167,6 +168,7 @@ public class AccessTokenManagerByDB implements Lifecycle {
logger.error("Scheduler shutdown interrupted", e);
}
this.tokens = null;
started = false;
if (logger.isWarn()){
@@ -238,34 +240,27 @@ public class AccessTokenManagerByDB implements Lifecycle {
try {
logger.debug("Executing token issuance for adapter group: {}", adapterGroupName);
SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, new Function<AccessTokenVO, AccessTokenVO>() {
// 현재 토큰 가져오기
AccessTokenVO accessToken = tokens.get(adapterGroupName);
@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;
}
// 다음 스케줄 시간 계산
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);
} else {
//토큰이 있지만 만료 시간이 없는 경우
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
return accessToken;
// 토큰이 아직 유효한 경우
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
}
}
});
} 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) {
@@ -287,24 +282,19 @@ public class AccessTokenManagerByDB implements Lifecycle {
}
}
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);
private void issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo, boolean isTokenExpiredWithinInterval) throws Exception {
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
boolean isExpired = true;
// if (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) {
logger.info("Issuing new token for adapter group: {}", adapterGroupName);
// tokens.remove(adapterGroupName);
tokens.remove(adapterGroupName);
AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
@@ -316,23 +306,16 @@ 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);
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
AccessTokenVO accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
this.tokens.put(adapterGroupName, accessToken);
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) {
@@ -341,7 +324,7 @@ public class AccessTokenManagerByDB implements Lifecycle {
task.cancel(true);
logger.warn("Token task stopped for adapter group: {}", adapterGroupName);
}
SessionManager.getInstance().removeOutboundAccessToken(adapterGroupName);
tokens.remove(adapterGroupName);
logger.debug("Token removed for adapter group: {}", adapterGroupName);
}
@@ -399,28 +382,15 @@ public class AccessTokenManagerByDB implements Lifecycle {
* @return Access 토큰 정보
**/
public AccessTokenVO getAccessTokenVO(String adapterGroupName) {
AccessTokenVO accessToken = null;
AccessTokenVO accessToken = null;
try {
accessToken = tokens.get(adapterGroupName);
} catch(Exception e) {
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
}
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;
return accessToken;
}
/**
@@ -433,9 +403,9 @@ public class AccessTokenManagerByDB implements Lifecycle {
**/
public String getAccessToken(String adapterGroupName) {
String accessToken = null;
try {
accessToken = getAccessTokenVO(adapterGroupName).getAccessToken();
accessToken = tokens.get(adapterGroupName).getAccessToken();
} catch(Exception e) {
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
}
@@ -465,29 +435,27 @@ public class AccessTokenManagerByDB implements Lifecycle {
* 3. 주의사항
*
**/
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties,
String oldToken) throws Exception {
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties, String oldToken) throws Exception {
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
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);
}
}
// 동시 요청이 발생할 경우 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);
}
tokens.put(adapterGroupName, accessToken);
}
return accessToken;
}
}
}
@@ -17,6 +17,10 @@ import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
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.CoreAlarmKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -41,7 +45,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
TypedCircuitBreakerVO defaultConfigVo;
HashMap<String, TypedCircuitBreakerVO> idCBConfigMap = new HashMap<>();
HashMap<String, TypedCircuitBreakerVO> apiIdCBConfigMap = new HashMap<>();
// HashMap<String, TypedCircuitBreakerVO> urlCBConfigMap = new HashMap<>();
HashMap<String, TypedCircuitBreakerVO> urlCBConfigMap = new HashMap<>();
private boolean started;
@Autowired
@@ -112,7 +116,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
CircuitBreakerConfig circuitBreakerConfig = this.createCircuitBreakerConfig(vo);
vo.setCircuitBreakerConfig(circuitBreakerConfig);
this.apiIdCBConfigMap.put(vo.getName(), vo);
this.idCBConfigMap.put(vo.getId(), vo);
this.idCBConfigMap.remove(vo.getId(), vo);
}
}
@@ -336,7 +340,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
switch (event.getEventType()) {
case ERROR :
TypedCircuitBreakerManager.logger
.error("CircuitBreaker ERROR event occurred. CircuitBreakerEvent={}" + event);
.error("CirbuitBreaker ERROR event occurred. CircuitBreakerEvent={}" + event);
break;
case STATE_TRANSITION :
if (event instanceof CircuitBreakerOnStateTransitionEvent) {
@@ -345,7 +349,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
CircuitBreaker.State toState = stateTransition.getToState();
CircuitBreaker.State fromState = stateTransition.getFromState();
TypedCircuitBreakerManager.logger.error(
"CircuitBreaker STATE_TRANSITION event occurred. {} : {} -> {}. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
"CirbuitBreaker 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)) {
@@ -355,19 +359,18 @@ public class TypedCircuitBreakerManager implements Lifecycle {
// } else {
// this.sendAlert(stateTransitionEvent);
// }
// AlarmEvent alarmEvent = AlarmEvent.builder().key(CoreAlarmKey.CircuitBreaker)
// .condition(AlarmCondition.builder().build())
// .message(String.format("CircuitBreaker state changed. apiId=%s, %s -> %s", event.getCircuitBreakerName(), fromState, toState))
// .build();
//
// AlarmStateManager.getAlarmStateManager().onEvnet(alarmEvent);
AlarmEvent alarmEvent = AlarmEvent.builder().key(CoreAlarmKey.CircuitBreaker)
.condition(AlarmCondition.builder().build())
.message(String.format("CircuitBreaker state changed. apiId=%s, %s -> %s", event.getCircuitBreakerName(), fromState, toState))
.build();
AlarmStateManager.getAlarmStateManager().onEvnet(alarmEvent);
TypedCircuitBreakerManager.logger.warn("CircuitBreaker state changed. apiId={}, {} -> {}", event.getCircuitBreakerName(), fromState, toState);
logger.warn("CircuitBreaker state changed. apiId={}, {} -> {}", event.getCircuitBreakerName(), fromState, toState);
} else {
TypedCircuitBreakerManager.logger.error(
"CircuitBreaker STATE_TRANSITION event occurred. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
"CirbuitBreaker STATE_TRANSITION event occurred. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
new Object[]{event, this.vo});
}
break;
@@ -7,10 +7,6 @@ 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;
@@ -753,53 +749,6 @@ 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()) {
@@ -1,266 +0,0 @@
package com.eactive.eai.common.hsm;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
/**
* SafeNet ProtectServer HSM 암복호화 서비스 (JDK 8)
*
* [ 조회]
* getPublicKey - 공개키 반환 (외부 노출). 최초 1회만 HSM 통신, 이후 캐시 반환.
* getSecretKey - AES 대칭키 반환 (CKA_EXTRACTABLE=true 필요). 최초 1회만 HSM 통신, 이후 캐시 반환.
*
* [암복호화]
* encryptRsa - 공개키로 JVM 소프트웨어 암호화 (HSM 불필요)
* decryptRsa - alias 기반, HSM 내부 복호화 ( 호출마다 HSM 통신 발생 - 불가피)
* encryptAes - SecretKey 객체로 AES/CBC 암호화
* decryptAes - SecretKey 객체로 AES/CBC 복호화
*/
@Service
public class HsmCryptoService implements PropertyChangeListener {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String PROP_GROUP = "HSM";
private static final String PROP_CACHE_RELOAD_YN = "CACHE_RELOAD_YN";
// HSM 통신 최소화: 최초 1회 조회 JVM 메모리에 캐싱
private final ConcurrentHashMap<String, SecretKey> secretKeyCache = new ConcurrentHashMap<String, SecretKey>();
private final ConcurrentHashMap<String, PublicKey> publicKeyCache = new ConcurrentHashMap<String, PublicKey>();
@Autowired
private PropManager propManager;
@PostConstruct
public void registerPropertyChangeListener() {
propManager.addPropertyChangeListener(this);
logger.warn("HsmCryptoService] PropManager PropertyChangeListener 등록 완료");
}
/**
* 관리포털에서 PropManager.reload("HSM") 호출 이벤트를 수신한다.
* HSM.CACHE_RELOAD_YN = Y 이면 캐시를 초기화한다.
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!PROP_GROUP.equals(evt.getPropertyName())) {
return;
}
String reloadYn = propManager.getProperty(PROP_GROUP, PROP_CACHE_RELOAD_YN, "N");
if ("Y".equalsIgnoreCase(reloadYn)) {
clearKeyCache();
}
}
// -------------------------------------------------------------------------
// 조회 (외부 노출)
// -------------------------------------------------------------------------
/**
* HSM KeyStore 에서 공개키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
*/
public PublicKey getPublicKey(String keyAlias) throws HsmException {
checkReady();
PublicKey cached = publicKeyCache.get(keyAlias);
if (cached != null) {
return cached;
}
try {
Certificate cert = HsmManager.getInstance().getKeyStore().getCertificate(keyAlias);
if (cert == null) {
throw new HsmException("인증서를 찾을 수 없습니다. alias=" + keyAlias);
}
PublicKey key = cert.getPublicKey();
publicKeyCache.put(keyAlias, key);
logger.warn("HsmCryptoService] 공개키 캐시 등록: alias=" + keyAlias);
return key;
} catch (HsmException e) {
throw e;
} catch (Exception e) {
throw new HsmException("공개키 조회 실패: " + e.getMessage(), e);
}
}
/**
* HSM KeyStore 에서 AES 대칭키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
* HSM 생성 CKA_EXTRACTABLE=true (ctkmu -x 플래그) 생성된 키만 반환됩니다.
*/
public SecretKey getSecretKey(String keyAlias) throws HsmException {
checkReady();
SecretKey cached = secretKeyCache.get(keyAlias);
if (cached != null) {
return cached;
}
try {
KeyStore keyStore = HsmManager.getInstance().getKeyStore();
java.security.Key key = keyStore.getKey(keyAlias, null);
if (!(key instanceof SecretKey)) {
throw new HsmException("AES 키를 찾을 수 없습니다 (CKA_EXTRACTABLE=true 확인 필요). alias=" + keyAlias);
}
SecretKey secretKey = (SecretKey) key;
secretKeyCache.put(keyAlias, secretKey);
logger.warn("HsmCryptoService] 대칭키 캐시 등록: alias=" + keyAlias);
return secretKey;
} catch (HsmException e) {
throw e;
} catch (Exception e) {
throw new HsmException("AES 키 조회 실패: " + e.getMessage(), e);
}
}
/** 캐시를 비웁니다. HSM 키 교체 후 재로드가 필요할 때 호출합니다. */
public void clearKeyCache() {
secretKeyCache.clear();
publicKeyCache.clear();
logger.warn("HsmCryptoService] 키 캐시 초기화 완료");
}
// -------------------------------------------------------------------------
// RSA
// -------------------------------------------------------------------------
/**
* RSA 암호화 - HSM 인증서의 공개키로 JVM 소프트웨어 암호화.
* 공개키는 HSM 에서 추출하므로 HsmManager 초기화되어 있어야 합니다.
*/
public byte[] encryptRsa(String keyAlias, byte[] plaintext) throws HsmException {
return encryptRsa(getPublicKey(keyAlias), plaintext);
}
/**
* RSA 암호화 - 전달받은 공개키로 JVM 소프트웨어 암호화.
* HSM 없이도 호출 가능합니다.
*/
public byte[] encryptRsa(PublicKey publicKey, byte[] plaintext) throws HsmException {
try {
// Provider 미명시 JVM 소프트웨어 Provider(SunRsaSign) 자동 선택
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plaintext);
} catch (Exception e) {
throw new HsmException("RSA 암호화 실패: " + e.getMessage(), e);
}
}
/**
* RSA 복호화 - 개인키 핸들을 HSM 에서 가져와 HSM 내부에서 복호화.
* HsmManager 초기화되지 않은 경우 JVM 소프트웨어로 fallback 합니다.
*
* @param keyAlias HSM KeyStore 개인키 별칭
* @param pin HSM 슬롯 PIN (null 이면 HsmManager 초기화 사용한 PIN 재사용)
* @param ciphertext 암호문 바이트
*/
public byte[] decryptRsa(String keyAlias, char[] pin, byte[] ciphertext) throws HsmException {
try {
HsmManager hsmManager = HsmManager.getInstance();
if (hsmManager.isReady()) {
// HSM 내부 복호화: pkcs11Provider 명시 필수
Provider pkcs11Provider = hsmManager.getPkcs11Provider();
java.security.Key key = hsmManager.getKeyStore().getKey(keyAlias, pin);
if (!(key instanceof PrivateKey)) {
throw new HsmException("개인키를 찾을 수 없습니다. alias=" + keyAlias);
}
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM, pkcs11Provider);
cipher.init(Cipher.DECRYPT_MODE, (PrivateKey) key);
return cipher.doFinal(ciphertext);
} else {
throw new HsmException("RSA 복호화 불가: HsmManager 가 초기화되지 않았습니다. alias=" + keyAlias);
}
} catch (HsmException e) {
throw e;
} catch (Exception e) {
throw new HsmException("RSA 복호화 실패: " + e.getMessage(), e);
}
}
// -------------------------------------------------------------------------
// AES
// -------------------------------------------------------------------------
/**
* AES/CBC/PKCS5Padding 암호화.
* extractable : provider=null JVM 소프트웨어 암호화.
* non-extractable : provider pkcs11Provider 전달해야 합니다.
*
* @param secretKey 대칭키 (HSM 추출 또는 소프트웨어 )
* @param iv 초기화 벡터 (16바이트)
* @param plaintext 평문 바이트
* @param provider null 이면 JVM 기본 Provider 사용
*/
public byte[] encryptAes(SecretKey secretKey, byte[] iv, byte[] plaintext, Provider provider) throws HsmException {
try {
Cipher cipher = (provider != null)
? Cipher.getInstance(AES_ALGORITHM, provider)
: Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
return cipher.doFinal(plaintext);
} catch (Exception e) {
throw new HsmException("AES 암호화 실패: " + e.getMessage(), e);
}
}
/** AES 암호화 - extractable 키 전용 (JVM 소프트웨어 Provider 사용). */
public byte[] encryptAes(SecretKey secretKey, byte[] iv, byte[] plaintext) throws HsmException {
return encryptAes(secretKey, iv, plaintext, null);
}
/**
* AES/CBC/PKCS5Padding 복호화.
* extractable : provider=null JVM 소프트웨어 복호화.
* non-extractable : provider pkcs11Provider 전달해야 합니다.
*
* @param secretKey 대칭키 (HSM 추출 또는 소프트웨어 )
* @param iv 초기화 벡터 (16바이트)
* @param ciphertext 암호문 바이트
* @param provider null 이면 JVM 기본 Provider 사용
*/
public byte[] decryptAes(SecretKey secretKey, byte[] iv, byte[] ciphertext, Provider provider) throws HsmException {
try {
Cipher cipher = (provider != null)
? Cipher.getInstance(AES_ALGORITHM, provider)
: Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
return cipher.doFinal(ciphertext);
} catch (Exception e) {
throw new HsmException("AES 복호화 실패: " + e.getMessage(), e);
}
}
/** AES 복호화 - extractable 키 전용 (JVM 소프트웨어 Provider 사용). */
public byte[] decryptAes(SecretKey secretKey, byte[] iv, byte[] ciphertext) throws HsmException {
return decryptAes(secretKey, iv, ciphertext, null);
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
private void checkReady() throws HsmException {
if (!HsmManager.getInstance().isReady()) {
throw new HsmException("HsmManager 가 초기화되지 않았습니다.");
}
}
}
@@ -1,13 +0,0 @@
package com.eactive.eai.common.hsm;
public class HsmException extends Exception {
private static final long serialVersionUID = 1L;
public HsmException(String msg) {
super(msg);
}
public HsmException(String msg, Throwable cause) {
super(msg, cause);
}
}
@@ -1,205 +0,0 @@
package com.eactive.eai.common.hsm;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
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;
import com.eactive.eai.common.exception.ExceptionUtil;
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.property.PropManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
/**
* SafeNet ProtectServer HSM 연동 관리자 (JDK 8 / SunPKCS11)
*
* PropManager 그룹 "HSM" 에서 읽는 :
* 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 {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private static final String GROUP_NAME = "HSM";
private static final String PROP_CONFIG = "PKCS11_CONFIG";
private static final String PROP_PIN = "PIN";
private Provider pkcs11Provider;
private KeyStore keyStore;
private boolean started;
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
private HsmManager() {
}
public static HsmManager getInstance() {
return ApplicationContextProvider.getContext().getBean(HsmManager.class);
}
@Override
public void start() throws LifecycleException {
if (started) {
throw new LifecycleException("RECEAIHSM001");
}
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
init();
} catch (Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIHSM002"));
}
started = true;
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
private void init() throws Exception {
String configContent = PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG);
String pin = PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN);
if (configContent == null || configContent.trim().isEmpty()) {
logger.warn("HsmManager] PKCS11_CONFIG 가 설정되지 않았습니다. HSM 초기화를 건너뜁니다.");
return;
}
pkcs11Provider = createProvider(configContent.trim().replace("\\n", "\n"));
// 이미 등록된 Provider 있으면 제거 재등록
Provider existing = Security.getProvider(pkcs11Provider.getName());
if (existing != null) {
Security.removeProvider(existing.getName());
}
Security.addProvider(pkcs11Provider);
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
char[] pinChars = (pin != null) ? pin.toCharArray() : null;
keyStore.load(null, pinChars);
logger.warn("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());
java.util.Enumeration<String> aliases = keyStore.aliases();
StringBuilder aliasList = new StringBuilder();
while (aliases.hasMoreElements()) {
if (aliasList.length() > 0) aliasList.append(", ");
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 + "]");
}
/**
* JDK 버전에 따라 SunPKCS11 Provider 생성한다.
*
* JDK 8 : SunPKCS11(InputStream) 생성자를 리플렉션으로 호출
* JDK 9+: Provider.configure(configFilePath) 리플렉션으로 호출
*
* 경로 모두 리플렉션을 사용하므로 컴파일 타임에 JDK 버전 의존성이 없다.
*/
static Provider createProvider(String cfgContent) throws Exception {
String javaVersion = System.getProperty("java.version");
if (javaVersion.startsWith("1.")) {
// JDK 8: new SunPKCS11(InputStream)
InputStream is = new ByteArrayInputStream(cfgContent.getBytes("UTF-8"));
return (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
.getConstructor(InputStream.class)
.newInstance(is);
} else {
// JDK 9+: Security.getProvider("SunPKCS11").configure(configFilePath)
// configure() JDK 9 에서 추가된 메서드이므로 리플렉션으로 호출
File tmp = File.createTempFile("pkcs11-hsm-", ".cfg");
tmp.deleteOnExit();
Files.write(tmp.toPath(), cfgContent.getBytes("UTF-8"));
Provider base = Security.getProvider("SunPKCS11");
Method configure = Provider.class.getMethod("configure", String.class);
return (Provider) configure.invoke(base, tmp.getAbsolutePath());
}
}
@Override
public void stop() throws LifecycleException {
if (!started) {
throw new LifecycleException("RECEAIHSM003");
}
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
if (pkcs11Provider != null) {
Security.removeProvider(pkcs11Provider.getName());
}
pkcs11Provider = null;
keyStore = null;
started = false;
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public boolean isStarted() {
return started;
}
public Provider getPkcs11Provider() {
return pkcs11Provider;
}
public KeyStore getKeyStore() {
return keyStore;
}
public boolean isReady() {
return started && pkcs11Provider != null && keyStore != null;
}
}
@@ -1,501 +0,0 @@
package com.eactive.eai.common.inflow;
import java.time.Duration;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.eactive.eai.common.exception.ExceptionUtil;
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.util.Logger;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import io.github.bucket4j.local.LocalBucket;
import io.github.bucket4j.local.LocalBucketBuilder;
public abstract class AbstractInflowControlManager implements Lifecycle, Bucket {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
public static final String QUOTA_TIMEUNIT_SECOND = "SEC";
public static final String QUOTA_TIMEUNIT_MINUTE = "MIN";
public static final String QUOTA_TIMEUNIT_HOUR = "HOU";
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
private volatile Map<String, CustomBucket> adapterBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomBucket> interfaceBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomGroupBucket> groupBucketList = new ConcurrentHashMap<>();
private volatile Map<String, String> interfaceToGroupMap = new ConcurrentHashMap<>();
private volatile Map<String, InflowGroupVO> groupVoMap = new ConcurrentHashMap<>();
private boolean started;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@Autowired
protected InflowControlDAO inflowControlDAO;
public void start() throws LifecycleException {
if (started)
throw new LifecycleException("RECEAICMM201");
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
init();
} catch (Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202"));
}
started = true;
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
private void init() throws Exception {
initAdapter();
initInterface();
initGroup();
}
private void initAdapter() throws Exception {
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
if (AbstractInflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.adapterBucketList = newMap;
}
private void initInterface() throws Exception {
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
if (AbstractInflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.interfaceBucketList = newMap;
}
private void initGroup() throws Exception {
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
for (InflowGroupVO groupVo : inflowGroupVoList) {
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
newGroupBucketList.put(groupVo.getGroupId(), bucket);
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
for (String interfaceId : groupVo.getInterfaceList()) {
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
}
}
}
if (logger.isInfo()) {
logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size()
+ ", mappings=" + newInterfaceToGroupMap.size());
}
this.groupBucketList = newGroupBucketList;
this.interfaceToGroupMap = newInterfaceToGroupMap;
this.groupVoMap = newGroupVoMap;
}
public synchronized void reloadAdapter() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initAdapter();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadInterface() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initInterface();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadAdapter(String adapter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByAdater(adapter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
adapterBucketList.put(inflowTarget.getName(), bucket);
} else {
adapterBucketList.remove(inflowTarget.getName());
}
} else {
adapterBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadInterface(String inter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByInterface(inter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
interfaceBucketList.put(inflowTarget.getName(), bucket);
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadGroup() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all Started ...");
}
initGroup();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all finished ...");
}
}
public synchronized void reloadGroup(String groupId) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId);
}
Map<String, InflowGroupVO> map = inflowControlDAO.getInflowTargetByGroup(groupId);
if (map != null) {
Iterator<Map.Entry<String, String>> mappingIt = interfaceToGroupMap.entrySet().iterator();
while (mappingIt.hasNext()) {
Map.Entry<String, String> entry = mappingIt.next();
if (groupId.equals(entry.getValue())) {
mappingIt.remove();
}
}
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
InflowGroupVO groupVo = map.get(key);
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
groupBucketList.put(groupVo.getGroupId(), bucket);
groupVoMap.put(groupVo.getGroupId(), groupVo);
for (String interfaceId : groupVo.getInterfaceList()) {
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup finished ...");
}
}
public void stop() throws LifecycleException {
if (!started)
throw new LifecycleException("RECEAICMM203");
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
adapterBucketList = new ConcurrentHashMap<>();
interfaceBucketList = new ConcurrentHashMap<>();
groupBucketList = new ConcurrentHashMap<>();
interfaceToGroupMap = new ConcurrentHashMap<>();
groupVoMap = new ConcurrentHashMap<>();
started = false;
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
public boolean isStarted() {
return this.started;
}
public String[] getAdapterAllKeys() {
Iterator<String> it = this.adapterBucketList.keySet().iterator();
String[] svcCd = new String[this.adapterBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public String[] getInterfaceAllKeys() {
Iterator<String> it = this.interfaceBucketList.keySet().iterator();
String[] svcCd = new String[this.interfaceBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public synchronized void removeAdapter(String adapter) {
adapterBucketList.remove(adapter);
}
public synchronized void removeInterface(String inter) {
interfaceBucketList.remove(inter);
}
public synchronized void removeGroup(String groupId) {
groupBucketList.remove(groupId);
groupVoMap.remove(groupId);
Iterator<Map.Entry<String, String>> it = interfaceToGroupMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
if (groupId.equals(entry.getValue())) {
it.remove();
}
}
}
public String[] getGroupAllKeys() {
Iterator<String> it = this.groupBucketList.keySet().iterator();
String[] groupIds = new String[this.groupBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
groupIds[i] = it.next();
}
Arrays.sort(groupIds);
return groupIds;
}
public CustomGroupBucket getGroupBucket(String groupId) {
return groupBucketList.get(groupId);
}
private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) {
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
LocalBucketBuilder builder = Bucket4j.builder();
if (inflowTarget.getThresholdPerSecond() > 0) {
builder.addLimit(Bandwidth.classic(inflowTarget.getThresholdPerSecond(),
Refill.greedy(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1))));
}
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
builder.addLimit(Bandwidth.classic(inflowTarget.getThreshold(),
Refill.intervally(inflowTarget.getThreshold(),
AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit()))));
}
return new CustomBucket(builder.build(), inflowTarget);
}
return null;
}
private CustomGroupBucket makeGroupBucket(InflowGroupVO groupVo) {
if (!isInflowGroupTarget(groupVo)) {
return null;
}
LocalBucket perSecondBucket = null;
LocalBucket thresholdBucket = null;
if (groupVo.getThresholdPerSecond() > 0) {
perSecondBucket = Bucket4j.builder()
.addLimit(Bandwidth.classic(groupVo.getThresholdPerSecond(),
Refill.greedy(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1))))
.build();
}
if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) {
thresholdBucket = Bucket4j.builder()
.addLimit(Bandwidth.classic(groupVo.getThreshold(),
Refill.intervally(groupVo.getThreshold(),
AbstractInflowControlManager.getPeriod(groupVo.getThresholdTimeUnit()))))
.build();
}
return new CustomGroupBucket(perSecondBucket, thresholdBucket, groupVo);
}
@Override
public boolean isAdapterPass(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public boolean isInterfacePass(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInflowThreashold(String inter) {
return null;
}
public InflowTargetVO getAdapterInflow(String adapter) {
return getAdapterInflowThreashold(adapter);
}
public InflowTargetVO getInterfaceInflow(String inter) {
return getInterfaceInflowThreashold(inter);
}
@Override
public String isGroupPass(String groupId) {
CustomGroupBucket b = groupBucketList.get(groupId);
if (b == null)
return null;
return b.tryConsume();
}
@Override
public InflowGroupVO getGroupInflowThreshold(String groupId) {
return groupVoMap.get(groupId);
}
@Override
public String getGroupIdByInterface(String interfaceId) {
return interfaceToGroupMap.get(interfaceId);
}
public InflowGroupVO getGroupInflow(String groupId) {
return getGroupInflowThreshold(groupId);
}
public static boolean isInflowTarget(InflowTargetVO inflowTarget) {
if (inflowTarget == null || !inflowTarget.isActivate()) {
return false;
}
return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static boolean isInflowGroupTarget(InflowGroupVO groupVo) {
if (groupVo == null || !groupVo.isActivate()) {
return false;
}
return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static Duration getPeriod(String timeUnit) {
if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) {
return Duration.ofSeconds(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) {
return Duration.ofMinutes(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) {
return Duration.ofHours(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) {
return Duration.ofDays(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) {
Calendar calendar = Calendar.getInstance();
return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
} else {
return null;
}
}
}
@@ -41,36 +41,20 @@ public class InflowControlDAO extends BaseDAO {
@Autowired
private InflowControlGroupMappingLoader inflowControlGroupMappingLoader;
// -------------------------------------------------------------------------
// Enum 기반 범용 메서드 type 코드를 직접 노출하지 않음
// -------------------------------------------------------------------------
public List<InflowTargetVO> getInflowList(InflowType type) throws DAOException {
return getInflowList(type.code());
}
public Map<String, InflowTargetVO> getInflowMap(InflowType type, String name) throws DAOException {
return getInflowMap(type.code(), name);
}
// -------------------------------------------------------------------------
// 기존 명명 메서드 enum 기반 메서드로 위임
// -------------------------------------------------------------------------
public List<InflowTargetVO> getInflowAdapterList() throws DAOException {
return getInflowList(InflowType.ADAPTER);
return getInflowList("01");
}
public List<InflowTargetVO> getInflowInterfaceList() throws DAOException {
return getInflowList(InflowType.INTERFACE);
return getInflowList("02");
}
public Map<String, InflowTargetVO> getInflowTargetByAdater(String name) throws DAOException {
return getInflowMap(InflowType.ADAPTER, name);
return getInflowMap("01", name);
}
public Map<String, InflowTargetVO> getInflowTargetByInterface(String name) throws DAOException {
return getInflowMap(InflowType.INTERFACE, name);
return getInflowMap("02", name);
}
private List<InflowTargetVO> getInflowList(String type) throws DAOException {
@@ -1,11 +1,54 @@
package com.eactive.eai.common.inflow;
import java.time.Duration;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.eactive.eai.common.exception.ExceptionUtil;
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.message.EAIMessage;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.local.LocalBucket;
import io.github.bucket4j.local.LocalBucketBuilder;
@Component
public class InflowControlManager extends AbstractInflowControlManager {
public class InflowControlManager implements Lifecycle, Bucket {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
public static final String QUOTA_TIMEUNIT_SECOND = "SEC";
public static final String QUOTA_TIMEUNIT_MINUTE = "MIN";
public static final String QUOTA_TIMEUNIT_HOUR = "HOU";
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
private Map<String, CustomBucket> adapterBucketList = new HashMap<>();
private Map<String, CustomBucket> interfaceBucketList = new HashMap<>();
private Map<String, CustomGroupBucket> groupBucketList = new HashMap<>();
private Map<String, String> interfaceToGroupMap = new HashMap<>();
private Map<String, InflowGroupVO> groupVoMap = new HashMap<>();
private boolean started;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@Autowired
private InflowControlDAO inflowControlDAO;
public static synchronized InflowControlManager getInstance() {
return ApplicationContextProvider.getContext().getBean(InflowControlManager.class);
@@ -14,4 +57,475 @@ public class InflowControlManager extends AbstractInflowControlManager {
public static synchronized Bucket getBucket() {
return ApplicationContextProvider.getContext().getBean(InflowControlManager.class);
}
/**
* 유량제어 대상인 거래인지 체크한다.(사이트에 맞게 구성)
*
* @param eaiServerManager
* @param eaiMessage
* @return
*/
public static Boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
InterfaceMapper mapper = eaiMessage.getMapper();
String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
if (STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) {
return Boolean.valueOf(true);
}
return Boolean.valueOf(false);
}
public void start() throws LifecycleException {
if (started)
throw new LifecycleException("RECEAICMM201");
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
init();
} catch (Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202"));
}
started = true;
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
private void init() throws Exception {
initAdapter();
initInterface();
initGroup();
}
private void initAdapter() throws Exception {
adapterBucketList = new HashMap<>();
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
if (InflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
adapterBucketList.put(inflowVo.getName(), bucket);
}
}
}
}
private void initInterface() throws Exception {
interfaceBucketList = new HashMap<>();
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
if (InflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
interfaceBucketList.put(inflowVo.getName(), bucket);
}
}
}
}
private void initGroup() throws Exception {
groupBucketList = new HashMap<>();
interfaceToGroupMap = new HashMap<>();
groupVoMap = new HashMap<>();
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
for (InflowGroupVO groupVo : inflowGroupVoList) {
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
groupBucketList.put(groupVo.getGroupId(), bucket);
groupVoMap.put(groupVo.getGroupId(), groupVo);
// 인터페이스 그룹 매핑 등록
for (String interfaceId : groupVo.getInterfaceList()) {
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
}
}
}
if (logger.isInfo()) {
logger.info("InflowControlManager] initGroup completed. groups=" + groupBucketList.size()
+ ", mappings=" + interfaceToGroupMap.size());
}
}
public synchronized void reloadAdapter() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initAdapter();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadInterface() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initInterface();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadAdapter(String adapter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByAdater(adapter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (InflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
adapterBucketList.put(inflowTarget.getName(), bucket);
} else {
adapterBucketList.remove(inflowTarget.getName());
}
} else {
adapterBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadInterface(String inter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByInterface(inter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (InflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
interfaceBucketList.put(inflowTarget.getName(), bucket);
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadGroup() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all Started ...");
}
initGroup();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all finished ...");
}
}
public synchronized void reloadGroup(String groupId) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId);
}
Map<String, InflowGroupVO> map = inflowControlDAO.getInflowTargetByGroup(groupId);
if (map != null) {
// 기존 그룹에 속한 인터페이스 매핑 제거
Iterator<Map.Entry<String, String>> mappingIt = interfaceToGroupMap.entrySet().iterator();
while (mappingIt.hasNext()) {
Map.Entry<String, String> entry = mappingIt.next();
if (groupId.equals(entry.getValue())) {
mappingIt.remove();
}
}
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
InflowGroupVO groupVo = map.get(key);
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
groupBucketList.put(groupVo.getGroupId(), bucket);
groupVoMap.put(groupVo.getGroupId(), groupVo);
// 인터페이스 그룹 매핑 재등록
for (String interfaceId : groupVo.getInterfaceList()) {
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup finished ...");
}
}
public void stop() throws LifecycleException {
// Validate and update our current component state
if (!started)
throw new LifecycleException("RECEAICMM203");
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
adapterBucketList.clear();
interfaceBucketList.clear();
groupBucketList.clear();
interfaceToGroupMap.clear();
groupVoMap.clear();
started = false;
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
public boolean isStarted() {
return this.started;
}
public String[] getAdapterAllKeys() {
Iterator<String> it = this.adapterBucketList.keySet().iterator();
String[] svcCd = new String[this.adapterBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public String[] getInterfaceAllKeys() {
Iterator<String> it = this.interfaceBucketList.keySet().iterator();
String[] svcCd = new String[this.interfaceBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public void removeAdapter(String adapter) {
adapterBucketList.remove(adapter);
}
public void removeInterface(String inter) {
interfaceBucketList.remove(inter);
}
public void removeGroup(String groupId) {
groupBucketList.remove(groupId);
groupVoMap.remove(groupId);
// 해당 그룹에 속한 인터페이스 매핑도 제거
Iterator<Map.Entry<String, String>> it = interfaceToGroupMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
if (groupId.equals(entry.getValue())) {
it.remove();
}
}
}
public String[] getGroupAllKeys() {
Iterator<String> it = this.groupBucketList.keySet().iterator();
String[] groupIds = new String[this.groupBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
groupIds[i] = it.next();
}
Arrays.sort(groupIds);
return groupIds;
}
private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) {
if (InflowControlManager.isInflowTarget(inflowTarget)) {
LocalBucketBuilder builder = Bucket4j.builder();
if (inflowTarget.getThresholdPerSecond() > 0) {
builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)));
}
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(),
InflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())));
}
return new CustomBucket(builder.build(), inflowTarget);
}
return null;
}
/**
* 그룹용 버킷 생성 - 초당/추가 임계치를 별도 버킷으로 분리
*/
private CustomGroupBucket makeGroupBucket(InflowGroupVO groupVo) {
if (!isInflowGroupTarget(groupVo)) {
return null;
}
LocalBucket perSecondBucket = null;
LocalBucket thresholdBucket = null;
// 초당 임계치 버킷 (별도)
if (groupVo.getThresholdPerSecond() > 0) {
perSecondBucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1)))
.build();
}
// 추가 임계치 버킷 (별도)
if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) {
thresholdBucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(groupVo.getThreshold(),
InflowControlManager.getPeriod(groupVo.getThresholdTimeUnit())))
.build();
}
return new CustomGroupBucket(perSecondBucket, thresholdBucket, groupVo);
}
@Override
public boolean isAdapterPass(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public boolean isInterfacePass(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInflowThreashold(String inter) {
return null;
}
public InflowTargetVO getAdapterInflow(String adapter) {
return getAdapterInflowThreashold(adapter);
}
public InflowTargetVO getInterfaceInflow(String inter) {
return getInterfaceInflowThreashold(inter);
}
@Override
public String isGroupPass(String groupId) {
CustomGroupBucket b = groupBucketList.get(groupId);
if (b == null)
return null;
return b.tryConsume();
}
@Override
public InflowGroupVO getGroupInflowThreshold(String groupId) {
return groupVoMap.get(groupId);
}
@Override
public String getGroupIdByInterface(String interfaceId) {
return interfaceToGroupMap.get(interfaceId);
}
public InflowGroupVO getGroupInflow(String groupId) {
return getGroupInflowThreshold(groupId);
}
public static boolean isInflowTarget(InflowTargetVO inflowTarget) {
if (inflowTarget == null || !inflowTarget.isActivate()) {
return false;
}
return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static boolean isInflowGroupTarget(InflowGroupVO groupVo) {
if (groupVo == null || !groupVo.isActivate()) {
return false;
}
return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static Duration getPeriod(String timeUnit) {
if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) {
return Duration.ofSeconds(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) {
return Duration.ofMinutes(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) {
return Duration.ofHours(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) {
return Duration.ofDays(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) {
Calendar calendar = Calendar.getInstance();
return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
} else {
return null;
}
}
}
@@ -8,10 +8,7 @@ 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.server.EAIServerManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;
public class InflowControlUtil {
private InflowControlUtil() {
@@ -19,70 +16,28 @@ public class InflowControlUtil {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private static volatile AbstractInflowControlManager cachedInflowControlManager;
private static volatile Bucket cachedBucket;
/**
* INFLOW.properties의 inflow.control.bucket.className 설정에 따라
* 활성화된 InflowControlManager 구현체를 반환한다.
*
* getBean(InflowControlManager.class) 대신 설정된 구체 타입으로 조회하므로
* InflowControlManager와 DualInflowControlManager가 모두 Bean으로 등록된
* 환경에서도 NoUniqueBeanDefinitionException 없이 올바른 인스턴스를 반환한다.
*
* 최초 1회만 조회하고 이후에는 캐시된 인스턴스를 반환한다.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static AbstractInflowControlManager getInflowControlManager() {
if (cachedInflowControlManager == null) {
synchronized (InflowControlUtil.class) {
if (cachedInflowControlManager == null) {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
cachedInflowControlManager = InflowControlManager.getInstance();
} else {
try {
Class clazz = Class.forName(className);
cachedInflowControlManager = (AbstractInflowControlManager) ApplicationContextProvider.getContext().getBean(clazz);
} catch (Exception e) {
logger.error("getInflowControlManager error class= {}", className);
logger.error(e.getMessage());
}
}
}
}
}
return cachedInflowControlManager;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Bucket getBucket() {
if (cachedBucket == null) {
synchronized (InflowControlUtil.class) {
if (cachedBucket == null) {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
cachedBucket = InflowControlManager.getBucket();
} else {
try {
Class clazz = Class.forName(className);
Method method = clazz.getMethod("getBucket", (Class<?>[]) null);
cachedBucket = (Bucket) method.invoke(null, (Object[]) null);
} catch (Exception e) {
logger.error("Method call error class= {}, method= getBucket", className);
logger.error(e.getMessage());
}
}
}
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
return InflowControlManager.getBucket();
} else {
try {
Class clazz = Class.forName(className);
Method method = clazz.getMethod("getBucket", (Class<?>[]) null);
return (Bucket) method.invoke(null, (Object[]) null);
} catch (Exception e) {
logger.error("Method call error class= {}, method= getBucket", className);
logger.error(e.getMessage());
}
}
return cachedBucket;
return null;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String controlInflow = inFlowProp.getProperty("inflow.control.yn", "N");
@@ -90,18 +45,21 @@ public class InflowControlUtil {
if (!"Y".equalsIgnoreCase(controlInflow)) {
return false;
}
try {
InterfaceMapper mapper = eaiMessage.getMapper();
String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
if (STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) {
return Boolean.valueOf(true);
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
return InflowControlManager.isTargetOfInflowControl(eaiServerManager, eaiMessage).booleanValue();
} else {
try {
Class clazz = Class.forName(className);
Method method = clazz.getMethod("isTargetOfInflowControl", EAIServerManager.class, EAIMessage.class);
Boolean returnBoolean = (Boolean) method.invoke(null, eaiServerManager, eaiMessage);
return returnBoolean.booleanValue();
} catch (Exception e) {
logger.error("Method call error class= {}, method= isTargetOfInflowControl", className);
logger.error(e.getMessage());
}
return Boolean.valueOf(false);
} catch (Exception e) {
logger.error("isTargetOfInflowControl call error", e.getMessage());
}
return false;
@@ -1,23 +0,0 @@
package com.eactive.eai.common.inflow;
/**
* 유량제어 대상 유형.
* {@link InflowControlDAO} type 컬럼 값을 enum으로 관리하여
* 호출부에 코드 문자열이 노출되지 않도록 한다.
*/
public enum InflowType {
ADAPTER("01"),
INTERFACE("02"),
CLIENT("03");
private final String code;
InflowType(String code) {
this.code = code;
}
public String code() {
return code;
}
}
@@ -1,25 +0,0 @@
package com.eactive.eai.common.inflow.dual;
import com.eactive.eai.common.inflow.Bucket;
/**
* Bucket 인터페이스 확장.
* 기존 isAdapterPass/isInterfacePass(boolean) 더해
* 어느 버킷(PER_SECOND/THRESHOLD)에서 차단됐는지 반환하는 메서드 추가.
*
* <p>클라이언트 유량제어는 {@link ClientDualBucket}으로 분리됨.
*/
public interface DualBucket extends Bucket {
/**
* 어댑터 유량제어 체크 차단 원인 포함.
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
*/
String isAdapterPassDetail(String adapter);
/**
* 인터페이스 유량제어 체크 차단 원인 포함.
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
*/
String isInterfacePassDetail(String inter);
}
@@ -1,58 +0,0 @@
package com.eactive.eai.common.inflow.dual;
import com.eactive.eai.common.inflow.InflowTargetVO;
import io.github.bucket4j.local.LocalBucket;
/**
* 어댑터/인터페이스 유량제어 버킷.
* 기존 CustomBucket(단일 버킷) 달리 perSecond/threshold를 별도 버킷으로 분리하여
* 어느 한도를 초과했는지 구분 가능 (CustomGroupBucket과 동일한 이중 구조).
*/
public class DualCustomBucket {
public static final String RESULT_PASS = null;
public static final String RESULT_BLOCKED_PER_SECOND = "PER_SECOND";
public static final String RESULT_BLOCKED_THRESHOLD = "THRESHOLD";
private final LocalBucket perSecondBucket;
private final LocalBucket thresholdBucket;
private final InflowTargetVO inflowTargetVo;
public DualCustomBucket(LocalBucket perSecondBucket, LocalBucket thresholdBucket, InflowTargetVO inflowTargetVo) {
this.perSecondBucket = perSecondBucket;
this.thresholdBucket = thresholdBucket;
this.inflowTargetVo = inflowTargetVo;
}
/**
* 버킷을 순서대로 소비 시도.
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
*/
public String tryConsume() {
boolean perSecondPass = (perSecondBucket == null) || perSecondBucket.tryConsume(1);
if (!perSecondPass) {
return RESULT_BLOCKED_PER_SECOND;
}
boolean thresholdPass = (thresholdBucket == null) || thresholdBucket.tryConsume(1);
if (!thresholdPass) {
// perSecond 토큰은 이미 소비됨 Token Bucket 특성상 롤백 불가
// 초당 버킷은 빠르게 리필되므로 실질적 영향 미미
return RESULT_BLOCKED_THRESHOLD;
}
return RESULT_PASS;
}
public long getPerSecondAvailableTokens() {
return (perSecondBucket != null) ? perSecondBucket.getAvailableTokens() : -1;
}
public long getThresholdAvailableTokens() {
return (thresholdBucket != null) ? thresholdBucket.getAvailableTokens() : -1;
}
public LocalBucket getPerSecondBucket() { return perSecondBucket; }
public LocalBucket getThresholdBucket() { return thresholdBucket; }
public InflowTargetVO getInflowTargetVo() { return inflowTargetVo; }
}
@@ -1,342 +0,0 @@
package com.eactive.eai.common.inflow.dual;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.Bucket;
import com.eactive.eai.common.inflow.CustomGroupBucket;
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;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import io.github.bucket4j.local.LocalBucket;
/**
* 어댑터/인터페이스 버킷을 이중 구조(perSecond + threshold) 관리하는 유량제어 관리자.
*
* <p>클라이언트 유량제어는 {@link ClientDualInflowControlManager} 분리됨.
*
* <p>적용 방법: INFLOW.properties에 아래 추가.
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager</pre>
*/
public class DualInflowControlManager extends AbstractInflowControlManager implements DualBucket {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
// InflowControlUtil이 리플렉션으로 호출하는 진입점
public static synchronized Bucket getBucket() {
return ApplicationContextProvider.getContext().getBean(DualInflowControlManager.class);
}
private volatile Map<String, DualCustomBucket> adapterBucketMap = new ConcurrentHashMap<>();
private volatile Map<String, DualCustomBucket> interfaceBucketMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, TargetMetrics> adapterMetricsMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, TargetMetrics> interfaceMetricsMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, TargetMetrics> groupMetricsMap = new ConcurrentHashMap<>();
public static class TargetMetrics {
public final AtomicLong allowed = new AtomicLong();
public final AtomicLong rejectedPerSecond = new AtomicLong();
public final AtomicLong rejectedThreshold = new AtomicLong();
public volatile long lastResetTime = System.currentTimeMillis();
public void reset() {
allowed.set(0);
rejectedPerSecond.set(0);
rejectedThreshold.set(0);
lastResetTime = System.currentTimeMillis();
}
}
// -------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------
@Override
public void start() throws LifecycleException {
super.start();
try {
initAdapters();
initInterfaces();
} catch (Exception e) {
throw new LifecycleException("DualInflowControlManager init failed: " + e.getMessage());
}
}
// -------------------------------------------------------------------------
// 초기화 / 리로드
// -------------------------------------------------------------------------
private void initAdapters() throws Exception {
this.adapterBucketMap = loadBucketMap(InflowType.ADAPTER);
if (logger.isInfo()) logger.info("DualInflowControlManager] initAdapters: " + adapterBucketMap.size() + " buckets");
}
private void initInterfaces() throws Exception {
this.interfaceBucketMap = loadBucketMap(InflowType.INTERFACE);
if (logger.isInfo()) logger.info("DualInflowControlManager] initInterfaces: " + interfaceBucketMap.size() + " buckets");
}
protected Map<String, DualCustomBucket> loadBucketMap(InflowType type) throws Exception {
Map<String, DualCustomBucket> newMap = new HashMap<>();
for (InflowTargetVO vo : inflowControlDAO.getInflowList(type)) {
DualCustomBucket bucket = makeBucket(vo);
if (bucket != null) newMap.put(vo.getName(), bucket);
}
return newMap;
}
@Override
public synchronized void reloadAdapter() throws Exception {
initAdapters();
adapterMetricsMap.clear();
}
@Override
public synchronized void reloadAdapter(String adapter) throws Exception {
reloadBucketMap(adapterBucketMap, InflowType.ADAPTER, adapter);
TargetMetrics m = adapterMetricsMap.get(adapter);
if (m != null) m.reset();
}
@Override
public synchronized void reloadInterface() throws Exception {
initInterfaces();
interfaceMetricsMap.clear();
}
@Override
public synchronized void reloadInterface(String inter) throws Exception {
reloadBucketMap(interfaceBucketMap, InflowType.INTERFACE, inter);
TargetMetrics m = interfaceMetricsMap.get(inter);
if (m != null) m.reset();
}
@Override
public synchronized void reloadGroup() throws Exception {
super.reloadGroup();
groupMetricsMap.clear();
}
@Override
public synchronized void reloadGroup(String groupId) throws Exception {
super.reloadGroup(groupId);
TargetMetrics m = groupMetricsMap.get(groupId);
if (m != null) m.reset();
}
protected void reloadBucketMap(Map<String, DualCustomBucket> targetMap, InflowType type, String name) throws Exception {
Map<String, InflowTargetVO> updated = inflowControlDAO.getInflowMap(type, name);
if (updated == null) return;
for (InflowTargetVO vo : updated.values()) {
DualCustomBucket bucket = makeBucket(vo);
if (bucket != null) targetMap.put(vo.getName(), bucket);
else targetMap.remove(vo.getName());
}
}
// -------------------------------------------------------------------------
// 어댑터/인터페이스 메트릭 접근자
// -------------------------------------------------------------------------
public TargetMetrics getAdapterMetrics(String adapter) { return adapterMetricsMap.get(adapter); }
public TargetMetrics getInterfaceMetrics(String inter) { return interfaceMetricsMap.get(inter); }
public Map<String, TargetMetrics> getAllAdapterMetrics() { return Collections.unmodifiableMap(adapterMetricsMap); }
public Map<String, TargetMetrics> getAllInterfaceMetrics() { return Collections.unmodifiableMap(interfaceMetricsMap); }
public void resetAdapterMetrics(String adapter) { TargetMetrics m = adapterMetricsMap.get(adapter); if (m != null) m.reset(); }
public void resetInterfaceMetrics(String inter) { TargetMetrics m = interfaceMetricsMap.get(inter); if (m != null) m.reset(); }
public void resetAllAdapterMetrics() { adapterMetricsMap.values().forEach(TargetMetrics::reset); }
public void resetAllInterfaceMetrics() { interfaceMetricsMap.values().forEach(TargetMetrics::reset); }
// -------------------------------------------------------------------------
// 그룹 메트릭 isGroupPass() 카운팅 접근자
// -------------------------------------------------------------------------
@Override
public String isGroupPass(String groupId) {
String result = super.isGroupPass(groupId);
TargetMetrics m = groupMetricsMap.computeIfAbsent(groupId, k -> new TargetMetrics());
if (result == null) {
m.allowed.incrementAndGet();
} else if (CustomGroupBucket.RESULT_BLOCKED_PER_SECOND.equals(result)) {
m.rejectedPerSecond.incrementAndGet();
} else {
m.rejectedThreshold.incrementAndGet();
}
return result;
}
public TargetMetrics getGroupMetrics(String groupId) {
return groupMetricsMap.get(groupId);
}
public Map<String, TargetMetrics> getAllGroupMetrics() {
return Collections.unmodifiableMap(groupMetricsMap);
}
public void resetGroupMetrics(String groupId) {
TargetMetrics m = groupMetricsMap.get(groupId);
if (m != null) m.reset();
}
public void resetAllGroupMetrics() {
groupMetricsMap.values().forEach(TargetMetrics::reset);
}
// -------------------------------------------------------------------------
// DualBucket 차단 원인 포함 메서드
// -------------------------------------------------------------------------
@Override
public String isAdapterPassDetail(String adapter) {
DualCustomBucket b = adapterBucketMap.get(adapter);
if (b == null) return DualCustomBucket.RESULT_PASS;
String result = b.tryConsume();
recordMetrics(adapterMetricsMap, adapter, result);
return result;
}
@Override
public String isInterfacePassDetail(String inter) {
DualCustomBucket b = interfaceBucketMap.get(inter);
if (b == null) return DualCustomBucket.RESULT_PASS;
String result = b.tryConsume();
recordMetrics(interfaceMetricsMap, inter, result);
return result;
}
protected void recordMetrics(ConcurrentHashMap<String, TargetMetrics> metricsMap, String key, String result) {
TargetMetrics m = metricsMap.computeIfAbsent(key, k -> new TargetMetrics());
if (result == null) m.allowed.incrementAndGet();
else if (DualCustomBucket.RESULT_BLOCKED_PER_SECOND.equals(result)) m.rejectedPerSecond.incrementAndGet();
else m.rejectedThreshold.incrementAndGet();
}
// -------------------------------------------------------------------------
// Bucket 인터페이스 오버라이드 이중 버킷으로 교체 (boolean 반환 유지)
// -------------------------------------------------------------------------
@Override
public boolean isAdapterPass(String adapter) {
return isAdapterPassDetail(adapter) == DualCustomBucket.RESULT_PASS;
}
@Override
public boolean isInterfacePass(String inter) {
return isInterfacePassDetail(inter) == DualCustomBucket.RESULT_PASS;
}
// -------------------------------------------------------------------------
// 어댑터/인터페이스 ·VO·삭제 Dual 기준으로 오버라이드
// -------------------------------------------------------------------------
@Override
public String[] getAdapterAllKeys() {
String[] keys = adapterBucketMap.keySet().toArray(new String[0]);
Arrays.sort(keys);
return keys;
}
@Override
public String[] getInterfaceAllKeys() {
String[] keys = interfaceBucketMap.keySet().toArray(new String[0]);
Arrays.sort(keys);
return keys;
}
@Override
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
DualCustomBucket b = adapterBucketMap.get(adapter);
return (b == null) ? null : b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
DualCustomBucket b = interfaceBucketMap.get(inter);
return (b == null) ? null : b.getInflowTargetVo();
}
@Override
public synchronized void removeAdapter(String adapter) {
adapterBucketMap.remove(adapter);
adapterMetricsMap.remove(adapter);
}
@Override
public synchronized void removeInterface(String inter) {
interfaceBucketMap.remove(inter);
interfaceMetricsMap.remove(inter);
}
// -------------------------------------------------------------------------
// 모니터링용 접근자
// -------------------------------------------------------------------------
public DualCustomBucket getAdapterBucket(String adapter) {
return adapterBucketMap.get(adapter);
}
public DualCustomBucket getInterfaceBucket(String inter) {
return interfaceBucketMap.get(inter);
}
public Map<String, DualCustomBucket> getAdapterBucketMap() {
return adapterBucketMap;
}
public Map<String, DualCustomBucket> getInterfaceBucketMap() {
return interfaceBucketMap;
}
// -------------------------------------------------------------------------
// 버킷 팩토리
// -------------------------------------------------------------------------
/**
* perSecond 버킷: greedy refill 초당 N개 요청을 균등하게 허용.
* threshold 버킷: intervally refill 기간(// ) 단위 쿼터를 기간 시작 일괄 충전.
* Bandwidth.simple은 버킷 모두 greedy로 생성되어 시간 단위 구분이 없어지므로 classic으로 교체.
*/
protected DualCustomBucket makeBucket(InflowTargetVO vo) {
if (!AbstractInflowControlManager.isInflowTarget(vo)) {
return null;
}
LocalBucket perSecondBucket = null;
LocalBucket thresholdBucket = null;
if (vo.getThresholdPerSecond() > 0) {
perSecondBucket = Bucket4j.builder()
.addLimit(Bandwidth.classic(vo.getThresholdPerSecond(),
Refill.greedy(vo.getThresholdPerSecond(), Duration.ofSeconds(1))))
.build();
}
if (vo.getThreshold() > 0 && StringUtils.isNotBlank(vo.getThresholdTimeUnit())) {
Duration period = AbstractInflowControlManager.getPeriod(vo.getThresholdTimeUnit());
thresholdBucket = Bucket4j.builder()
.addLimit(Bandwidth.classic(vo.getThreshold(),
Refill.intervally(vo.getThreshold(), period)))
.build();
}
return new DualCustomBucket(perSecondBucket, thresholdBucket, vo);
}
}
@@ -253,9 +253,7 @@ public class EAIFileLogger
* @exception DAOException
**/
public void setLog(EAIMessage message, Properties prop) throws Exception{
if(!tranLogger.isError())
return;
// 업무데이터 size 체크하여 업무데이터1,2에 insert하고
// addBwkDataSub(message) 호출 여부 결정
@@ -519,28 +517,27 @@ public class EAIFileLogger
//전장표 데이터부 JSON Array로 저장
//암호화
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;
// }
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,6 +3,8 @@ 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;
@@ -12,10 +14,13 @@ 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;
@@ -25,6 +30,7 @@ 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;
@@ -49,10 +55,11 @@ 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
@@ -471,12 +478,20 @@ public class EAILogDAO {
}
*/
try {
biz = EncryptionManager.getInstance().encryptDBData(bizMsgStr);
} catch (Exception e) {
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
// 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 {
biz = bizMsgStr;
}
}
// jwhong safeDB end
// 임시코드 시작
String logType = PropManager.getInstance().getProperty("BIZ_LOG_TYPE");
@@ -845,6 +860,88 @@ 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,8 +109,7 @@ public class EAIMessage implements EAIMessageKeys, Serializable, Comparable<EAIM
private String authkeyfieldname;
private String authCheckdYn;
private Properties callProp;
public EAIMessage() {
this.svcMsgs = new ArrayList<>();
@@ -665,14 +664,6 @@ 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,8 +64,7 @@ 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(), "HTT")) {
&& !StringUtils.equals(adptGrpVO.getType(), "RST")) {
// isLocal = true;
// if (logger.isInfo()) logger.info(guidLogPrefix + " TRANTYPE TAS [" + eaiMessage.getTranType() + "]");
PropManager manager = PropManager.getInstance();
@@ -1,173 +0,0 @@
package com.eactive.eai.common.security;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jcajce.spec.FPEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AESCryptoModuleExtension implements CryptoModuleExtension {
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 128;
private static final String DEFAULT_PROVIDER = "BC";
private Cipher encryptEngine;
private Cipher decryptEngine;
private String mode;
private String pad;
private byte[] iv;
private byte[] encKey;
private byte[] decKey;
/** 명시적 프로바이더. null이면 모든 모드에서 BC(DEFAULT_PROVIDER)를 사용한다. */
private String provider;
// ------------------------------------------------------------------
// init 모든 오버로드는 최종적으로 6+provider 버전에 위임한다
// ------------------------------------------------------------------
@Override
public void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey) throws Exception {
init(alg, mode, pad, null, encKey, decKey, null);
}
@Override
public void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey) throws Exception {
init(alg, mode, pad, iv, encKey, decKey, null);
}
@Override
public void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey, String provider) throws Exception {
init(alg, mode, pad, null, encKey, decKey, provider);
}
@Override
public void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey, String provider) throws Exception {
this.mode = mode;
this.pad = pad;
this.iv = iv;
this.encKey = encKey;
this.decKey = decKey;
this.provider = (provider != null && !provider.isEmpty()) ? provider : null;
// GCM 포함 모든 모드에서 BC 프로바이더를 사용 기본값 BC
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
if (!mode.equalsIgnoreCase("GCM")) {
String m = mode != null ? mode : "";
String p = pad != null ? pad : "";
String transformation = String.format("AES/%s/%s", m, p);
Key encKeySpec = new SecretKeySpec(encKey, "AES");
Key decKeySpec = new SecretKeySpec(decKey, "AES");
AlgorithmParameterSpec param = iv != null ? new IvParameterSpec(iv) : null;
if (m.toLowerCase().contains("ff")) {
param = new FPEParameterSpec(256, iv);
}
String prov = this.provider != null ? this.provider : DEFAULT_PROVIDER;
this.encryptEngine = Cipher.getInstance(transformation, prov);
this.decryptEngine = Cipher.getInstance(transformation, prov);
this.encryptEngine.init(Cipher.ENCRYPT_MODE, encKeySpec, param, (SecureRandom) null);
this.decryptEngine.init(Cipher.DECRYPT_MODE, decKeySpec, param, (SecureRandom) null);
}
}
// ------------------------------------------------------------------
// encrypt
// ------------------------------------------------------------------
@Override
public int encrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception {
return this.encryptEngine.doFinal(src, sindex, slength, dst, dindex);
}
@Override
public byte[] encrypt(byte[] src, int sindex, int slength) throws Exception {
return encrypt(src, sindex, slength, null);
}
/**
* AAD를 명시한 암호화.
* GCM 모드에서 aad != null 이면 해당 값을 사용하고, null 이면 iv를 AAD로 사용하는 기본 동작을 따른다.
* -GCM 모드는 AAD를 무시하고 사전 초기화된 엔진으로 처리한다.
*/
@Override
public byte[] encrypt(byte[] src, int sindex, int slength, byte[] aad) throws Exception {
if (this.mode.equalsIgnoreCase("GCM")) {
Key key = new SecretKeySpec(this.encKey, "AES");
byte[] nonce = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(nonce);
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
String prov = this.provider != null ? this.provider : DEFAULT_PROVIDER;
Cipher cipher = Cipher.getInstance(String.format("AES/%s/%s", this.mode, this.pad), prov);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
byte[] effectiveAad = aad != null ? aad : this.iv;
if (effectiveAad != null) cipher.updateAAD(effectiveAad);
byte[] cipherTextWithTag = cipher.doFinal(src, sindex, slength);
byte[] output = new byte[GCM_IV_LENGTH + cipherTextWithTag.length];
System.arraycopy(nonce, 0, output, 0, GCM_IV_LENGTH);
System.arraycopy(cipherTextWithTag, 0, output, GCM_IV_LENGTH, cipherTextWithTag.length);
return output;
}
int outLen = this.encryptEngine.getOutputSize(slength);
byte[] dst = new byte[outLen];
int actual = encrypt(src, sindex, slength, dst, 0);
return ByteBuffer.allocate(actual).put(dst, 0, actual).array();
}
// ------------------------------------------------------------------
// decrypt
// ------------------------------------------------------------------
@Override
public int decrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception {
return this.decryptEngine.doFinal(src, sindex, slength, dst, dindex);
}
@Override
public byte[] decrypt(byte[] src, int sindex, int slength) throws Exception {
return decrypt(src, sindex, slength, null);
}
/**
* AAD를 명시한 복호화.
* GCM 모드에서 aad != null 이면 해당 값을 사용하고, null 이면 iv를 AAD로 사용하는 기본 동작을 따른다.
* -GCM 모드는 AAD를 무시하고 사전 초기화된 엔진으로 처리한다.
*/
@Override
public byte[] decrypt(byte[] src, int sindex, int slength, byte[] aad) throws Exception {
if (this.mode.equalsIgnoreCase("GCM")) {
Key key = new SecretKeySpec(this.decKey, "AES");
byte[] nonce = new byte[GCM_IV_LENGTH];
System.arraycopy(src, sindex, nonce, 0, GCM_IV_LENGTH);
byte[] cipherTextWithTag = new byte[slength - GCM_IV_LENGTH];
System.arraycopy(src, sindex + GCM_IV_LENGTH, cipherTextWithTag, 0, cipherTextWithTag.length);
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
String prov = this.provider != null ? this.provider : DEFAULT_PROVIDER;
Cipher cipher = Cipher.getInstance(String.format("AES/%s/%s", this.mode, this.pad), prov);
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
byte[] effectiveAad = aad != null ? aad : this.iv;
if (effectiveAad != null) cipher.updateAAD(effectiveAad);
return cipher.doFinal(cipherTextWithTag);
}
return this.decryptEngine.doFinal(src, sindex, slength);
}
}
@@ -1,68 +0,0 @@
package com.eactive.eai.common.security;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jcajce.spec.FPEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class ARIACryptoModuleExtension implements CryptoModuleExtension {
private Cipher encryptEngine;
private Cipher decryptEngine;
@Override
public void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey) throws Exception {
this.init("ARIA", mode, pad, (byte[]) null, encKey, decKey);
}
@Override
public void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey) throws Exception {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
String m = mode != null ? mode : "";
String p = pad != null ? pad : "";
Key encKeySpec = new SecretKeySpec(encKey, "ARIA");
Key decKeySpec = new SecretKeySpec(decKey, "ARIA");
AlgorithmParameterSpec param = iv != null ? new IvParameterSpec(iv) : null;
if (m.toLowerCase().contains("ff")) {
param = new FPEParameterSpec(256, iv);
}
this.encryptEngine = Cipher.getInstance(String.format("ARIA/%s/%s", m, p));
this.encryptEngine.init(Cipher.ENCRYPT_MODE, encKeySpec, param, (SecureRandom) null);
this.decryptEngine = Cipher.getInstance(String.format("ARIA/%s/%s", m, p));
this.decryptEngine.init(Cipher.DECRYPT_MODE, decKeySpec, param, (SecureRandom) null);
}
@Override
public int encrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception {
return this.encryptEngine.doFinal(src, sindex, slength, dst, dindex);
}
@Override
public byte[] encrypt(byte[] src, int sindex, int slength) throws Exception {
int size = (slength + 17) / this.encryptEngine.getBlockSize() * this.encryptEngine.getBlockSize();
size += (slength + 17) % this.encryptEngine.getBlockSize() == 0 ? 0 : this.encryptEngine.getBlockSize();
byte[] dst = new byte[this.encryptEngine.getOutputSize(size)];
size = this.encrypt(src, sindex, slength, dst, 0);
return ByteBuffer.allocate(size).put(dst, 0, size).array();
}
@Override
public int decrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception {
return this.decryptEngine.doFinal(src, sindex, slength, dst, dindex);
}
@Override
public byte[] decrypt(byte[] src, int sindex, int slength) throws Exception {
return this.decryptEngine.doFinal(src, sindex, slength);
}
}
@@ -1,23 +0,0 @@
package com.eactive.eai.common.security;
import lombok.Data;
@Data
public class CryptoModuleConfigVO {
String cryptoId;
String cryptoName;
String cryptoDesc;
String algType;
String cipherMode;
String padding;
String ivHex;
String keySourceType;
String encKeyHex;
String decKeyHex;
String keyDerivStrategy;
String keyDerivParams;
String cacheYn;
Integer cacheTtlSec;
String useYn;
}
@@ -1,52 +0,0 @@
package com.eactive.eai.common.security;
public interface CryptoModuleExtension {
void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey) throws Exception;
void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey) throws Exception;
/** 프로바이더를 명시한 초기화. 미지원 구현체는 provider를 무시하고 기본 동작한다. */
default void init(String alg, String mode, String pad, byte[] encKey, byte[] decKey, String provider) throws Exception {
init(alg, mode, pad, encKey, decKey);
}
/** 프로바이더를 명시한 초기화 (IV 포함). 미지원 구현체는 provider를 무시하고 기본 동작한다. */
default void init(String alg, String mode, String pad, byte[] iv, byte[] encKey, byte[] decKey, String provider) throws Exception {
init(alg, mode, pad, iv, encKey, decKey);
}
int encrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception;
byte[] encrypt(byte[] src, int sindex, int slength) throws Exception;
/** AAD를 명시한 암호화. null이면 구현체 기본 동작(IV를 AAD로 사용하거나 AAD 없음)을 따른다. */
default byte[] encrypt(byte[] src, int sindex, int slength, byte[] aad) throws Exception {
return encrypt(src, sindex, slength);
}
default byte[] encrypt(byte[] src) throws Exception {
return encrypt(src, 0, src.length);
}
default byte[] encrypt(byte[] src, byte[] aad) throws Exception {
return encrypt(src, 0, src.length, aad);
}
int decrypt(byte[] src, int sindex, int slength, byte[] dst, int dindex) throws Exception;
byte[] decrypt(byte[] src, int sindex, int slength) throws Exception;
/** AAD를 명시한 복호화. null이면 구현체 기본 동작을 따른다. */
default byte[] decrypt(byte[] src, int sindex, int slength, byte[] aad) throws Exception {
return decrypt(src, sindex, slength);
}
default byte[] decrypt(byte[] src) throws Exception {
return decrypt(src, 0, src.length);
}
default byte[] decrypt(byte[] src, byte[] aad) throws Exception {
return decrypt(src, 0, src.length, aad);
}
}
@@ -1,248 +0,0 @@
package com.eactive.eai.common.security;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.bind.DatatypeConverter;
import org.springframework.stereotype.Component;
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.security.keyderiv.DerivedKey;
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
import com.eactive.eai.common.security.loader.CryptoModuleConfigLoader;
import com.eactive.eai.common.security.mapper.CryptoModuleConfigMapper;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class CryptoModuleManager implements Lifecycle {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private static final ObjectMapper objectMapper = new ObjectMapper();
private final ConcurrentHashMap<String, CryptoModuleConfigVO> configMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, KeyDerivationStrategy> strategyCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, CachedDerivedKey> dynamicKeyCache = new ConcurrentHashMap<>();
private boolean started;
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
private CryptoModuleManager() {}
public static CryptoModuleManager getInstance() {
return ApplicationContextProvider.getContext().getBean(CryptoModuleManager.class);
}
@Override
public void start() throws LifecycleException {
if (started) throw new LifecycleException("RECEAICRY001");
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
loadAll();
} catch (Exception e) {
throw new LifecycleException("RECEAICRY002");
}
started = true;
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
@Override
public void stop() throws LifecycleException {
if (!started) throw new LifecycleException("RECEAICRY003");
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
configMap.clear();
dynamicKeyCache.clear();
strategyCache.clear();
started = false;
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
private void loadAll() {
CryptoModuleConfigLoader loader = ctx().getBean(CryptoModuleConfigLoader.class);
CryptoModuleConfigMapper mapper = ctx().getBean(CryptoModuleConfigMapper.class);
List<CryptoModuleConfig> all = loader.findAll();
configMap.clear();
for (CryptoModuleConfig entity : all) {
if ("Y".equalsIgnoreCase(entity.getUseYn())) {
CryptoModuleConfigVO vo = mapper.toVo(entity);
configMap.put(vo.getCryptoName(), vo);
}
}
logger.warn("CryptoModuleManager] 암호화모듈 로드 완료. 건수=" + configMap.size());
}
public void reload(String cryptoId) {
CryptoModuleConfigLoader loader = ctx().getBean(CryptoModuleConfigLoader.class);
CryptoModuleConfigMapper mapper = ctx().getBean(CryptoModuleConfigMapper.class);
configMap.values().removeIf(vo -> cryptoId.equals(vo.getCryptoId()));
evictDynamicCache(cryptoId);
loader.findById(cryptoId).ifPresent(entity -> {
if ("Y".equalsIgnoreCase(entity.getUseYn())) {
CryptoModuleConfigVO vo = mapper.toVo(entity);
configMap.put(vo.getCryptoName(), vo);
logger.warn("CryptoModuleManager] 재로드 완료: " + vo.getCryptoName());
}
});
}
/**
* STATIC 방식 초기화된 CryptoModuleExtension 반환.
* Cipher는 thread-safe하지 않으므로 호출마다 인스턴스를 생성한다.
*/
public CryptoModuleExtension createExtension(String cryptoName) throws Exception {
CryptoModuleConfigVO vo = getVO(cryptoName);
byte[] encKey = DatatypeConverter.parseHexBinary(vo.getEncKeyHex());
byte[] decKey = vo.getDecKeyHex() != null
? DatatypeConverter.parseHexBinary(vo.getDecKeyHex())
: encKey;
byte[] iv = vo.getIvHex() != null ? DatatypeConverter.parseHexBinary(vo.getIvHex()) : null;
return buildExtension(vo, iv, encKey, decKey);
}
/**
* DYNAMIC 방식 전략으로 키를 도출하고 캐시를 적용한 CryptoModuleExtension 반환.
*/
public CryptoModuleExtension createExtension(String cryptoName, Map<String, String> runtimeContext)
throws Exception {
CryptoModuleConfigVO vo = getVO(cryptoName);
if ("STATIC".equalsIgnoreCase(vo.getKeySourceType())) {
return createExtension(cryptoName);
}
KeyDerivationStrategy strategy = resolveStrategy(vo.getKeyDerivStrategy());
Map<String, String> params = parseParams(vo.getKeyDerivParams());
String cacheKey = strategy.buildCacheKey(cryptoName, params, runtimeContext);
DerivedKey derivedKey = null;
if ("Y".equalsIgnoreCase(vo.getCacheYn())) {
CachedDerivedKey cached = dynamicKeyCache.get(cacheKey);
if (cached != null && !cached.isExpired()) {
derivedKey = cached.getDerivedKey();
}
}
if (derivedKey == null) {
derivedKey = strategy.deriveKey(params, runtimeContext);
if ("Y".equalsIgnoreCase(vo.getCacheYn())) {
int ttl = vo.getCacheTtlSec() != null ? vo.getCacheTtlSec() : 300;
dynamicKeyCache.put(cacheKey, new CachedDerivedKey(derivedKey, ttl));
}
}
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());
}
private CryptoModuleConfigVO getVO(String cryptoName) {
CryptoModuleConfigVO vo = configMap.get(cryptoName);
if (vo == null) {
throw new IllegalArgumentException("암호화모듈 미등록: " + cryptoName);
}
return vo;
}
private CryptoModuleExtension buildExtension(CryptoModuleConfigVO vo, byte[] iv,
byte[] encKey, byte[] decKey) throws Exception {
CryptoModuleExtension ext = "ARIA".equalsIgnoreCase(vo.getAlgType())
? new ARIACryptoModuleExtension()
: new AESCryptoModuleExtension();
ext.init(vo.getAlgType(), vo.getCipherMode(), vo.getPadding(), iv, encKey, decKey);
return ext;
}
/**
* DB key_deriv_strategy 컬럼에 저장된 FQCN으로 전략 클래스를 로드한다.
* 로드된 인스턴스는 strategyCache에 보관하여 재사용한다.
*/
private KeyDerivationStrategy resolveStrategy(String fqcn) {
return strategyCache.computeIfAbsent(fqcn, key -> {
try {
Class<?> clazz = Class.forName(key);
return (KeyDerivationStrategy) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("전략 클래스 로드 실패: " + key, e);
}
});
}
private Map<String, String> parseParams(String json) throws Exception {
if (json == null || json.trim().isEmpty()) {
return new HashMap<>();
}
return objectMapper.readValue(json, new TypeReference<Map<String, String>>() {});
}
private void evictDynamicCache(String cryptoId) {
configMap.values().stream()
.filter(vo -> cryptoId.equals(vo.getCryptoId()))
.map(CryptoModuleConfigVO::getCryptoName)
.forEach(name -> {
Iterator<String> it = dynamicKeyCache.keySet().iterator();
while (it.hasNext()) {
if (it.next().startsWith(name + ":")) it.remove();
}
});
}
private org.springframework.context.ApplicationContext ctx() {
return ApplicationContextProvider.getContext();
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public boolean isStarted() {
return started;
}
private static class CachedDerivedKey {
private final DerivedKey derivedKey;
private final long expireAt;
CachedDerivedKey(DerivedKey derivedKey, int ttlSec) {
this.derivedKey = derivedKey;
this.expireAt = System.currentTimeMillis() + (ttlSec * 1000L);
}
boolean isExpired() {
return System.currentTimeMillis() > expireAt;
}
DerivedKey getDerivedKey() {
return derivedKey;
}
}
}
@@ -1,77 +0,0 @@
package com.eactive.eai.common.security;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.eactive.eai.common.util.ApplicationContextProvider;
/**
* 어댑터 필터 등에서 암복호화를 위임하는 진입점.
*
* STATIC 방식:
* service.encrypt("MODULE_NAME", plainBytes);
*
* DYNAMIC 방식 (런타임 컨텍스트 전달):
* Map<String,String> ctx = Map.of("X-Api-Enc-Key", request.getHeader("X-Api-Enc-Key"));
* service.encrypt("MODULE_NAME", ctx, plainBytes);
*
* AAD 명시 방식 (GCM 인증 데이터 전달):
* byte[] aad = request.getHeader("X-Api-Request-Id").getBytes(StandardCharsets.UTF_8);
* service.encrypt("MODULE_NAME", ctx, aad, plainBytes);
*/
@Service
public class CryptoModuleService {
public static CryptoModuleService getInstance() {
return ApplicationContextProvider.getContext().getBean(CryptoModuleService.class);
}
// ------------------------------------------------------------------
// STATIC
// ------------------------------------------------------------------
public byte[] encrypt(String cryptoName, byte[] plainBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName);
return ext.encrypt(plainBytes, 0, plainBytes.length);
}
public byte[] encrypt(String cryptoName, byte[] aad, byte[] plainBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName);
return ext.encrypt(plainBytes, 0, plainBytes.length, aad);
}
public byte[] decrypt(String cryptoName, byte[] cipherBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName);
return ext.decrypt(cipherBytes, 0, cipherBytes.length);
}
public byte[] decrypt(String cryptoName, byte[] aad, byte[] cipherBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName);
return ext.decrypt(cipherBytes, 0, cipherBytes.length, aad);
}
// ------------------------------------------------------------------
// DYNAMIC
// ------------------------------------------------------------------
public byte[] encrypt(String cryptoName, Map<String, String> runtimeContext, byte[] plainBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName, runtimeContext);
return ext.encrypt(plainBytes, 0, plainBytes.length);
}
public byte[] encrypt(String cryptoName, Map<String, String> runtimeContext, byte[] aad, byte[] plainBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName, runtimeContext);
return ext.encrypt(plainBytes, 0, plainBytes.length, aad);
}
public byte[] decrypt(String cryptoName, Map<String, String> runtimeContext, byte[] cipherBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName, runtimeContext);
return ext.decrypt(cipherBytes, 0, cipherBytes.length);
}
public byte[] decrypt(String cryptoName, Map<String, String> runtimeContext, byte[] aad, byte[] cipherBytes) throws Exception {
CryptoModuleExtension ext = CryptoModuleManager.getInstance().createExtension(cryptoName, runtimeContext);
return ext.decrypt(cipherBytes, 0, cipherBytes.length, aad);
}
}
@@ -1,33 +0,0 @@
package com.eactive.eai.common.security.keyderiv;
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() {
return encKey;
}
public byte[] getDecKey() {
return decKey;
}
public byte[] getIv() {
return iv;
}
}
@@ -1,28 +0,0 @@
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";
/**
* 키를 도출한다.
*
* @param params DB의 key_deriv_params (JSON 파싱된 Map)
* @param runtimeContext 런타임 컨텍스트 (HTTP 헤더, 거래 정보 호출자가 채워서 전달)
*/
DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception;
/**
* 캐시 키를 생성한다. 전략마다 어떤 runtimeContext 값이 키를 결정하는지 다르다.
*
* @param cryptoName 암호화모듈 이름
* @param params DB의 key_deriv_params
* @param runtimeContext 런타임 컨텍스트
*/
String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext);
}
@@ -1,43 +0,0 @@
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);
}
}

Some files were not shown because too many files have changed in this diff Show More