57 Commits

Author SHA1 Message Date
curry772 96fae0c53e DJB 표준전문 및 오류 응답 관련 수정 2026-06-17 19:23:27 +09:00
curry772 30b04e6e19 KJB 관련 소스 이동 2026-06-17 19:23:06 +09:00
curry772 fef16d1b99 오류응답 처리 수정 2026-06-17 13:54:08 +09:00
curry772 cf8756053e getTobeDesc 메소드 추가 2026-06-17 13:52:38 +09:00
curry772 3fcc112113 kjb 필터 이동 2026-06-17 09:27:13 +09:00
curry772 b85301763f kjb 필터 이동 2026-06-17 09:27:00 +09:00
curry772 3ebe90d595 표준전문헤더부 desc에 설정할 TobeDesc 메소드 추가 2026-06-17 09:26:38 +09:00
curry772 bf03a97fa3 오류코드 메시지를 프로퍼티 그룹에 등록하여 매핑 치환하는 AdapterErrorMsgHandler 개발
- AdapterErrorMessageHandler{CODE_CONVERT} 에 등록
- TemplateCodeConvertAdapterErrorMsgHandler json만 지원하는 handler 개발
2026-06-15 14:45:18 +09:00
curry772 756523c5c3 HSM 저장 키 디버그 로그 추가 2026-06-14 12:00:05 +09:00
curry772 ec48226671 BIZ_DATA 배열내에 있는 경우 못찾는 문제 처리 2026-06-14 11:56:05 +09:00
curry772 44e3f686a4 outbound target url - path variable 지원
- {var1}/aaa/{httpHeaderGroup.var2} depth 있는 경우도 지원도되록 변경
2026-06-14 11:55:41 +09:00
curry772 5551affe3e 오류 처리 로직 추가
- 역방향(당발) 거래 시 circuitbreaker 오류에 대해 유량제어 오류가 발생하도록 수정
- Adapter Error handler에 Outbound Process(RestProcess)에서 호출 할 수 있는 메소드
추가
2026-06-12 09:18:24 +09:00
curry772 a616cd6334 불필요한 로직 제거 및 json 처리 메소드 이동 2026-06-12 09:14:28 +09:00
curry772 05a456b461 afterFoundEaiMessage 함수 추가 2026-06-12 09:03:56 +09:00
curry772 056583b542 필요없는 로직 제거(중복 호출)
- EAIMessage setRspErr 호출되면 coordinateSetStandardMessageError 호출됨
2026-06-12 09:03:33 +09:00
curry772 2ed893e70d 표준 HTTP inbound adapter 응답 content type을 요청과 동일하게 리턴되도록 수정 2026-06-12 08:57:10 +09:00
curry772 23eaf725b6 표준 전문의 BIZ_DATA(개별부) path를 mapping config properties 파일 설정으로 변경
- standard-message-mapping-config-djb.properties 참고
- 배열 형태의 경우, path를 정상적으로 적용 못하는 문제 우회
2026-06-12 08:55:42 +09:00
curry772 2354af516c eaiSvcCd를 표준헤더 생성시 StandardMessage 객체내에 저장하도록 변경
- 비표준은 이 값을 이용(표준은 전문에서 추출)
2026-06-12 08:52:24 +09:00
curry772 209c1ba15d 파라미터 타입 변경 2026-06-12 08:48:32 +09:00
curry772 895d346bb6 표준전문이 JSON MS949인 경우, 한글 깨짐 처리 2026-06-10 15:10:33 +09:00
curry772 940e5bb90c EAIMessage에 callProp를 추가하고, 동기 거래에 대한 Outbound에서 세팅한 callProp가
RequestProcessor, EAIMessage를 통해 접근 가능하도록 변경
2026-06-10 08:53:39 +09:00
curry772 909cda9ffb JSON 메시지 이원을 위해서, 리턴을 byte[] -> String으로 변경 2026-06-10 08:51:28 +09:00
curry772 a538b9838e TemplateAdapterErrorMsgHandler 구현 작업 내역
-인바운드/아웃바운드/내부 오류 발생 시 어댑터별로 프로퍼티에 등록된 템플릿을 사용하여
 커스텀 에러 응답 메시지를 생성하는 TemplateAdapterErrorMsgHandler 구현 및 연동 작업.
- ExceptionHandler 변경
인바운드 어댑터에 ERR_MSG_HANDLER 프로퍼티가 설정된 경우 우선 실행
- ApiController에서 사용할 수 있는
generateNonStandardInboundErrorResponseMessage 추가, Controller에서도
Adapter가 식별되면 설정된 핸들러에 의해 에러메시지 행성 가능
2026-06-09 13:47:25 +09:00
curry772 d15b2d563e AdapterName이 null이어서 NullPointerException 발생하는 문제 처리 2026-06-05 17:17:41 +09:00
curry772 037fcd6302 feat: TxSiftContext 추가 및 HttpAdapterServiceSupport 적용
- TxSiftContext 클래스 추가: SIFT MDC 등록/해제 및 appender close 공통 처리
     - begin(): isSiftEnabled() 확인 후 MDC 등록, 이미 설정된 경우 스킵
     - end(): appender 즉시 close 후 MDC 제거 (UUID 등 1회성 key 용도)
   - HttpAdapterServiceSupport: MDC 직접 제어 코드를 TxSiftContext.begin/end로
교체
2026-06-05 14:05:10 +09:00
curry772 d9ff1fa40b 잘못된 주석 제거 2026-06-05 13:00:15 +09:00
curry772 4ce22937af 미확인(unkown) 로그 파일 로그 추가
- EAIDBLogControl.isEnable() DB 정상 확인
- DB 비정상일 시 파일 로그 출력
2026-06-05 10:35:17 +09:00
curry772 e1e2353139 거래데이터 DB 암호화 DAMO 모듈 적용
- DB 거래로그 : EAILogDAO.java
- FILE 거래로그 : EAIFileLogger.java
- 미확인(Unkown) 로그 : InboundErrorInfoDAO.java
2026-06-05 09:47:31 +09:00
curry772 bc16dbcf13 Outbound HTTP 거래에 대해 Simulation거래도 Rest와 동일하게 변경 2026-06-04 16:14:24 +09:00
curry772 176c9845d7 api id, eai svc cd 추출 필터 추가 2026-06-02 18:04:51 +09:00
curry772 3ee53c469c 디버그 로그 추가 2026-06-02 18:03:44 +09:00
curry772 6776153582 KJB_ROOTLESS_ARRAY -> DJB_ROOTLESS_ARRAY 변경 2026-06-02 08:31:47 +09:00
curry772 ea09113dbe XML 메시지 지원 개발 2026-06-02 08:29:11 +09:00
curry772 113b9227c9 DJB에 사용하지 않는 resource 파일 정리 2026-06-01 16:54:24 +09:00
curry772 f3c2e64191 JWT 서명 알고리즘 RS256 → PS256 교체 — PssJwtAccessTokenConverter, RsaPssSigner,
RsaPssVerifier 추가

- RsaPssSigner: spring-security-jwt Signer 구현,
RSASSA-PSS(SHA-256/MGF1/salt=32) 서명
- RsaPssVerifier: SignatureVerifier 구현, 동일 PSS 파라미터로 검증
- PssJwtAccessTokenConverter: JwtHelper를 우회하여 PS256 JWT 직접 발급·검증
  - encode/decode 오버라이드로 JwtAlgorithms PS256 미등록 문제 해결
  - 생성자에서 공개키 PEM을 보관하여 getKey() 엔드포인트 대응
- build.gradle: spring-security-jwt:1.1.1.RELEASE api 의존성 추가
- RsaPssSignerVerifierTest: 단위테스트 13개 (서명/검증/라운드트립/시각확인)
2026-05-28 13:27:34 +09:00
curry772 6809bf66b8 Dynamic IV 도출 지원 — DerivedKey iv 필드 추가, HsmKeyAndIvDerivationStrategy 구현 2026-05-27 13:47:58 +09:00
curry772 29a486bf9b RequestProcessor 메소드 시그니쳐 변경 2026-05-27 10:32:30 +09:00
curry772 7415828e82 클라이언트 유량제어 관련 단위테스트 eapim-online 이동 2026-05-26 16:36:06 +09:00
curry772 a4dbf4ab27 Client 유량제어 관련 eapim-online으로 이동 2026-05-26 16:32:32 +09:00
curry772 4f18d25a58 CryptoModuleServier 단위테스트 수정 2026-05-26 16:32:12 +09:00
curry772 0e96e9470e 어댑터 암복호화 필터 리팩토링
- 커스텀 패키지 지정
- 변수 이동
2026-05-26 09:54:11 +09:00
curry772 a51453a57f MaskingUtils elink-online-core로 이동 2026-05-20 17:16:37 +09:00
curry772 6eedac51f7 마스킹 유틸리티 추가 (MaskingUtils + 단위테스트)
- 보안아키텍처 마스킹 정책에 따라 고유식별정보·금융식별정보·인증정보·신상정보·온라인정보
- 카테고리별 마스킹 처리 유틸 및 JUnit 5 단위테스트(3단계 번호 체계) 신규 작성
2026-05-20 16:33:21 +09:00
curry772 8d63586749 거래파일로그 기능 추가
- HTTP Inbound 거래도 출력되도록 수정
- HTTP Header 파일로그 추가
2026-05-19 13:41:35 +09:00
curry772 7b0699fdb9 로그 오타 수정 2026-05-19 11:20:23 +09:00
curry772 b6a5dce74a 거래파일로그(SIFT_LOGGER) 출력기능 개발
- UUID별로 거래파일로그 생성
2026-05-19 11:18:59 +09:00
curry772 265a30ccf3 Outbound Client oAuth Token 기능 개선 및 패치
- 광주은행 내용 적용
- ignite 사용
2026-05-18 10:58:10 +09:00
curry772 d458d1b008 Merge branch 'feature/cryptofilter' 2026-05-14 15:24:04 +09:00
curry772 29b8b7eaac perf: encryptField/decryptField JSON 파싱 횟수 절감
JsonPathUtil에 JsonNode 기반 저수준 API(toTree/getAt/setAt/fromTree) 추가.
기존 getValueAtPath/setValueAtPath/mergeAtRoot는 내부적으로 이를 위임.

AbstractCryptoFilter.encryptField, decryptField는 파싱된 JsonNode를
재사용하여 JSON 파싱을 기존 2~3회에서 1회로 단축.
- encryptField non-root: 파싱 2회 → 1회
- decryptField non-root: 파싱 2회 → 1회
- decryptField toPath="/": 파싱 3회 → 2회 (body 1회 + mergeJson 1회)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:55:29 +09:00
curry772 74302df5a2 fix: decryptField toPath='/' 시 기존 필드 보존 - mergeAtRoot 연동
toPath='/'일 때 복호화된 텍스트로 body 전체를 교체하던 동작을
JsonPathUtil.mergeAtRoot 를 통해 fromPath 필드만 제거하고
복호화된 JSON을 body에 병합하는 방식으로 수정.

- AbstractCryptoFilter.decryptField: return plainText → mergeAtRoot 호출
- JsonPathUtil.mergeAtRoot: 신규 메서드 추가
- InCryptoFilterTest: 4-4 병합 동작 검증 테스트 추가, 4-2 설명 수정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:34:55 +09:00
curry772 9a15b5506f fix: CryptoFilterHsmIntegrationTest - BaseCryptoFilter 잔여 참조 InCryptoFilter로 교체
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:02 +09:00
curry772 0b68b8166f feat: InCryptoFilter / OutCryptoFilter 추가 및 암호화 필터 계층 재구성
- AbstractCryptoFilter (신규, http.filter 패키지): 공통 암복호화 로직 및 상수 분리
- InCryptoFilter (신규, dynamic.filter): HttpAdapterFilter 구현 - 수신 요청 복호화, 응답 암호화
- OutCryptoFilter (신규, client.impl.filter): HttpClientAdapterFilter 구현 - 송신 요청 암호화, 응답 복호화
- BaseCryptoFilter, CryptoFilter 제거 → InCryptoFilter로 통합
- CryptoFilterHsmIntegrationTest: 새 클래스명으로 업데이트

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:26:36 +09:00
curry772 8db66f8bef DefaultProcess - 표준전문 오류응답을 수신 응답 설정(setResMsg) 로직 변경
- AdapterErrorMessageHandler를 사용하는 경우, BWK_FAILMSG_CODE(RIBEAI999999)를
설정하도록 변경
- AdapterErrorMessageHandler 설정이 없은 경우, Exception 발생 부분 처리
2026-05-14 10:42:12 +09:00
curry772 c4a250db08 refactor: HttpAdapterFilterFactoryKjb 제거 및 HttpAdapterFilterFactory 통합
- HttpAdapterFilterFactoryKjb 삭제
- HttpAdapterFilterFactory에 HMAC_SHA256, REFLECTALLHEADER 케이스 추가
- classForName() 헬퍼로 basePackage / customBasePackage 순차 탐색 적용
- HttpAdapterServiceSupport 임포트를 HttpAdapterFilterFactory로 교체

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:09:22 +09:00
curry772 db7a728344 feat: HttpClient5 어댑터 필터 패키지 eapim-online에서 이동
- HttpClient5AdapterServiceRestAddFilter 이동
- HttpClientAdapterFilter / HttpClient5AdapterFilterFactory 등 filter 패키지 이동
  (AllHeaderFilter, AsyncReponseFilter, JBOBPFilter, KAKAOBankFilter,
   KFTCFaceFilter, KFTCP2PFilter, NAVERFinFilter, NICECreditFilter,
   SimpleFrameworkFilter, SimpleframeworkBodyFilter, TOSSBankFilter, TraceBodyFilter)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:58:07 +09:00
curry772 813bb99952 test: CryptoModuleServiceTest DYNAMIC AES/CBC 출력 로그 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:53:17 +09:00
curry772 becd8612e8 Merge branch 'feature/crypto-module'
CryptoFilter 프로퍼티 키 규격화, HSM 통합 테스트, SoftHSM2 연동 검증 완료
2026-05-13 13:29:14 +09:00
118 changed files with 10424 additions and 1883 deletions
+2
View File
@@ -135,6 +135,8 @@ dependencies {
compileOnly group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.13.1'
api "org.springframework.security:spring-security-jwt:1.1.1.RELEASE"
testRuntimeOnly 'com.h2database:h2:2.1.214'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
Binary file not shown.
-29
View File
@@ -1,29 +0,0 @@
#-----------------------------------------------------------------------------
# comment start with #
# level; // 0 : root ~ n
# type; // 0: Message, 1: Field, 2:Group 3, Grid, 4 : Field Array, 9 : BIZ DATA
# size // 0 : variable, > 0 fixed
# fieldType; // 0: ELEMENT, 1: ATTRIBUTE,
# length;
# dataType; // 1: String, 2: Number,11: ZZ String, 12: LL Number
#-----------------------------------------------------------------------------
# name ,level ,type ,size ,fieldType ,length ,dataType ,,refPath, refValue,value
#-----------------------------------------------------------------------------
ElinkHeader ,0,0,1,0, 0,0,,,
Header ,1,2,1,1, 0,0,,,
StndCicsTrncd ,2,1,1,1,10,0,,,
StndIntnlStndTelgmLen,2,1,1,1,10,11,,,0
StndTranBaseYmd ,2,1,1,1,10,0,,,
Common ,1,2,0,0, 0,0,Header.StndCicsTrncd,JI6H,null
TranInfo ,2,2,1,0, 0,0,,,
StndCicsTrncd ,3,1,1,0, 3,0,,,
StndTelgmRecvTranCd ,3,1,1,0,10,0,,,
StndPrcssRtdTranCd ,3,1,1,0,10,0,,,
Array ,1,3,0,0, 3,0,,,
Item1 ,2,1,1,0,10,0,,,JI6H
Group ,2,3,1,0, 1,0,,,
gitem1 ,3,1,1,0,10,1,,,726
gitem2 ,3,1,1,0,10,0,,,20220905
FArray ,1,4,0,0,10,0,,,
bizData ,1,9,1,0, 0,0,,,
zzData ,1,1,1,0, 2,10,,,ZZ
1 #-----------------------------------------------------------------------------
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
@@ -1,41 +0,0 @@
#-----------------------------------------------------------------------------
# comment start with #
# level; // 0 : root ~ n
# type; // 0: Message, 1: Field, 2:Group 3, Grid, 4 : Field Array, 9 : BIZ DATA
# size // 0 : variable, > 0 fixed
# fieldType; // 0: ELEMENT, 1: ATTRIBUTE,
# length;
# dataType; // 1: String, 2: Number,11: ZZ String, 12: LL Number
#-----------------------------------------------------------------------------
# name ,level ,type ,size ,fieldType ,length ,dataType ,,refPath, refValue,value
#-----------------------------------------------------------------------------
# name ,level ,type ,size ,fieldType ,length ,dataType ,refPath, refValue, value
STD_HNCPHEADER_REQ ,0 ,1 ,1 ,1 ,0 ,1 , , ,,표준헤더
msg_len ,1 ,2 ,1 ,1 ,6 ,12 , , ,199,전문길이
global_id ,1 ,2 ,1 ,1 ,29 ,1 , , ,GIDNO00000000001111,Global아이디
service_id ,1 ,2 ,1 ,1 ,10 ,1 , , ,ELINKSVC01,거래코드
service_cmt ,1 ,2 ,1 ,1 ,100 ,1 , , ,ELINK TEST Service,거래코드설명
service_tn ,1 ,2 ,1 ,1 ,1 ,1 , , ,1,거래구분
Session_id ,1 ,2 ,1 ,1 ,100 ,1 , , ,SESSIONID001,Session아이디
encrypt_yn ,1 ,2 ,1 ,1 ,2 ,1 , , ,NO,전문암호화여부
http_url ,1 ,2 ,1 ,1 ,100 ,1 , , ,,HTTPURL정보
screen_id ,1 ,2 ,1 ,1 ,13 ,1 , , ,SCREEN0000001,화면ID
scr_no ,1 ,2 ,1 ,1 ,30 ,1 , , ,000000000100000000020000000003,화면번호
scr_dsc ,1 ,2 ,1 ,1 ,100 ,1 , , ,,화면명
usr_id ,1 ,2 ,1 ,1 ,10 ,1 , , ,USER000001,사용자ID
ip_addr ,1 ,2 ,1 ,1 ,47 ,1 , , ,,IPAddress
ip_mac ,1 ,2 ,1 ,1 ,12 ,1 , , ,,ClientMAC Address
com_code ,1 ,2 ,1 ,1 ,4 ,1 , , ,EACT,그룹사코드
head_code ,1 ,2 ,1 ,1 ,7 ,1 , , ,EACT001,본부코드
dept_cd ,1 ,2 ,1 ,1 ,7 ,1 , , ,ELINK01,부서코드
conn_tp ,1 ,2 ,1 ,1 ,2 ,1 , , ,CH,요청채널구분
Inter_chn ,1 ,2 ,1 ,1 ,2 ,1 , , ,EA,시스템구분
recv_tm ,1 ,2 ,1 ,1 ,17 ,1 , , ,20220928000000000,전문수신시간
send_tm ,1 ,2 ,1 ,1 ,17 ,1 , , ,,전문송신시간
return_gubun ,1 ,2 ,1 ,1 ,1 ,1 , , ,S,전문응답구분
return_value ,1 ,2 ,1 ,1 ,1 ,1 , , ,,처리결과
msg_id ,1 ,2 ,1 ,1 ,7 ,1 , , ,,오류코드
msg_nm ,1 ,2 ,1 ,1 ,250 ,1 , , ,,오류메시지
filler ,1 ,2 ,1 ,1 ,125 ,1 , , ,,Filler
bizData ,1 ,9 ,0 ,1 ,0 ,1 , , ,,업무데이터
zzData ,1 ,2 ,0 ,1 ,2 ,11 , , ,,ZZ플래그
1 #-----------------------------------------------------------------------------
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플래그
@@ -1,11 +0,0 @@
layout.file.type=CSV
layout.file.path=./resources/standard-layout-sample.csv
mapper.class=com.eactive.eai.message.test.TestInterfaceMapper
mapper.definition=./resources/standard-message-mapping-config.properties
reader.JSON=com.eactive.eai.message.parser.JsonReader
reader.JSN=com.eactive.eai.message.parser.JsonReader
reader.UJN=com.eactive.eai.message.parser.JsonReader
reader.XML=com.eactive.eai.message.parser.XmlReader
reader.UXL=com.eactive.eai.message.parser.XmlReader
reader.ASC=com.eactive.eai.message.parser.FlatReader
reader.FLAT=com.eactive.eai.message.parser.FlatReader
@@ -1,20 +0,0 @@
# layout : StandardMessage layout definition
layout.file.type=CSV
layout.file.path=./resources/standard-layout.csv
layout.filter.FLAT=com.eactive.eai.message.filter.FlatMessageFilter
# mapper : StandardMessage's fields getter/setter interface
mapper.class=com.eactive.eai.message.service.DefaultInterfaceMapper
mapper.definition=./resources/standard-message-mapping-config.properties
# reader : parsing input data to StandardMessage
reader.JSON=com.eactive.eai.message.parser.JsonReader
reader.JSN=com.eactive.eai.message.parser.JsonReader
reader.UJN=com.eactive.eai.message.parser.JsonReader
reader.XML=com.eactive.eai.message.parser.XmlReader
reader.UXL=com.eactive.eai.message.parser.XmlReader
reader.ASC=com.eactive.eai.message.parser.FlatReader
reader.FLAT=com.eactive.eai.message.parser.FlatReader
encode.flat=euc-kr
encode.value=euc-kr
encode.xml=utf-8
# version.layout.name=VERSION_SAMPLE
# version.mapper.definition=./resources/version-message-mapping-config.properties
@@ -1,28 +0,0 @@
REQ_SYS_CODE=systemHeader.DMND_SYS_CD
CHANNEL_TYPE_CD=stdcommon.chnlTycd
CHANNEL_DETAIL_CD=stdcommon.chnlDtlsClcd
SEND_TIME=stdheader.send_tm
GUID=stdheader.global_id
SYSTEM_TYPE=stdheader.Inter_chn
RECV_TIME=stdheader.recv_tm
RETURN_VALUE=stdheader.return_value
GUID_ORG=stdcommon.ortrGuid
GUID_SEQ=stdheader.global_id
#SERVICE_ID=stdheader.service_id
SERVICE_ID=service_id
RETURN_SERVICE_ID=stdcommon.procsRsltRcmsSrvcId
INTERFACE_ID=stdheader.service_id
SESSION_ID=stdheader.Session_id
#SEND_RECV_DIVISION=stdheader.return_gubun
SEND_RECV_DIVISION=service_tn
IN_EX_DIVISION=stdcommon.hmabDvcd
OPERATION_ENV=stdcommon.sysOprtEnvDvcd
FIRST_REQ_IP=stdheader.ip_addr
RECOVER_YN=stdcommon.ortrRestrYn
INST_CODE=stdheader.com_code
TIMEOUT=stdheader.gnrzTmotTktm
ERROR_CODE=stdheader.msg_id
ERROR_MSG=stdheader.msg_nm
USER_ID=stdheader.usr_id
RESPONSE_TYPE=stdcommon.procsRsltDvcd
SYNC_ASYNC_TYPE=stdcommon.txSynzDvcd
@@ -1,26 +0,0 @@
REQ_SYS_CODE=Inter_chn
CHANNEL_TYPE_CD=
CHANNEL_DETAIL_CD=conn_tp
SEND_TIME=send_tm
GUID=global_id
SYSTEM_TYPE=service_tn
RECV_TIME=recv_tm
RETURN_VALUE=return_value
GUID_ORG=stdcommon.ortrGuid
GUID_SEQ=stdheader.global_id
SERVICE_ID=service_id
RETURN_SERVICE_ID=stdcommon.procsRsltRcmsSrvcId
INTERFACE_ID=service_id
SESSION_ID=Session_id
SEND_RECV_DIVISION=service_tn
IN_EX_DIVISION=stdcommon.hmabDvcd
OPERATION_ENV=stdcommon.sysOprtEnvDvcd
FIRST_REQ_IP=ip_addr
RECOVER_YN=stdcommon.ortrRestrYn
INST_CODE=stdheader.com_code
TIMEOUT=stdheader.gnrzTmotTktm
ERROR_CODE=msg_id
ERROR_MSG=msg_nm
USER_ID=usr_id
RESPONSE_TYPE=stdcommon.procsRsltDvcd
SYNC_ASYNC_TYPE=stdcommon.txSynzDvcd
@@ -1,4 +0,0 @@
service_id=header.if_id
global_id=header.guid
service_tn=header.req_type
bizData=bizData
@@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -163,6 +164,9 @@ public class AdapterManager implements Lifecycle {
}
public AdapterVO getAdapterVO(String adapterGroupName, String adapterName) {
if(StringUtils.isAnyBlank(adapterGroupName, adapterName))
return null;
AdapterGroupVO gvo = adapterGroups.get(adapterGroupName);
if (gvo == null)
return null;
@@ -2,9 +2,50 @@ package com.eactive.eai.adapter.handler;
import java.util.Properties;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.common.message.EAIMessage;
public interface AdapterErrorMessageHandler {
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName, Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception;
/**
* 비표준 -> 표준 거래
* 아웃바운드 DefaultProcess에서 응답 표준헤더에 에러응답이 세팅되어 있는 경우, 비표준 응답 메시지 생성용
*/
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg) throws Exception;
/**
* 비표준 -> 표준 거래
* GW 내부 오류가 발생한 경우, RequestPrcessor에서 에러 응답을 생성하는 경우, ExceptionHandler에 의해 호출, 비표준 응답 메시지 생성용
*/
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName,
String inboudnAdapterName, Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg)
throws Exception;
/**
* 비표준 -> 표준 거래
* GW RequestProcess 호출 전에 Adapter(Filter)에서 오류가 발생한 경우, ApiAdapterController에 Exception에 맞추어, 비표준 응답 메시지 생성용
*/
public Object generateNonStandardInboundErrorResponseMessage(String inboudnAdapterGroupName,
String inboudnAdapterName, Properties callProp, Object inboundRequestData, Object inboundResponseData,
Throwable e) throws Exception;
/**
* 표준 -> 비표준 거래
* 아웃바운드 어댑터에서 Exception이 발생했을 때, Outbound 어댑터에 의해서 호출하여 응답 메시지 생성
* 수신한 응답 메시지가 있을 수도 있고 없을 수도 있다.
*/
default public Object generateOutboundErrorResponseMessage(String outboundadapterGroupName,
String outboundadapterName, Properties callProp, Object outboundRequestData, Object outboundResponseData,
Throwable e) throws Exception {
return null;
}
/**
* 표준 -> 비표준 거래
* 아웃바운드 어댑터에서 Exception이 발생했을 때, Outbound Process 응답 메시지 생성
*/
default public Object generateOutboundErrorResponseMessage(String outboundadapterGroupName,
String outboundadapterName, Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg)
throws Exception {
return null;
}
}
@@ -2,13 +2,25 @@ package com.eactive.eai.adapter.handler;
import java.util.Properties;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.common.message.EAIMessage;
public class DefaultAdapterErrorMessageHandler implements AdapterErrorMessageHandler {
@Override
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception {
Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg) throws Exception {
return null;
}
@Override
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg) throws Exception {
return null;
}
@Override
public Object generateNonStandardInboundErrorResponseMessage(String adapterGroupName, String adapterName,
Properties callProp, Object inboundRequestData, Object inboundResponseData, Throwable e) throws Exception {
return null;
}
@@ -1,5 +1,7 @@
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;
@@ -8,13 +10,23 @@ 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.google.gson.JsonElement;
import com.google.gson.JsonParser;
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;
/**
* 템플릿 기반 어댑터 에러 메시지 핸들러.
@@ -78,7 +90,7 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
/** AdapterPropManager 에서 템플릿을 조회할 프로퍼티 그룹 이름 */
/** PropManager 에서 템플릿을 조회할 프로퍼티 그룹 이름 */
static final String PROP_GROUP = "AdapterErrorMessageHandler";
/** callProp 참조 변수 접두어: ${callprop.키} */
@@ -87,14 +99,22 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
/** callProp 간접 참조 접두어: ${callprop[표준전문경로]} */
static final String CALLPROP_INDIRECT_PREFIX = "callprop[";
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 */
/** 예외 객체 필드 참조 접두어: ${exception.필드명} */
static final String EXCEPTION_PREFIX = "exception.";
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 (renderRow 내부에서 사용) */
private static final Pattern VAR_PATTERN =
Pattern.compile("\\$\\{([^}]+)\\}");
/** {{#foreach path}}...{{/foreach}} 배열 반복 블록 패턴 */
private static final Pattern FOREACH_PATTERN =
Pattern.compile("\\{\\{#foreach\\s+([^}]+)\\}\\}(.*?)\\{\\{/foreach\\}\\}",
Pattern.DOTALL);
/**
* 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(
@@ -102,9 +122,10 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
String inboundAdapterName,
Properties callProp,
Object outboundRequestData,
StandardMessage resStandardMessage) throws Exception {
EAIMessage resEaiMsg) throws Exception {
String templateKey = inboundAdapterGroupName + ".template";
StandardMessage resStandardMessage = resEaiMsg.getStandardMessage();
String templateKey = inboundAdapterGroupName + ".template";
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
if (StringUtils.isBlank(template)) {
@@ -118,55 +139,73 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
return render(template, resStandardMessage, callProp);
}
/** 3-인자 위임: exception 없이 호출하는 기존 경로 유지 */
String render(String template, StandardMessage msg, Properties callProp) {
return render(template, msg, callProp, null);
}
/**
* 템플릿에 StandardMessage callProp 값을 치환해 최종 문자열을 반환한다.
* 템플릿에 StandardMessage, callProp, exception 값을 치환해 최종 문자열을 반환한다.
* foreach 블록과 스칼라 변수를 좌→우 순서로 단일 패스 처리한다.
* package-private: 단위 테스트에서 직접 호출 가능
*/
String render(String template, StandardMessage msg, Properties callProp) {
// 1단계: {{#foreach path}}...{{/foreach}} 배열 반복 처리
StringBuffer sb = new StringBuffer();
Matcher foreachMatcher = FOREACH_PATTERN.matcher(template);
while (foreachMatcher.find()) {
String arrayPath = foreachMatcher.group(1).trim();
String blockContent = foreachMatcher.group(2);
String rendered = renderForeach(arrayPath, blockContent, msg);
foreachMatcher.appendReplacement(sb, Matcher.quoteReplacement(rendered));
}
foreachMatcher.appendTail(sb);
// 2단계: 나머지 ${path} / ${callprop.키} 스칼라 변수 치환
String intermediate = sb.toString();
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 varMatcher = VAR_PATTERN.matcher(intermediate);
while (varMatcher.find()) {
String expr = varMatcher.group(1).trim();
String value = resolveScalar(expr, msg, callProp);
varMatcher.appendReplacement(result, Matcher.quoteReplacement(value));
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));
}
varMatcher.appendTail(result);
matcher.appendTail(result);
return result.toString();
}
/**
* 변수 표현식을 해석해 값을 반환한다.
* 모든 형태에서 ':기본값' 접미사를 지원한다. 기본값 미지정 시 빈 문자열.
* - "callprop[경로][:기본값]" → 표준전문 경로 값을 키로 callProp 간접 조회
* - "callprop.키[:기본값]" → callProp 직접/중첩 조회
* - "경로[:기본값]" → StandardMessage.findItemValue(경로)
* - "callprop[경로][:기본값]" → 표준전문 경로 값을 키로 callProp 간접 조회
* - "callprop.키[:기본값]" → callProp 직접/중첩 조회
* - "exception.필드[:기본값]" → 리플렉션으로 예외 객체 getter/필드 조회
* - "경로[:기본값]" → StandardMessage.findItemValue(경로)
*/
private String resolveScalar(String expr, StandardMessage msg, Properties callProp) {
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 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) : "";
@@ -176,13 +215,22 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
/**
* StandardMessage 경로를 해석한다.
* 1. findItemValue(path) 로 직접 조회한다.
* 2. null 이면 경로를 뒤에서부터 분리하며, 부모 경로 값이 JSON 문자열인 경우
* 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;
@@ -200,6 +248,44 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
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 면 문자열로,
@@ -276,7 +362,7 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
* 결과를 쉼표로 이어 반환한다. (foreach 블록 내부는 callProp 참조 미지원)
*/
private String renderForeach(String arrayPath, String blockContent, StandardMessage msg) {
StandardItem arrayItem = msg.findItem(arrayPath);
StandardItem arrayItem = msg.findItem(arrayPath);
if (arrayItem == null) {
if (logger.isWarn()) logger.warn("TemplateAdapterErrorMsgHandler] foreach path not found: " + arrayPath);
return "";
@@ -316,4 +402,132 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
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;
}
}
@@ -0,0 +1,126 @@
package com.eactive.eai.adapter.handler;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class TemplateCodeConvertAdapterErrorMsgHandler extends TemplateAdapterErrorMsgHandler {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
/** PropManager 에서 코드 변환 설정을 조회할 프로퍼티 그룹 이름 */
static final String PROP_GROUP = "AdapterErrorMessageHandler{CODE_CONVERT}";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public Object generateNonStandardErrorResponseMessage(
String inboundAdapterGroupName,
String inboundAdapterName,
Properties callProp,
Object outboundRequestData,
EAIMessage resEaiMsg) throws Exception {
Object responseMsessage = super.generateNonStandardErrorResponseMessage(
inboundAdapterGroupName,
inboundAdapterName,
callProp,
outboundRequestData,
resEaiMsg);
logger.debug("generateNonStandardErrorResponseMessage - {}", responseMsessage);
if (!(responseMsessage instanceof String)) {
return responseMsessage;
}
String jsonStr = (String) responseMsessage;
if (StringUtils.isBlank(jsonStr)) {
return responseMsessage;
}
Properties props = PropManager.getInstance().getProperties(PROP_GROUP);
if (props == null) {
return responseMsessage;
}
String fieldsKey = inboundAdapterGroupName + ".convert.fields";
String fieldsValue = props.getProperty(fieldsKey);
if (StringUtils.isBlank(fieldsValue)) {
return responseMsessage;
}
JsonNode rootNode = OBJECT_MAPPER.readTree(jsonStr);
boolean modified = false;
for (String rawField : fieldsValue.split(",")) {
String fieldPath = rawField.trim();
if (StringUtils.isBlank(fieldPath)) continue;
String currentValue = getJsonValue(rootNode, fieldPath);
if (currentValue == null) continue;
// key: inboundAdapterGroupName.{fieldPath}.{현재값} → 폴백: ...{fieldPath}.default
String mappingKey = inboundAdapterGroupName + "." + fieldPath + "." + currentValue;
String newValue = props.getProperty(mappingKey);
if (newValue == null) {
String defaultKey = inboundAdapterGroupName + "." + fieldPath + ".default";
newValue = props.getProperty(defaultKey);
}
if (newValue == null) continue;
setJsonValue(rootNode, fieldPath, newValue);
modified = true;
if (logger.isDebug()) {
logger.debug("code convert: field={}, {} -> {}", fieldPath, currentValue, newValue);
}
}
if (modified) {
responseMsessage = OBJECT_MAPPER.writeValueAsString(rootNode);
}
return responseMsessage;
}
/**
* dot 구분 경로로 JsonNode 에서 값을 꺼낸다.
* 최종 노드가 값 노드(primitive)가 아니면 null 반환.
*/
private String getJsonValue(JsonNode node, String fieldPath) {
JsonNode current = node;
for (String part : fieldPath.split("\\.", -1)) {
if (current == null || !current.isObject()) return null;
current = current.get(part);
}
if (current == null || current.isNull() || !current.isValueNode()) return null;
return current.asText();
}
/**
* dot 구분 경로로 ObjectNode 의 최종 필드 값을 newValue 로 교체한다.
* 중간 경로가 ObjectNode 가 아니면 무시한다.
*/
private void setJsonValue(JsonNode rootNode, String fieldPath, String newValue) {
String[] parts = fieldPath.split("\\.", -1);
JsonNode current = rootNode;
for (int i = 0; i < parts.length - 1; i++) {
if (current == null || !current.isObject()) return;
current = current.get(parts[i]);
}
if (current instanceof ObjectNode) {
((ObjectNode) current).put(parts[parts.length - 1], newValue);
}
}
}
@@ -41,6 +41,12 @@ 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;
}
@@ -570,8 +570,8 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
public abstract Object execute(Properties prop, Object message, Properties tempProp) throws Exception;
protected boolean isTas(Properties tempProp) {
String simYn = tempProp.getProperty("SIM_YN");
String tranType = tempProp.getProperty("tranType");
return EAIServerManager.getInstance().isTASEnabledEAIServer()
&& StringUtils.equals(simYn, EAIMessageKeys.TRANTYPE_TAS);
&& StringUtils.equals(tranType, EAIMessageKeys.TRANTYPE_TAS);
}
}
@@ -0,0 +1,24 @@
package com.eactive.eai.adapter.http.client.impl;
import java.nio.charset.Charset;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.net.WWWFormCodec;
/**
* HttpComponent5에서 지원하는 UrlEncodedFormEntity 클래스는 chunked 옵션 설정을 지원하지 않아 새롭게 만들었음.
*/
public class CustomUrlEncodedFormEntity extends StringEntity {
public CustomUrlEncodedFormEntity(final Iterable<? extends NameValuePair> parameters, final Charset charset,
final boolean chunked) {
super(WWWFormCodec.format(parameters,
charset != null ? charset : ContentType.APPLICATION_FORM_URLENCODED.getCharset()),
charset != null ? ContentType.APPLICATION_FORM_URLENCODED.withCharset(charset)
: ContentType.APPLICATION_FORM_URLENCODED,
chunked);
}
}
@@ -7,6 +7,8 @@ import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.TxFileLogger;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
@@ -118,7 +120,9 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
}
TxFileLogger.logTxFile(tempProp, sendData, "[OUT_SEND]");
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
HttpContext context = new BasicHttpContext();
context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid);
@@ -147,7 +151,8 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
}
stopWatch.stop();
TxFileLogger.logTxFile(tempProp, responseString, "[OUT_RECV]");
if (status >= 400 && status < 500) {
String errMsg = String.format(
@@ -172,7 +177,8 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
+ CommonLib.getDumpMessage(responseMessage));
}
return responseMessage;
// return responseMessage;
return responseString;
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
if (vo.getTraceLevel() >= 3) {
@@ -6,6 +6,7 @@ import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -35,7 +36,6 @@ import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
@@ -75,6 +75,11 @@ import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.JSONUtils;
import com.eactive.eai.common.util.TxFileLogger;
import com.eactive.eai.common.util.XMLUtils;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.kjbank.encrypt.exchange.crypto.AES256Cipher;
@@ -116,7 +121,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id"; // jwhong
static final String KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : ";
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
private boolean useAdapterToken;
private boolean encryptResponseApply;
@@ -138,17 +143,6 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
// 2025.04.09 Authorization 헤더값을 프로퍼티(AUTH_TOKEN)에서 지정하여 설정할 수 있도록 수정
// 2025.05.29 프로퍼티 그룹 AUTH_TOKEN이 없을때 Null Point Exception 발생하여 수정
String bizCode = "";
String authToken = "";
try {
Properties authProp = PropManager.getInstance().getProperties(AUTH_TOKEN); //Authorization
bizCode = vo.getAdapterGroupName().substring(1, 4);
authToken = authProp.getProperty(bizCode, "");
} catch (Exception e) {
logger.debug("Property Group AUTH_TOKEN is not Exist !!");
}
logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken);
String headerGroupName = prop.getProperty(HEADER_GROUP);
String relayResponseHeaderKeys = prop.getProperty(HEADER_KEYS, "");
useAdapterToken = StringUtils.equalsIgnoreCase(prop.getProperty("ADAPTER_TOKEN_USE_YN", "N"), "Y");
@@ -194,42 +188,56 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
String rmethod = (String) restOptionObject.getOrDefault("method", inboundMethod);
String contentType = (String) restOptionObject.getOrDefault("contentType", "application/json");
ContentType oContentType = ContentType.parse(contentType);
Charset contentTypeCharset = oContentType.getCharset();
String charset = vo.getEncode();
if(contentTypeCharset != null)
charset = contentTypeCharset.displayName();
logger.debug("HttpClient5AdapterServiceRest] charset = {}", charset);
String mimeType = oContentType.getMimeType();
String adapterTokenUseYn = (String) restOptionObject.get("adapterTokenUseYn");
if (StringUtils.isNotBlank(adapterTokenUseYn)) { // interface 에 설정된게 우선한다.
useAdapterToken = StringUtils.equalsIgnoreCase(adapterTokenUseYn, "Y");
}
String cnkUseYn = (String) restOptionObject.get("cnkUseYn");
boolean chunked = false;
if (StringUtils.isNotBlank(cnkUseYn)) {
chunked = StringUtils.equalsIgnoreCase(cnkUseYn, "Y");
}
HttpClient mclient = this.client;
String sendData = "";
Object sendData = "";
if (data instanceof String) {
sendData = (String) data;
sendData = data;
} else if (data instanceof byte[]) {
sendData = new String((byte[]) data, vo.getEncode());
} else if (data instanceof JSONObject) {
sendData = data;
}
////mclient.getParams().setContentCharset(vo.getEncode());
JSONObject dataObject = null;
Object dataObject = null;
Object httpHeader = null;
Object dataContent = null;
//Object dataContent = null;
if (MessageType.JSON.equals(messageType)) {
Object parsed = parseJsonGeneric(sendData);
Object parsed = JSONUtils.parseJsonGeneric(sendData);
if(parsed instanceof JSONObject) {
dataObject = parseJson(sendData);
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
httpHeader = dataObject.get(headerGroupName);
dataObject.remove(headerGroupName);
}
dataContent = dataObject;
if (dataObject.containsKey("innerList")) {
JSONArray innerList = (JSONArray) dataObject.get("innerList");
dataContent = innerList;
}
JSONObject jsonObject = (JSONObject) parsed;
dataObject = jsonObject;
} else if(parsed instanceof JSONArray) {
// not support
}
} else if (MessageType.XML.equals(messageType)) {
Document doc = XMLUtils.convertXmlDocument(sendData);
dataObject = doc;
}
// if (MessageType.JSON.equals(messageType)) {
@@ -246,10 +254,29 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
Map<String, String> inboundPathVariables = (Map<String, String>) tempProp.get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES);
// interface 에 설정된게 우선한다.
String uri = null;
if (dataObject == null) {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
if(this.isTas(tempProp)) {
uri = vo.getUrl();
} else {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
if (dataObject == null) {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
} else {
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
}
}
if(dataObject instanceof JSONObject) {
if (dataObject != null && StringUtils.isNotBlank(headerGroupName)) {
httpHeader = ((JSONObject)dataObject).get(headerGroupName);
((JSONObject)dataObject).remove(headerGroupName);
}
} else if (dataObject instanceof Document) {
Document doc = (Document)dataObject;
if (doc != null && StringUtils.isNotBlank(headerGroupName)) {
Element root = doc.getRootElement();
Element httpHeaderElement = root.element(headerGroupName);
root.remove(httpHeaderElement);
httpHeader = httpHeaderElement;
}
}
logger.debug("HttpClientAdapterServiceRest] dataObject2 = [" + dataObject + "]");
@@ -349,6 +376,18 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
+ ") RequestHeader [" + method.getHeader("Authorization").getName() + ": " + method.getHeader("Authorization").getValue() + "]");
}
}
// 업무별 고정 token
String authToken = "";
try {
Properties authProp = PropManager.getInstance().getProperties(AUTH_TOKEN); //Authorization
bizCode = vo.getAdapterGroupName().substring(1, 4);
authToken = authProp.getProperty(bizCode, "");
} catch (Exception e) {
logger.debug("Property Group AUTH_TOKEN is not Exist !!");
}
logger.debug("authToken :::::::::::::::::::::::::::::::::::" + authToken);
if(!StringUtils.isBlank(authToken)) {
setAuthHeaders(method, authToken);
@@ -382,8 +421,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
// assignPostBody(dataObject, contentType, vo.getEncode(), method);
//assignPostBody(dataContent, contentType, vo.getEncode(), method); // jwhong commnet
// KJBank API 추적정보를 Header에 제공. jwhong
String inboundToken= assignRequestKJHeaders(method, prop, tempProp );
assignPostBody(dataContent, contentType, vo.getEncode(), method, vo.getAdapterName(), auth, inboundToken, currentDate);
// String inboundToken= assignRequestKJHeaders(method, prop, tempProp );
assignPostBody(dataObject, mimeType, charset, method, chunked);
break;
default:
break;
@@ -402,17 +441,19 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
int status = -1;
Header[] responseHeaders = null;
String responseString = null;
try {
byte[] responseMessage = null;
byte[] responseMessageOri = null;
Header[] responseHeaders;
// byte[] responseMessageOri = null;
String responseContentType = null;
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
if (dataObject != null) {
if (dataObject instanceof JSONObject) {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = ["
+ dataObject.toJSONString() + "]");
+ ((JSONObject)dataObject).toJSONString() + "]");
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = "
+ CommonLib.getDumpMessage(dataObject.toJSONString()));
+ CommonLib.getDumpMessage(((JSONObject)dataObject).toJSONString()));
} else {
logger.debug("HttpClientAdapterServiceRest] SEND (" + vo.getAdapterGroupName() + ") = [" + sendData
+ "]");
@@ -427,6 +468,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
}
TxFileLogger.logTxFile(tempProp, sendData, "[OUT_SEND]");
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
HttpContext context = new BasicHttpContext();
@@ -439,8 +482,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
}
// encrypt algorithm type 추출 from adapter property jwhong
Properties properties = AdapterPropManager.getInstance().getProperties(vo.getAdapterName()); // jwhong
String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong
// Properties properties = AdapterPropManager.getInstance().getProperties(vo.getAdapterName()); // jwhong
// String encryptAlgorithm = properties.getProperty("ENCRYPT_ALGORITHM"); // jwhong
try {
if (logger.isDebug()) {
@@ -465,18 +508,30 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
//responseMessage = EntityUtils.toByteArray(response.getEntity());
// 역기서 responseMessage를 decryption 해야 하나 by jwhong
responseMessageOri = EntityUtils.toByteArray(response.getEntity());
logger.debug("[responseMessageOri 바이트 배열 길이 ]" + responseMessageOri.length);
responseMessage = EntityUtils.toByteArray(response.getEntity());
logger.debug("[responseMessage 바이트 배열 길이 ]" + responseMessage.length);
responseMessage = doOutboundPostFilter(responseMessageOri, encryptAlgorithm, vo.getAdapterName(), accessToken); // jwhong
// responseMessage = doOutboundPostFilter(responseMessageOri, encryptAlgorithm, vo.getAdapterName(), accessToken); // jwhong
// 여기까지
responseHeaders = response.getHeaders();
Header headerContentType = response.getHeader("Content-Type");
if(headerContentType != null) {
responseContentType = headerContentType.getValue();
}
if(responseContentType == null)
responseContentType = ContentType.APPLICATION_JSON.getMimeType();
tempProp.setProperty("RES_CONTENT_TYPE", responseContentType);
TxFileLogger.logTxFile(tempProp, new String(responseMessage, vo.getEncode()), "[OUT_RECV]");
if (logger.isDebug()) {
logger.debug("[received status]" + status);
logger.debug("HttpClientAdapterServiceRest] RECV (" + vo.getAdapterGroupName() + ") = [" + responseMessage + "]");
}
}
} catch (ConnectException e) {
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
@@ -502,16 +557,8 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
*/
boolean needReissue = false;
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
String responseString = new String(responseMessage, vo.getEncode());
responseString = new String(responseMessage, vo.getEncode());
Map<String, Object> map = new HashMap<String, Object>();
tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp);
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString);
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status);
if (isStatusInIgnore(status,errIgnore )) {
// 이경우 정상으로 처리함
} else if (status >= 200 && status <= 207) {
@@ -580,10 +627,35 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
+ ") RETRY TOKEN = [" + newaccessToken + "]");
}
try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
// executor 생성 (mclient, timeout은 기존 설정 그대로)
DeadlineAwareRetryExecutor executor =
new DeadlineAwareRetryExecutor((CloseableHttpClient)mclient,
vo.getConnectionTimeout(),
vo.getTimeout());
// Properties에서 RetryPolicy 로드
RetryPolicy policy = RetryPolicy.from(prop); // 설정 로드 방식에 맞게
try (CloseableHttpResponse response = (CloseableHttpResponse) executor.execute(method, context, policy)) {
// try (CloseableHttpResponse response = (CloseableHttpResponse) mclient.execute(method)) {
status = response.getCode();
responseMessage = EntityUtils.toByteArray(response.getEntity());
// 여기서도 decryption 해야 할듯 하나.. by jwhong
logger.debug("[responseMessageOri 바이트 배열 길이 ]" + responseMessage.length);
// responseMessage = doOutboundPostFilter(responseMessageOri, encryptAlgorithm, vo.getAdapterName(), accessToken); // jwhong
responseHeaders = response.getHeaders();
Header headerContentType = response.getHeader("Content-Type");
if(headerContentType != null) {
responseContentType = headerContentType.getValue();
}
if(responseContentType == null)
responseContentType = ContentType.APPLICATION_JSON.getMimeType();
tempProp.setProperty("RES_CONTENT_TYPE", responseContentType);
TxFileLogger.logTxFile(tempProp, new String(responseMessage, vo.getEncode()), "[OUT_RECV]");
if (logger.isDebug()) {
logger.debug("[received status]" + status);
@@ -627,12 +699,14 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
String jsonStr = new String(responseMessage, vo.getEncode());
if ( jsonStr.trim().startsWith("[") ) {
jsonStr = KJB_ROOTLESS_ARRAY + jsonStr + "}";
jsonStr = DJB_ROOTLESS_ARRAY + jsonStr + "}";
responseMessage = jsonStr.getBytes(vo.getEncode());
}
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), messageType,
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), responseContentType,
relayResponseHeaderKeys, status, responseHeaderProp);
} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
if (vo.getTraceLevel() >= 3) {
@@ -660,6 +734,10 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
}
logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
e);
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
throw e;
} finally {
if (status != HttpStatus.SC_OK) {
@@ -673,6 +751,15 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
}
}
private void assignOutboundPropertyMap(Properties tempProp, int status, String responseString,
Properties responseHeaderProp) {
Map<String, Object> map = new HashMap<String, Object>();
tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp);
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString);
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status);
}
private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) {
Properties headerProp = new Properties();
if (responseHeaders == null || responseHeaders.length == 0) {
@@ -699,57 +786,99 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
* @throws Exception
*/
@SuppressWarnings("unchecked")
private String assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName,
String encode, String messageType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception {
if (!MessageType.JSON.equals(messageType) || StringUtils.isBlank(headerGroupName)
private Object assignResponseHeaders(HttpUriRequestBase method, byte[] responseMessage, String headerGroupName,
String encode, String responseContentType, String relayHeaderKeys, int status, Properties responseHeaderProp) throws Exception {
String messageType = MessageType.JSON;
if (StringUtils.startsWith(responseContentType, ContentType.APPLICATION_XML.getMimeType())) {
logger.debug("Content-Type xml received.");
messageType = MessageType.XML;
} else if (StringUtils.startsWith(responseContentType, ContentType.APPLICATION_JSON.getMimeType())) {
logger.debug("Content-Type json received.");
messageType = MessageType.JSON;
} else {
messageType = "ETC";
}
// charset 추출, 없으면 파라미터 encdoe 이용
ContentType oContentType = ContentType.parse(responseContentType);
Charset contentTypeCharset = oContentType.getCharset();
String charset = encode;
if (contentTypeCharset != null)
charset = contentTypeCharset.displayName();
if (!StringUtils.equalsAny(messageType, MessageType.JSON, MessageType.XML) || StringUtils.isBlank(headerGroupName)
|| StringUtils.isBlank(relayHeaderKeys)) {
return new String(responseMessage, encode);
return new String(responseMessage, charset);
}
JSONObject headerJson = new JSONObject();
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
String key = (String)e.nextElement();
headerJson.put(StringUtils.lowerCase(key), responseHeaderProp.getProperty(key));
}
} else {
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
for (String key : relayKeyArr) {
String value = responseHeaderProp.getProperty(key);
if (StringUtils.isNotBlank(value)) {
headerJson.put(StringUtils.lowerCase(key), value);
if(StringUtils.equalsAny(messageType, MessageType.JSON)) {
JSONObject headerJson = new JSONObject();
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
String key = (String)e.nextElement();
headerJson.put(StringUtils.lowerCase(key), responseHeaderProp.getProperty(key));
}
} else {
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
for (String key : relayKeyArr) {
String value = responseHeaderProp.getProperty(key);
if (StringUtils.isNotBlank(value)) {
headerJson.put(StringUtils.lowerCase(key), value);
}
}
}
}
headerJson.put(HTTP_STATUS, String.valueOf(status));
if (headerJson.size() <= 0) {
return new String(responseMessage, encode);
}
JSONObject message = null;
if (responseMessage == null || responseMessage.length == 0) {
message = new JSONObject();
} else {
message = parseJson(new String(responseMessage, encode));
if (message == null) {
message = new JSONObject();
message.put("Malformed_Response_Message", new String(responseMessage, encode));
headerJson.put(HTTP_STATUS, String.valueOf(status));
if (headerJson.size() <= 0) {
return new String(responseMessage, charset);
}
JSONObject message = null;
if (responseMessage == null || responseMessage.length == 0) {
message = new JSONObject();
} else {
message = parseJson(new String(responseMessage, charset));
if (message == null) {
message = new JSONObject();
message.put("Malformed_Response_Message", new String(responseMessage, charset));
}
}
if (StringUtils.isNotBlank(headerGroupName) && message != null)
message.put(headerGroupName, headerJson);
return message;
} else if (StringUtils.equalsAny(messageType, MessageType.XML)) {
Document doc = XMLUtils.convertXmlDocument(new String(responseMessage, charset));
Element root = doc.getRootElement();
Element headerGroupEle = root.addElement(headerGroupName);
if (StringUtils.equalsIgnoreCase(relayHeaderKeys, "ALL")) {
for (Enumeration<Object> e = responseHeaderProp.keys(); e.hasMoreElements(); ) {
String key = (String)e.nextElement();
headerGroupEle.addElement(StringUtils.lowerCase(key)).setText(responseHeaderProp.getProperty(key));
}
} else {
String[] relayKeyArr = org.springframework.util.StringUtils.tokenizeToStringArray(relayHeaderKeys, ",");
for (String key : relayKeyArr) {
String value = responseHeaderProp.getProperty(key);
if (StringUtils.isNotBlank(value)) {
headerGroupEle.addElement(StringUtils.lowerCase(key)).setText(value);
}
}
}
return doc.asXML();
} else {
return new String(responseMessage, charset);
}
if (StringUtils.isNotBlank(headerGroupName) && message != null)
message.put(headerGroupName, headerJson);
return message.toJSONString();
}
private void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
protected void setAuthHeaders(HttpUriRequestBase method, String authorization) throws Exception {
method.setHeader("Authorization",
String.format("%s", authorization));
}
private void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
protected void setAuthHeaders(HttpUriRequestBase method, OAuth2AccessTokenVO accessToken) throws Exception {
method.setHeader("Authorization",
String.format("%s %s", AccessTokenVO.BEARER_TYPE, accessToken.getAccessToken()));
}
@@ -816,7 +945,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
return false;
}
private void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
protected void assignRequestHeaders(HttpUriRequestBase method, Object httpHeader, Properties prop) {
if (httpHeader != null) {
if (httpHeader instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) httpHeader;
@@ -870,7 +999,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
}
private void assignFilterHeaders(HttpUriRequestBase method, Properties tempProp) {
protected void assignFilterHeaders(HttpUriRequestBase method, Properties tempProp) {
try {
Properties filterHeaders = (Properties)tempProp.get("FILTERHEADER");
if (filterHeaders != null){
@@ -963,45 +1092,64 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
return map;
}
@SuppressWarnings("deprecation")
private void assignPostBody(Object eaiBody, String contentType, String charset, HttpUriRequestBase method, String adapterName, String niceAuth, String inboundToken, Date currentDate) throws Exception {
protected String assignPostBody(Object eaiBody, String mimeType, String charset, HttpUriRequestBase method,
boolean chunked) throws Exception {
if (eaiBody == null) {
return;
return "";
}
if (eaiBody instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) eaiBody;
if( jsonObject.containsKey("KJB_ROOTLESS_ARRAY") ) {
eaiBody = jsonObject.get("KJB_ROOTLESS_ARRAY");
if( jsonObject.containsKey("DJB_ROOTLESS_ARRAY") ) {
eaiBody = jsonObject.get("DJB_ROOTLESS_ARRAY");
}
}
if (StringUtils.contains(contentType, "application/x-www-form-urlencoded")) {
if (StringUtils.contains(mimeType, "application/x-www-form-urlencoded")) {
List<NameValuePair> params = new ArrayList<>();
if (eaiBody instanceof JSONObject) {
List<NameValuePair> params = new ArrayList<>();
JSONObject jsonObject = (JSONObject) eaiBody;
for (Object key : jsonObject.keySet()) {
if( jsonObject.get(key) instanceof String ) {
params.add(new BasicNameValuePair((String) key, (String) jsonObject.get(key)));
}
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8);
method.setEntity(entity);
} else if (eaiBody instanceof Document) {
Document bodyDoc = (Document) eaiBody;
Element root = bodyDoc.getRootElement();
List<Element> childrenEle = XMLUtils.getChilds(root);
for (Element child : childrenEle) {
params.add(new BasicNameValuePair(child.getName(), child.getText()));
}
}
ContentType contentTypeObj = ContentType.create(mimeType, charset);
CustomUrlEncodedFormEntity entity = new CustomUrlEncodedFormEntity(params, contentTypeObj.getCharset(), chunked);
method.setEntity(entity);
return params.toString();
} else {
ContentType contentTypeObj = ContentType.create(contentType, charset);
ContentType contentTypeObj = ContentType.create(mimeType, charset);
String eaiBodyMessage = doOutboundPreFilter(eaiBody, adapterName, niceAuth, inboundToken, currentDate); // jwhong
StringEntity entity = new StringEntity(eaiBodyMessage, contentTypeObj);
// 요청 본문을 StringEntity 객체로 생성
//StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj);
String body = "";
if (eaiBody instanceof Document) {
body = ((Document)eaiBody).asXML();
} else if(eaiBody instanceof JSONObject) {
body = ((JSONObject)eaiBody).toJSONString();
} else {
body = eaiBody.toString();
}
StringEntity entity = new StringEntity(body, contentTypeObj, chunked);
// 요청에 본문 추가
method.setEntity(entity);
return body;
// ContentType contentTypeObj = ContentType.create(contentType, charset);
//
// String eaiBodyMessage = doOutboundPreFilter(eaiBody, adapterName, niceAuth, inboundToken, currentDate); // jwhong
// StringEntity entity = new StringEntity(eaiBodyMessage, contentTypeObj);
// // 요청 본문을 StringEntity 객체로 생성
// //StringEntity entity = new StringEntity(eaiBody.toString(), contentTypeObj);
//
// // 요청에 본문 추가
// method.setEntity(entity);
}
}
@@ -1531,23 +1679,26 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
uriVariables.add(fieldName);
}
if (MessageType.JSON.equals(messageType)) {
JSONObject jsonObject;
if (sendData instanceof JSONObject) {
jsonObject = (JSONObject) sendData;
} else {
jsonObject = (JSONObject) JSONValue.parse((String) sendData);
}
for (Object tempObject : uriVariables) {
String urlVaribleId = (String) tempObject;
String uriVariable = null;
if( jsonObject != null ) {
uriVariable = (String) jsonObject.get(urlVaribleId);
jsonObject.remove(urlVaribleId);
}
JsonNode jsonNode = JsonPathUtil.toTree(sendData);
// JSONObject jsonObject;
// if (sendData instanceof JSONObject) {
// jsonObject = (JSONObject) sendData;
//
// } else {
// jsonObject = (JSONObject) JSONValue.parse((String) sendData);
// }
for (String urlVaribleId : uriVariables) {
String uriVariable = JsonPathUtil.getAt(jsonNode, urlVaribleId, "\\.");
// String uriVariable = null;
// if( jsonObject != null ) {
// uriVariable = (String) jsonObject.get(urlVaribleId);
// jsonObject.remove(urlVaribleId);
// }
if (StringUtils.isBlank(uriVariable) && inboundPathVariables.containsKey(urlVaribleId)) {
uriVariable = inboundPathVariables.get(urlVaribleId);
}
urlVariableList.add(uriVariable);
}
@@ -0,0 +1,142 @@
package com.eactive.eai.adapter.http.client.impl;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Node;
import org.json.simple.JSONObject;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.impl.filter.HttpClient5AdapterFilterFactory;
import com.eactive.eai.adapter.http.client.impl.filter.HttpClientAdapterFilter;
/**
* 1. 기능 : HttpClient5AdapterServiceRest 호출 전후 Filter 적용할 수 있는 기능을 제공한다.<br>
* 2. 처리 개요 : <br>
* 3. 주의사항 <br>
*
* @author :
* @version : v 1.0.0
* @see : HttpClientAdapterServiceFactory.java,
* HttpClientAdapterServiceSupport.java, HttpClient5AdapterServiceRest.java
* @since :
*
*/
public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterServiceRest
implements HttpClientAdapterServiceKey {
// 요청전 수행할 필터(쉼표(,)로 구분)
static final String PRE_FILTERS = "PRE_FILTERS";
// 요청후 수행할 필터(쉼표(,)로 구분)
static final String POST_FILTERS = "POST_FILTERS";
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
/**
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
* 3. 주의사항 <br>
*
* @param prop Http Adapter 속성 정보
* @return 반환 된 Object
* @exception Exception 수동 시스템 간 통신 중 발생
*/
public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
String adptGrpName = tempProp.getProperty(ADAPTER_GROUP_NAME);
String adptName = tempProp.getProperty(ADAPTER_NAME);
data = doPreFilters(adptGrpName, adptName, prop, data, tempProp);
Object adapterResponse = super.execute(prop, data, tempProp);
adapterResponse = doPostFilters(adptGrpName, adptName, prop, adapterResponse, tempProp);
if (adapterResponse instanceof JSONObject)
adapterResponse = ((JSONObject) adapterResponse).toJSONString();
if (adapterResponse instanceof Node)
adapterResponse = ((Node) adapterResponse).asXML();
return adapterResponse;
}
protected Object doPreFilters(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
// boolean isSetCommonFilterInAdapterProp = false;
// 1. adapter에 설정된 필터 수행
String preFiltersStr = prop.getProperty(PRE_FILTERS);
if (StringUtils.isNotEmpty(preFiltersStr)) {
String[] preFilters = StringUtils.split(preFiltersStr, ",");
for (String filterName : preFilters) {
if (StringUtils.isBlank(filterName)) {
continue;
}
logger.debug("HttpClient5AdapterServiceRestAddFilter] Processing Start [" + filterName + "]");
HttpClientAdapterFilter adapterFilter = HttpClient5AdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
// if (adapterFilter instanceof IBKOutCommonFilter)
// isSetCommonFilterInAdapterProp = true;
message = adapterFilter.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
} else {
throw new Exception("Failed get Filter Class: " + filterName);
}
}
}
// // 2. IBKOutCommonFilter 필터 수행(adapter 필터에서 수행하지 않을 때만)
// if (!isSetCommonFilterInAdapterProp) {
// HttpClientAdapterFilter ibkFilter = HttpClient5AdapterFilterFactory
// .createFilter(IBKOutCommonFilter.class.getName());
// if (ibkFilter != null) {
// message = ibkFilter.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
// }
// } else {
// logger.debug("IBKOutCommonFilter isSetCommonFilterInAdapterProp 'POST_FILTERS'. already in adapter filters");
// }
return message;
}
protected Object doPostFilters(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
// boolean isSetCommonFilterInAdapterProp = false;
// 1. adapter에 설정된 필터 수행
String postFiltersStr = prop.getProperty(POST_FILTERS);
if (StringUtils.isNotEmpty(postFiltersStr)) {
String[] postFilters = StringUtils.split(postFiltersStr, ",");
for (String filterName : postFilters) {
if (StringUtils.isBlank(filterName)) {
continue;
}
HttpClientAdapterFilter adapterFilter = HttpClient5AdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
// if (adapterFilter instanceof IBKOutCommonFilter)
// isSetCommonFilterInAdapterProp = true;
message = adapterFilter.doPostFilter(adptGrpName, adptName, prop, message, tempProp);
} else {
throw new Exception("Failed get Filter Class: " + filterName);
}
}
}
// // 2. IBKOutCommonFilter 필터 수행
// if (!isSetCommonFilterInAdapterProp) {
// HttpClientAdapterFilter ibkFilter = HttpClient5AdapterFilterFactory
// .createFilter(IBKOutCommonFilter.class.getName());
// if (ibkFilter != null) {
// message = ibkFilter.doPostFilter(adptGrpName, adptName, prop, message, tempProp);
// }
// } else {
// logger.debug("IBKOutCommonFilter isSetCommonFilterInAdapterProp 'PRE_FILTERS'. already in adapter filters");
// }
return message;
}
}
@@ -0,0 +1,121 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class AllHeaderFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
public static final String HEADER_KEY_NAMES = "AllHeaderFilter.blackList";
String[] HttpHeaderRelayBlackList = {
"Content-Length",
"Transfer-Encoding",
"Host",
"Authorization",
"Accept",
"Host",
"Cookie",
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"TE",
"Trailer",
"Transfer-Encoding",
"Upgrade",
"Content-Type",
"Content-Encoding",
"Content-Language",
"Content-Location",
"Content-MD5",
"Expect",
};
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("doPreFilter Processing Start.");
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
String[] userSettingBlackList = propValue.split(",");
if( userSettingBlackList.length > 0 ) {
HttpHeaderRelayBlackList = Stream
.concat(Arrays.stream(HttpHeaderRelayBlackList), Arrays.stream(userSettingBlackList))
.toArray(String[]::new);
}
for(String keyName : inboundHeaderMap.keySet() ) {
if( StringUtils.equalsAnyIgnoreCase( keyName, HttpHeaderRelayBlackList ) ) {
logger.debug("Skip Processing Key ["+keyName+"], value ["+inboundHeaderMap.get(keyName)+"] in HttpHeaderRelayBlackList");
continue;
}
filterHeaders.put(keyName, inboundHeaderMap.get(keyName));
logger.debug("Processing Key ["+keyName+"], value ["+inboundHeaderMap.get(keyName)+"]");
}
logger.debug("doPreFilter Processing End.");
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,118 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class AsyncReponseFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String X_TRACE_ID = "partnerTraceId";
public static final String TRACE_ID = "traceId";
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
public static final String HEADER_KEY_NAMES = "AsyncReponseFilter";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("AsyncReponseFilter] PreFilter Processing Start!!");
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
String[] headerKeyNames = propValue.split(",");
headerKeyNames = Arrays.stream(headerKeyNames)
.map(String::trim)
.toArray(String[]::new);
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_TRACE_ID)) {
filterHeaders.put(X_TRACE_ID, inboundHeaderMap.get(X_TRACE_ID));
logger.debug("AsyncReponseFilter] Processing Key ["+X_TRACE_ID+"], value ["+inboundHeaderMap.get(X_TRACE_ID)+"]");
}
if (inboundHeaderMap.containsKey(TransactionContextKeys.X_LOAN_TOKEN)) {
filterHeaders.put(TransactionContextKeys.X_LOAN_TOKEN, inboundHeaderMap.get(TransactionContextKeys.X_LOAN_TOKEN));
logger.debug("AsyncReponseFilter] Processing Key ["+TransactionContextKeys.X_LOAN_TOKEN+"], value ["+inboundHeaderMap.get(TransactionContextKeys.X_LOAN_TOKEN)+"]");
}
}
filterHeaders.setProperty(TRACE_ID, traceId);
logger.debug("AsyncReponseFilter] Processing Key ["+TRACE_ID+"], value ["+traceId+"]");
for (String keyName : inboundHeaderMap.keySet()) {
if (StringUtils.equalsAnyIgnoreCase(keyName, headerKeyNames)) {
String value = inboundHeaderMap.get(keyName);
filterHeaders.put(keyName, value);
logger.debug("AsyncReponseFilter] Processing Key [" + keyName + "], value [" + value + "]");
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,90 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.concurrent.ConcurrentHashMap;
import com.eactive.eai.common.util.Logger;
public class HttpClient5AdapterFilterFactory {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
static String customBasePackage = "com.eactive.eai.custom.adapter.http.client.impl.filter";
static String basePackage = "com.eactive.eai.adapter.http.client.impl.filter";
private static ConcurrentHashMap<String, HttpClientAdapterFilter> h = new ConcurrentHashMap<>();
private HttpClient5AdapterFilterFactory() {
throw new IllegalStateException("Utility class");
}
public static HttpClientAdapterFilter createFilter(String type) {
if (h.containsKey(type)) {
return h.get(type);
}
HttpClientAdapterFilter filter = null;
switch (HttpClient5AdapterFilterType.getValue(type)) {
case SIMPLEFRAMEWORK:
filter = new SimpleFrameworkFilter();
break;
case SIMPLEFRAMEWORKBODY:
filter = new SimpleframeworkBodyFilter();
break;
case JBOBP:
filter = new JBOBPFilter();
break;
case KFTCFACE:
filter = new KFTCFaceFilter();
break;
case KFTCP2P:
filter = new KFTCP2PFilter();
break;
case KAKAOBANK:
filter = new KAKAOBankFilter();
break;
case NAVERFIN:
filter = new NAVERFinFilter();
break;
case NICECREDIT:
filter = new NICECreditFilter();
break;
case TRACEBODY:
filter = new TraceBodyFilter();
break;
case TOSSBANK:
filter = new TOSSBankFilter();
break;
case ASYNCRESPONSE:
filter = new AsyncReponseFilter();
break;
case ALLHEADER:
filter = new AllHeaderFilter();
break;
default:
filter = classForName(type);
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
}
if (filter == null) {
filter = classForName(basePackage + "." + type);
}
break;
}
if (filter != null) {
h.put(type, filter);
return filter;
} else {
return null;
}
}
private static HttpClientAdapterFilter classForName(String type) {
try {
Class<?> cl = Class.forName(type);
return (HttpClientAdapterFilter) cl.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.error("Cannot create a HttpClientAdapterFilter. - {}", type);
return null;
}
}
}
@@ -0,0 +1,27 @@
package com.eactive.eai.adapter.http.client.impl.filter;
public enum HttpClient5AdapterFilterType {
SIMPLEFRAMEWORK,
SIMPLEFRAMEWORKBODY,
JBOBP,
KFTCFACE,
KFTCP2P,
KAKAOBANK,
NAVERFIN,
NICECREDIT,
TOSSBANK,
TRACEBODY,
ASYNCRESPONSE,
ALLHEADER,
UNKNOWN;
public static HttpClient5AdapterFilterType getValue(String type) {
try {
return valueOf(type.toUpperCase());
} catch (NullPointerException e) {
return UNKNOWN;
} catch (Exception e) {
return UNKNOWN;
}
}
}
@@ -0,0 +1,12 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
public interface HttpClientAdapterFilter {
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception;
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception;
}
@@ -0,0 +1,35 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import lombok.Getter;
public class HttpClientAdapterFilterException extends RuntimeException {
private static final long serialVersionUID = -1208983360831093751L;
@Getter
private final String code;
@Getter
private final Properties prop;
@Getter
private final Properties tempProp;
public HttpClientAdapterFilterException(String code, String msg, Properties prop, Properties tmepProp) {
super(msg);
this.code = code;
this.prop = prop;
this.tempProp = tmepProp;
}
public HttpClientAdapterFilterException(String code, String msg, Properties prop, Properties tempProp, Throwable cause) {
super(msg, cause);
this.code = code;
this.prop = prop;
this.tempProp = tempProp;
}
@Override
public String toString() {
return "HttpClientAdapterFilterException [code=" + code + ", prop=" + prop + ", tempProp=" + tempProp + ", parent=" + super.toString() + "]";
}
}
@@ -0,0 +1,106 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class JBOBPFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String X_OBP_PARTNERCODE = "x-obp-partnercode";
public static final String X_OBP_TXID = "x-obp-txid";
public static final String X_OBP_TRUST_SYSTEM = "x-obp-trust-system";
String systemTrustKey = "APIMGW-0001-QVBJTUdXLTAwMDE=";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("JBOBPFilter] PreFilter Processing Start!!");
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) systemTrustKey = propGroupVo.getProperty(X_OBP_TRUST_SYSTEM, systemTrustKey);
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
String obpPartnerCode = "000000-00";
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_OBP_PARTNERCODE)) {
filterHeaders.put(X_OBP_PARTNERCODE, inboundHeaderMap.get(X_OBP_PARTNERCODE));
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+inboundHeaderMap.get(X_OBP_PARTNERCODE)+"]");
} else {
filterHeaders.put(X_OBP_PARTNERCODE, obpPartnerCode);
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+obpPartnerCode+"]");
}
} else {
filterHeaders.put(X_OBP_PARTNERCODE, obpPartnerCode);
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_PARTNERCODE+"], value ["+obpPartnerCode+"]");
}
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
if (StringUtils.isNotBlank(uuid)) {
filterHeaders.setProperty(X_OBP_TXID, uuid); // JB OBP Framework용
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_TXID+"], value ["+uuid+"]");
}
if (StringUtils.isNotBlank(systemTrustKey)) {
filterHeaders.setProperty(X_OBP_TRUST_SYSTEM, systemTrustKey); // JB OBP Framework용
logger.debug("JBOBPFilter] Processing Key ["+X_OBP_TRUST_SYSTEM+"], value ["+systemTrustKey+"]");
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,135 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class KAKAOBankFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "kakaobank";
public static final String X_KKB_PARTNER_CODE = "X-KKB-PARTNER-CODE";
public static final String X_KKB_API_NAME = "X-KKB-API-NAME";
public static final String X_KKB_API_TX_ID = "X-KKB-API-TX-ID";
public static final String X_KKB_TX_TIME = "X-KKB-TX-TIME";
public static final String X_KKB_CMPR_MGMT_NO = "X-KKB-CMPR-MGMT-NO";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("KAKAOBankFilter] PreFilter Processing Start!!");
Object returnMessage = message;
String partnerCode = "KJB";
String apiName = "status_change";
String apiTxId = "0";
String txTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String cmprMgmtNo = "0";
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
partnerCode = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_PARTNER_CODE);
apiName = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_API_NAME);
apiTxId = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_API_TX_ID);
cmprMgmtNo = propGroupVo.getProperty(PROP_PREFIX + "." + X_KKB_CMPR_MGMT_NO);
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_KKB_PARTNER_CODE)) {
filterHeaders.put(X_KKB_PARTNER_CODE, inboundHeaderMap.get(X_KKB_PARTNER_CODE));
} else {
filterHeaders.put(X_KKB_PARTNER_CODE, partnerCode);
}
if (inboundHeaderMap.containsKey(X_KKB_API_NAME)) {
filterHeaders.put(X_KKB_API_NAME, inboundHeaderMap.get(X_KKB_API_NAME));
} else {
filterHeaders.put(X_KKB_API_NAME, apiName);
}
if (inboundHeaderMap.containsKey(X_KKB_API_TX_ID)) {
filterHeaders.put(X_KKB_API_TX_ID, inboundHeaderMap.get(X_KKB_API_TX_ID));
} else {
filterHeaders.put(X_KKB_API_TX_ID, apiTxId);
}
if (inboundHeaderMap.containsKey(X_KKB_TX_TIME)) {
filterHeaders.put(X_KKB_TX_TIME, inboundHeaderMap.get(X_KKB_TX_TIME));
} else {
filterHeaders.put(X_KKB_TX_TIME, txTime);
}
if (inboundHeaderMap.containsKey(X_KKB_CMPR_MGMT_NO)) {
filterHeaders.put(X_KKB_CMPR_MGMT_NO, inboundHeaderMap.get(X_KKB_CMPR_MGMT_NO));
} else {
filterHeaders.put(X_KKB_CMPR_MGMT_NO, cmprMgmtNo);
}
} else {
filterHeaders.put(X_KKB_PARTNER_CODE, partnerCode);
filterHeaders.put(X_KKB_API_NAME, apiName);
filterHeaders.put(X_KKB_API_TX_ID, apiTxId);
filterHeaders.put(X_KKB_TX_TIME, txTime);
filterHeaders.put(X_KKB_CMPR_MGMT_NO, cmprMgmtNo);
}
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_PARTNER_CODE+"], value ["+filterHeaders.getProperty(X_KKB_PARTNER_CODE)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_API_NAME+"], value ["+filterHeaders.getProperty(X_KKB_API_NAME)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_API_TX_ID+"], value ["+filterHeaders.getProperty(X_KKB_API_TX_ID)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_TX_TIME+"], value ["+filterHeaders.getProperty(X_KKB_TX_TIME)+"]");
logger.debug("KAKAOBankFilter] Processing Key ["+X_KKB_CMPR_MGMT_NO+"], value ["+filterHeaders.getProperty(X_KKB_CMPR_MGMT_NO)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return returnMessage;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,154 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.Logger;
public class KFTCFaceFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "kftcface";
public static final String H_CLIENT_ID = "Client-Id";
public static final String B_ORG_CODE = "org_code";
public static final String B_TRANSACTION_ID = "transaction_id";
public static final String B_REQUEST_DATETIME = "request_datetime";
public static String instId = null;
static {
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
instId = eaiServerManager.getInstId();
}
@SuppressWarnings({ "unchecked" })
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("KFTCFaceFilter] PreFilter Processing Start!!");
JSONObject returnMessage = null;
String clientId = "";
String orgCode = "034"; // 광주은행 행코드
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
if (StringUtils.isEmpty(clientId))
clientId = propGroupVo.getProperty(PROP_PREFIX + "." + H_CLIENT_ID);
orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + B_ORG_CODE);
}
String request_datetime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String reqDate = new SimpleDateFormat("yyMMdd").format(new Date());
String reqTime = new SimpleDateFormat("HHmmss").format(new Date());
// 기관코드(3) + 요청일자(6) -- 금결원 표준화 항목, 고정 9자리.
// 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 기관별 생성 거래고유번호(11자리)
// 기관코드(3) + 요청일자(6) + 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 20자리.
String transaction_id = orgCode + reqDate + reqTime + instId + RandomStringUtils.random(3, true, true).toUpperCase();
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(H_CLIENT_ID)) {
filterHeaders.put(H_CLIENT_ID, inboundHeaderMap.get(H_CLIENT_ID));
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+inboundHeaderMap.get(H_CLIENT_ID)+"]");
} else {
filterHeaders.put(H_CLIENT_ID, clientId);
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+clientId+"]");
}
} else {
filterHeaders.setProperty(H_CLIENT_ID, clientId);
logger.debug("KFTCFaceFilter] Processing Key ["+H_CLIENT_ID+"], value ["+clientId+"]");
}
} catch (Exception e) {
logger.error(e.getMessage());
}
AdapterManager adapterManager = AdapterManager.getInstance();
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
String encode = adptGrpVO.getMessageEncode();
if (message == null) {
returnMessage = new JSONObject();
} else {
if (message instanceof JSONObject) {
returnMessage = (JSONObject) message;
} else if (message instanceof String) {
returnMessage = parseJson((String) message);
} else if (message instanceof byte[]) {
returnMessage = parseJson(new String((byte[]) message, encode));
}
}
returnMessage.put(B_ORG_CODE, orgCode);
returnMessage.put(B_TRANSACTION_ID, transaction_id);
returnMessage.put(B_REQUEST_DATETIME, request_datetime);
return returnMessage.toJSONString();
}
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,122 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.RandomStringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.Logger;
public class KFTCP2PFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "kftcp2p";
public static final String H_ORG_CODE = "org_code";
public static final String H_TRX_NO = "api_trx_no";
public static final String H_TRX_DTM = "api_trx_dtm";
public static String instId = null;
static {
EAIServerManager eaiServerManager = EAIServerManager.getInstance();
instId = eaiServerManager.getInstId();
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("KFTCP2PFilter] PreFilter Processing Start!!");
String orgCode = "D210400012"; // 광주은행 개발 기관코드(운영: K210800020)
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + H_ORG_CODE);
}
String apiTrxDtm = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
String apiTrxTm = new SimpleDateFormat("HHmmss").format(new Date());
// 기관코드(10) + 인스턴스ID(2) + 일시(6) + RandomString(2) -- 총 20자리
String apiTrxNo = orgCode + instId + apiTrxTm + RandomStringUtils.random(2, true, true).toUpperCase();
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(H_TRX_NO)) {
filterHeaders.put(H_TRX_NO, inboundHeaderMap.get(H_TRX_NO));
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+inboundHeaderMap.get(H_TRX_NO)+"]");
} else {
filterHeaders.put(H_TRX_NO, apiTrxNo);
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+apiTrxNo+"]");
}
if (inboundHeaderMap.containsKey(H_TRX_DTM)) {
filterHeaders.put(H_TRX_DTM, inboundHeaderMap.get(H_TRX_DTM));
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+inboundHeaderMap.get(H_TRX_DTM)+"]");
} else {
filterHeaders.put(H_TRX_DTM, apiTrxDtm);
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+apiTrxDtm+"]");
}
} else {
filterHeaders.put(H_TRX_NO, apiTrxNo);
filterHeaders.put(H_TRX_DTM, apiTrxDtm);
}
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_NO+"], value ["+filterHeaders.getProperty(H_TRX_NO)+"]");
logger.debug("KFTCP2PFilter] Processing Key ["+H_TRX_DTM+"], value ["+filterHeaders.getProperty(H_TRX_DTM)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,107 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class NAVERFinFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "naverfin";
public static final String X_PARTNER_ID = "X-Partner-Id";
public static final String X_FINTECH_ID = "X-Fintech-Id";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("NAVERFinFilter] PreFilter Processing Start!!");
String partnerID = "r0jtqwC9gAW5";
String fintechId = "NF";
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
partnerID = propGroupVo.getProperty(PROP_PREFIX + "." + X_PARTNER_ID);
fintechId = propGroupVo.getProperty(PROP_PREFIX + "." + X_FINTECH_ID);
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_PARTNER_ID)) {
filterHeaders.put(X_PARTNER_ID, inboundHeaderMap.get(X_PARTNER_ID));
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+inboundHeaderMap.get(X_PARTNER_ID)+"]");
} else {
filterHeaders.put(X_PARTNER_ID, partnerID);
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+partnerID+"]");
}
if (inboundHeaderMap.containsKey(X_FINTECH_ID)) {
filterHeaders.put(X_FINTECH_ID, inboundHeaderMap.get(X_FINTECH_ID));
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+inboundHeaderMap.get(X_FINTECH_ID)+"]");
} else {
filterHeaders.put(X_FINTECH_ID, fintechId);
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+fintechId+"]");
}
} else {
filterHeaders.put(X_PARTNER_ID, partnerID);
filterHeaders.put(X_FINTECH_ID, fintechId);
}
logger.debug("NAVERFinFilter] Processing Key ["+X_PARTNER_ID+"], value ["+filterHeaders.getProperty(X_PARTNER_ID)+"]");
logger.debug("NAVERFinFilter] Processing Key ["+X_FINTECH_ID+"], value ["+filterHeaders.getProperty(X_FINTECH_ID)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,138 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map.Entry;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class NICECreditFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "nicecredit";
public static final String H_PRODUCTID = "ProductID";
public static final String B_REQ_DTM = "req_dtm";
public static final String B_GOODS_CLS = "goods_cls";
@SuppressWarnings({ "unchecked" })
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
JSONObject returnMessage = null;
String productId = "2303102120";
String reqDtm = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String goodsCls = "KJBAPI0001";
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
productId = propGroupVo.getProperty(PROP_PREFIX + "." + H_PRODUCTID);
goodsCls = propGroupVo.getProperty(PROP_PREFIX + "." + B_GOODS_CLS);
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (inboundHeaderMap != null && inboundHeaderMap.containsKey(H_PRODUCTID)) {
for (Entry<String, String> e : inboundHeaderMap.entrySet()) {
String key = (String) e.getKey();
String value = (String) e.getValue();
if (StringUtils.equalsIgnoreCase(key, H_PRODUCTID)) {
filterHeaders.setProperty(key, value);
}
if (logger.isDebugEnabled()) {
logger.debug("Request Header :" + key + "=[" + value + "]");
}
}
} else {
filterHeaders.setProperty(H_PRODUCTID, productId);
}
} catch (Exception e) {
logger.error(e.getMessage());
}
AdapterManager adapterManager = AdapterManager.getInstance();
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
String encode = adptGrpVO.getMessageEncode();
if (message == null) {
returnMessage = new JSONObject();
} else {
if (message instanceof JSONObject) {
returnMessage = (JSONObject) message;
} else if (message instanceof String) {
returnMessage = parseJson((String) message);
} else if (message instanceof byte[]) {
returnMessage = parseJson(new String((byte[]) message, encode));
}
}
returnMessage.put(B_REQ_DTM, reqDtm);
returnMessage.put(B_GOODS_CLS, goodsCls);
return returnMessage.toJSONString();
}
private JSONObject parseJson(String message) throws Exception {
if (message == null) {
return null;
}
return (JSONObject) JSONValue.parse(message);
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,85 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
/**
* HTTP 클라이언트 어댑터용 암복호화 필터 (송신 방향).
*
* doPreFilter : 외부 시스템으로 보내는 요청 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
* doPostFilter : 외부 시스템에서 받은 응답 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
*
* DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
*
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
* AAD 관련 프로퍼티:
* PROP_AAD_HEADER tempProp에서 AAD 값을 조회할 키 이름.
* 미설정 또는 해당 키 없으면 aad=null → IV를 AAD 대체값으로 사용.
*/
public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientAdapterFilter {
/**
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
* 서브클래스에서 오버라이드하여 tempProp으로부터 값을 추출할 수 있다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, Properties tempProp) {
return new HashMap<>();
}
/**
* CRYPTO_AAD_PROP 프로퍼티에 지정된 키로 tempProp에서 AAD 값을 조회한다.
* 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
*/
protected byte[] buildAad(Properties prop, Properties tempProp) {
String propKey = prop.getProperty(PROP_AAD_HEADER);
if (StringUtils.isBlank(propKey) || tempProp == null) {
return null;
}
String value = tempProp.getProperty(propKey);
return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
String body = toBodyString(message);
Map<String, String> runtimeCtx = buildRuntimeContext(prop, tempProp);
byte[] aad = buildAad(prop, tempProp);
if (SCOPE_FIELD.equals(scope)) {
return encryptField(body, moduleName, runtimeCtx, aad,
getProp(tempProp, PROP_ENC_FROM_PATH, PATH_ROOT),
getProp(tempProp, PROP_ENC_TO_PATH, PATH_ENCDATA));
}
return encryptBody(body, moduleName, runtimeCtx, aad);
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
String body = toBodyString(message);
Map<String, String> runtimeCtx = buildRuntimeContext(prop, tempProp);
byte[] aad = buildAad(prop, tempProp);
if (SCOPE_FIELD.equals(scope)) {
return decryptField(body, moduleName, runtimeCtx, aad,
getProp(tempProp, PROP_DEC_FROM_PATH, PATH_ENCDATA),
getProp(tempProp, PROP_DEC_TO_PATH, PATH_ROOT));
}
return decryptBody(body, moduleName, runtimeCtx, aad);
}
}
@@ -0,0 +1,109 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.util.Logger;
public class SimpleFrameworkFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String CLIENTID = "clientId";
public static final String TRACEID = "traceId";
public static final String GUID = "guid";
public static final String RECEIVEDTIMESTAMP = "receivedTimestamp";
public static final String PARTNER_TRACE_ID = "partnerTraceId";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("SimpleFrameworkFilter] PreFilter Processing Start!!");
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
try {
/* 업체정보 */
String clientId = tempProp.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
if (StringUtils.isEmpty(clientId)) clientId = "";
filterHeaders.put(CLIENTID, clientId);
logger.debug("SimpleFrameworkFilter] Processing Key ["+CLIENTID+"], value ["+clientId+"]");
/* APIM 거래추적자 */
String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
filterHeaders.put(TRACEID, uuid);
logger.debug("SimpleFrameworkFilter] Processing Key ["+TRACEID+"], value ["+uuid+"]");
/* GUID */
String guid = tempProp.getProperty(TransactionContextKeys.GUID, "");
filterHeaders.put(GUID, guid);
logger.debug("SimpleFrameworkFilter] Processing Key ["+GUID+"], value ["+guid+"]");
/* 최초요청시간 */
String receivedTimestamp = tempProp.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME, "");
filterHeaders.put(RECEIVEDTIMESTAMP, receivedTimestamp);
logger.debug("SimpleFrameworkFilter] Processing Key ["+RECEIVEDTIMESTAMP+"], value ["+receivedTimestamp+"]");
String partnerTraceId = inboundHeaderMap.getOrDefault(PARTNER_TRACE_ID, "");
filterHeaders.put(PARTNER_TRACE_ID, partnerTraceId);
logger.debug("SimpleFrameworkFilter] Processing Key ["+PARTNER_TRACE_ID+"], value ["+partnerTraceId+"]");
String x_loan_token = inboundHeaderMap.getOrDefault(TransactionContextKeys.X_LOAN_TOKEN, "");
filterHeaders.put(TransactionContextKeys.X_LOAN_TOKEN, x_loan_token);
logger.debug("SimpleFrameworkFilter] Processing Key ["+TransactionContextKeys.X_LOAN_TOKEN+"], value ["+x_loan_token+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,28 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.common.TransactionContextKeys;
public class SimpleframeworkBodyFilter extends SimpleFrameworkFilter {
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
Object returnMessage = super.doPreFilter(adptGrpName, adptName, prop, message, tempProp);
JSONObject bizJsonStr = null;
if ( returnMessage instanceof String ) {
bizJsonStr = (JSONObject) JSONValue.parse( (String)returnMessage);
if( bizJsonStr != null ) {
bizJsonStr.put("GUID", tempProp.getOrDefault(TransactionContextKeys.GUID, "") );
}
}
return bizJsonStr != null ? bizJsonStr.toJSONString() : returnMessage;
}
}
@@ -0,0 +1,97 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
public class TOSSBankFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String KJB_HEADER = "KJB_Header";
public static final String PROP_PREFIX = "tossbank";
public static final String X_TOSSBANK_LOAN_TRACE_ID = "X-TOSSBANK-LOAN-TRACE-ID";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("TOSSBankFilter] PreFilter Processing Start!!");
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER);
if (propGroupVo != null) {
}
Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER");
if (filterHeaders == null) {
filterHeaders = new Properties();
tempProp.put("FILTERHEADER", filterHeaders);
}
try {
Map<String, String> inboundHeaderMap = getInboundHeaderProp(tempProp);
if (!inboundHeaderMap.isEmpty()) {
if (inboundHeaderMap.containsKey(X_TOSSBANK_LOAN_TRACE_ID)) {
filterHeaders.put(X_TOSSBANK_LOAN_TRACE_ID, inboundHeaderMap.get(X_TOSSBANK_LOAN_TRACE_ID));
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+inboundHeaderMap.get(X_TOSSBANK_LOAN_TRACE_ID)+"]");
} else {
filterHeaders.put(X_TOSSBANK_LOAN_TRACE_ID, traceId);
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+traceId+"]");
}
} else {
/* APIM 거래추적자 */
filterHeaders.setProperty(X_TOSSBANK_LOAN_TRACE_ID, traceId);
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+traceId+"]");
}
logger.debug("TOSSBankFilter] Processing Key ["+X_TOSSBANK_LOAN_TRACE_ID+"], value ["+filterHeaders.getProperty(X_TOSSBANK_LOAN_TRACE_ID)+"]");
} catch (Exception e) {
logger.error(e.getMessage());
}
return message;
}
public Map<String, String> getInboundHeaderProp(Properties prop) {
Properties header = new Properties();
Map<String, String> inboundHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
Object headerObj = prop.get(HttpAdapterServiceKey.INBOUND_HEADER);
if (headerObj instanceof Properties) { //tomcat
header = (Properties) headerObj;
for (String name : header.stringPropertyNames()) {
inboundHeaderMap.put(name, header.getProperty(name));
}
} else if (headerObj instanceof String) { //weblogic
String str = (String) headerObj;
str = str.substring(1, str.length() - 1);
// key=value 쌍 분리
String[] entries = str.split(",");
for (String entry : entries) {
String[] kv = entry.split("=", 2);
if (kv.length == 2) {
inboundHeaderMap.put(kv[0].trim(), kv[1].trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return inboundHeaderMap;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
return message;
}
}
@@ -0,0 +1,71 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.util.Logger;
import com.nimbusds.jose.shaded.gson.JsonObject;
public class TraceBodyFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String BIZ_HEADER = "header";
public static final String GUID = "guid";
public static final String TRACEID = "traceId";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
JSONObject bizJsonStr = null;
if (message instanceof String) {
bizJsonStr = (JSONObject) JSONValue.parse((String) message);
if ( bizJsonStr == null ) return message; //json string이 아님.
if (bizJsonStr.containsKey(BIZ_HEADER)) {
JSONObject bizHeader = (JSONObject) bizJsonStr.get(BIZ_HEADER);
putBizHeaderData(bizHeader, tempProp);
}else {
JSONObject bizHeader = new JSONObject();
bizJsonStr.put(BIZ_HEADER, bizHeader);
putBizHeaderData(bizHeader, tempProp);
}
}
return bizJsonStr != null ? bizJsonStr.toJSONString() : message;
}
static private void putBizHeaderData(JSONObject bizHeader, Properties tempProp) {
String traceId = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID, "");
String guid = tempProp.getProperty(TransactionContextKeys.GUID, "");
bizHeader.put(GUID, guid);
logger.debug("Processing Key [" + GUID + "], value [" + guid + "]");
bizHeader.put(TRACEID, traceId);
logger.debug("Processing Key [" + TRACEID + "], value [" + traceId + "]");
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
// TODO Auto-generated method stub
return message;
}
}
@@ -0,0 +1,897 @@
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;
}
}
@@ -0,0 +1,680 @@
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;
}
}
@@ -0,0 +1,888 @@
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,6 +6,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
@@ -13,10 +14,13 @@ import com.eactive.eai.adapter.RequestDispatcher;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactoryKjb;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterType;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.TxSiftContext;
import com.eactive.eai.common.util.UUIDGenerator;
public abstract class HttpAdapterServiceSupport implements HttpAdapterService, HttpAdapterServiceKey {
private static final String LOG_PREFIX = "HttpAdapterServiceSupport] ";
@@ -27,53 +31,67 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
public Object service(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String transactionId = request.getHeader(HttpClientAdapterServiceKey.TRANSACTION_ID);
if (StringUtils.isNotBlank(transactionId)) {
prop.put(HttpClientAdapterServiceKey.TRANSACTION_ID, transactionId);
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
boolean bMDCput = false;
if(uuid == null) {
uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
prop.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
}
bMDCput = TxSiftContext.begin(uuid);
try {
String clientId = request.getHeader(HEADER_NAME_CLIENT_ID);
if (StringUtils.isNotBlank(clientId)) {
prop.put(PROPERTIES_NAME_CLIENT_ID, clientId);
String transactionId = request.getHeader(HttpClientAdapterServiceKey.TRANSACTION_ID);
if (StringUtils.isNotBlank(transactionId)) {
prop.put(HttpClientAdapterServiceKey.TRANSACTION_ID, transactionId);
}
String instanceId = request.getHeader(HttpClientAdapterServiceKey.INSTANCE_ID);
if (StringUtils.isNotBlank(instanceId)) {
prop.put(HttpClientAdapterServiceKey.INSTANCE_ID, instanceId);
try {
String clientId = request.getHeader(HEADER_NAME_CLIENT_ID);
if (StringUtils.isNotBlank(clientId)) {
prop.put(PROPERTIES_NAME_CLIENT_ID, clientId);
}
String instanceId = request.getHeader(HttpClientAdapterServiceKey.INSTANCE_ID);
if (StringUtils.isNotBlank(instanceId)) {
prop.put(HttpClientAdapterServiceKey.INSTANCE_ID, instanceId);
}
} catch (Exception e) {
logger.warn(LOG_PREFIX + "Failed to read request headers: " + e.getMessage());
}
} catch (Exception e) {
}
Object obj = null;
try {
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
} catch (HttpStatusException e) { // inbound error
logger.warn(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
throw e;
} catch (JwtAuthException e) { // filter error
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
"RECEAIIRP202", e);
throw e;
} catch (Exception e) { // filter error
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
"RECEAIIRP201", e);
throw e;
}
if (obj == null) {
return null;
} else if (obj instanceof byte[]) {
return (byte[]) obj;
} else if (obj instanceof String) {
return (String) obj;
} else {
throw new Exception("RECEAIAHA001");
Object obj = null;
try {
message = doPreFilters(adptGrpName, adptName, message, prop, request, response);
obj = RequestDispatcher.getRequestDispatcher(adptGrpName).handle(adptName, message, prop);
obj = doPostFilters(adptGrpName, adptName, obj, prop, request, response);
} catch (HttpStatusException e) { // inbound error
logger.warn(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
throw e;
} catch (JwtAuthException e) { // filter error
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
"RECEAIIRP202", e);
throw e;
} catch (Exception e) { // filter error
logger.error(LOG_PREFIX + transactionId + "-" + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
UnkownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, message, prop, request, response,
"RECEAIIRP201", e);
throw e;
}
if (obj == null) {
return null;
} else if (obj instanceof byte[]) {
return (byte[]) obj;
} else if (obj instanceof String) {
return (String) obj;
} else {
throw new Exception("RECEAIAHA001");
}
} finally {
if(bMDCput)
TxSiftContext.end();
}
}
@@ -85,7 +103,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
if (!"HTC".equals(group.getType())) {
String allowIp = prop.getProperty(ALLOW_IP,"");
if (StringUtils.isNotBlank(allowIp)) {
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(HttpAdapterFilterType.ADAPTERALLOWIPLISTFILTER.toString());
if (adapterFilter != null) {
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
}
@@ -103,7 +121,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
continue;
}
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
message = adapterFilter.doPreFilter(adptGrpName, adptName, message, prop, request, response);
} else {
@@ -126,7 +144,7 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
continue;
}
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactoryKjb.createFilter(filterName.trim());
HttpAdapterFilter adapterFilter = HttpAdapterFilterFactory.createFilter(filterName.trim());
if (adapterFilter != null) {
resultMessage = adapterFilter.doPostFilter(adptGrpName, adptName, resultMessage, prop, request,
response);
@@ -1,19 +1,17 @@
package com.eactive.eai.adapter.http.dynamic.filter;
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 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
public class AdapterAllowIPListFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
@@ -30,9 +28,9 @@ public class AdapterAllowIPListFilter implements HttpAdapterFilter {
}
if (!IpUtil.isMatchIp(allowedIps, requestIp)) {
throw new HttpStatusException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
throw new FilterException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
}
} catch (HttpStatusException e) {
} catch (FilterException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
@@ -8,14 +8,12 @@ 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 {
@@ -28,9 +26,9 @@ public class AdapterBlockIpListFilter implements HttpAdapterFilter {
}
if (IpUtil.isMatchIp(blockIps, requestIp)) {
throw new HttpStatusException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
throw new FilterException("This IP was not allowed-" + requestIp, HttpStatus.FORBIDDEN.value());
}
} catch (HttpStatusException e) {
} catch (FilterException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
@@ -46,9 +44,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,17 +93,23 @@ public class ApiAuthFilter implements HttpAdapterFilter {
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
if (StringUtils.isBlank(apiId)) {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Can not find apiId(apiCode) info");
String apiId = prop.getProperty("FINAL_STD_MESSAGE_KEY");
String eaiSvcCd = prop.getProperty("API_SERVICE_CODE");
if(StringUtils.isEmpty(eaiSvcCd)) {
if(StringUtils.isEmpty(apiId))
apiId = assignApiId(adptGrpName, adptName, message, prop, request);
if (StringUtils.isBlank(apiId)) {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Can not find apiId(apiCode) info");
}
/**
* TODO: 중복되는 표준메시지 조회로 리소드 낭비가 있을 수 있으므로 체크 자체를 추후 RequestProcessor혹은 RestProcessor로 이동 필요 해 보임
* ApiRequestProcessor을 만드는것도 괜찮을 수 있을 것 같으나 관리가 안될 것 같음
*/
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
eaiSvcCd = StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
}
/**
* TODO: 중복되는 표준메시지 조회로 리소드 낭비가 있을 수 있으므로 체크 자체를 추후 RequestProcessor혹은 RestProcessor로 이동 필요 해 보임
* ApiRequestProcessor을 만드는것도 괜찮을 수 있을 것 같으나 관리가 안될 것 같음
*/
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
String eaiSvcCd = StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
// if (StringUtils.isNotEmpty(eaiSvcCd) && !StringUtils.equals(eaiSvcCd, ))
// eaiSvcCd = prop.getProperty("API_SERVICE_CODE");
EAIMessage eaiMessage = EAIMessageManager.getInstance().getEAIMessage(eaiSvcCd);
@@ -0,0 +1,95 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.message.EAIMessageManager;
import com.eactive.eai.common.stdmessage.STDMessageManager;
import com.eactive.eai.common.stdmessage.STDMsgInfoAddOnVO;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.inbound.action.ActionFactory;
import com.eactive.eai.inbound.action.RequestAction;
import com.eactive.eai.inbound.processor.Processor;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.manager.StandardMessageManager;
import com.eactive.eai.message.service.InterfaceMapper;
/**
* 요청 url를 기반으로 interface id 찾기
* tx prop에 STD_MESSAGE_KEY, FINAL_STD_MESSAGE_KEY, INTERFACE_ID, EAI_MESSAGE 세팅
*/
public class ApiKeyExtractFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String STD_MESSAGE_KEY = "STD_MESSAGE_KEY";
public static final String FINAL_STD_MESSAGE_KEY = "FINAL_STD_MESSAGE_KEY";
public static final String API_SERVICE_CODE = "API_SERVICE_CODE";
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String actionName = prop.getProperty(Processor.REQUEST_ACTION);
RequestAction action = ActionFactory.createAction(actionName);
action.setAdapterInfo(adptGrpName, adptName, prop);
String[] keys = action.perform(message);
String requestPath = keys[0];
prop.setProperty(STD_MESSAGE_KEY, requestPath);// action class 통해서 생성
// PathVariable 지원 추가
String ruledPath = getMatchedKey(requestPath);
if(ruledPath == null)
throw new IllegalAccessException("path not found - " + requestPath);
prop.setProperty(FINAL_STD_MESSAGE_KEY, ruledPath);// StandardMessageUtil.getMatchedKey(), url
// pathvariable도 처리
String apiId = prop.getProperty(ApiKeyExtractFilter.FINAL_STD_MESSAGE_KEY);
if (StringUtils.isBlank(apiId)) {
throw new JwtAuthException(MessageUtil.ERROR_CODE_AUTH_FAIL, "Can not find apiId(apiCode) info");
}
String apiSvcCode = this.getEaiSvcCode(apiId);
prop.put(API_SERVICE_CODE, apiSvcCode);
return message;
}
private String getEaiSvcCode(String apiId) throws Exception {
StandardMessage standardMessage = STDMessageManager.getInstance().getSTDMessage(apiId);
return StandardMessageManager.getInstance().getMapper().getEaiSvcCode(standardMessage);
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
/**
* API 서비스에서 실제 요청된 path를 설정된 PathVariable 형태의 key를 찾는다
* ex) /api/bank/account/12345 --> /api/bank/account/{accountNo}
* @param key
* @param actionName
* @return
*/
public static String getMatchedKey(String key) {
STDMessageManager stdMessageManager = STDMessageManager.getInstance();
String[] keys = stdMessageManager.getAllSTDMessageKeys();
for (String keyStr : keys) {
if (StringUtils.equals(key, keyStr)) {
return keyStr;
}
}
return stdMessageManager.getMatchedPathVariable(key);
}
}
@@ -1,21 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
/**
* HTTP 어댑터 요청/응답 암복호화 필터 기반 클래스.
*
*/
public class BaseCryptoFilter extends CryptoFilter {
/**
* 빈 Runtime Context를 생성한다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
return new HashMap<>();
}
}
@@ -1,232 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.security.CryptoModuleService;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.JsonPathUtil;
/**
* HTTP 어댑터 요청/응답 암복호화 필터 기반 클래스.
*
* 서브클래스는 {@link #buildRuntimeContext}를 구현하여
* DYNAMIC 키 도출에 필요한 컨텍스트 맵을 제공한다.
* STATIC 키 모듈을 사용하는 경우 빈 Map을 반환하면 된다.
* (CryptoModuleManager가 KEY_SOURCE_TYPE=STATIC이면 runtimeContext를 무시하고 고정 키를 사용한다.)
*
* 어댑터 프로퍼티 키:
* CRYPTO_MODULE_NAME 필수. DB에 등록된 암호화 모듈명.
* CRYPTO_SCOPE BODY | FIELD (기본 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 요청 헤더명 (예: X-Api-Request-Id).
* 미설정 또는 헤더값 없으면 aad=null → IV를 AAD 대체값으로 사용.
*/
public abstract class CryptoFilter implements HttpAdapterFilter {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String PROP_MODULE_NAME = "CRYPTO_MODULE_NAME";
public static final String PROP_SCOPE = "CRYPTO_SCOPE";
public static final String PROP_DEC_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";
// ------------------------------------------------------------------
// 추상 메서드
// ------------------------------------------------------------------
/**
* DYNAMIC 키 도출에 필요한 runtimeContext 맵을 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
*/
protected abstract Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request);
// ------------------------------------------------------------------
// doPreFilter: 요청 복호화
// ------------------------------------------------------------------
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
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);
}
// ------------------------------------------------------------------
// doPostFilter: 응답 암호화
// ------------------------------------------------------------------
@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);
}
// ------------------------------------------------------------------
// 복호화 구현
// ------------------------------------------------------------------
private 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에 반영.
* toPath = "/" → 복호화된 텍스트로 body 전체 교체.
*/
private String decryptField(String body, String moduleName, Map<String, String> runtimeCtx, byte[] aad,
String fromPath, String toPath) throws Exception {
String encBase64 = JsonPathUtil.getValueAtPath(body, fromPath);
if (StringUtils.isBlank(encBase64)) {
logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
return body;
}
byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
if (PATH_ROOT.equals(toPath)) {
return plainText;
}
return JsonPathUtil.setValueAtPath(body, toPath, plainText, false);
}
// ------------------------------------------------------------------
// 암호화 구현
// ------------------------------------------------------------------
private String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx, 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)에 반영.
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑.
*/
private String encryptField(String body, String moduleName, Map<String, String> runtimeCtx, byte[] aad,
String fromPath, String toPath) throws Exception {
String plainText;
if (PATH_ROOT.equals(fromPath)) {
plainText = body;
} else {
plainText = JsonPathUtil.getValueAtPath(body, fromPath);
if (StringUtils.isBlank(plainText)) {
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
return body;
}
}
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
plainText.getBytes(StandardCharsets.UTF_8));
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
if (PATH_ROOT.equals(fromPath)) {
String fieldName = toPath.replaceFirst("^/", "");
return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
}
return JsonPathUtil.setValueAtPath(body, toPath, encBase64, false);
}
// ------------------------------------------------------------------
// 유틸
// ------------------------------------------------------------------
/**
* 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
* <ul>
* <li>{@code String} — 그대로 반환</li>
* <li>{@code byte[]} — UTF-8 디코딩</li>
* <li>{@code JSONObject} / 기타 — {@code toString()} 호출
* (json-simple JSONObject는 toString()이 toJSONString()을 위임함)</li>
* </ul>
*/
protected String toBodyString(Object message) {
if (message instanceof String) {
return (String) message;
}
if (message instanceof byte[]) {
return new String((byte[]) message, StandardCharsets.UTF_8);
}
return message.toString();
}
/**
* 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);
}
protected String required(Properties prop, String key) {
String v = prop.getProperty(key);
if (StringUtils.isBlank(v)) {
throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
}
return v;
}
private String getProp(Properties prop, String key, String defaultVal) {
String v = prop.getProperty(key);
return StringUtils.isBlank(v) ? defaultVal : v;
}
}
@@ -0,0 +1,18 @@
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);
}
}
@@ -0,0 +1,21 @@
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 KJB_ROOTLESS_ARRAY = "{ \"KJB_ROOTLESS_ARRAY\" : ";
static final String DJB_ROOTLESS_ARRAY = "{ \"DJB_ROOTLESS_ARRAY\" : ";
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
@@ -63,8 +63,8 @@ public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter {
String chkBody = inboundBody;
String clientSecret = assignClientSecret(prop, request);
if (chkBody.startsWith(KJB_ROOTLESS_ARRAY)) {
chkBody = chkBody.substring(KJB_ROOTLESS_ARRAY.length());
if (chkBody.startsWith(DJB_ROOTLESS_ARRAY)) {
chkBody = chkBody.substring(DJB_ROOTLESS_ARRAY.length());
if (chkBody.endsWith("}")) {
chkBody = chkBody.substring(0, chkBody.length() - 1);
@@ -161,8 +161,8 @@ public class HmacSha256VerifyFilterKjb implements HttpAdapterFilter {
Object root = JSONValue.parse(outboundBody);
if (root instanceof JSONObject && ((JSONObject) root).containsKey("KJB_ROOTLESS_ARRAY")) {
JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("KJB_ROOTLESS_ARRAY");
if (root instanceof JSONObject && ((JSONObject) root).containsKey("DJB_ROOTLESS_ARRAY")) {
JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("DJB_ROOTLESS_ARRAY");
outboundBody = jsonArray.toJSONString();
}
@@ -6,13 +6,10 @@ import com.eactive.eai.common.util.Logger;
public class HttpAdapterFilterFactory {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
static String basePackage = "com.eactive.eai.adapter.http.dynamic.filter";
static String customBasePackage = "com.eactive.eai.custom.adapter.http.dynamic.filter";
static private ConcurrentHashMap<String, HttpAdapterFilter> h = new ConcurrentHashMap<String, HttpAdapterFilter>();
public HttpAdapterFilterFactory() {
super();
}
@SuppressWarnings("rawtypes")
public static HttpAdapterFilter createFilter(String type) {
if (h.containsKey(type)) {
return (HttpAdapterFilter) h.get(type);
@@ -32,12 +29,19 @@ public class HttpAdapterFilterFactory {
case ADAPTERALLOWIPLISTFILTER:
filter = new AdapterAllowIPListFilter();
break;
case HMAC_SHA256:
filter = new HmacSha256VerifyFilterKjb();
break;
case REFLECTALLHEADER:
filter = new ReflectAllHeaderFilter();
break;
default:
try {
Class cl = Class.forName(type);
filter = (HttpAdapterFilter) cl.newInstance();
} catch (Exception e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
filter = classForName(type);
if (filter == null) {
filter = classForName(basePackage + "." + type);
}
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
}
break;
}
@@ -49,4 +53,14 @@ public class HttpAdapterFilterFactory {
return null;
}
}
private static HttpAdapterFilter classForName(String type) {
try {
Class<?> cl = Class.forName(type);
return (HttpAdapterFilter) cl.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
return null;
}
}
}
@@ -1,71 +0,0 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.concurrent.ConcurrentHashMap;
import com.eactive.eai.common.util.Logger;
public class HttpAdapterFilterFactoryKjb extends HttpAdapterFilterFactory {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
static String basePackage = "com.eactive.eai.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 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);
}
if (filter == null) {
filter = classForName(customBasePackage + "." + type);
}
break;
}
if (filter != null) {
h.put(type, filter);
return filter;
} else {
return null;
}
}
private static HttpAdapterFilter classForName(String type) {
try {
Class<?> cl = Class.forName(type);
return (HttpAdapterFilter) cl.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
logger.error("Cannot create a HttpAdapterFilter. - {}", type);
return null;
}
}
}
@@ -0,0 +1,85 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
/**
* HTTP 서버 어댑터용 암복호화 필터 (수신 방향).
*
* doPreFilter : 수신 요청 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
* doPostFilter : 송신 응답 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
*
* DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
*
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
*/
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {
/**
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
* 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 수 있다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
return new HashMap<>();
}
/**
* CRYPTO_AAD_HEADER 프로퍼티에 지정된 헤더값을 UTF-8 바이트로 반환한다.
* 미설정 또는 헤더값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
*/
protected byte[] buildAad(Properties prop, HttpServletRequest request) {
String headerName = prop.getProperty(PROP_AAD_HEADER);
if (StringUtils.isBlank(headerName) || request == null) {
return null;
}
String headerValue = request.getHeader(headerName);
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
String body = toBodyString(message);
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
byte[] aad = buildAad(prop, request);
if (SCOPE_FIELD.equals(scope)) {
return decryptField(body, moduleName, runtimeCtx, aad,
getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
}
return decryptBody(body, moduleName, runtimeCtx, aad);
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String moduleName = required(prop, PROP_MODULE_NAME);
String scope = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
String body = toBodyString(resultMessage);
Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
byte[] aad = buildAad(prop, request);
if (SCOPE_FIELD.equals(scope)) {
return encryptField(body, moduleName, runtimeCtx, aad,
getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
}
return encryptBody(body, moduleName, runtimeCtx, aad);
}
}
@@ -0,0 +1,71 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonToSetStatusFilter implements HttpAdapterFilter {
private ObjectMapper mapper = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
JsonNode rootNode = parseJson(adptGrpName, message);
int httpStatus = 200;
if (rootNode.has("apiRsltCd") && !rootNode.get("apiRsltCd").asText().isEmpty()) {
try {
String statusParam = rootNode.get("apiRsltCd").asText();
httpStatus = Integer.parseInt(statusParam);
} catch (NumberFormatException e) {
httpStatus = 400; // 잘못된 입력일 경우 400 Bad Request 반환
}
// HTTP 상태 코드 설정
response.setStatus(httpStatus);
}
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
int httpStatus = 200;
if (rootNode.has("apiRsltCd") && !rootNode.get("apiRsltCd").asText().isEmpty()) {
try {
String statusParam = rootNode.get("apiRsltCd").asText();
httpStatus = Integer.parseInt(statusParam);
} catch (NumberFormatException e) {
httpStatus = 400; // 잘못된 입력일 경우 400 Bad Request 반환
}
// HTTP 상태 코드 설정
response.setStatus(httpStatus);
}
return resultMessage;
}
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException, JsonProcessingException {
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
String orgMessageString;
if(message instanceof byte[]) {
orgMessageString = new String((byte[])message, charset);
} else {
orgMessageString = (String) message;
}
return mapper.readTree(orgMessageString);
}
}
@@ -0,0 +1,110 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class JsonToStdConverterFilter implements HttpAdapterFilter {
private ObjectMapper mapper = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
JsonNode rootNode = parseJson(adptGrpName, message);
if (rootNode.has("header_part")) {
JsonNode headerPart = rootNode.path("header_part");
if (!headerPart.has("eaiIntfId") || headerPart.get("eaiIntfId").asText().isEmpty()) {
// /api/v1/public/getUserInfo.svc
String inboundUri = prop.getProperty(HttpAdapterServiceKey.INBOUND_EXTURI);
String apiPath = prop.getProperty(HttpAdapterServiceKey.API_PATH) + "/";
// getUserInfo.svc
String eaiIntfId = org.apache.commons.lang.StringUtils.removeStart(inboundUri, apiPath);
// header_part 아래 eaiIntfId가 추가 또는 수정
if (headerPart instanceof ObjectNode) {
((ObjectNode) headerPart).put("eaiIntfId", eaiIntfId);
// 요청 응답 구분코드 강제 설정 -> API명 찾아갈때 필요
if (!headerPart.has("reqRspnsDscd")) {
((ObjectNode) headerPart).put("reqRspnsDscd", "S");
}
// GUID 강제 설정
String orgnlTx = "";
if (headerPart.has("orgnlTx") && !headerPart.get("orgnlTx").asText().isEmpty()) {
orgnlTx = headerPart.get("orgnlTx").asText();
}
if (!headerPart.has("tlgrWrtnDt")) {
((ObjectNode) headerPart).put("tlgrWrtnDt", orgnlTx);
}
if (!headerPart.has("tlgrCrtnSysNm")) {
((ObjectNode) headerPart).put("tlgrCrtnSysNm", "");
}
if (!headerPart.has("tlgrSrlNo")) {
((ObjectNode) headerPart).put("tlgrSrlNo", "");
}
}
}
}
else {
return message;
}
return rootNode.toString();
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
if (rootNode.has("header_part")) {
JsonNode headerPart = rootNode.path("header_part");
if (!headerPart.has("eaiIntfId") || headerPart.get("eaiIntfId").asText().isEmpty()) {
// /api/v1/public/getUserInfo.svc
String inboundUri = prop.getProperty(HttpAdapterServiceKey.INBOUND_EXTURI);
String apiPath = prop.getProperty(HttpAdapterServiceKey.API_PATH) + "/";
// getUserInfo.svc
String eaiIntfId = org.apache.commons.lang.StringUtils.removeStart(inboundUri, apiPath);
// header_part 아래 eaiIntfId가 추가 또는 수정
if (headerPart instanceof ObjectNode) {
((ObjectNode) headerPart).put("eaiIntfId", eaiIntfId);
// 요청 응답 구분코드 강제 설정 -> API명 찾아갈때 필요
((ObjectNode) headerPart).put("reqRspnsDscd", "S");
}
}
}
else {
return resultMessage;
}
return rootNode.toString();
}
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException, JsonProcessingException {
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
String orgMessageString;
if(message instanceof byte[]) {
orgMessageString = new String((byte[])message, charset);
} else {
orgMessageString = (String) message;
}
return mapper.readTree(orgMessageString);
}
}
@@ -0,0 +1,68 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class KbankEaiJsonParseFilter implements HttpAdapterFilter {
private ObjectMapper mapper = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
JsonNode rootNode = parseJson(adptGrpName, message);
ObjectNode replacedJson = mapper.createObjectNode();
for(Iterator<String> it = rootNode.fieldNames(); it.hasNext();) {
String fieldName = it.next();
String value = rootNode.get(fieldName).asText();
JsonNode jsonNode = mapper.readTree(value);
replacedJson.set(fieldName, jsonNode);
}
return replacedJson.toString();
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
JsonNode rootNode = parseJson(adptGrpName, resultMessage);
ObjectNode replacedJson = mapper.createObjectNode();
for(Iterator<String> it = rootNode.fieldNames(); it.hasNext();) {
String fieldName = it.next();
JsonNode childNode = rootNode.get(fieldName);
String childString = mapper.writeValueAsString(childNode);
replacedJson.put(fieldName, childString);
}
return replacedJson.toString();
}
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException, JsonProcessingException {
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
String orgMessageString;
if(message instanceof byte[]) {
orgMessageString = new String((byte[])message, charset);
} else {
orgMessageString = (String) message;
}
return mapper.readTree(orgMessageString);
}
}
@@ -0,0 +1,215 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.text.SimpleDateFormat;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.Mac;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.common.property.PropManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.net.util.Base64;
import org.springframework.http.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.server.Keys;
import com.eactive.eai.common.util.Logger;
import com.fasterxml.jackson.databind.ObjectMapper;
public class KbankHmacSha256VerifyFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private static final String SERVICE_CODE_UNKNOWN = "00";
private static final int TIMESTAMP_EXPIRATION_SECONDS = 60*10*1000;
private static final String GROUP_NAME = "HMAC_INFO";
private ObjectMapper mapper = new ObjectMapper();
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
String hmacSignature = getHeaderCaseInsensitive(request, "hmac_signature");
String hmacTimestamp = getHeaderCaseInsensitive(request, "hmac_timestamp");
if(StringUtils.isBlank(hmacSignature) || StringUtils.isBlank(hmacTimestamp)) {
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Can not find hmac info");
}
long inputTime = new SimpleDateFormat("yyyyMMddHHmmss").parse(hmacTimestamp).getTime();
long currentTime = new Date().getTime();
String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
logger.debug("### inputTime :"+inputTime);
logger.debug("### currentTime :"+currentTime);
logger.debug("### timeStamp :"+timeStamp);
String systemMode = System.getProperty(Keys.EAI_SYSTEMMODE);
if(currentTime - inputTime > TIMESTAMP_EXPIRATION_SECONDS) {
logger.debug("### systemMode:" + systemMode);
logger.debug("### currentTime - inputTime:" + (currentTime - inputTime));
if("P".equals(systemMode)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN,"00"), "Signature verifying failed");
}
}
String sMessage = (String)message;
sMessage = sMessage.replace(" ","");
sMessage = sMessage.replace("\n","");
sMessage = sMessage.replace("\r","");
sMessage = sMessage + hmacTimestamp;
logger.info("### sMessage:"+sMessage);
String alCoId = "AL2021012901001";
JsonNode rootNode = parseJson(adptGrpName, message);
for(Iterator<String> it = rootNode.fieldNames(); it.hasNext();){
String fieldName = it.next();
if(fieldName.equals("ALCO_ID")) alCoId = rootNode.get(fieldName).asText();
}
logger.info("### alCoId:"+alCoId);
Properties hmacProp = PropManager.getInstance().getProperties(GROUP_NAME);
String hmacKey = hmacProp.getProperty(alCoId+"-hmack-key","");
String hmacKek = hmacProp.getProperty(alCoId+"-hmack-kek","");
String hmacIvForKey = hmacProp.getProperty(alCoId+"-hmack-iv-for-key","");
logger.debug("### hmacKey:"+hmacKey);
logger.debug("### hmacIvForKey:"+hmacIvForKey);
logger.debug("### hmacKek:"+hmacKek);
//kbank.payment.hmac-key
int hmacKeyLen = hmacKey.length();
byte[] hmacKeyData = new byte[hmacKeyLen/2];
for(int i=0;i<hmacKeyLen;i+=2){
hmacKeyData[i/2] = (byte)((Character.digit(hmacKey.charAt(i), 16) << 4) + Character.digit(hmacKey.charAt(i+1), 16));
}
//kbank.payment.hmac-kek
int hmacKekLen = hmacKek.length();
byte[] hmacKekData = new byte[hmacKekLen/2];
for(int i=0;i<hmacKekLen;i+=2){
hmacKekData[i/2] = (byte)((Character.digit(hmacKek.charAt(i),16) << 4) + Character.digit(hmacKek.charAt(i+1), 16));
}
//kbank.payment.hmac-iv-for-key
int hmacIvForKeyLen = hmacIvForKey.length();
byte[] hmacIvForKeyData = new byte[hmacIvForKeyLen/2];
for(int i=0;i<hmacIvForKeyLen;i+=2){
hmacIvForKeyData[i/2] = (byte)((Character.digit(hmacIvForKey.charAt(i),16) << 4) + Character.digit(hmacIvForKey.charAt(i+1), 16));
}
byte[] decrypedHmacKey = null;
try{
Cipher cp = Cipher.getInstance("AES/CBC/PKCS5Padding");
cp.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hmacIvForKeyData,"AES"), new IvParameterSpec(hmacIvForKeyData));
decrypedHmacKey = cp.doFinal(hmacKeyData);
} catch (Exception e) {
logger.debug(e.getMessage());
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed");
}
SecretKeySpec secretKey = new SecretKeySpec(decrypedHmacKey, "HmacSHA256");
String madeSignature = "";
logger.info("### secretKey:"+secretKey);
try{
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
madeSignature= Base64.encodeBase64String(mac.doFinal(sMessage.getBytes()));
} catch (Exception e) {
logger.debug(e.getMessage());
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed");
}
logger.info("### madeSignature:"+madeSignature);
if(!StringUtils.equals(hmacSignature, madeSignature)){
logger.info("### hmacSignature:"+hmacSignature);
logger.info("### madeSignature:"+madeSignature);
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed" );
}
} catch (JwtAuthException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
logger.debug(e.getMessage());
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Signature verifying failed(Unknown)" );
}
logger.info("### pass: "+"success");
return message;
}
/**
* 대소문자 구분 없이 HTTP 헤더 값을 가져오는 메소드
* @param request HTTP 요청 객체
* @param headerName 찾고자 하는 헤더 이름
* @return 헤더 값 또는 null
*/
public static String getHeaderCaseInsensitive(HttpServletRequest request, String headerName) {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String header = headerNames.nextElement();
if (header != null && header.equalsIgnoreCase(headerName)) {
return request.getHeader(header);
}
}
return null;
}
public JsonNode parseJson(String adptGrpName, Object message) throws UnsupportedEncodingException, JsonMappingException,JsonProcessingException {
String charset = StringUtils.defaultIfBlank(AdapterManager.getInstance().getAdapterGroupVO(adptGrpName).getMessageEncode(), "UTF-8");
String orgMessageString;
if(message instanceof byte[]) {
orgMessageString = new String((byte[])message, charset);
} else {
orgMessageString = (String) message;
}
return mapper.readTree(orgMessageString);
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
}
@@ -0,0 +1,205 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.vo.ClientVO;
import com.eactive.eai.common.util.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SnapHmacSha512VerifyFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private static final String SERVICE_CODE_UNKNOWN = "00";
private static final int X_TIMESTAMP_EXPIRATION_SECONDS = 60 * 5;
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// @formatter:off
/*
* HMAC_SHA512 (clientSecret, stringToSign)
*
* stringToSign = HTTPMethod:EndpointUrl:AccessToken
* :Lowercase(HexEncode(SHA-256(minify(RequestBody)))):TimeStamp
*/
try {
String xSignature = request.getHeader("X-SIGNATURE");
if (StringUtils.isBlank(xSignature)) {
throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Can not find X-SIGNATURE");
}
String clientSecret = assignClientSecret(prop, request);
StringBuilder sb = new StringBuilder();
sb.append(request.getMethod())
.append(":")
.append(getEndpointUrl(request))
.append(":")
.append(SnapSimpleOauth2Filter.extractToken(request))
.append(":")
.append(getRequestBody((String)message))
.append(":")
.append(getTimeStamp(request));
// @formatter:on
if (logger.isDebug()) {
logger.debug(sb.toString());
}
String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, clientSecret).hmacHex(sb.toString());
if (!StringUtils.equals(xSignature, hmac)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Signature verifying failed");
}
} catch (JwtAuthException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
logger.error(e.getMessage());
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Signature verifying failed(unkown)");
}
return message;
}
private String assignClientSecret(Properties prop, HttpServletRequest request) throws Exception {
String clientId = assignClientId(prop, request);
if (StringUtils.isBlank(clientId)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Can not find clientId info");
}
ClientVO client = (ClientVO) OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId);
if (client == null) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Can not find client info");
}
return client.getClientSecret();
}
private String assignClientId(Properties prop, HttpServletRequest request) {
String clientId = null;
try {
// 이전 filter에서 셋팅한 clientId 확보
clientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
// 이전 filter에서 셋팅한 값이 없다면 header 정보에서 확보
if (StringUtils.isBlank(clientId)) {
clientId = request.getHeader(HttpAdapterServiceSupport.HEADER_NAME_CLIENT_ID);
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return clientId;
}
private String getEndpointUrl(HttpServletRequest request) {
String servletPath = request.getServletPath();
String pathInfo = request.getPathInfo();
String queryString = request.getQueryString();
StringBuilder url = new StringBuilder();
url.append(servletPath);
if (pathInfo != null) {
url.append(pathInfo);
}
if (queryString != null) {
url.append("?").append(queryString);
}
return url.toString();
}
private String getRequestBody(String message) throws Exception {
if (StringUtils.isEmpty(message)) {
return "";
}
// Lowercase(HexEncode(SHA-256(minify(RequestBody))))
String body = minifyJson(message);
try {
body = DigestUtils.sha256Hex(body);
} catch (Exception e) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"SHA-256 digest error");
}
return body.toLowerCase();
}
private String minifyJson(String json) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readValue(json, JsonNode.class);
return jsonNode.toString();
} catch (Exception e) {
// json이 아닐경우
return json;
}
}
private String getTimeStamp(HttpServletRequest request) throws Exception {
String timeStamp = request.getHeader("X-TIMESTAMP");
if (StringUtils.isBlank(timeStamp)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Can not find X-TIMESTAMP");
}
// 2020-12-23T09:10:11+07:00(현재시간이랑 비교로직 추가-보안)
ZonedDateTime xTimeStamp = ZonedDateTime.parse(timeStamp, DateTimeFormatter.ISO_DATE_TIME);
xTimeStamp = xTimeStamp.plusSeconds(X_TIMESTAMP_EXPIRATION_SECONDS);
ZonedDateTime now = ZonedDateTime.now();
if (xTimeStamp.isBefore(now.withZoneSameInstant(xTimeStamp.getZone()))) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Too old X-TIMESTAMP");
}
return timeStamp;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
public static void main(String[] args) {
String str = "POST:/api/test/aaa/type02/555:"
+ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJzbmFwIl0sImV4cCI6MTY3NjUyNDI1MSwianRpIjoiNzI0MWMyZGUtZjJhNi00NTFmLWExMjQtZGM3NmI0ZGY1OThjIiwiY2xpZW50X2lkIjoic1NHSkR3bEpzZWw1V2VYb2R6ZDNKM01RMjBLWUNacEwifQ.Obe8t8IwuQ3P7i4yCSASYze-9DLmVwXe0g_gBYlwv_Jzc7V8-ooTwp6SVnaNsM6pp1GV-9lxMpAzQO_CRHQq_hdMeiPI_CXHh3gETvjQThn57QGEGdCNp_lUEVYtMPUxi5u3X6Of07o0_OB83WI7rA1zD0DaP8V67pgfq-jMRe_3IXztOYu_nwMgREbGvs9tqcsjxppRKkEHcK1S8Eq4HzaLwjwyHJAhIlTba27yd4T8ELC7IpFphJBfEPF_t0ZpYW15lOrg5pHPjy1xpTV0Kgipog5wycTZlFUeCWWjfxTrpVmt_1XlEOlZH9X6xAefWrH93_fpbt3OcxwP4u9KhA"
+ ":424058b889b9d860d13b79d90df52ddcbefba2fc9d27607e999c7c3c4d61d9c8:" + "2023-02-16T09:47:07+07:00";
String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_512,
"o3Hre3voTrHbLueDrHC0LYM5effHQZILI8t23htbD12TKD5QJUMONqFqv4494iQS46bzHS0xW1fBC5LHo3D0SzhZvQcPUaJZw0iQ79NnzYFUCTrEsoFJRL300dp3Ql4y")
.hmacHex(str);
// System.out.println(hmac);
}
}
@@ -0,0 +1,237 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthFilter;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.common.property.PropGroupVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.inbound.action.ActionFactory;
import com.eactive.eai.inbound.action.RequestAction;
import com.eactive.eai.inbound.processor.Processor;
import com.eactive.eai.message.StandardMessageUtil;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.util.IOUtils;
import com.nimbusds.jwt.SignedJWT;
public class SnapJwtAuthFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String PROP_GROUP_AUTH_SERVER = "OAuthServer";
public static final String PROP_KEYSTORE_PATH = "certification.publicKeyPath";
private static final String SERVICE_CODE_UNKNOWN = "00";
public static final String PAYLOAD_PARAM_NAME_SCOPE = "scope";
public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id";
private JWSVerifier jwsVerifier;
public SnapJwtAuthFilter() {
super();
PropGroupVO vo = PropManager.getInstance().getPropGroupVO(PROP_GROUP_AUTH_SERVER);
String publicKeyPath = "/certificate/elink-oauth-dev.pub";
if (vo != null) {
publicKeyPath = vo.getProperty(PROP_KEYSTORE_PATH);
} else {
logger.warn("The properties has not been set.[" + PROP_GROUP_AUTH_SERVER + "]");
}
Resource resource = new ClassPathResource(publicKeyPath);
String publicKey = null;
try {
publicKey = IOUtils.readInputStreamToString(resource.getInputStream());
publicKey = StringUtils.replace(publicKey, "-----BEGIN PUBLIC KEY-----", "");
publicKey = StringUtils.replace(publicKey, "-----END PUBLIC KEY-----", "");
publicKey = StringUtils.remove(publicKey, "\r");
publicKey = StringUtils.remove(publicKey, "\n");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
jwsVerifier = new RSASSAVerifier((RSAPublicKey) keyFactory.generatePublic(keySpec));
} catch (final IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
if (StringUtils.isBlank(apiId)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Can not find apiId(apiCode) info");
}
try {
String token = JwtAuthFilter.extractJWTToken(request);
SignedJWT signedJWT = SignedJWT.parse(token);
if (new Date().after(signedJWT.getJWTClaimsSet().getExpirationTime())) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
"Invalid Token - expired (B2B)");
}
if (!signedJWT.verify(jwsVerifier)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
"Invalid Token (B2B)");
}
OAuth2Manager manager = OAuth2Manager.getInstance();
HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
if (!verifyScope(scopeSet, signedJWT)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
// header 값으로 전송된 clientId와 토큰 소유 clientId check
if (!verifyClientId(prop, signedJWT)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"client_id not matched(header/token)");
}
} catch (JwtAuthException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
logger.error(e.getMessage());
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
"Invalid Token (B2B)");
}
return message;
}
private boolean verifyClientId(Properties prop, SignedJWT signedJWT) {
try {
// 이전 filter에서 셋팅한 clientId 확보
String headerClientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
String tokenClientId = (String) signedJWT.getJWTClaimsSet().getClaim(PAYLOAD_PARAM_NAME_CLIENT_ID);
// 이전 filter에서 셋팅한 값이 없다면 token 정보에서 확보
if (StringUtils.isBlank(headerClientId)) {
if (StringUtils.isNotBlank(tokenClientId)) {
prop.put(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, tokenClientId);
}
} else { // header로 전달된 clientId가 있을때만 비교
if (!headerClientId.equals(tokenClientId)) {
return false;
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return true;
}
private String assignApiId(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request) {
String apiId = null;
try {
String actionName = prop.getProperty(Processor.REQUEST_ACTION);
RequestAction action = ActionFactory.createAction(actionName);
action.setAdapterInfo(adptGrpName, adptName, prop);
String[] keys = action.perform(message);
apiId = keys[0];
// PathVariable 지원 추가
apiId = StandardMessageUtil.getMatchedKey(apiId, actionName);
} catch (Exception e) {
logger.error(e.getMessage());
}
// // header 에서 확보
// String apiId = request.getHeader(HEADER_NAME_API_CODE);
// if (StringUtils.isBlank(apiId)) {
// // url에서 확보
// // /ONLWeb/api/v1/public/getUserInfo.svc/
// apiId = request.getRequestURI();
// // /ONLWeb/api/v1/public/getUserInfo.svc
// apiId = StringUtils.removeEnd(apiId, "/");
// // getUserInfo.svc
// apiId = StringUtils.substringAfterLast(apiId, "/");
// }
return apiId;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
private boolean verifyScope(HashSet<String> scopeSet, SignedJWT signedJWT) throws ParseException {
if (scopeSet == null || scopeSet.isEmpty()) {
return true; // If no scope is specified in the api, all are allowed
}
String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE);
if (scopeArr == null || scopeArr.length == 0) {
return false;
}
for (String scope : scopeArr) {
if (scopeSet.contains(scope)) {
return true;
}
}
return false;
}
public static String extractJWTToken(HttpServletRequest request) throws JwtAuthException {
String authorization = request.getHeader("Authorization");
if (StringUtils.isBlank(authorization)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"No have header info: Authorization");
}
String[] components = authorization.split("\\s");
if (components.length != 2) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Malformat [Authorization] content");
}
if (!StringUtils.equalsIgnoreCase(components[0], "Bearer")) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"[Bearer] is needed");
}
return components[1].trim();
}
}
@@ -0,0 +1,205 @@
package com.eactive.eai.adapter.http.dynamic.filter.custom;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.util.BeanUtils;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.inbound.action.ActionFactory;
import com.eactive.eai.inbound.action.RequestAction;
import com.eactive.eai.inbound.processor.Processor;
import com.eactive.eai.message.StandardMessageUtil;
public class SnapSimpleOauth2Filter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private static final String SERVICE_CODE_UNKNOWN = "00";
private TokenStore tokenStore;
public SnapSimpleOauth2Filter() {
super();
tokenStore = BeanUtils.getBean("tokenStore", TokenStore.class);
}
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
if (StringUtils.isBlank(apiId)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Can not find apiId(apiCode) info");
}
String tokenStr = extractToken(request);
OAuth2AccessToken token = tokenStore.readAccessToken(tokenStr);
if (token == null) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "03"),
"Token Not Found (B2B)");
}
if (token.isExpired()) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
"Invalid Token - expired (B2B)");
}
OAuth2Authentication authentication = tokenStore.readAuthentication(token);
if (!authentication.isAuthenticated()) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
"Invalid Token (B2B)");
}
// check scope
OAuth2Manager manager = OAuth2Manager.getInstance();
HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
if (!verifyScope(scopeSet, authentication)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
// header 값으로 전송된 clientId와 토큰 소유 clientId check
if (!verifyClientId(prop, authentication)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"client_id not matched(header/token)");
}
} catch (JwtAuthException e) {
logger.debug(e.getMessage());
throw e;
} catch (Exception e) {
logger.error(e.getMessage());
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
"Invalid Token (B2B)");
}
return message;
}
private boolean verifyScope(HashSet<String> scopeSet, OAuth2Authentication auth) {
if (scopeSet == null || scopeSet.isEmpty()) {
return true; // If no scope is specified in the api, all are allowed
}
Set<String> scopesInToken = auth.getOAuth2Request().getScope();
if (scopesInToken == null || scopesInToken.isEmpty()) {
return false;
}
for (String scope : scopesInToken) {
if (scopeSet.contains(scope)) {
return true;
}
}
return false;
}
private boolean verifyClientId(Properties prop, Authentication authentication) {
try {
// 이전 filter에서 셋팅한 clientId 확보
String headerClientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
String tokenClientId = ((OAuth2Authentication) authentication).getOAuth2Request().getClientId();
// 이전 filter에서 셋팅한 값이 없다면 token 정보에서 확보
if (StringUtils.isBlank(headerClientId)) {
if (StringUtils.isNotBlank(tokenClientId)) {
prop.put(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, tokenClientId);
}
} else { // header로 전달된 clientId가 있을때만 비교
if (!headerClientId.equals(tokenClientId)) {
return false;
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
return true;
}
private String assignApiId(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request) {
String apiId = null;
try {
String actionName = prop.getProperty(Processor.REQUEST_ACTION);
RequestAction action = ActionFactory.createAction(actionName);
action.setAdapterInfo(adptGrpName, adptName, prop);
String[] keys = action.perform(message);
apiId = keys[0];
// PathVariable 지원 추가
apiId = StandardMessageUtil.getMatchedKey(apiId, actionName);
} catch (Exception e) {
logger.error(e.getMessage());
}
// // header 에서 확보
// String apiId = request.getHeader(HEADER_NAME_API_CODE);
// if (StringUtils.isBlank(apiId)) {
// // url에서 확보
// // /ONLWeb/api/v1/public/getUserInfo.svc/
// apiId = request.getRequestURI();
// // /ONLWeb/api/v1/public/getUserInfo.svc
// apiId = StringUtils.removeEnd(apiId, "/");
// // getUserInfo.svc
// apiId = StringUtils.substringAfterLast(apiId, "/");
// }
return apiId;
}
public static String extractToken(HttpServletRequest request) throws JwtAuthException {
String authorization = request.getHeader("Authorization");
if (StringUtils.isBlank(authorization)) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"No have header info: Authorization");
}
String[] components = authorization.split("\\s");
if (components.length != 2) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"Malformat [Authorization] content");
}
if (!StringUtils.equalsIgnoreCase(components[0], "Bearer")) {
throw new JwtAuthException(
String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
"[Bearer] is needed");
}
return components[1].trim();
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return resultMessage;
}
}
@@ -21,16 +21,19 @@ import com.eactive.eai.inbound.processor.Processor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.core5.http.ContentType;
import org.apache.mina.common.ByteBuffer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.springframework.jms.support.converter.MessageType;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
@@ -194,7 +197,7 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
Object root = JSONValue.parse(body);
if( root instanceof JSONArray ) {
JSONObject wrappedObject = new JSONObject();
wrappedObject.put("KJB_ROOTLESS_ARRAY", root);
wrappedObject.put("DJB_ROOTLESS_ARRAY", root);
requestBytes = wrappedObject.toJSONString().getBytes(encode);
}
@@ -212,6 +215,7 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
stopWatch.stop();
byte[] responseBytes = null;
String responseData = "";
if (RESPONSE_TYPE_ASYNC.equals(responseType)){
if (stopWatch.getTime() > slowTranTime){
@@ -222,17 +226,22 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
if ( result == null) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
responseBytes = responseData.getBytes();
} else if (result instanceof byte[]) {
responseData = new String((byte[]) result, encode);
responseBytes = (byte[]) result;
} else if (result instanceof String) {
responseData = (String) result;
if ( StringUtils.isBlank( responseData ) ) {
responseBytes = ((String)result).getBytes();
if ( StringUtils.isBlank((String)result ) ) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
responseBytes = responseData.getBytes();
}
}
response.setCharacterEncoding(encode);
response.getWriter().print(responseData);
// response.getWriter().print(responseData);
response.getOutputStream().write(responseBytes);
if (traceLevel >= 3){
HttpMemoryLogger.txlog(adptGrpName+adptName, "SEND "+"["+responseData+"]"+
((responseData == null)?"":CommonLib.getDumpMessage(responseData))
@@ -240,45 +249,58 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
}
} else {
if(requestBytes != null) {
if (result instanceof byte[]) {
if ( result == null) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
responseBytes = responseData.getBytes();
} else if (result instanceof byte[]) {
responseData = new String((byte[]) result, encode);
responseBytes = (byte[]) result;
} else if (result instanceof String) {
responseData = (String) result;
responseBytes = ((String)result).getBytes();
if ( StringUtils.isBlank((String)result ) ) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
responseBytes = responseData.getBytes();
}
}
logger.info("종료 >> encode = [" + encode + "]");
if (addData.length() > 0){
responseData = addData + responseData;
responseBytes = responseData.getBytes();
}
try {
Object root = JSONValue.parse(responseData);
if( root instanceof JSONObject ) {
JSONObject wrappedObject = (JSONObject) root;
responseData = wrappedObject.get("KJB_ROOTLESS_ARRAY").toString();
}
}catch (Exception e) {
// ignore json이 아니기에 해줄것이 없음.
}
// try {
// Object root = JSONValue.parse(responseData);
// if( root instanceof JSONObject ) {
// JSONObject wrappedObject = (JSONObject) root;
// responseData = wrappedObject.get("DJB_ROOTLESS_ARRAY").toString();
// }
//
// }catch (Exception e) {
// // ignore json이 아니기에 해줄것이 없음.
// }
// 300응답에서 받은 Header정보 사용하기 위해 읽어옴.
Map<String, Object> responsePropertyMap = (Map<String, Object>) prop.get(OUTBOUND_PROPERTY_MAP);
if(responsePropertyMap != null){
Properties responseHeaders = (Properties)responsePropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
Map<String, String> responseHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// Properties 내용을 모두 복사
for (String name : responseHeaders.stringPropertyNames()) {
responseHeaderMap.put(name, responseHeaders.getProperty(name));
}
String contentTytpe = responseHeaderMap.get("Content-Type");
response.setContentType(contentTytpe); // 300응답에서 받은 Content-Type을 그대로 적용.
}
// Map<String, Object> responsePropertyMap = (Map<String, Object>) prop.get(OUTBOUND_PROPERTY_MAP);
// if(responsePropertyMap != null){
// Properties responseHeaders = (Properties)responsePropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
// Map<String, String> responseHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// // Properties 내용을 모두 복사
// for (String name : responseHeaders.stringPropertyNames()) {
// responseHeaderMap.put(name, responseHeaders.getProperty(name));
// }
// String contentTytpe = responseHeaderMap.get("Content-Type");
// response.setContentType(contentTytpe); // 300응답에서 받은 Content-Type을 그대로 적용.
// }
// UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
response.setCharacterEncoding(encode);
response.getWriter().print(responseData);
// response.getWriter().print(responseData);
String resContentType = request.getContentType();
response.setContentType(resContentType);
response.getOutputStream().write(responseBytes);
if (logger.isDebug()){
logger.debug("HttpAdapter] SEND ("+adptGrpName+") = [" + responseData + "]");
logger.debug("HttpAdapter] SEND ("+adptGrpName+") = " +
@@ -0,0 +1,153 @@
package com.eactive.eai.adapter.http.filter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.security.CryptoModuleService;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode;
/**
* InCryptoFilter / OutCryptoFilter 공통 기반 클래스.
*
* BODY/FIELD 범위 암복호화 로직, 프로퍼티 상수, 유틸리티 메서드를 제공한다.
* HTTP 서버 어댑터용은 {@link InCryptoFilter}, 클라이언트 어댑터용은 {@link OutCryptoFilter} 참조.
*
* 프로퍼티 키:
* CRYPTO_MODULE_NAME 필수. DB에 등록된 암호화 모듈명.
* CRYPTO_SCOPE BODY | FIELD (기본값: FIELD)
* BODY : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
* FIELD : 특정 JSON 경로의 값만 암복호화
*
* FIELD 범위 전용:
* CRYPTO_DEC_FROM_PATH 복호화 대상 JSON 경로 (기본값: /encrypted_data)
* CRYPTO_DEC_TO_PATH 복호화 결과 반영 경로. "/" = 전체 body 교체.
* CRYPTO_ENC_FROM_PATH 암호화 대상 JSON 경로. "/" = 전체 body 암호화.
* CRYPTO_ENC_TO_PATH 암호화 결과(Base64)를 넣을 JSON 경로 (기본값: /encrypted_data)
*
* GCM AAD 전용 (선택):
* CRYPTO_AAD_HEADER AAD로 사용할 HTTP 요청 헤더명 (InCryptoFilter 전용).
* 미설정 또는 헤더값 없으면 aad=null → IV를 AAD 대체값으로 사용.
*/
public abstract class AbstractCryptoFilter {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
public static final String PROP_MODULE_NAME = "CRYPTO_MODULE_NAME";
public static final String PROP_SCOPE = "CRYPTO_SCOPE";
public static final String PROP_DEC_FROM_PATH = "CRYPTO_DEC_FROM_PATH";
public static final String PROP_DEC_TO_PATH = "CRYPTO_DEC_TO_PATH";
public static final String PROP_ENC_FROM_PATH = "CRYPTO_ENC_FROM_PATH";
public static final String PROP_ENC_TO_PATH = "CRYPTO_ENC_TO_PATH";
public static final String PROP_AAD_HEADER = "CRYPTO_AAD_HEADER";
public static final String SCOPE_BODY = "BODY";
public static final String SCOPE_FIELD = "FIELD";
public static final String PATH_ROOT = "/";
public static final String PATH_ENCDATA = "/encrypted_data";
// ------------------------------------------------------------------
// 복호화 구현
// ------------------------------------------------------------------
protected String decryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad) throws Exception {
byte[] cipherBytes = Base64.getDecoder().decode(body.trim());
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
return new String(plainBytes, StandardCharsets.UTF_8);
}
/**
* fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
* toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
*/
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad, String fromPath, String toPath) throws Exception {
JsonNode root = JsonPathUtil.toTree(body);
String encBase64 = JsonPathUtil.getAt(root, fromPath);
if (StringUtils.isBlank(encBase64)) {
logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
return body;
}
byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
byte[] plainBytes = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
String plainText = new String(plainBytes, StandardCharsets.UTF_8);
if (PATH_ROOT.equals(toPath)) {
return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
}
JsonPathUtil.setAt(root, toPath, plainText);
return JsonPathUtil.fromTree(root);
}
// ------------------------------------------------------------------
// 암호화 구현
// ------------------------------------------------------------------
protected String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad) throws Exception {
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
body.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(cipherBytes);
}
/**
* fromPath 값을 암호화하여 toPath(Base64)에 반영. JSON 파싱 1회.
* fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
*/
protected String encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
byte[] aad, String fromPath, String toPath) throws Exception {
if (PATH_ROOT.equals(fromPath)) {
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
body.getBytes(StandardCharsets.UTF_8));
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
String fieldName = toPath.replaceFirst("^/", "");
return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
}
JsonNode root = JsonPathUtil.toTree(body);
String plainText = JsonPathUtil.getAt(root, fromPath);
if (StringUtils.isBlank(plainText)) {
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
return body;
}
byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
plainText.getBytes(StandardCharsets.UTF_8));
String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
JsonPathUtil.setAt(root, toPath, encBase64);
return JsonPathUtil.fromTree(root);
}
// ------------------------------------------------------------------
// 유틸
// ------------------------------------------------------------------
/**
* 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
*/
protected String toBodyString(Object message) {
if (message instanceof String) {
return (String) message;
}
if (message instanceof byte[]) {
return new String((byte[]) message, StandardCharsets.UTF_8);
}
return message.toString();
}
protected String required(Properties prop, String key) {
String v = prop.getProperty(key);
if (StringUtils.isBlank(v)) {
throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
}
return v;
}
protected String getProp(Properties prop, String key, String defaultVal) {
String v = prop.getProperty(key);
return StringUtils.isBlank(v) ? defaultVal : v;
}
}
@@ -1,5 +1,6 @@
package com.eactive.eai.agent.encryption;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.eactive.eai.common.exception.ExceptionUtil;
@@ -11,6 +12,8 @@ import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.Keys;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
import com.eactive.ext.kjb.safedb.Utils;
@Component
public class EncryptionManager implements Lifecycle {
@@ -18,6 +21,8 @@ public class EncryptionManager implements Lifecycle {
private static final String GROUP_NAME = "ENCRYPTION";
private String encryptYN = "";
private String dbEncryptSolutionName = "DAMO";
private boolean started;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@@ -55,8 +60,15 @@ public class EncryptionManager implements Lifecycle {
encryptYN = "N";
}
}
logger.warn("EncryptionManager] init :: encryption YN [ " + encryptYN + " ]");
dbEncryptSolutionName = PropManager.getInstance().getProperty(GROUP_NAME, "dbEncryptSolutionName");
if (dbEncryptSolutionName == null) {
dbEncryptSolutionName = "DAMO";
} else {
if (dbEncryptSolutionName.trim().length() == 0) {
dbEncryptSolutionName = "DAMO";
}
}
logger.warn("EncryptionManager] init :: encryption YN [ " + encryptYN + " ], dbEncryptSolutionName [ " + dbEncryptSolutionName + " ]");
}
public synchronized void reload() throws Exception {
@@ -99,4 +111,101 @@ public class EncryptionManager implements Lifecycle {
public String getEncryptYN() {
return encryptYN;
}
public String getDBEncryptSolutionName() {
return dbEncryptSolutionName;
}
public boolean isEncrypt() {
return "Y".equalsIgnoreCase(encryptYN);
}
public String encryptDBData(String plainText) {
if(isEncrypt()) {
if ("DAMO".equals(dbEncryptSolutionName)) {
com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
return damoManager.encrypt(plainText);
} else if ("SAFEDB".equals(dbEncryptSolutionName)) {
String originalAttribute = plainText;
if (StringUtils.isNotEmpty(plainText)) {
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
plainText = wrapper.encryptNotRnnoString(plainText);
if (logger.isInfo()) {
// originalAttribute 홀수 글자 마스킹
StringBuilder sb = new StringBuilder();
for (int i = 0; i < originalAttribute.length(); i++) {
if (i % 2 == 0) {
sb.append(originalAttribute.charAt(i));
} else {
sb.append("*");
}
}
originalAttribute = sb.toString();
// System.out.println("DB 암호화 #2 : {"+ originalAttribute +"} ->
// {"+attribute+"}");
// logger.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute);
}
}
return plainText;
} else {
return plainText;
}
} else {
return plainText;
}
}
public String decryptDBData(String dbData) {
if (StringUtils.isNotEmpty(dbData)) {
if (this.isEncrypted(dbData)) {
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
try {
dbData = safeDBWrapper.decryptNotRnno(dbData);
} catch (Exception e) {
// 복호화 실패시 원본 반환
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
return dbData;
}
} else {
logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
}
}
// logger.debug("DB 복호화 #2 : {} -> {}", dbDataOriginal, dbData);
return dbData;
}
/**
* 암호화 여부 확인(base64 인코딩 여부로 판단, 또는 0-9, - 로 구성된 경우 암호화 되지 않은 걸로 판단(연락처))
*
* @param data
* @return
*/
private boolean isEncrypted(String data) {
if (StringUtils.isEmpty(data)) {
return false;
}
// 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처)
if (data.matches("^[0-9-]+$")) {
return false;
}
data = data.trim();
// Base64 형식 체크
if (!Utils.isBase64(data)) {
return false;
}
// Base64 길이 체크 (4의 배수)
if (data.length() % 4 != 0) {
return false;
}
return true;
}
}
@@ -1,60 +0,0 @@
package com.eactive.eai.agent.inflow;
import com.eactive.eai.agent.command.Command;
import com.eactive.eai.agent.command.CommandException;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowClientControlCommand extends Command {
private static final long serialVersionUID = 1L;
public ReloadInflowClientControlCommand() {
super("ReloadInflowClientControlCommand");
}
public Object execute() throws CommandException {
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
if (logger.isInfo())
logger.info(this.name + " is executed");
if (!(args instanceof String)) {
String rspErrorCode = "RECEAIMCM001";
String msg = makeException(rspErrorCode, null);
if (logger.isError())
logger.error(msg);
throw new CommandException(msg);
}
String keyName = (String) args;
try {
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
if (!(base instanceof ClientDualInflowControlManager)) {
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
}
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
if (keyName != null) {
if ("ALL".equals(keyName)) {
manager.reloadClient();
if (logger.isWarn())
logger.warn(this.name + "] all rule Reload.");
} else {
manager.reloadClient(keyName);
if (logger.isWarn())
logger.warn(this.name + "] " + keyName + " Reload.");
}
}
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
String msg = makeException(rspErrorCode, e);
if (logger.isError())
logger.error(msg, e);
throw new CommandException(msg);
}
return "success";
}
}
@@ -1,50 +0,0 @@
package com.eactive.eai.agent.inflow;
import com.eactive.eai.agent.command.Command;
import com.eactive.eai.agent.command.CommandException;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowClientControlCommand extends Command {
private static final long serialVersionUID = 1L;
public RemoveInflowClientControlCommand() {
super("RemoveInflowClientControlCommand");
}
public Object execute() throws CommandException {
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
if (logger.isInfo())
logger.info(this.name + " is executed");
if (!(args instanceof String)) {
String rspErrorCode = "RECEAIMCM001";
String msg = makeException(rspErrorCode, null);
if (logger.isError())
logger.error(msg);
throw new CommandException(msg);
}
String key = (String) args;
try {
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
if (!(base instanceof ClientDualInflowControlManager)) {
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
}
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
manager.removeClient(key);
if (logger.isWarn())
logger.warn(this.name + "] " + key + " removed.");
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
String msg = makeException(rspErrorCode, e);
if (logger.isError())
logger.error(msg, e);
throw new CommandException(msg);
}
return "success";
}
}
@@ -0,0 +1,123 @@
package com.eactive.eai.authserver.jwt;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* PS256(RSA-PSS) JWT를 발급·검증하는 JwtAccessTokenConverter 확장 클래스.
*
* spring-security-jwt 1.1.1은 JwtAlgorithms에 PS256을 등록하지 않아
* JwtHelper.encode/decodeAndVerify가 PS256 헤더를 처리하지 못한다.
* 따라서 encode/decode를 직접 구현하여 JwtHelper를 완전히 우회한다.
*/
public class PssJwtAccessTokenConverter extends JwtAccessTokenConverter {
/** PS256 JWT 헤더 {"alg":"PS256","typ":"JWT"} 의 Base64URL 인코딩 (고정값) */
private static final String ENCODED_HEADER = Base64.getUrlEncoder().withoutPadding()
.encodeToString("{\"alg\":\"PS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
private final RsaPssSigner pssSigner;
private final RsaPssVerifier pssVerifier;
private final String publicKeyPem;
private final ObjectMapper jackson = new ObjectMapper();
public PssJwtAccessTokenConverter(RSAPrivateKey privateKey, RSAPublicKey publicKey) {
this.pssSigner = new RsaPssSigner(privateKey);
this.pssVerifier = new RsaPssVerifier(publicKey);
this.publicKeyPem = "-----BEGIN PUBLIC KEY-----\n"
+ Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(publicKey.getEncoded())
+ "\n-----END PUBLIC KEY-----";
// 부모 setVerifierKey — afterPropertiesSet() 내부에서 참조하는 verifierKey 필드 초기화
setVerifierKey(this.publicKeyPem);
// getKey() (/oauth/token_key 엔드포인트)가 동작하도록 부모의 verifier 필드를 주입
try {
Field f = JwtAccessTokenConverter.class.getDeclaredField("verifier");
f.setAccessible(true);
f.set(this, pssVerifier);
} catch (Exception ignored) { }
}
@Override
public void afterPropertiesSet() throws Exception {
// 부모 afterPropertiesSet()은 signingKey 기반 초기화(JwtHelper 의존)를 시도하므로 건너뜀
}
/**
* PS256 JWT 발급. JwtHelper.encode를 사용하지 않고 직접 구성한다.
* signingInput = Base64URL(header) + "." + Base64URL(payload)
* JWT = signingInput + "." + Base64URL(PSS_sign(signingInput))
*/
@Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, ?> claims = getAccessTokenConverter().convertAccessToken(accessToken, authentication);
String payload;
try {
payload = jackson.writeValueAsString(claims);
} catch (Exception e) {
throw new IllegalStateException("Access token을 JSON으로 변환 실패", e);
}
return buildJwt(payload);
}
/**
* PS256 JWT 검증 및 클레임 추출. JwtHelper.decodeAndVerify를 사용하지 않고 직접 파싱한다.
*/
@Override
protected Map<String, Object> decode(String token) {
String[] parts = token.split("\\.");
if (parts.length != 3) {
throw new IllegalArgumentException("잘못된 JWT 형식");
}
byte[] signingInput = (parts[0] + "." + parts[1]).getBytes(StandardCharsets.US_ASCII);
byte[] sig = Base64.getUrlDecoder().decode(padBase64Url(parts[2]));
pssVerifier.verify(signingInput, sig); // 실패 시 InvalidSignatureException
byte[] payloadBytes = Base64.getUrlDecoder().decode(padBase64Url(parts[1]));
try {
@SuppressWarnings("unchecked")
Map<String, Object> result = jackson.readValue(payloadBytes, Map.class);
// JwtAccessTokenConverter 원본 동작 유지: exp 필드 Integer → Long 변환
if (result.containsKey(EXP) && result.get(EXP) instanceof Integer) {
result.put(EXP, ((Integer) result.get(EXP)).longValue());
}
return result;
} catch (Exception e) {
throw new IllegalArgumentException("JWT 페이로드 파싱 실패", e);
}
}
/** /oauth/token_key 엔드포인트 응답 — alg와 PEM 공개키 반환 */
@Override
public Map<String, String> getKey() {
Map<String, String> result = new LinkedHashMap<>();
result.put("alg", "PS256");
result.put("value", publicKeyPem);
return result;
}
public String buildJwt(String payload) {
String encodedPayload = Base64.getUrlEncoder().withoutPadding()
.encodeToString(payload.getBytes(StandardCharsets.UTF_8));
String signingInput = ENCODED_HEADER + "." + encodedPayload;
byte[] sig = pssSigner.sign(signingInput.getBytes(StandardCharsets.US_ASCII));
return signingInput + "." + Base64.getUrlEncoder().withoutPadding().encodeToString(sig);
}
private static String padBase64Url(String s) {
int mod = s.length() % 4;
if (mod == 2) return s + "==";
if (mod == 3) return s + "=";
return s;
}
}
@@ -0,0 +1,46 @@
package com.eactive.eai.authserver.jwt;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import org.springframework.security.jwt.crypto.sign.Signer;
/**
* PS256 (RSA-PSS + SHA-256) 서명 구현체.
* RFC 7518 §3.5 파라미터: Hash=SHA-256, MGF=MGF1/SHA-256, saltLen=32, trailer=0xBC
* JDK 11+ 내장 RSASSA-PSS 사용. JDK 8 환경은 BouncyCastle 프로바이더 등록이 필요하다.
*
* JwtHelper를 우회하는 PssJwtAccessTokenConverter와 함께 사용한다.
* (spring-security-jwt 1.1.1은 JwtAlgorithms에 PS256을 등록하지 않아 JwtHelper 직접 사용 불가)
*/
public class RsaPssSigner implements Signer {
private static final PSSParameterSpec PSS_SPEC =
new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
private final RSAPrivateKey privateKey;
public RsaPssSigner(RSAPrivateKey privateKey) {
this.privateKey = privateKey;
}
@Override
public byte[] sign(byte[] bytes) {
try {
Signature sig = Signature.getInstance("RSASSA-PSS");
sig.setParameter(PSS_SPEC);
sig.initSign(privateKey);
sig.update(bytes);
return sig.sign();
} catch (Exception e) {
throw new RuntimeException("RSA-PSS 서명 실패", e);
}
}
@Override
public String algorithm() {
return "PS256";
}
}
@@ -0,0 +1,47 @@
package com.eactive.eai.authserver.jwt;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import org.springframework.security.jwt.crypto.sign.InvalidSignatureException;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
/**
* PS256 (RSA-PSS + SHA-256) 서명 검증 구현체.
* RFC 7518 §3.5 파라미터: Hash=SHA-256, MGF=MGF1/SHA-256, saltLen=32, trailer=0xBC
*/
public class RsaPssVerifier implements SignatureVerifier {
private static final PSSParameterSpec PSS_SPEC =
new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
private final RSAPublicKey publicKey;
public RsaPssVerifier(RSAPublicKey publicKey) {
this.publicKey = publicKey;
}
@Override
public void verify(byte[] content, byte[] signature) {
try {
Signature sig = Signature.getInstance("RSASSA-PSS");
sig.setParameter(PSS_SPEC);
sig.initVerify(publicKey);
sig.update(content);
if (!sig.verify(signature)) {
throw new InvalidSignatureException("RSA-PSS 서명 검증 실패");
}
} catch (InvalidSignatureException e) {
throw e;
} catch (Exception e) {
throw new InvalidSignatureException("RSA-PSS 서명 검증 실패: " + e.getMessage());
}
}
@Override
public String algorithm() {
return "PS256";
}
}
@@ -13,15 +13,21 @@ import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.session.SessionManager;
import com.eactive.eai.common.session.SessionManagerForIgnite;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.mchange.v1.cachedstore.CachedStore.Manager;
import com.openbanking.eai.common.token.AccessTokenVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 1. 기능 : Access 토큰 정보를 DB로 부터 로딩하고 만료시 재발급 정보를 메모리에 관리하는 Manager 클래스
@@ -55,13 +61,8 @@ public class AccessTokenManagerByDB implements Lifecycle {
/**
* 기동 여부
*/
private boolean started;
/**
* 토큰 정보를 저장하는 collection
*/
private HashMap<String, AccessTokenVO> tokens;
private boolean started;
/**
* 인증 정보를 저장하는 Map
*/
@@ -78,7 +79,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
private static final int TOKEN_SCHEDULER_THREAD_POOL_SIZE = 30;
public AccessTokenManagerByDB() {
tokens = new HashMap<>();
scheduledTasks = new ConcurrentHashMap<>();
outboundOAuthCredentialVos = new HashMap<>();
runningTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -117,7 +117,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
tokens.clear();
init();
} catch(Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201"));
@@ -168,7 +167,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
logger.error("Scheduler shutdown interrupted", e);
}
this.tokens = null;
started = false;
if (logger.isWarn()){
@@ -240,27 +238,34 @@ public class AccessTokenManagerByDB implements Lifecycle {
try {
logger.debug("Executing token issuance for adapter group: {}", adapterGroupName);
// 현재 토큰 가져오기
AccessTokenVO accessToken = tokens.get(adapterGroupName);
SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, new Function<AccessTokenVO, AccessTokenVO>() {
// 다음 스케줄 시간 계산
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
// 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
if (accessToken == null) {
issueToken(credential, false);
} else if(accessToken.getExpiration() != null) {
// 토큰이 있고 만료시간이 설정 된 경우
if(accessToken.getExpiration().before(new Date(intervalTime))){
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
issueToken(credential, true);
@Override
public AccessTokenVO apply(AccessTokenVO accessToken) {
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
// // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
if (accessToken == null) {
return issueToken(credential);
} else if(accessToken.getExpiration() != null) {
// 토큰이 있고 만료시간이 설정 된 경우
if(accessToken.getExpiration().before(new Date(intervalTime))){
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
return issueToken(credential);
} else {
// 토큰이 아직 유효한 경우
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
return accessToken;
}
} else {
// 토큰이 아직 유효한 경우
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
//토큰이 있지만 만료 시간이 없는 경우
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
return accessToken;
}
} else {
//토큰이 있지만 만료 시간이 없는 경우
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
}
}
});
logger.debug("Token issuance completed for adapter group: {}", adapterGroupName);
} catch (Exception e) {
@@ -282,19 +287,24 @@ public class AccessTokenManagerByDB implements Lifecycle {
}
}
private void issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo, boolean isTokenExpiredWithinInterval) throws Exception {
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
boolean isExpired = true;
private AccessTokenVO issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo) {
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
AccessTokenVO accessToken = null;
try {
// boolean isExpired = true;
// if (tokens.containsKey(adapterGroupName)) {
// AccessTokenVO accessToken = tokens.get(adapterGroupName);
// isExpired = accessToken.isExpired();
// }
// logger.debug("Token status for adapter group: {}, expired: {}, isTokenExpiredWithinInterval: {}", adapterGroupName, isExpired, isTokenExpiredWithinInterval);
if (tokens.containsKey(adapterGroupName)) {
AccessTokenVO accessToken = tokens.get(adapterGroupName);
isExpired = accessToken.isExpired();
}
logger.debug("Token status for adapter group: {}, expired: {}, isTokenExpiredWithinInterval: {}", adapterGroupName, isExpired, isTokenExpiredWithinInterval);
if (isExpired || isTokenExpiredWithinInterval) {
// if (isExpired || isTokenExpiredWithinInterval) {
logger.info("Issuing new token for adapter group: {}", adapterGroupName);
tokens.remove(adapterGroupName);
// tokens.remove(adapterGroupName);
AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
@@ -306,16 +316,23 @@ public class AccessTokenManagerByDB implements Lifecycle {
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
if(service != null) {
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
AccessTokenVO accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
this.tokens.put(adapterGroupName, accessToken);
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
logger.info("New token issued successfully for adapter group: {}", adapterGroupName);
return accessToken;
} else {
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
}
} else {
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
}
}
}catch (Exception e) {
logger.error("Task execution failed for adapter group: {}", adapterGroupName, e);
}
return accessToken;
}
public void stopToken(String adapterGroupName) {
@@ -324,7 +341,7 @@ public class AccessTokenManagerByDB implements Lifecycle {
task.cancel(true);
logger.warn("Token task stopped for adapter group: {}", adapterGroupName);
}
tokens.remove(adapterGroupName);
SessionManager.getInstance().removeOutboundAccessToken(adapterGroupName);
logger.debug("Token removed for adapter group: {}", adapterGroupName);
}
@@ -382,15 +399,28 @@ public class AccessTokenManagerByDB implements Lifecycle {
* @return Access 토큰 정보
**/
public AccessTokenVO getAccessTokenVO(String adapterGroupName) {
AccessTokenVO accessToken = null;
try {
accessToken = tokens.get(adapterGroupName);
} catch(Exception e) {
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
}
AccessTokenVO accessToken = null;
return accessToken;
try {
accessToken = SessionManager.getInstance().getOutboundAccessToken(adapterGroupName,
new Function<AccessTokenVO, AccessTokenVO>() {
@Override
public AccessTokenVO apply(AccessTokenVO t) {
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos
.get(adapterGroupName);
return issueToken(outboundOAuthCredentialVo);
}
});
} catch (Exception e) {
if (logger.isError())
logger.error("토큰을 찾을 수 없습니다. [" + adapterGroupName + "] ", e);
}
return accessToken;
}
/**
@@ -403,9 +433,9 @@ public class AccessTokenManagerByDB implements Lifecycle {
**/
public String getAccessToken(String adapterGroupName) {
String accessToken = null;
try {
accessToken = tokens.get(adapterGroupName).getAccessToken();
accessToken = getAccessTokenVO(adapterGroupName).getAccessToken();
} catch(Exception e) {
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
}
@@ -435,27 +465,29 @@ public class AccessTokenManagerByDB implements Lifecycle {
* 3. 주의사항
*
**/
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties, String oldToken) throws Exception {
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties,
String oldToken) throws Exception {
// 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다.
if (accessToken == null || accessToken.isExpired()
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
// 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다.
if (accessToken == null || accessToken.isExpired()
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
if (isOAuthCredentialRegistered(adapterGroupName) == false) {
throw new Exception(
"There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
}
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
if (service != null) {
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties,
outboundOAuthCredentialVo);
}
}
if(isOAuthCredentialRegistered(adapterGroupName) == false){
throw new Exception("There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
}
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
if(service != null) {
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
}
tokens.put(adapterGroupName, accessToken);
}
return accessToken;
}
}
}
@@ -336,7 +336,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
switch (event.getEventType()) {
case ERROR :
TypedCircuitBreakerManager.logger
.error("CirbuitBreaker ERROR event occurred. CircuitBreakerEvent={}" + event);
.error("CircuitBreaker ERROR event occurred. CircuitBreakerEvent={}" + event);
break;
case STATE_TRANSITION :
if (event instanceof CircuitBreakerOnStateTransitionEvent) {
@@ -345,7 +345,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
CircuitBreaker.State toState = stateTransition.getToState();
CircuitBreaker.State fromState = stateTransition.getFromState();
TypedCircuitBreakerManager.logger.error(
"CirbuitBreaker STATE_TRANSITION event occurred. {} : {} -> {}. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
"CircuitBreaker STATE_TRANSITION event occurred. {} : {} -> {}. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
new Object[]{event.getCircuitBreakerName(), fromState, toState, event, this.vo});
// if (!toState.equals(State.OPEN) && !toState.equals(State.HALF_OPEN)
// && !toState.equals(State.CLOSED)) {
@@ -367,7 +367,7 @@ public class TypedCircuitBreakerManager implements Lifecycle {
} else {
TypedCircuitBreakerManager.logger.error(
"CirbuitBreaker STATE_TRANSITION event occurred. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
"CircuitBreaker STATE_TRANSITION event occurred. CircuitBreakerEvent={}, UrlCircuitBreakerVO={}",
new Object[]{event, this.vo});
}
break;
@@ -7,6 +7,10 @@ import org.json.simple.JSONValue;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandlerFactory;
import com.eactive.eai.common.EAIKeys;
import com.eactive.eai.common.logger.EAILogSender;
import com.eactive.eai.common.message.EAIMessage;
@@ -749,6 +753,53 @@ public class ExceptionHandler {
String serviceType, String psvItfTp, String charset) {
String error = null;
EAIMessage resultEAIMessage = null;
// AdapterErrorMessageHandler(TemplateAdapterErrorMsgHandler) 설정이 있으면 우선 호출
try {
AdapterGroupVO inboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(sngSysItfTp);
if (inboundAdapterGroupVO != null) {
AdapterVO inboundAdapterVO = inboundAdapterGroupVO.nextAdapterVO();
if (inboundAdapterVO != null) {
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
if (StringUtils.isNotBlank(errorHandlerClass)) {
if (logger.isInfo())
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
if (handler != null) {
// 표준전문에 에러코드/에러메시지 세팅
try {
StandardMessageManager standardManager = StandardMessageManager.getInstance();
standardManager.getMessageCoordinator().coordinateSetStandardMessageError(
eaiMessage.getStandardMessage(), eaiMessage.getMapper(), errCode, errMsg);
} catch (Exception e) {
if (logger.isWarn())
logger.warn("ExceptionHandler] errorSyncSend coordinateSetStandardMessageError 처리 실패: " + e.getMessage());
}
Object responseObj = handler.generateNonStandardInternalErrorResponseMessage(
sngSysItfTp, inboundAdapterVO.getName(), null, tmpMsg, eaiMessage);
if (responseObj != null) {
resultEAIMessage = eaiMessage;
try {
resultEAIMessage.getStandardMessage().setBizData(responseObj, charset);
} catch (Exception e) {
logger.error("setBizData error", e);
}
resultEAIMessage.getMapper().setResponseType(
resultEAIMessage.getStandardMessage(), STDMessageKeys.RESPONSE_TYPE_CODE_E);
resultEAIMessage.setRspErrCd(EAIMessageKeys.EAI_SUCCESS_CODE, false);
return resultEAIMessage;
}
}
}
}
}
} catch (Exception e) {
if (logger.isWarn())
logger.warn("ExceptionHandler] errorSyncSend AdapterErrorMessageHandler 처리 실패: " + e.getMessage());
}
try {
if (transClassify) {
if (logger.isInfo()) {
@@ -7,7 +7,12 @@ import java.lang.reflect.Method;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Base64;
import javax.crypto.SecretKey;
import org.springframework.stereotype.Component;
@@ -24,13 +29,17 @@ import com.eactive.eai.common.util.Logger;
* SafeNet ProtectServer HSM 연동 관리자 (JDK 8 / SunPKCS11)
*
* PropManager 그룹 "HSM" 에서 읽는 :
* PKCS11_CONFIG - pkcs11.cfg 파일 내용 (name/library/slot 설정을 DB에 직접 저장)
* PKCS11_CONFIG - pkcs11.cfg 파일 내용
* PIN - HSM 슬롯 PIN
*
* PKCS11_CONFIG 예시 (개행은 \n 으로 입력):
* name = ProtectServer
* library = /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so
* slot = 0
*
* name = SoftHSM
* library = C:/SoftHSM2/lib/softhsm2-x64.dll
* slotListIndex = 0
*/
@Component
public class HsmManager implements Lifecycle {
@@ -99,7 +108,20 @@ public class HsmManager implements Lifecycle {
StringBuilder aliasList = new StringBuilder();
while (aliases.hasMoreElements()) {
if (aliasList.length() > 0) aliasList.append(", ");
aliasList.append(aliases.nextElement());
String alias = aliases.nextElement();
aliasList.append(alias);
java.security.Key key = keyStore.getKey(alias, null);
if (key instanceof SecretKey) {
SecretKey secretKey = (SecretKey) key;
if(secretKey.getEncoded() != null) {
String encBase64 = Base64.getEncoder().encodeToString(secretKey.getEncoded());
logger.debug("HsmManager] HSM - {} : [{}]", alias, encBase64);
} else {
logger.debug("HsmManager] HSM - secretKey null {} : [{}]", alias, secretKey);
}
}
}
logger.warn("HsmManager] HSM 키 목록: [" + aliasList + "]");
}
@@ -1,22 +0,0 @@
package com.eactive.eai.common.inflow.dual;
import com.eactive.eai.common.inflow.InflowTargetVO;
/**
* DualBucket에 클라이언트 유량제어 메서드를 추가한 인터페이스.
* {@link ClientDualInflowControlManager} 구현한다.
*/
public interface ClientDualBucket extends DualBucket {
/**
* 클라이언트 유량제어 체크 차단 원인 포함.
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
*/
String isClientPassDetail(String clientId);
/**
* 클라이언트 유량제어 설정 조회 에러 메시지 생성용.
* @return 등록된 설정이 없으면 null
*/
InflowTargetVO getClientInflowThreshold(String clientId);
}
@@ -1,112 +0,0 @@
package com.eactive.eai.common.inflow.dual;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;
import com.eactive.eai.common.inflow.Bucket;
import com.eactive.eai.common.inflow.InflowTargetVO;
import com.eactive.eai.common.inflow.InflowType;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
/**
* 어댑터/인터페이스 이중 버킷(DualInflowControlManager) 클라이언트 유량제어를 추가한 관리자.
*
* <p>적용 방법: INFLOW.properties에 아래 추가.
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager</pre>
*/
@Component
public class ClientDualInflowControlManager extends DualInflowControlManager implements ClientDualBucket {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
public static synchronized Bucket getBucket() {
return ApplicationContextProvider.getContext().getBean(ClientDualInflowControlManager.class);
}
private volatile Map<String, DualCustomBucket> clientBucketMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, TargetMetrics> clientMetricsMap = new ConcurrentHashMap<>();
// -------------------------------------------------------------------------
// Lifecycle
// -------------------------------------------------------------------------
@Override
public void start() throws LifecycleException {
super.start();
try {
initClients();
} catch (Exception e) {
throw new LifecycleException("ClientDualInflowControlManager init failed: " + e.getMessage());
}
}
// -------------------------------------------------------------------------
// 초기화 / 리로드
// -------------------------------------------------------------------------
private void initClients() throws Exception {
this.clientBucketMap = loadBucketMap(InflowType.CLIENT);
if (logger.isInfo()) logger.info("ClientDualInflowControlManager] initClients: " + clientBucketMap.size() + " buckets");
}
public synchronized void reloadClient() throws Exception {
initClients();
clientMetricsMap.clear();
}
public synchronized void reloadClient(String clientId) throws Exception {
reloadBucketMap(clientBucketMap, InflowType.CLIENT, clientId);
TargetMetrics m = clientMetricsMap.get(clientId);
if (m != null) m.reset();
}
public synchronized void removeClient(String clientId) {
clientBucketMap.remove(clientId);
clientMetricsMap.remove(clientId);
}
// -------------------------------------------------------------------------
// 클라이언트 메트릭 접근자
// -------------------------------------------------------------------------
public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); }
public Map<String, TargetMetrics> getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); }
public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); }
public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); }
// -------------------------------------------------------------------------
// ClientDualBucket 클라이언트 유량제어
// -------------------------------------------------------------------------
@Override
public String isClientPassDetail(String clientId) {
DualCustomBucket b = clientBucketMap.get(clientId);
if (b == null) return DualCustomBucket.RESULT_PASS;
String result = b.tryConsume();
recordMetrics(clientMetricsMap, clientId, result);
return result;
}
@Override
public InflowTargetVO getClientInflowThreshold(String clientId) {
DualCustomBucket b = clientBucketMap.get(clientId);
return (b == null) ? null : b.getInflowTargetVo();
}
// -------------------------------------------------------------------------
// 모니터링용 접근자
// -------------------------------------------------------------------------
public DualCustomBucket getClientBucket(String clientId) {
return clientBucketMap.get(clientId);
}
public Map<String, DualCustomBucket> getClientBucketMap() {
return clientBucketMap;
}
}
@@ -253,7 +253,9 @@ public class EAIFileLogger
* @exception DAOException
**/
public void setLog(EAIMessage message, Properties prop) throws Exception{
if(!tranLogger.isError())
return;
// 업무데이터 size 체크하여 업무데이터1,2에 insert하고
// addBwkDataSub(message) 호출 여부 결정
@@ -517,27 +519,28 @@ public class EAIFileLogger
//전장표 데이터부 JSON Array로 저장
//암호화
Charset hexaCharset = Charset.forName("euc-kr");
if("Y".equalsIgnoreCase(EncryptionManager.getInstance().getEncryptYN()) ){
try{
biz = HexaConverter.bytesToHexa(bizMsgStr.getBytes(hexaCharset));
}catch(Exception e){
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
}
try{
header = HexaConverter.bytesToHexa(header.getBytes(hexaCharset));
}catch(Exception e){
logger.error("EAIFileLogger] header encryption Error - " + e.getMessage());
}
try{
mdclData = HexaConverter.bytesToHexa(mdclData.getBytes(hexaCharset));
}catch(Exception e){
logger.error("EAIFileLogger] mdclData encryption Error - " + e.getMessage());
}
}else {
biz = bizMsgStr;
}
biz = EncryptionManager.getInstance().encryptDBData(bizMsgStr);
// Charset hexaCharset = Charset.forName("euc-kr");
// if("Y".equalsIgnoreCase(EncryptionManager.getInstance().getEncryptYN()) ){
// try{
// biz = HexaConverter.bytesToHexa(bizMsgStr.getBytes(hexaCharset));
// }catch(Exception e){
// logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
// }
// try{
// header = HexaConverter.bytesToHexa(header.getBytes(hexaCharset));
// }catch(Exception e){
// logger.error("EAIFileLogger] header encryption Error - " + e.getMessage());
// }
// try{
// mdclData = HexaConverter.bytesToHexa(mdclData.getBytes(hexaCharset));
// }catch(Exception e){
// logger.error("EAIFileLogger] mdclData encryption Error - " + e.getMessage());
// }
//
// }else {
// biz = bizMsgStr;
// }
//임시코드 시작
String logType = PropManager.getInstance().getProperty("BIZ_LOG_TYPE");
@@ -3,8 +3,6 @@ package com.eactive.eai.common.logger;
import java.nio.charset.Charset;
import java.util.Properties;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.vo.ClientVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@@ -14,13 +12,10 @@ import org.springframework.transaction.annotation.Transactional;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.agent.encryption.EncryptionManager;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.vo.ClientVO;
import com.eactive.eai.common.dao.DAOException;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.logger.EAILogLogger;
import com.eactive.eai.common.logger.StatAdapterLogLogger;
import com.eactive.eai.common.logger.StatLogLogger;
import com.eactive.eai.common.logger.StatTranLogLogger;
import com.eactive.eai.common.logger.SubMessageLogLogger;
import com.eactive.eai.common.logger.mapper.StatAdapterLogMapper;
import com.eactive.eai.common.logger.mapper.StatLogMapper;
import com.eactive.eai.common.logger.mapper.StatTranLogMapper;
@@ -30,7 +25,6 @@ import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.message.ServiceMessage;
import com.eactive.eai.common.monitor.StatMonitorLogVO;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.server.EAIServerVO;
import com.eactive.eai.common.server.loader.EAIServerLoader;
@@ -55,11 +49,10 @@ import com.eactive.eai.transformer.layout.Item;
import com.eactive.eai.transformer.layout.Layout;
import com.eactive.eai.transformer.transform.Transform;
import com.eactive.eai.transformer.transform.TransformManager;
import com.eactive.eai.util.HexaConverter;
import com.ext.eai.common.stdmessage.STDMessageKeys;
// safeDB
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
import com.eactive.ext.kjb.safedb.Utils;
import com.ext.eai.common.stdmessage.STDMessageKeys;
@Service
@@ -478,20 +471,12 @@ public class EAILogDAO {
}
*/
// jwhong safeDB 암호화
if ("Y".equalsIgnoreCase(EncryptionManager.getInstance().getEncryptYN())) {
try {
biz = EncryptSafeDb(bizMsgStr);
} catch (Exception e) {
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
biz = bizMsgStr;
}
} else {
try {
biz = EncryptionManager.getInstance().encryptDBData(bizMsgStr);
} catch (Exception e) {
logger.error("EAIFileLogger] bizMsgStr encryption Error - " + e.getMessage());
biz = bizMsgStr;
}
// jwhong safeDB end
}
// 임시코드 시작
String logType = PropManager.getInstance().getProperty("BIZ_LOG_TYPE");
@@ -860,88 +845,6 @@ public class EAILogDAO {
}
return false;
}
private String EncryptSafeDb(String attribute) {
String originalAttribute = attribute;
if (StringUtils.isNotEmpty(attribute)) {
KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance();
attribute = wrapper.encryptNotRnnoString(attribute);
if (logger.isInfo()) {
// originalAttribute 홀수 글자 마스킹
StringBuilder sb = new StringBuilder();
for (int i = 0; i < originalAttribute.length(); i++) {
if (i % 2 == 0) {
sb.append(originalAttribute.charAt(i));
} else {
sb.append("*");
}
}
originalAttribute = sb.toString();
// System.out.println("DB 암호화 #2 : {"+ originalAttribute +"} -> {"+attribute+"}");
//logger.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute);
}
}
return attribute;
}
public String DecryptSafeDb(String dbData) {
String dbDataOriginal = dbData;
if (StringUtils.isNotEmpty(dbData)) {
if (this.isEncrypted(dbData)) {
KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance();
try {
dbData = safeDBWrapper.decryptNotRnno(dbData);
} catch (Exception e) {
// 복호화 실패시 원본 반환
String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "...";
logger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length());
return dbData;
}
} else {
logger.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData);
}
}
//logger.debug("DB 복호화 #2 : {} -> {}", dbDataOriginal, dbData);
return dbData;
}
/**
* 암호화 여부 확인(base64 인코딩 여부로 판단, 또는 0-9, - 구성된 경우 암호화 되지 않은 걸로 판단(연락처))
* @param data
* @return
*/
private boolean isEncrypted(String data) {
if (StringUtils.isEmpty(data)) {
return false;
}
// 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처)
if (data.matches("^[0-9-]+$")) {
return false;
}
data = data.trim();
// Base64 형식 체크
if (!Utils.isBase64(data)) {
return false;
}
// Base64 길이 체크 (4의 배수)
if (data.length() % 4 != 0) {
return false;
}
return true;
}
/*
@@ -1,11 +1,11 @@
package com.eactive.eai.common.message;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.manager.StandardMessageManager;
@@ -109,7 +109,8 @@ public class EAIMessage implements EAIMessageKeys, Serializable, Comparable<EAIM
private String authkeyfieldname;
private String authCheckdYn;
private Properties callProp;
public EAIMessage() {
this.svcMsgs = new ArrayList<>();
@@ -664,6 +665,14 @@ public class EAIMessage implements EAIMessageKeys, Serializable, Comparable<EAIM
public Map<String, Object> getTransactionContextTransfer() {
return transactionContextTransfer;
}
public Properties getCallProp() {
return callProp;
}
public void setCallProp(Properties callProp) {
this.callProp = callProp;
}
/**
* [비동기 전달 전용] 컨텍스트 전달용 Map 설정
@@ -64,7 +64,8 @@ public class IFRouter
AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(service.getPsvSysItfTp());
if( EAIMessageKeys.TRANTYPE_TAS.equals(eaiMessage.getTranType())
// RESTAdapter는 교체하지 않는다
&& !StringUtils.equals(adptGrpVO.getType(), "RST")) {
&& !StringUtils.equals(adptGrpVO.getType(), "RST")
&& !StringUtils.equals(adptGrpVO.getType(), "HTT")) {
// isLocal = true;
// if (logger.isInfo()) logger.info(guidLogPrefix + " TRANTYPE TAS [" + eaiMessage.getTranType() + "]");
PropManager manager = PropManager.getInstance();
@@ -144,8 +144,12 @@ public class CryptoModuleManager implements Lifecycle {
dynamicKeyCache.put(cacheKey, new CachedDerivedKey(derivedKey, ttl));
}
}
byte[] iv = vo.getIvHex() != null ? DatatypeConverter.parseHexBinary(vo.getIvHex()) : null;
byte[] iv = null;
if(derivedKey.getIv() != null) {
iv = derivedKey.getIv();
} else if (vo.getIvHex() != null){
iv = DatatypeConverter.parseHexBinary(vo.getIvHex());
}
return buildExtension(vo, iv, derivedKey.getEncKey(), derivedKey.getDecKey());
}
@@ -4,10 +4,19 @@ public class DerivedKey {
private final byte[] encKey;
private final byte[] decKey;
private final byte[] iv;
public DerivedKey(byte[] encKey, byte[] decKey) {
this.encKey = encKey;
this.decKey = decKey != null ? decKey : encKey;
this.iv = null;
}
public DerivedKey(byte[] encKey, byte[] decKey, byte[] iv) {
this.encKey = encKey;
this.decKey = decKey != null ? decKey : encKey;
this.iv = iv;
}
public byte[] getEncKey() {
@@ -17,4 +26,8 @@ public class DerivedKey {
public byte[] getDecKey() {
return decKey;
}
public byte[] getIv() {
return iv;
}
}
@@ -3,7 +3,12 @@ package com.eactive.eai.common.security.keyderiv;
import java.util.Map;
public interface KeyDerivationStrategy {
public static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
public static final String PARAM_HSM_IV_ALIAS = "hsmIvAlias";
public static final String PARAM_CONTEXT_KEY = "contextKey";
/**
* 키를 도출한다.
*
@@ -14,8 +14,6 @@ import com.eactive.eai.common.util.ApplicationContextProvider;
*/
public abstract class BaseHsmKeyDerivationStrategy implements KeyDerivationStrategy {
protected static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
protected byte[] getHsmKey(Map<String, String> params) throws HsmException {
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
return getHsmKey(hsmKeyAlias);
@@ -26,6 +24,11 @@ public abstract class BaseHsmKeyDerivationStrategy implements KeyDerivationStrat
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()) {
@@ -23,9 +23,6 @@ import com.eactive.eai.common.util.ApplicationContextProvider;
*/
public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy {
private static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
private static final String PARAM_CONTEXT_KEY = "contextKey";
@Override
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS);
@@ -26,8 +26,6 @@ public class HsmContextXorKeyDerivationStrategy implements KeyDerivationStrategy
/** DB key_deriv_strategy 컬럼에 사용하는 FQCN 참조용 상수. */
public static final String STRATEGY_CLASS = HsmContextXorKeyDerivationStrategy.class.getName();
private static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias";
private static final String PARAM_CONTEXT_KEY = "contextKey";
private static final String PARAM_OFFSET = "offset";
private static final String PARAM_LENGTH = "length";
@@ -0,0 +1,25 @@
package com.eactive.eai.common.security.keyderiv.strategy;
import java.util.Map;
import com.eactive.eai.common.security.keyderiv.DerivedKey;
/**
* HSM 마스터키를 사용하는 전략
*
* key_deriv_params (JSON) 예시: { "hsmKeyAlias" : "MASTER_KEY_AES", "hsmIvAlias" : "MASTER_IV_AES"}
*/
public class HsmKeyAndIvDerivationStrategy extends BaseHsmKeyDerivationStrategy {
@Override
public DerivedKey deriveKey(Map<String, String> params, Map<String, String> runtimeContext) throws Exception {
byte[] masterKeyBytes = super.getHsmKey(params);
byte[] iv = super.getHsmIv(params);
return new DerivedKey(masterKeyBytes, masterKeyBytes, iv);
}
@Override
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
return cryptoName;
}
}
@@ -1,5 +1,10 @@
package com.eactive.eai.common.session;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.ignite.IgniteCache;
import com.eactive.eai.authserver.service.BearerTokenInfo;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
@@ -11,6 +16,7 @@ import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.server.EAIServerVO;
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
import com.eactive.eai.common.util.Logger;
import com.openbanking.eai.common.token.AccessTokenVO;
/**
* 1. 기능 :
@@ -60,6 +66,7 @@ public abstract class SessionManager implements Lifecycle {
public static final String WEBSOCKER_CAHCE_NAME = "webSocketTimeout";
public static final String TERMINAL_CAHCE_NAME = "terminalSession";
public static final String CAGATEWAY_TOKEN_CACHE_NAME = "CAGatewayToken";
public static final String OUTBOUND_ACCESS_TOKEN_CACHE_NAME = "OutBoundAccessToken";
public static final String SESSION_MANAGER_GROUPNAME = "RMIInfo";
public static final String SESSION_MANAGER_CACHETYPE = "cacheType";
@@ -204,6 +211,13 @@ public abstract class SessionManager implements Lifecycle {
public abstract void removeCAToken(String key);
//Outbound Access Token Cache
public abstract AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> getNewToken);
public abstract void removeOutboundAccessToken(String key);
public abstract void clearOutboundAccessToken();
// HTTP Cache
public abstract SessionVO getHttpLogin(String key);
@@ -245,4 +259,5 @@ public abstract class SessionManager implements Lifecycle {
public abstract void closeManager();
public abstract String getCacheStatus();
}
@@ -2,6 +2,8 @@ package com.eactive.eai.common.session;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import com.eactive.eai.authserver.service.BearerTokenInfo;
import com.eactive.eai.common.ehcache.CustomRMICacheReplicatorFactory;
@@ -14,6 +16,7 @@ import com.eactive.eai.common.sessioninfo.TerminalInfoDAO;
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.util.HealthCheckManager;
import com.openbanking.eai.common.token.AccessTokenVO;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
@@ -937,4 +940,19 @@ public class SessionManagerForEhcache extends SessionManager {
public void removeCAToken(String key) {
throw new UnsupportedOperationException();
}
@Override
public AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> getNewToken) {
throw new UnsupportedOperationException();
}
@Override
public void removeOutboundAccessToken(String key) {
throw new UnsupportedOperationException();
}
@Override
public void clearOutboundAccessToken() {
throw new UnsupportedOperationException();
}
}
@@ -4,6 +4,10 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.cache.Cache;
import javax.cache.configuration.Factory;
@@ -54,6 +58,7 @@ import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.worker.Future;
import com.eactive.eai.common.worker.ResponseMap;
import com.eactive.eai.util.HealthCheckManager;
import com.openbanking.eai.common.token.AccessTokenVO;
import ch.qos.logback.classic.Level;
@@ -66,6 +71,8 @@ import ch.qos.logback.classic.Level;
//------------------------------------------------------------------------------------
public class SessionManagerForIgnite extends SessionManager {
private static final String DISTRIBUTED_TOKEN_LOCK = "DISTRIBUTED_TOKEN_LOCK";
// evictMaster Instance - single socket 관련
private static IgniteCache<String, String> evictMasterCache = null;
// Socket Session cache 정보
@@ -76,8 +83,11 @@ public class SessionManagerForIgnite extends SessionManager {
private static IgniteCache<String, CacheStoreObject> cacheStore = null;
// CA Gateway Token 관리용
private static IgniteCache<String, BearerTokenInfo> cacheCAToken = null;
// OutboundAccessToekn 관리용
private static IgniteCache<String, AccessTokenVO> cacheOutBoundAccessToken = null;
// webSocket Login cache 정보
private static IgniteCache<String, SessionVO> cacheLogin = null; // key = loginId
private static IgniteCache<String, String> cacheConvertUserId = null; // 단말ID userID로 변경
// HTTP Polling 대한 timeout 관리용
private static IgniteCache<String, SessionVO> httpLogin = null; // key == uuid
@@ -392,6 +402,7 @@ public class SessionManagerForIgnite extends SessionManager {
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, USERID_CAHCE_NAME, 20000, false));
caches.add(initCache(config, webSocketTimeout, bootstrapAsynchronously, WEBSOCKER_CAHCE_NAME, 20000, false));
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, TERMINAL_CAHCE_NAME, 20000, false));
caches.add(initCache(config, 0, bootstrapAsynchronously, OUTBOUND_ACCESS_TOKEN_CACHE_NAME, 1000, false, true)); // transactionMode true
caches.add(initCache(config, 0, bootstrapAsynchronously, CAGATEWAY_TOKEN_CACHE_NAME, 1000, false));
config.setCacheConfiguration(caches.stream().toArray(CacheConfiguration[]::new));
@@ -421,6 +432,7 @@ public class SessionManagerForIgnite extends SessionManager {
cacheTopic = manager.cache(TOPIC_CAHCE_NAME);
cacheStore = manager.cache(STORE_CAHCE_NAME);
cacheCAToken = manager.cache(CAGATEWAY_TOKEN_CACHE_NAME);
cacheOutBoundAccessToken = manager.cache(OUTBOUND_ACCESS_TOKEN_CACHE_NAME);
cacheLogin = manager.cache(LOGIN_CAHCE_NAME);
httpLogin = manager.cache(HTTP_CAHCE_NAME);
@@ -428,49 +440,56 @@ public class SessionManagerForIgnite extends SessionManager {
cacheWebSocketTimeout = manager.cache(WEBSOCKER_CAHCE_NAME);
cacheTerminal = manager.cache(TERMINAL_CAHCE_NAME);
}
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs,
String bootstrapAsynchronously, String name, int maxsize, boolean useCacheWriteModeAsync,
boolean transactionMode) {
if (logger.isDebug()) {
logger.debug(logPrefix + " create " + name + " cache expiryDurationSecs=" + expiryDurationSecs);
}
// near-cache
NearCacheConfiguration nearConfiguration = new NearCacheConfiguration();
LruEvictionPolicy nearEvictionPolicy = new LruEvictionPolicy();
nearEvictionPolicy.setMaxSize(maxsize);
nearConfiguration.setNearEvictionPolicy(nearEvictionPolicy);
CacheConfiguration cacheConfig = new CacheConfiguration(name);
cacheConfig.setCacheMode(CacheMode.PARTITIONED);
// ATOMIC,TRANSACTIONAL
if (transactionMode) {
cacheConfig.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
} else {
cacheConfig.setAtomicityMode(CacheAtomicityMode.ATOMIC);
}
// Set the number of backups (replicas) for each partition : default(0)
cacheConfig.setBackups(1);
// Enable reading from backups to improve read consistency : default(true)
cacheConfig.setReadFromBackup(true);
// FULL_SYNC/FULL_ASYNC/PRIMARY_SYNC(default)
if (useCacheWriteModeAsync) {
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_ASYNC);
} else {
// cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
}
if (expiryDurationSecs > 0) {
cacheConfig.setExpiryPolicyFactory(
ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, expiryDurationSecs)));
} else {
cacheConfig.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(Duration.ETERNAL));
}
cacheConfig.setNearConfiguration(nearConfiguration);
return cacheConfig;
}
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs, String bootstrapAsynchronously,
String name, int maxsize, boolean useCacheWriteModeAsync) {
if (logger.isDebug()) {
logger.debug(logPrefix + " create " + name + " cache expiryDurationSecs=" + expiryDurationSecs);
}
// near-cache
NearCacheConfiguration nearConfiguration = new NearCacheConfiguration();
LruEvictionPolicy nearEvictionPolicy = new LruEvictionPolicy();
nearEvictionPolicy.setMaxSize(maxsize);
nearConfiguration.setNearEvictionPolicy(nearEvictionPolicy);
CacheConfiguration cacheConfig = new CacheConfiguration(name);
// REPLICATED,PARTITIONED(default)
// cacheConfig.setCacheMode(CacheMode.REPLICATED);
cacheConfig.setCacheMode(CacheMode.PARTITIONED);
// ATOMIC,TRANSACTIONAL
cacheConfig.setAtomicityMode(CacheAtomicityMode.ATOMIC);
// Set the number of backups (replicas) for each partition : default(0)
cacheConfig.setBackups(1);
// Enable reading from backups to improve read consistency : default(true)
cacheConfig.setReadFromBackup(true);
// FULL_SYNC/FULL_ASYNC/PRIMARY_SYNC(default)
if (useCacheWriteModeAsync) {
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_ASYNC);
} else {
// cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
}
if(expiryDurationSecs > 0) {
cacheConfig
.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, expiryDurationSecs)));
}
else {
cacheConfig
.setExpiryPolicyFactory( ModifiedExpiryPolicy.factoryOf(Duration.ETERNAL) );
}
cacheConfig.setNearConfiguration(nearConfiguration);
return cacheConfig;
return initCache(config, expiryDurationSecs, bootstrapAsynchronously, name, maxsize, useCacheWriteModeAsync, false);
}
// WebSocket Login 관련
@@ -868,5 +887,114 @@ public class SessionManagerForIgnite extends SessionManager {
cacheCAToken.remove(key);
}
private void sleepQuietly(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
// 인터럽트 상태 복구 (중요)
Thread.currentThread().interrupt();
}
}
public AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> tokenSupplier) {
// 1. 먼저 캐시에서 조회 ( 없이 빠르게)
AccessTokenVO token = cacheOutBoundAccessToken.get(key);
// 2. 만료되었거나 없다면 획득 시도
if (token == null || token.isExpired()) {
Lock lock = cacheOutBoundAccessToken.lock(DISTRIBUTED_TOKEN_LOCK); // Ignite 분산
boolean acquired = false;
try {
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock");
acquired = lock.tryLock(60, TimeUnit.SECONDS);
if ( acquired ) {
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock success=>"+acquired);
// 3. 획득 다시 확인 ( 사이 다른 인스턴스가 갱신했을 있음)
token = cacheOutBoundAccessToken.get(key);
logger.debug(String.format("getting token in ignite=%s", token));
if (token == null || token.isExpired()) {
// 4. 실제로 비어있을 때만 외부 API 호출 (I/O 발생)
token = tokenSupplier.apply(token);
logger.debug(String.format("getting token is null or expired borrow new token =%s", token));
cacheOutBoundAccessToken.put(key, token);
}
}
else
{
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock fail=>"+acquired);
}
}catch(Throwable e) {
logger.error("occuring exception in getOutboundAccessToken", e);
}finally {
try {
if( acquired ) {
lock.unlock();
logger.debug("calling func getOutboundAccessToken = distributed ignite unlock");
}
}catch(Throwable e) {
logger.error("occuring exception in getOutboundAccessToken unlock fail.", e);
}
}
}
return token;
}
@Override
public void removeOutboundAccessToken(String key) {
Lock lock = cacheOutBoundAccessToken.lock(DISTRIBUTED_TOKEN_LOCK); // Ignite 분산
boolean acquired = false;
try {
logger.debug("calling func removeOutboundAccessToken = distributed ignite trylock");
acquired = lock.tryLock(60, TimeUnit.SECONDS);
if( acquired ) {
if (logger.isDebug()) {
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock success=>"
+ acquired);
logger.debug("calling func removeOutboundAccessToken Remove OutBoundAccessToken key - " + key);
}
cacheOutBoundAccessToken.remove(key);
}else {
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock fail=>"+acquired);
}
} catch (Throwable e) {
logger.error("occuring exception in removeOutboundAccessToken", e);
} finally {
try {
if (acquired) {
lock.unlock();
logger.debug("calling func removeOutboundAccessToken = distributed ignite unlock");
}
} catch (Throwable e) {
logger.error("occuring exception in removeOutboundAccessToken unlock fail.", e);
}
}
}
@Override
public void clearOutboundAccessToken() {
}
}
@@ -49,9 +49,10 @@ public class STDMessageDAO extends BaseDAO {
for (StandardMessageInfo info : list) {
vo = standardManager.getStandardMessage();
vo.setServiceKey(info.getBzwksvckeyname());
vo.setEaiSvcCd(info.getEaisvcname());
// TODO
// to be defined...
mapper.setInterfaceId(vo, info.getEaitranname());
// mapper.setInterfaceId(vo, info.getEaitranname());
mapper.setSendRecvDivision(vo, info.getEaisendrecv()); // S | R
mapper.setInExDivision(vo, info.getEaidirection()); // 1 | 2
List<StandardMessageItem> items = info.getStandardMessageItems();
@@ -72,6 +72,9 @@ public class HttpAdapterExtraLogUtil {
}
httpAdapterExtraLogVo.setHeaderList(headerVoList);
httpAdapterExtraLogVo.setHttpStatus(httpStatus);
logger.debug("[{}] Insert HTTP Log. - {}", guid, httpAdapterExtraLogVo);
if(ElinkConfig.isUseAsyncLogging()) {
AsyncHttpLoggingPoolManager.getInstance().publish(httpAdapterExtraLogVo);
} else {
@@ -0,0 +1,448 @@
package com.eactive.eai.common.util;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class JSONUtils {
private JSONUtils() {
throw new IllegalStateException("Utility class");
}
public static Object parseJsonGeneric(Object jsonData) {
if (jsonData == null) {
return new JSONObject();
}
if (jsonData instanceof JSONObject) {
return (JSONObject) jsonData;
} else if (jsonData instanceof String) {
if (StringUtils.isBlank((String) jsonData)) {
return new JSONObject();
}
try {
JSONParser parser = new JSONParser();
Object parsedObj = parser.parse((String) jsonData);
return parsedObj;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
} else
throw new IllegalArgumentException("not support json type. " + jsonData.getClass());
}
public static JSONObject parseJson(Object message) {
if (message == null) {
return new JSONObject();
}
if (message instanceof JSONObject)
return (JSONObject) message;
else if (message instanceof String)
try {
return (JSONObject) JSONValue.parseWithException((String) message);
} catch (ParseException e) {
throw new RuntimeException("json parsing exception : " + message, e);
}
else
throw new IllegalArgumentException("not support json type. " + message.getClass());
}
public static JSONArray parseJsonArray(Object message) {
if (message == null) {
return new JSONArray();
}
if (message instanceof JSONArray)
return (JSONArray) message;
else if (message instanceof String)
try {
return (JSONArray) JSONValue.parseWithException((String) message);
} catch (ParseException e) {
throw new RuntimeException("json parsing exception : " + message, e);
}
else
throw new IllegalArgumentException("not support json type. " + message.getClass());
}
@SuppressWarnings("unchecked")
public static Set<Map.Entry<String, Object>> getField(JSONObject json) {
return json.entrySet();
}
public static JSONObject getChildJson(JSONObject json, String childName) {
return (JSONObject)json.get(childName);
}
@SuppressWarnings("unchecked")
public static void setChild(JSONObject json, String childName, Object value) {
json.put(childName, value);
}
@SuppressWarnings("unchecked")
public static void setChildWithPath(JSONObject json, String path, Object value) {
String[] keys = path.split("\\.");
JSONObject current = json;
for(int i = 0; i < keys.length - 1; i++) {
current = (JSONObject) current.get(keys[i]);
if(current == null)
return;
}
current.put(keys[keys.length - 1], value);
}
/**
* json array의 String 값의 ',' 구분자
* @param jsonArray
* @return
*/
public static String toString(JSONArray jsonArray) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for(Object ele : jsonArray ) {
if(ele instanceof String || ele instanceof Number) {
if(first)
first = false;
else
sb.append(",");
sb.append(ele);
}
else
throw new IllegalArgumentException("not support type - " + ele.getClass().getName());
}
return sb.toString();
}
public static String toString2(JSONArray jsonArray) {
String strJsonArray = jsonArray.toJSONString();
strJsonArray = replace(strJsonArray);
return strJsonArray;
}
private static String replace(String strJsonArray) {
strJsonArray = StringUtils.replace(strJsonArray, "\"", "");
strJsonArray = StringUtils.replace(strJsonArray, "[", "");
strJsonArray = StringUtils.replace(strJsonArray, "]", "");
return strJsonArray;
}
public static JSONArray getJSONArray(JSONObject jsonObject, String path) {
if(jsonObject == null || path == null || path.isEmpty())
return null;
String[] keys = path.split("\\.");
JSONObject current = jsonObject;
for(int i = 0; i < keys.length - 1; i++) {
current = (JSONObject) current.get(keys[i]);
if(current == null)
return null;
}
return (JSONArray)current.get(keys[keys.length - 1]);
}
public static String getStringWithPath(JSONObject jsonObject, String path) {
if(jsonObject == null || path == null || path.isEmpty())
return null;
String[] keys = path.split("\\.");
JSONObject current = jsonObject;
for(int i = 0; i < keys.length - 1; i++) {
current = (JSONObject) current.get(keys[i]);
if(current == null)
return null;
}
return (String)current.get(keys[keys.length - 1]);
}
public static String removeStringWithPath(JSONObject jsonObject, String path) {
if(jsonObject == null || path == null || path.isEmpty())
return null;
String[] keys = path.split("\\.");
JSONObject current = jsonObject;
for(int i = 0; i < keys.length - 1; i++) {
current = (JSONObject) current.get(keys[i]);
if(current == null)
return null;
}
return (String)current.remove(keys[keys.length - 1]);
}
public static void removeEmptyWithPath(JSONObject jsonObject, String path) {
if(jsonObject == null || path == null || path.isEmpty())
return;
String[] keys = path.split("\\.");
JSONObject current = jsonObject;
for(int i = 0; i < keys.length - 1; i++) {
current = (JSONObject) current.get(keys[i]);
if(current == null)
return;
}
String value = (String) current.get(keys[keys.length - 1]);
if (StringUtils.isEmpty(value)) {
current.remove(keys[keys.length - 1]);
return;
} else {
return;
}
}
protected static com.eactive.eai.common.util.Logger logger = com.eactive.eai.common.util.Logger
.getLogger(com.eactive.eai.common.util.Logger.LOGGER_DEFAULT);
public static void removeEmptyWithPath2(JSONObject jsonObject, String path) {
if (jsonObject == null || path == null || path.isEmpty())
return;
String[] keys = path.split("\\.");
Object current = jsonObject;
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
if (current instanceof JSONObject) {
JSONObject currentObj = (JSONObject) current;
if (currentObj.get(key) == null) {
if (i == keys.length - 1) {
// 마지막
currentObj.remove(key);
return;
} else {
logger.debug("경로를 찾을 수 없음. {} of {}", path, key);
return;
}
} else {
if (i == keys.length - 1) {
if (currentObj.get(key) instanceof String && StringUtils.isEmpty((String)currentObj.get(key))) {
// 마지막
currentObj.remove(key);
return;
} else {
logger.debug("경로 값이 빈값이 아님. {} of {}'s value [{}]", path, key, currentObj.get(key));
return;
}
} else {
current = currentObj.get(key);
}
}
} else if (current instanceof JSONArray) {
JSONArray currentArray = (JSONArray) current;
for (int j = 0; j < currentArray.size(); j++) {
Object arrayElement = currentArray.get(j);
if (arrayElement instanceof JSONObject) {
List<String> lastPath = new ArrayList<>();
for (int k = i; k < keys.length; k++)
lastPath.add(keys[k]);
removeEmptyWithPath2((JSONObject) arrayElement, String.join(".", lastPath));
}
}
}
}
}
public static void removeZeroWithPath2(JSONObject jsonObject, String path) {
if (jsonObject == null || path == null || path.isEmpty())
return;
String[] keys = path.split("\\.");
Object current = jsonObject;
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
if (current instanceof JSONObject) {
JSONObject currentObj = (JSONObject) current;
if (currentObj.get(key) == null) {
if (i == keys.length - 1) {
logger.debug("경로 값이 null. {} of {}", path, key);
return;
} else {
logger.debug("경로를 찾을 수 없음. {} of {}", path, key);
return;
}
} else {
if (i == keys.length - 1) {
if (isZero(key, currentObj)) {
// 마지막
currentObj.remove(key);
return;
} else {
logger.debug("경로 값이 0이 아님. {} of {}'s value [{}]", path, key, currentObj.get(key));
return;
}
} else {
current = currentObj.get(key);
}
}
} else if (current instanceof JSONArray) {
JSONArray currentArray = (JSONArray) current;
for (int j = 0; j < currentArray.size(); j++) {
Object arrayElement = currentArray.get(j);
if (arrayElement instanceof JSONObject) {
List<String> lastPath = new ArrayList<>();
for (int k = i; k < keys.length; k++)
lastPath.add(keys[k]);
removeZeroWithPath2((JSONObject) arrayElement, String.join(".", lastPath));
}
}
}
}
}
private static final Pattern numberPattern = Pattern.compile("[+-]?\\d*(\\.\\d+)?");
private static boolean isZero(String key, JSONObject currentObj) {
Object value = currentObj.get(key);
if (value instanceof Number && ((Number) value).doubleValue() == 0) {
return true;
} else if (value instanceof String && StringUtils.isNotEmpty((String) value)
&& numberPattern.matcher((String) value).matches()) {
try {
return Double.parseDouble((String) value) == 0.0;
} catch (NumberFormatException e) {
logger.debug("숫자 형식이 아님. [{}]", value);
return false;
}
}
return false;
}
/**
* (문자열)'"aaa", "bbb", "ccc"' -> (json array)["aaa", "bbb", "ccc"]
* @param doublequotedSeperatedStr
* @return
*/
public static JSONArray createJSONArray(String doublequotedSeperatedStr) {
return (JSONArray) JSONValue.parse("[" + doublequotedSeperatedStr + "]");
}
@SuppressWarnings("unchecked")
public static JSONArray createJSONArray(String[] strArray ) {
JSONArray jsonArray = new JSONArray();
for(String str : strArray) {
jsonArray.add(str);
}
return jsonArray;
}
@SuppressWarnings("unchecked")
public static JSONArray createJSONArray(List<String> strList ) {
JSONArray jsonArray = new JSONArray();
for(String str : strList) {
jsonArray.add(str);
}
return jsonArray;
}
@SuppressWarnings("unchecked")
public static JSONObject replaceSpacesWith(JSONObject json, String plus) {
if (json == null || plus == null || plus.isEmpty())
return json;
JSONObject result = new JSONObject();
for (Object key : json.keySet()) {
Object value = json.get(key);
if(value instanceof String) {
result.put(key, ((String)value).replace(" ", plus));
} else if (value instanceof JSONObject) {
result.put(key, replaceSpacesWith((JSONObject)value, plus));
} else if ( value instanceof JSONArray) {
result.put(key, replaceSpacesInArrayWith((JSONArray)value, plus));
} else {
result.put(key, value);
}
}
return result;
}
@SuppressWarnings("unchecked")
public static JSONArray replaceSpacesInArrayWith(JSONArray array, String plus) {
JSONArray result = new JSONArray();
for(int i =0; i < array.size(); i++) {
Object value = array.get(i);
if(value instanceof String) {
result.add(((String)value).replace(" ", plus));
} else if(value instanceof JSONObject) {
result.add(replaceSpacesWith((JSONObject)value, plus));
} else if(value instanceof JSONArray) {
result.add(replaceSpacesInArrayWith((JSONArray)value, plus));
} else {
result.add(value);
}
}
return result;
}
@SuppressWarnings("unchecked")
public static JSONObject stripTrailingZeros(JSONObject json, String exceptionHeaderName) {
if (json == null)
return json;
JSONObject result = new JSONObject();
for (Object key : json.keySet()) {
Object value = json.get(key);
if(value instanceof Number) {
BigDecimal bd = new BigDecimal(value.toString());
BigDecimal stripped = bd.stripTrailingZeros();
if(stripped.scale() <= 0)
result.put(key, stripped.intValue());
else
result.put(key, stripped);
} else if (value instanceof JSONObject) {
result.put(key, stripTrailingZeros((JSONObject)value, exceptionHeaderName));
} else if ( value instanceof JSONArray) {
result.put(key, stripTrailingZerosInArrayWith((JSONArray)value, exceptionHeaderName));
} else {
result.put(key, value);
}
}
return result;
}
@SuppressWarnings("unchecked")
public static JSONArray stripTrailingZerosInArrayWith(JSONArray array, String exceptionHeaderName) {
JSONArray result = new JSONArray();
for(int i =0; i < array.size(); i++) {
Object value = array.get(i);
if(value instanceof Number) {
BigDecimal bd = new BigDecimal(value.toString());
BigDecimal stripped = bd.stripTrailingZeros();
if(stripped.scale() <= 0)
result.add(stripped.intValue());
else
result.add(stripped);
} else if(value instanceof JSONObject) {
result.add(stripTrailingZeros((JSONObject)value, exceptionHeaderName));
} else if(value instanceof JSONArray) {
result.add(stripTrailingZerosInArrayWith((JSONArray)value, exceptionHeaderName));
} else {
result.add(value);
}
}
return result;
}
}
@@ -412,9 +412,16 @@ public final class MessageUtil {
if ("RECEAIAHA022".equals(oldErrCode)) { // UUID가 같지 않은 에러
return STDMessageErrorKeys.UUID_MISMATCH_ERROR_CODE_VALUE;
}
if ("RECEAIIRP070".equals(oldErrCode)) { // 거래제한
return STDMessageErrorKeys.RESTRICT_ERROR_CODE_VALUE;
}
if ("RECEAIIRP072".equals(oldErrCode)) { // 유량제어 초과 에러
return STDMessageErrorKeys.RATELIMIT_ERROR_CODE_VALUE;
}
if ("RECEAIOHP201".equals(oldErrCode)) { // CIRCUITBREAKER 초과 에러
return STDMessageErrorKeys.RATELIMIT_ERROR_CODE_VALUE;
}
if (EAIServiceMonitor.getInstance().isTimeOutCodes(oldErrCode)){ //TIMEOUT일경우
return STDMessageErrorKeys.TIMEOUT_ERROR_CODE_VALUE;
}else if (EAIServiceMonitor.getInstance().isBizErrorCodes(oldErrCode)){ //업무오류일경우
@@ -445,9 +452,15 @@ public final class MessageUtil {
if ("RECEAIAHA022".equals(oldErrCode)) { // UUID가 같지 않은 에러
return STDMessageErrorKeys.UUID_MISMATCH_ERROR_MSG_VALUE;
}
if ("RECEAIIRP070".equals(oldErrCode)) { // 거래제한
return STDMessageErrorKeys.RESTRICT_ERROR_MSG_VALUE;
}
if ("RECEAIIRP072".equals(oldErrCode)) { // 유량제어 초과 에러
return STDMessageErrorKeys.RATELIMIT_ERROR_MSG_VALUE;
}
if ("RECEAIOHP201".equals(oldErrCode)) { // CIRCUITBREAKER 초과 에러
return STDMessageErrorKeys.RATELIMIT_ERROR_MSG_VALUE;
}
if (EAIServiceMonitor.getInstance().isTimeOutCodes(oldErrCode) ){ //TIMEOUT일경우
return STDMessageErrorKeys.TIMEOUT_ERROR_MSG_VALUE;
}else if (EAIServiceMonitor.getInstance().isBizErrorCodes(oldErrCode)){ //업무오류일경우
@@ -455,7 +468,32 @@ public final class MessageUtil {
}else{
return STDMessageErrorKeys.SYSTEM_ERROR_MSG_VALUE;
}
}
}
public static String getTobeDesc(String oldErrCode){
if ("".equals(oldErrCode)){
return STDMessageErrorKeys.SYSTEM_ERROR_DESC_VALUE;
}
if ("RECEAIIRP020,RECEAIIRP120,RECEAIIRP110".indexOf(oldErrCode) >= 0 ){//유효값 오류
return STDMessageErrorKeys.VALID_VALUE_ERROR_DESC_VALUE;
}
if ("RECEAIIRP070".equals(oldErrCode)) { // 거래제한
return STDMessageErrorKeys.RESTRICT_ERROR_DESC_VALUE;
}
if ("RECEAIIRP072".equals(oldErrCode)) { // 유량제어 초과 에러
return STDMessageErrorKeys.RATELIMIT_ERROR_DESC_VALUE;
}
if ("RECEAIOHP201".equals(oldErrCode)) { // CIRCUITBREAKER 초과 에러
return STDMessageErrorKeys.RATELIMIT_ERROR_DESC_VALUE;
}
if (EAIServiceMonitor.getInstance().isTimeOutCodes(oldErrCode) ){ //TIMEOUT일경우
return STDMessageErrorKeys.TIMEOUT_ERROR_DESC_VALUE;
}else if (EAIServiceMonitor.getInstance().isBizErrorCodes(oldErrCode)){ //업무오류일경우
return STDMessageErrorKeys.BIZ_ERROR_DESC_VALUE;
}else{
return STDMessageErrorKeys.SYSTEM_ERROR_DESC_VALUE;
}
}
// TODO json tunning 요소 다시 확인해봐야됨(불필요한 파싱)
public static String addJsonRootItem(String jsonString, String rootItemName) {
@@ -0,0 +1,15 @@
package com.eactive.eai.common.util;
import java.util.Properties;
public class TxFileLogger {
public static void logTxFile(Properties transactionProp, Object message, String prefix) {
Logger siftLogger = Logger.getLogger(Logger.LOGGER_SIFT);
if (siftLogger.isInfoEnabled()) {
siftLogger.info(prefix + message);
if (siftLogger.isDebugEnabled())
siftLogger.debug(CommonLib.getDumpMessage(message));
}
}
}
@@ -0,0 +1,31 @@
package com.eactive.eai.common.util;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
public class TxSiftContext {
/**
* 거래 시작 SIFT discriminator를 MDC에 등록한다.
* SIFT가 비활성화 상태이면 아무 작업도 하지 않는다.
*/
public static boolean begin(String discriminatingValue) {
if (!Logger.isSiftEnabled()) return false;
if(MDC.get(Logger.DISCRIMINATOR) == null) {
MDC.put(Logger.DISCRIMINATOR, discriminatingValue);
return true;
}
return false;
}
/**
* 거래 종료 MDC에 등록된 SIFT appender를 닫고 MDC를 정리한다.
* begin() 에서 MDC가 설정되지 않았으면 아무 작업도 하지 않는다.
*/
public static void end() {
String discriminatingValue = MDC.get(Logger.DISCRIMINATOR);
if (StringUtils.isBlank(discriminatingValue)) return;
Logger.closeSiftAppender(discriminatingValue);
MDC.remove(Logger.DISCRIMINATOR);
}
}
@@ -0,0 +1,93 @@
package com.eactive.eai.common.util;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class XMLUtils {
private XMLUtils() {
throw new IllegalStateException("Utility class");
}
public static Document convertXmlDocument(Object message) throws DocumentException {
if (message == null)
return DocumentHelper.createDocument();
else if (message instanceof Document)
return (Document) message;
else if (message instanceof String) {
SAXReader builder = new SAXReader();
return builder.read(new StringReader((String)message));
} else
throw new IllegalArgumentException("not support xml type. " + message.getClass());
}
public static List<Element> getChilds(Element parent) {
List<Element> elements = new ArrayList<>();
Iterator<Element> iterator = parent.elementIterator();
while(iterator.hasNext()) {
Element child = iterator.next();
elements.add(child);
elements.addAll(getChilds(child));
}
return elements;
}
public static List<Element> getChildsLevel1(Element parent) {
List<Element> elements = new ArrayList<>();
Iterator<Element> iterator = parent.elementIterator();
while(iterator.hasNext()) {
Element child = iterator.next();
elements.add(child);
}
return elements;
}
@SuppressWarnings("unchecked")
public static Object elementToJSONObject(Element element) {
JSONObject json = new JSONObject();
List<Element> children = element.elements();
if(children.isEmpty()) {
return element.getText();
}
for(Element child : children) {
String childName = child.getName();
if(json.containsKey(childName)) {
Object existing = json.get(childName);
if(existing instanceof JSONArray) {
((JSONArray) existing).add(elementToJSONObject(child));
} else {
JSONArray list = new JSONArray();
list.add(existing);
list.add(elementToJSONObject(child));
json.put(childName, list);
}
} else {
json.put(childName, elementToJSONObject(child));
}
}
return json;
}
public static Element getElement(Element parent, String path) {
String[] keys = path.split("\\.");
Element current = parent;
for(String key : keys) {
current = current.element(key);
}
return (Element) current.elements().get(0);
}
}
@@ -1,5 +1,6 @@
package com.eactive.eai.inbound.error;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -8,6 +9,7 @@ import com.eactive.eai.agent.encryption.EncryptionManager;
import com.eactive.eai.common.dao.BaseDAO;
import com.eactive.eai.common.dao.DAOException;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.logger.EAIDBLogControl;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.ScpDbAgentUtil;
@@ -28,38 +30,6 @@ public class InboundErrorInfoDAO extends BaseDAO {
private InboundErrorInfoMapper inboundErrorInfoMapper;
public void addInboundErrorInfo(InboundErrorInfoVO vo) throws DAOException {
EncryptionManager manager = EncryptionManager.getInstance();
String bzwkDataCtnt = vo.getBwkDataTxt();
// 업무데이터를 암호화할 경우 길이가 늘어나는 문제가 있으므로 double check
if ("Y".equalsIgnoreCase(manager.getEncryptYN())) {
if (bzwkDataCtnt.length() > 2000) {
bzwkDataCtnt = StringUtil.chunkString(bzwkDataCtnt, 2000);
}
String encbzwkDataCtnt = null;
try {
encbzwkDataCtnt = ScpDbAgentUtil.ScpEncB64(
PropManager.getInstance().getProperties(ScpDbAgentUtil.EXT_MODULE), bzwkDataCtnt, "EUC-KR");
} catch (Exception e) {
logger.error("addInboundErrorInfo] scpdb_agent encryption Error - " + e.getMessage());
}
// double check
if (encbzwkDataCtnt != null && encbzwkDataCtnt.length() > 4000) {
bzwkDataCtnt = StringUtil.chunkString(bzwkDataCtnt, 500);
try {
encbzwkDataCtnt = ScpDbAgentUtil.ScpEncB64(
PropManager.getInstance().getProperties(ScpDbAgentUtil.EXT_MODULE), bzwkDataCtnt, "EUC-KR");
bzwkDataCtnt = encbzwkDataCtnt;
} catch (Exception e) {
logger.error("addInboundErrorInfo] scpdb_agent encryption Error - " + e.getMessage());
}
}
} else {
bzwkDataCtnt = StringUtil.chunkString(bzwkDataCtnt, 4000);
}
try {
vo.setErrTm((vo.getErrTm()).substring(0, 16));
vo.setErrTxt(StringUtil.chunkString(vo.getErrTxt(), 4000));
@@ -74,9 +44,23 @@ public class InboundErrorInfoDAO extends BaseDAO {
}
vo.setBzwkSvcKeyName(bizKey);
String bzwkDataCtnt = vo.getBwkDataTxt();
// db 암호화
bzwkDataCtnt = EncryptionManager.getInstance().encryptDBData(bzwkDataCtnt);
vo.setBwkDataTxt(bzwkDataCtnt);
if (!EAIDBLogControl.isEnable()) {
logger.warn("DB logging is disabled.");
logger.error(vo.toString());
return;
}
String errCode = StringUtils.substring(vo.getErrCd(), 0, 12);
vo.setErrCd(errCode);
InboundErrorInfo entity = inboundErrorInfoMapper.toEntity(vo);
inboundErrorInfoLoader.save(entity);
} catch (Exception e) {
} catch (Throwable e) {
logger.error(e);
throw new DAOException(ExceptionUtil.getErrorCode(e, "RECEAIIEI001"), e);
}
@@ -4,9 +4,6 @@ import java.util.Calendar;
import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.context.ElinkTransactionContext;
import com.eactive.eai.common.util.*;
@@ -25,7 +22,6 @@ import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.common.bizkey.BizKeyManager;
import com.eactive.eai.common.bizkey.BizKeyVO;
import com.eactive.eai.common.dao.DAOFactory;
import com.eactive.eai.common.errorcode.ErrorCodeHandler;
import com.eactive.eai.common.exception.ExceptionHandler;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.inflow.Bucket;
@@ -59,9 +55,6 @@ import com.eactive.eai.message.service.InterfaceMapper;
import com.eactive.eai.util.SimHeaderUtil;
import com.ext.eai.common.stdmessage.STDMessageKeys;
import org.json.simple.JSONObject;
import java.util.HashMap;
import java.util.Map;
//import org.apache.commons.lang3.StringUtils;
//import org.springframework.http.HttpStatus;
@@ -129,8 +122,8 @@ public class RequestProcessor extends RequestProcessorSupport {
String testMasterYn = adptGrpVO.getTestMasterYn();
// UUID 생성 : UUID에서 - 없는 32자리
String uuid = "";
uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
uuid = uuid == null ? UUIDGenerator.getUUID().toString().replaceAll("-", "") : uuid;
// UUID 생성 : UUID = server구분4자리 + UUID
/*
String uuid = "";
@@ -464,7 +457,9 @@ public class RequestProcessor extends RequestProcessorSupport {
logUnknownMessage(vo,orgMessage);
return getInboundErrorResponse(prop, standardMessage, mapper, vo.getRspErrorCode(), vo.getRspErrorMsg(), inboundCharset);
}
eaiMsg.setCallProp(prop);
// set standardMessage & mapper to EAIMessage
eaiMsg.setStandardMessage(standardMessage);
eaiMsg.setMapper(mapper);
@@ -475,7 +470,9 @@ public class RequestProcessor extends RequestProcessorSupport {
if(svrLogLevel >= 1) {
if (esbLogger.isInfo()) esbLogger.info("RequestProcessor] started... - EAI Server Log Level is INFO");
}
afterFoundEaiMessage(eaiMsg, vo, prop);
// 3-1. 수신메시지와 초기값을 EAIMessage에 설정한다.
// 메시지 수신시각 설정
eaiMsg.setMsgRcvTm(vo.getErrTm());
@@ -956,10 +953,7 @@ public class RequestProcessor extends RequestProcessorSupport {
// 거래통제 대상 거래일 경우 '1' 설정한다.
eaiMsg.setSvcTmEn(EAIMessageKeys.YES_FLAG);
vo.setRspErrorCode(EAIMessageKeys.EAI_BLOCKED_CODE);
vo.setRspErrorMsg(ExceptionUtil.make(vo.getRspErrorCode(), new String[]{vo.getEaiSvcCd()}));
// eaiMsg.setRspErrCd(vo.getRspErrorCode());
// eaiMsg.setRspErrMsg(vo.getRspErrorMsg());
vo.setRspErrorMsg(buildRestrictErrorMsg(eaiMsg, prop));
eaiMsg.setRspErr(vo.getRspErrorCode(),vo.getRspErrorMsg());
// 거래통제 로그에 로그생성
@@ -1033,9 +1027,8 @@ public class RequestProcessor extends RequestProcessorSupport {
Bucket bucket = InflowControlUtil.getBucket();
String blockedType = "";
// Client 유량제어 체크 clientId가 있는 경우에만
String clientBlockedType = checkClientInflow(bucket, clientId);
if (clientBlockedType != null) {
String customBlockedType = checkCustomInflow(bucket, eaiMsg, prop);
if (customBlockedType != null) {
blockedType += "C";
}
// Adapter 유량제어 체크
@@ -1066,7 +1059,7 @@ public class RequestProcessor extends RequestProcessorSupport {
InflowTargetVO inflowTargetVO = null;
InflowGroupVO inflowGroupVO = null;
if (blockedType.startsWith("C")) {
inflowTargetVO = resolveClientInflowThreshold(bucket, clientId);
inflowTargetVO = resolveCustomInflowThreshold(bucket, eaiMsg, prop);
} else if (blockedType.startsWith("A")) {
inflowTargetVO = bucket.getAdapterInflowThreashold(vo.getAdapterGroupName());
} else if (blockedType.contains("G")) {
@@ -1089,7 +1082,7 @@ public class RequestProcessor extends RequestProcessorSupport {
}
} else if (inflowTargetVO != null) {
String targetBlockedType;
if (blockedType.startsWith("C")) targetBlockedType = clientBlockedType;
if (blockedType.startsWith("C")) targetBlockedType = customBlockedType;
else if (blockedType.startsWith("A")) targetBlockedType = adapterBlockedType;
else targetBlockedType = interfaceBlockedType;
inflowErrorMsg = buildInflowTargetErrorMsg(inflowTargetVO, targetBlockedType);
@@ -1257,9 +1250,10 @@ public class RequestProcessor extends RequestProcessorSupport {
if(svrLogLevel >= 1 && esbLogger.isInfo()) {
esbLogger.info(guidLogPrefix + " : FlowControl ROUTING_TYPE [" + prop.getProperty(RouteKeys.ROUTING_TYPE) + "]");
}
beforeRoute(eaiMsg, prop);
retEaiMsg = FlowRouter.process(eaiMsg, prop);
retEaiMsg.setCallProp(prop);
// 응답메시지에 Error Code기 설정되어 있을 경우
// ExceptionHandler를 통해 응답메시지를 생성한다.
if(retEaiMsg != null && !MessageUtil.checkRspErrCd(retEaiMsg.getRspErrCd())) {
@@ -1285,8 +1279,9 @@ public class RequestProcessor extends RequestProcessorSupport {
throw new RequestProcessorException(vo.getRspErrorMsg());
}
}
// TODO : StandardMessage -> SubMessage
afterRoute(eaiMsg, retEaiMsg, prop);
// TODO : StandardMessage -> SubMessage
if(Keys.IF_SUBSTANDARD.equals(vo.getStdMsgTypeCode())) {
StandardMessage stdMessage = retEaiMsg.getStandardMessage();
try {
@@ -1398,12 +1393,15 @@ public class RequestProcessor extends RequestProcessorSupport {
}
}
// 클라이언트 유량제어 체크 기본 구현은 체크 없음, DJErpRequestProcessor가 오버라이드
protected String checkClientInflow(Bucket bucket, String clientId) {
protected void afterFoundEaiMessage(EAIMessage eaiMsg, ProcessVO vo, Properties prop) {
}
protected String checkCustomInflow(Bucket bucket, EAIMessage eaiMsg, Properties prop) {
return null;
}
protected InflowTargetVO resolveClientInflowThreshold(Bucket bucket, String clientId) {
protected InflowTargetVO resolveCustomInflowThreshold(Bucket bucket, EAIMessage eaiMsg, Properties prop) {
return null;
}
@@ -1420,6 +1418,14 @@ public class RequestProcessor extends RequestProcessorSupport {
return String.format("API 호출 한도 초과 [%s]", inflowTargetVO.getName());
}
protected String buildRestrictErrorMsg(EAIMessage eaiMsg, Properties prop) {
return ExceptionUtil.make(EAIMessageKeys.EAI_BLOCKED_CODE, new String[]{eaiMsg.getEAISvcCd()});
}
protected void beforeRoute(EAIMessage eaiMsg, Properties prop) {}
protected void afterRoute(EAIMessage reqMsg, EAIMessage retMsg, Properties prop) {}
protected String getInboundErrorResponse(Properties prop, StandardMessage standardMessage, InterfaceMapper mapper
, String errCode, String errMsg, String charset) {
String errorResponse = null;
@@ -18,6 +18,9 @@ import com.eactive.eai.message.filter.MessageFilter;
public class StandardMessage extends StandardItem {
private static final long serialVersionUID = 8250470942348479859L;
static Logger logger = LoggerFactory.getLogger(StandardMessage.class);
String eaiSvcCd = null;
int readPosition = 0;
private String serviceKey = null;
@@ -30,6 +33,14 @@ public class StandardMessage extends StandardItem {
private MessageFilter flatFilter = null;
public String getEaiSvcCd() {
return eaiSvcCd;
}
public void setEaiSvcCd(String eaiSvcCd) {
this.eaiSvcCd = eaiSvcCd;
}
public String getBizDataCharset() {
return bizDataCharset;
}
@@ -51,6 +51,7 @@ public class StandardMessageUtil {
// API 서비스일경우 PathVariable 지원 추가
// ex) /api/account/{no}
if (StringUtils.equalsAny(vo.getActionName(), REST_URL_ACTION0, REST_URL_ACTION1, REST_URL_ACTION2)) {
logger.debug("getSTDMessageForPathVariable call");
standardMessage = stdMessageManager.getSTDMessageForPathVariable(vo.getSelectedKey());
}
@@ -61,6 +62,7 @@ public class StandardMessageUtil {
standardMessage.setBizData(message, charset);
standardManager.getMessageCoordinator().coordinateAfterCreateMessageByRule(standardMessage, vo, prop);
logger.debug("coordinateAfterCreateMessageByRule called");
return standardMessage;
}
@@ -282,7 +284,7 @@ public class StandardMessageUtil {
if (item.getLevel() == curItem.getLevel() + 1) {
if (curItem.getType() == StandardType.GRID) {
curItem.addArrayItem(0,
setRecursiveItems(message, itemList, i, getFullPath(parentPath, curItem.getName())));
setRecursiveItems(message, itemList, i, getFullPath(parentPath, curItem.getName() + "[0]")));
} else {
curItem.addItem(
setRecursiveItems(message, itemList, i, getFullPath(parentPath, curItem.getName())));
@@ -334,6 +334,8 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
}
if(logger.isDebug()) logger.debug("mapperClass : {}", mapper.getClass().getCanonicalName());
standardMessage.setBizDataPath(mapper.getPath(InterfaceMapper.BIZ_DATA));
String versionMapperClass = config.getProperty(VERSION_MAPPER);
if(StringUtils.isEmpty(versionMapperClass)) {
versionMapperClass = "com.eactive.eai.message.mapper.DefaultMessageMapper";

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