80 Commits

Author SHA1 Message Date
curry772 61c9aa9864 REST, HTTP 어댑터 재처리 기능 보완 2026-07-13 13:25:38 +09:00
curry772 a515d0f8b0 디버그 로그 필터 추가 2026-07-10 09:45:15 +09:00
curry772 b389ef2962 REST Header 로그에 송신 Body 출력할 수 있는 기능 추가 2026-07-09 16:24:42 +09:00
curry772 c15e52d205 UnkownMessageLogUtil 클래스명 오타 수정 2026-07-09 14:55:04 +09:00
curry772 2829d4362c 파라미터 메소드 생성 assignGetParam, assignRequestHeaders 파라미터 추가 2026-07-09 14:14:27 +09:00
curry772 5a9225e93e 로그 항목 수정 2026-07-09 14:08:11 +09:00
curry772 815a064cd9 제주은행 EAI와 응답 시 데이터부가 없을 경우(API GW 오류 발생), 500으로 리턴되도록 수정 2026-07-09 11:08:58 +09:00
curry772 5a943e3399 인코딩 오류 수정 2026-07-08 12:26:20 +09:00
curry772 3090f745ba weblogic 재배포 대응 2026-07-07 11:24:07 +09:00
curry772 af6b84f57d HSM 장애 대응 기능 개발 2026-07-06 09:22:38 +09:00
curry772 90633b3d26 JsonNode 사용방식으로 변경 2026-07-03 09:00:32 +09:00
curry772 822072147e 수신 시스템이 비표준일 경우, 오류 메시지를 표준메시지헤더에서 가져오지 않도록 수정 2026-07-03 08:59:06 +09:00
curry772 3527884e31 내부 ObjectMapper 추가 2026-07-03 08:57:42 +09:00
curry772 8df8a73175 hsm keystore reload 주기 설정 추가 2026-07-03 08:57:09 +09:00
curry772 5d9676b504 수신 시각이 프러퍼티에 있을 경우 그 값을 사용하도록 수정 2026-07-03 08:55:37 +09:00
curry772 40f496cfba 암복호화 기능 수정 2026-06-30 16:24:46 +09:00
curry772 efc06e6023 hsm 키 추가 2026-06-30 09:43:10 +09:00
curry772 a923ff3f9f HSM, 암호화 관련 테스트 수행 중 수정 2026-06-29 13:16:39 +09:00
curry772 ab9b86e7cf DAMO 라이브러리 업데이트 2026-06-25 10:00:33 +09:00
curry772 d78e422368 PreFilter에서 헤더복사하도록 수정 2026-06-25 09:56:23 +09:00
curry772 3cc66fcaa8 JacksonUtil 개발 2026-06-25 09:56:01 +09:00
curry772 73ed49372a ERP용 token 발급 개발 2026-06-25 09:55:27 +09:00
curry772 3574ad9e0c damo 테스트 추가 2026-06-23 09:55:50 +09:00
curry772 49eb8fbe85 API에 scope이 적용되어 있지 않은 경우, Client에 API 적용여부를 확인하도록 수정 2026-06-23 09:55:20 +09:00
curry772 253716f4b2 암복호화 적용 2026-06-23 09:54:07 +09:00
curry772 02e282c7ba Bypass REST Outbound Adapter 이동 2026-06-23 09:46:54 +09:00
curry772 180ea8cac1 거래차단 거래 503 Service unavailable throw 하도록 수정 2026-06-23 09:33:14 +09:00
curry772 2c21c691df UnkownMessageLogUtils.logUnkownMessage 로그 위치 변경 2026-06-23 09:23:17 +09:00
curry772 0da31a8de2 OAuthServer 프러퍼티 그룹 암호화 적용 2026-06-23 08:54:00 +09:00
curry772 46d88d77f2 필터 에러코드 추가 2026-06-19 18:00:31 +09:00
curry772 51967284ba api 찾지 못할 경우 FilterException throw로 변경 2026-06-19 18:00:13 +09:00
curry772 a218291748 rest outbound adapter에서 통신오류가 아닌 경우에만, adaptererrorhandler를 호출하도록 수정 2026-06-19 11:21:22 +09:00
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
117 changed files with 9804 additions and 2985 deletions
+3
View File
@@ -135,11 +135,14 @@ 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'
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.15'
testImplementation 'junit:junit:4.4'
testImplementation files('libs/damo-manager.jar', 'libs/kjb-safedb.jar')
}
test {
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 인코딩된 엔티티를 복원 (예: # → #)
template = template.replace("#", "#")
.replace("&", "&")
.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;
}
@@ -11,7 +11,9 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
@@ -30,6 +32,7 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuil
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpResponseInterceptor;
@@ -40,6 +43,7 @@ import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.Keys;
import com.eactive.eai.adapter.http.secure.HttpClient5SSLContextFactory;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.logger.HttpAdapterExtraHeaderVo;
import com.eactive.eai.common.message.EAIMessageKeys;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
@@ -416,8 +420,24 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
logProcessNo = 200;
}
HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, logProcessNo,
contextAdapterGroupName, contextAdapterName, request.getHeaders(), url, request.getMethod());
Header[] headers = request.getHeaders();
List<HttpAdapterExtraHeaderVo> headerVoList = null;
if ( headers != null && headers.length != 0 ) {
headerVoList = HttpAdapterExtraLogUtil.convertHeaderToListOfHttpAdapterExtraHeaderVo(headers);
} else {
headerVoList = new ArrayList<HttpAdapterExtraHeaderVo>();
}
String trimedPostBody = (String) context.getAttribute(HttpAdapterExtraLogUtil.BODY_FIELD_NAME);
if(StringUtils.isNotEmpty(trimedPostBody)) {
HttpAdapterExtraHeaderVo vo = new HttpAdapterExtraHeaderVo();
vo.setName(HttpAdapterExtraLogUtil.BODY_FIELD_NAME);
vo.setValue(trimedPostBody);
headerVoList.add(vo);
}
HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, logProcessNo,
contextAdapterGroupName, contextAdapterName, headerVoList, url, request.getMethod(), 0);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
@@ -443,7 +463,7 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
}else{
logProcessNo += 100;
}
HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, logProcessNo,
contextAdapterGroupName, contextAdapterName, response.getHeaders(), url, request.getMethod(), response.getCode());
} catch (URISyntaxException e) {
@@ -570,8 +590,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);
}
}
@@ -88,10 +88,17 @@ public class DeadlineAwareRetryExecutor {
if (!policy.shouldRetryOnStatus(request, status)) {
// 성공 또는 재시도 불필요 → 호출부에서 close 책임
log.debug("non-retryable response status={}, attempt={}", status, attempt + 1);
log.info("non-retryable response status={}, attempt={}", status, attempt + 1);
return response;
}
// ④ 마지막 시도였으면 종료
if (attempt >= policy.getMaxRetries()) {
// 최대 재처리에 도달하여, 재시도하지 않고, 응답 객체를 리턴한다.
log.info("최대 재시도 횟수 도달 response status={}, attempt={}", status, attempt + 1);
return response;
}
// 재시도 대상
log.warn("retryable status={}, attempt={}/{}",
status, attempt + 1, policy.getMaxRetries());
@@ -127,9 +134,6 @@ public class DeadlineAwareRetryExecutor {
}
}
// ④ 마지막 시도였으면 바로 종료
if (attempt >= policy.getMaxRetries()) break;
// ⑤ 백오프 대기 전 남은 시간 재확인
long remainingBeforeWait = totalDeadlineMs - (System.currentTimeMillis() - startTime);
if (remainingBeforeWait <= policy.getBackoffMs()) {
@@ -171,10 +175,6 @@ public class DeadlineAwareRetryExecutor {
log.info("RequestConfig = {}", config);
}
private boolean isSuccess(int status) {
return status >= 200 && status < 300;
}
private void closeQuietly(CloseableHttpResponse response) {
try {
response.close();
@@ -7,11 +7,14 @@ 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;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
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;
import org.apache.hc.core5.http.HttpHost;
@@ -118,7 +121,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);
@@ -131,7 +136,15 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
byte[] responseMessage = null;
String responseString = "";
try (CloseableHttpResponse response = (CloseableHttpResponse) this.client.execute(method, context)) {
DeadlineAwareRetryExecutor executor =
new DeadlineAwareRetryExecutor((CloseableHttpClient)this.client,
vo.getConnectionTimeout(),
vo.getTimeout());
// Properties에서 RetryPolicy 로드
RetryPolicy policy = RetryPolicy.from(prop); // 설정 로드 방식에 맞게
try (CloseableHttpResponse response = (CloseableHttpResponse) executor.execute(method, context, policy)) {
status = response.getCode();
responseMessage = EntityUtils.toByteArray(response.getEntity());
responseString = new String(responseMessage, vo.getEncode());
@@ -147,7 +160,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 +186,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) {
@@ -0,0 +1,679 @@
package com.eactive.eai.adapter.http.client.impl;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.springframework.http.HttpHeaders;
import org.springframework.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.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;
}
}
@@ -9,6 +9,9 @@ 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;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* 1. 기능 : HttpClient5AdapterServiceRest 호출 전후 Filter 적용할 수 있는 기능을 제공한다.<br>
@@ -34,6 +37,8 @@ public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterSe
// // Adapter에서 Exception이 발생한 경우, 처리할 필터
// static final String EXCEPTION_FILTER = "EXCEPTION_FILTER";
private ObjectMapper mapper = new ObjectMapper();
/**
* 1. 기능 : HttpClient 호출 전후 Filter 적용용 2. 처리 개요 : <br>
* 3. 주의사항 <br>
@@ -54,8 +59,9 @@ public class HttpClient5AdapterServiceRestAddFilter extends HttpClient5AdapterSe
adapterResponse = doPostFilters(adptGrpName, adptName, prop, adapterResponse, tempProp);
if (adapterResponse instanceof JSONObject)
adapterResponse = ((JSONObject) adapterResponse).toJSONString();
if (adapterResponse instanceof Node)
else if (adapterResponse instanceof ObjectNode)
adapterResponse = mapper.writeValueAsString((ObjectNode) adapterResponse);
else if (adapterResponse instanceof Node)
adapterResponse = ((Node) adapterResponse).asXML();
return adapterResponse;
@@ -65,7 +65,7 @@ public class RetryPolicy {
return new RetryPolicy(getInt(props, RETRY_MAX_RETRIES, 0), getLong(props, RETRY_BACKOFF_MS, 1000L), codes,
getBoolean(props, RETRY_ON_CONNECT_FAIL, false), getBoolean(props, RETRY_ON_TIMEOUT, false),
getBoolean(props, RETRY_IDEMPOTENT_ONLY, true));
getBoolean(props, RETRY_IDEMPOTENT_ONLY, false));
}
// ----------------------------------------------------------------
@@ -0,0 +1,33 @@
package com.eactive.eai.adapter.http.client.impl.filter;
import java.util.Properties;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.common.util.Logger;
public class DebugLoggerOutFilter implements HttpClientAdapterFilter, HttpAdapterServiceKey {
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
@Override
public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp)
throws Exception {
logger.debug("Outbound Filter SND adptGrpName : {}", adptGrpName);
logger.debug("Outbound Filter SND adptName : {}", adptName);
logger.debug("Outbound Filter SND prop : {}", prop);
logger.debug("Outbound Filter SND message : {}", message);
logger.debug("Outbound Filter SND tempProp : {}", tempProp);
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
Properties tempProp) throws Exception {
logger.debug("Outbound Filter RCV adptGrpName : {}", adptGrpName);
logger.debug("Outbound Filter RCV adptName : {}", adptName);
logger.debug("Outbound Filter RCV prop : {}", prop);
logger.debug("Outbound Filter RCV message : {}", message);
logger.debug("Outbound Filter RCV tempProp : {}", tempProp);
return message;
}
}
@@ -6,7 +6,7 @@ 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.filter";
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<>();
@@ -19,21 +19,27 @@ import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
* STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
*
* 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
* AAD 관련 추가 프로퍼티:
* CRYPTO_AAD_PROP tempProp에서 AAD 값을 조회할 키 이름.
* AAD 관련 프로퍼티:
* PROP_AAD_HEADER tempProp에서 AAD 값을 조회할 키 이름.
* 미설정 또는 해당 키 없으면 aad=null → IV를 AAD 대체값으로 사용.
*/
public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientAdapterFilter {
public static final String PROP_AAD_PROP = "CRYPTO_AAD_PROP";
/**
* DYNAMIC 키 도출용 컨텍스트를 구성한다.
* STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
* 서브클래스에서 오버라이드하여 tempProp으로부터 값을 추출할 수 있다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, Properties tempProp) {
return new HashMap<>();
Map<String, String> context = new HashMap<>();
for (String key : prop.stringPropertyNames()) {
context.put(key, prop.getProperty(key));
}
for (String key : tempProp.stringPropertyNames()) {
context.put(key, tempProp.getProperty(key));
}
return context;
}
/**
@@ -41,11 +47,10 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA
* 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
*/
protected byte[] buildAad(Properties prop, Properties tempProp) {
String propKey = prop.getProperty(PROP_AAD_PROP);
if (StringUtils.isBlank(propKey) || tempProp == null) {
return null;
}
String value = tempProp.getProperty(propKey);
if (tempProp == null) {
return null;
}
String value = tempProp.getProperty(PROP_AAD_HEADER);
return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
}
@@ -61,8 +66,8 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA
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));
getProp(tempProp, PROP_ENC_FROM_PATH, PATH_ROOT),
getProp(tempProp, PROP_ENC_TO_PATH, PATH_ENCDATA));
}
return encryptBody(body, moduleName, runtimeCtx, aad);
}
@@ -79,8 +84,8 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA
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));
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,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,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.json.simple.JSONObject;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
@@ -16,7 +17,12 @@ import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilterFactory;
import com.eactive.eai.adapter.http.dynamic.filter.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;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class HttpAdapterServiceSupport implements HttpAdapterService, HttpAdapterServiceKey {
private static final String LOG_PREFIX = "HttpAdapterServiceSupport] ";
@@ -24,56 +30,82 @@ public abstract class HttpAdapterServiceSupport implements HttpAdapterService, H
public static final String HEADER_NAME_CLIENT_ID = "x-elink-client-id";
public static final String PROPERTIES_NAME_CLIENT_ID = "clientId";
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
private ObjectMapper mapper = new ObjectMapper();
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);
if (message instanceof JSONObject) {
message = ((JSONObject) message).toJSONString();
} else if (message instanceof JsonNode) {
message = mapper.writeValueAsString((JsonNode) message);
}
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 if (obj instanceof JSONObject) {
return ((JSONObject) obj).toJSONString();
} else if (obj instanceof JsonNode) {
return (JsonNode) obj;
} else {
throw new Exception("RECEAIAHA001");
}
} finally {
if(bMDCput)
TxSiftContext.end();
}
}
@@ -22,10 +22,10 @@ import com.eactive.eai.inbound.error.InboundErrorInfoVO;
import com.eactive.eai.inbound.error.InboundErrorKeys;
import com.eactive.eai.util.HexaConverter;
public class UnkownMessageLogUtils {
public class UnknownMessageLogUtils {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private UnkownMessageLogUtils() {
private UnknownMessageLogUtils() {
throw new IllegalStateException("Utility class");
}
@@ -49,7 +49,7 @@ public class UnkownMessageLogUtils {
StringBuffer sb = new StringBuffer();
sb.append(errorMsg).append("\n").append("Remote Addr : ").append(request.getRemoteAddr()).append("\n").append("Request URI : ")
.append(request.getRequestURI()).append("\n").append("clientId=").append(clientId).append(",hexClientId=").append(hexClientId).append(",txProp=").append(prop).append("\n").append("Exception : ").append(e.getMessage());
UnkownMessageLogUtils.logUnknownMessage(txId, adptGrpName, adptName,
UnknownMessageLogUtils.logUnknownMessage(txId, adptGrpName, adptName,
"", "", false, errCode, sb.toString(), System.currentTimeMillis(),
serverName, InboundErrorKeys.IN_UNKNOWN, message);
} catch (Exception ex) {
@@ -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;
}
@@ -24,6 +24,8 @@ import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.util.IOUtils;
import com.nimbusds.jwt.SignedJWT;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -93,17 +95,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);
@@ -135,26 +143,32 @@ public class ApiAuthFilter implements HttpAdapterFilter {
OAuth2Manager manager = OAuth2Manager.getInstance();
HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE);
if (scopeArr == null || scopeArr.length == 0) {
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL,
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
for (String scope : scopeArr) {
if (StringUtils.equals(scope, "oob") || StringUtils.equals(scope, "public")) {
isPassScope = true;
}
}
// API에 scope 설정이 안되어 있는 경우, scope 체크를 pass하고 나중에 client API 맵을 체크한다.
if(CollectionUtils.isEmpty(scopeSet))
isPassScope = true;
if (!isPassScope) {
if (!verifyScope(scopeSet, signedJWT)) {
if(!isPassScope) {
String[] scopeArr = signedJWT.getJWTClaimsSet().getStringArrayClaim(PAYLOAD_PARAM_NAME_SCOPE);
if (scopeArr == null || scopeArr.length == 0) {
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL,
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
for (String scope : scopeArr) {
if (StringUtils.equals(scope, "oob") || StringUtils.equals(scope, "public")) {
isPassScope = true;
}
}
if (!isPassScope) {
if (!verifyScope(scopeSet, signedJWT)) {
throw new JwtAuthException(ERROR_AUTHORIZATION_FAIL,
String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
}
}
}
// header 값으로 전송된 clientId와 토큰 소유 clientId check
if (!verifyClientId(prop, signedJWT)) {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "client_id not matched(header/token)");
@@ -0,0 +1,96 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.http.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 FilterException("path not found - " + requestPath, ERROR_PRE_FAIL, HttpStatus.FORBIDDEN.value());
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);
}
}
@@ -0,0 +1,33 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.eactive.eai.common.util.Logger;
public class DebugLoggerInFilter implements HttpAdapterFilter {
protected 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 {
logger.debug("Inbound Filter RCV adptGrpName : {}", adptGrpName);
logger.debug("Inbound Filter RCV adptName : {}", adptName);
logger.debug("Inbound Filter RCV message : {}", message);
logger.debug("Inbound Filter RCV prop : {}", prop);
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.debug("Inbound Filter SND adptGrpName : {}", adptGrpName);
logger.debug("Inbound Filter SND adptName : {}", adptName);
logger.debug("Inbound Filter SND resultMessage : {}", resultMessage);
logger.debug("Inbound Filter SND prop : {}", prop);
return resultMessage;
}
}
@@ -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();
}
@@ -7,6 +7,9 @@ import javax.servlet.http.HttpServletResponse;
public interface HttpAdapterFilter {
public static final String ERROR_PRE_FAIL = "E.PREFILTER_FAIL";
public static final String ERROR_POST_FAIL = "E.POSTFILTER_FAIL";
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception;
@@ -25,13 +25,19 @@ import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
*/
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {
/**
public static final String ERROR_DEC_FAIL = "E.DECRYPT_FAIL";
public static final String ERROR_ENC_FAIL = "E.ENCRYPT_FAIL";
/**
* DYNAMIC 도출용 컨텍스트를 구성한다.
* STATIC 모듈이면 Map({@code new HashMap<>()}) 반환한다.
* 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 있다.
*/
protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
return new HashMap<>();
Map<String, String> context = new HashMap<>();
for (String key : prop.stringPropertyNames()) {
context.put(key, prop.getProperty(key));
}
return context;
}
/**
@@ -43,7 +49,9 @@ public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterF
if (StringUtils.isBlank(headerName) || request == null) {
return null;
}
String headerValue = request.getHeader(headerName);
Properties inboundHeaderProp = (Properties)prop.get("INBOUND_HEADER");
String headerValue = inboundHeaderProp.getProperty(headerName);
// String headerValue = request.getHeader(headerName);
return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
}
@@ -51,13 +51,6 @@ public class ReflectAllHeaderFilter implements HttpAdapterFilter {
@Override
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.debug("doPreFilter ReflectAllHeaderFilter Start.");
String propValue = PropManager.getInstance().getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, "").trim();
@@ -89,6 +82,45 @@ public class ReflectAllHeaderFilter implements HttpAdapterFilter {
}
logger.debug("doPreFilter ReflectAllHeaderFilter End.");
return message;
}
@Override
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.debug("doPostFilter ReflectAllHeaderFilter Start.");
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);
}
try {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
if( StringUtils.equalsAnyIgnoreCase( headerName, HttpHeaderRelayBlackList ) ) {
logger.debug("Skip Processing Key ["+headerName+"], value ["+headerValue+"] in HttpHeaderRelayBlackList");
continue;
}
response.setHeader(headerName, headerValue);
}
} catch (Exception e) {
logger.error(e.getMessage());
}
logger.debug("doPostFilter ReflectAllHeaderFilter End.");
return resultMessage;
}
@@ -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;
}
}
@@ -1,5 +1,27 @@
package com.eactive.eai.adapter.http.dynamic.impl;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.mina.common.ByteBuffer;
import org.json.simple.JSONArray;
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.AdapterPropManager;
@@ -8,8 +30,8 @@ import com.eactive.eai.adapter.Keys;
import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.UnknownMessageLogUtils;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.util.CommonLib;
@@ -19,27 +41,6 @@ import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.env.ElinkConfig;
import com.eactive.eai.inbound.processor.Processor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.mina.common.ByteBuffer;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
{
public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";
@@ -63,7 +64,8 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
AdapterGroupVO adapterGroupVo = null;
Properties prop = null;
byte[] requestBytes = null;
String encode = "MS949";
try {
AdapterManager adapterManager = AdapterManager.getInstance();
AdapterVO adptVO = adapterManager.getAdapterVO(adptGrpName,adptName);
@@ -75,9 +77,9 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
}
Properties httpProp = manager.getProperties(adptVO.getPropGroupName());
encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(), "MS949");
String responseType = httpProp.getProperty(RESPONSE_TYPE,"SYNC");
String urlDecodeYn = httpProp.getProperty(URL_DECODE_YN,"N");
String encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(), "MS949");
String messageType = adapterManager.getAdapterGroupVO(adptGrpName).getMessageType();
String requestBodyYn = httpProp.getProperty(REQUEST_BODY_YN,"N");
String parameterName = httpProp.getProperty(PARAMETER_NAME,PARAMETER_MESSAGE);
@@ -104,7 +106,6 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
logger.info("시작 >> encode = [" + encode + "]");
byte[] requestBytes = null;
if ("Y".equals(requestBodyYn)){
ServletInputStream sis = request.getInputStream();
@@ -188,19 +189,19 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
prop.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod());
prop.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, ""));
String body = new String ( requestBytes,encode);
try {
Object root = JSONValue.parse(body);
if( root instanceof JSONArray ) {
JSONObject wrappedObject = new JSONObject();
wrappedObject.put("KJB_ROOTLESS_ARRAY", root);
requestBytes = wrappedObject.toJSONString().getBytes(encode);
}
}catch (Exception e) {
// ignore json이 아니기에 해줄것이 없음.
}
// String body = new String ( requestBytes,encode);
//
// try {
// Object root = JSONValue.parse(body);
// if( root instanceof JSONArray ) {
// JSONObject wrappedObject = new JSONObject();
// wrappedObject.put("DJB_ROOTLESS_ARRAY", root);
// requestBytes = wrappedObject.toJSONString().getBytes(encode);
// }
//
// }catch (Exception e) {
// // ignore json이 아니기에 해줄것이 없음.
// }
// 로컬 서비스 호출
Object result = null;
@@ -212,6 +213,11 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
stopWatch.stop();
String apiInternalErrorCode = prop.getProperty("API_INTERNAL_ERROR_CODE", "");
if(StringUtils.isNotEmpty(apiInternalErrorCode))
response.setStatus(500);
byte[] responseBytes = null;
String responseData = "";
if (RESPONSE_TYPE_ASYNC.equals(responseType)){
if (stopWatch.getTime() > slowTranTime){
@@ -222,17 +228,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 +251,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(encode);
if ( StringUtils.isBlank((String)result ) ) {
responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
responseBytes = responseData.getBytes(encode);
}
}
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+") = " +
@@ -316,7 +340,14 @@ public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
}
} catch (Exception e) {
if (traceLevel >= 3){
try {
UnknownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, requestBytes != null ? new String(requestBytes, encode) : null, prop, request, response,
"RECEAIIRP202", e);
} catch (UnsupportedEncodingException e1) {
UnknownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, requestBytes != null ? new String(requestBytes) : null, prop, request, response,
"RECEAIIRP202", e);
}
if (traceLevel >= 3){
HttpMemoryLogger.error(adptGrpName+adptName, e.toString(),e);
}
logger.error("HttpAdapter] "+adptGrpName+"-"+adptName, e);
@@ -66,7 +66,7 @@ public abstract class AbstractCryptoFilter {
* fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
* toPath = "/" fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
*/
protected String decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
protected Object 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);
@@ -81,7 +81,7 @@ public abstract class AbstractCryptoFilter {
return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
}
JsonPathUtil.setAt(root, toPath, plainText);
return JsonPathUtil.fromTree(root);
return root;
}
// ------------------------------------------------------------------
@@ -99,7 +99,7 @@ public abstract class AbstractCryptoFilter {
* fromPath 값을 암호화하여 toPath(Base64) 반영. JSON 파싱 1회.
* fromPath = "/" body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
*/
protected String encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
protected Object 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,
@@ -112,13 +112,13 @@ public abstract class AbstractCryptoFilter {
String plainText = JsonPathUtil.getAt(root, fromPath);
if (StringUtils.isBlank(plainText)) {
logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
return body;
return root;
}
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);
return root;
}
// ------------------------------------------------------------------
@@ -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.djb.DamoManager;
import com.eactive.ext.kjb.safedb.KjbSafedbWrapper;
@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,115 @@ 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)) {
if ("DAMO".equals(dbEncryptSolutionName)) {
com.eactive.ext.djb.DamoManager damoManager = new com.eactive.ext.djb.DamoManager();
return damoManager.decrypt(dbData);
} else if ("SAFEDB".equals(dbEncryptSolutionName)) {
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 (!isBase64(data)) {
return false;
}
// Base64 길이 체크 (4의 배수)
if (data.length() % 4 != 0) {
return false;
}
return true;
}
public static boolean isBase64(String str) {
if (str == null || str.isEmpty()) {
return false;
}
String base64Pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$";
return str.matches(base64Pattern);
}
}
@@ -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,33 @@ 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);
} else {
// 토큰이 아직 유효한 경우
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
@Override
public AccessTokenVO apply(AccessTokenVO accessToken) {
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
// // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
if (accessToken == null) {
logger.debug("Token not exists for adapter group: {}", adapterGroupName);
return issueToken(credential);
} else if(accessToken.getExpiration() != null) {
// 토큰이 있고 만료시간이 설정 경우
if(accessToken.getExpiration().before(new Date(intervalTime))){
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
logger.debug("Token expired : {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
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 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 +286,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 +315,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 +340,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 +398,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 +432,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 +464,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);
if(isOAuthCredentialRegistered(adapterGroupName) == false){
throw new Exception("There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
// 동시 요청이 발생할 경우 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);
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);
}
if (service != null) {
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties,
outboundOAuthCredentialVo);
}
}
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;
@@ -1,12 +1,16 @@
package com.eactive.eai.common.exception;
import java.nio.charset.Charset;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
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;
@@ -56,6 +60,9 @@ public class ExceptionHandler {
}
public static EAIMessage handle(EAIMessage eaiMessage) {
Properties prop = eaiMessage.getCallProp();
prop.setProperty("API_INTERNAL_ERROR_CODE", eaiMessage.getRspErrCd());
String inAdapterGroupName = eaiMessage.getSngSysItfTp();
AdapterManager manager = AdapterManager.getInstance();
AdapterGroupVO group = manager.getAdapterGroupVO(inAdapterGroupName);
@@ -749,6 +756,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()) {
@@ -13,6 +13,7 @@ import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -44,9 +45,26 @@ public class HsmCryptoService implements PropertyChangeListener {
private static final String PROP_GROUP = "HSM";
private static final String PROP_CACHE_RELOAD_YN = "CACHE_RELOAD_YN";
private static final String PROP_CACHE_TTL_SEC = "CACHE_TTL_SEC";
private static long CACHE_TTL_MS = 10 * 60 * 1000; // 10분, 필요시 PropManager로 외부화
private static class CachedKey<T> {
final T key;
final long cachedAt;
CachedKey(T key) {
this.key = key;
this.cachedAt = System.currentTimeMillis();
}
boolean isExpired() {
return System.currentTimeMillis() - cachedAt > CACHE_TTL_MS;
}
}
// HSM 통신 최소화: 최초 1회 조회 JVM 메모리에 캐싱
private final ConcurrentHashMap<String, SecretKey> secretKeyCache = new ConcurrentHashMap<String, SecretKey>();
private final ConcurrentHashMap<String, PublicKey> publicKeyCache = new ConcurrentHashMap<String, PublicKey>();
private final ConcurrentHashMap<String, CachedKey<SecretKey>> secretKeyCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, CachedKey<PublicKey>> publicKeyCache = new ConcurrentHashMap<>();
@Autowired
private PropManager propManager;
@@ -70,6 +88,9 @@ public class HsmCryptoService implements PropertyChangeListener {
if ("Y".equalsIgnoreCase(reloadYn)) {
clearKeyCache();
}
String propCacheTtlSec = propManager.getProperty(PROP_GROUP, PROP_CACHE_TTL_SEC, "600");
CACHE_TTL_MS = (Integer.parseInt(propCacheTtlSec.trim())) * 1000; // 10분, 필요시 PropManager로 외부화
}
// -------------------------------------------------------------------------
@@ -78,54 +99,97 @@ public class HsmCryptoService implements PropertyChangeListener {
/**
* HSM KeyStore 에서 공개키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
* HSM 장애 만료된 캐시가 있으면 그것을 반환하여 서비스 연속성을 유지합니다.
*/
public PublicKey getPublicKey(String keyAlias) throws HsmException {
checkReady();
PublicKey cached = publicKeyCache.get(keyAlias);
if (cached != null) {
return cached;
// 1. 유효한 캐시 즉시 반환 (HSM 상태 무관)
CachedKey<PublicKey> cached = publicKeyCache.get(keyAlias);
if (cached != null && !cached.isExpired()) {
return cached.key;
}
try {
Certificate cert = HsmManager.getInstance().getKeyStore().getCertificate(keyAlias);
if (cert == null) {
throw new HsmException("인증서를 찾을 수 없습니다. alias=" + keyAlias);
// 2. 캐시 미스 또는 만료 HSM 갱신 시도
if (HsmManager.getInstance().isReady()) {
try {
Certificate cert = HsmManager.getInstance().getKeyStore().getCertificate(keyAlias);
if (cert == null) {
throw new HsmException("인증서를 찾을 수 없습니다. alias=" + keyAlias);
}
PublicKey key = cert.getPublicKey();
publicKeyCache.put(keyAlias, new CachedKey<>(key));
logger.warn("HsmCryptoService] 공개키 캐시 등록: alias=" + keyAlias);
return key;
} catch (HsmException e) {
throw e;
} catch (Exception e) {
logger.warn("HsmCryptoService] 공개키 HSM 조회 실패: " + e.getMessage());
}
PublicKey key = cert.getPublicKey();
publicKeyCache.put(keyAlias, key);
logger.warn("HsmCryptoService] 공개키 캐시 등록: alias=" + keyAlias);
return key;
} catch (HsmException e) {
throw e;
} catch (Exception e) {
throw new HsmException("공개키 조회 실패: " + e.getMessage(), e);
}
// 3. HSM 조회 불가 만료 캐시 fallback
if (cached != null) {
logger.warn("HsmCryptoService] HSM 장애, 만료 공개키 캐시 fallback: alias=" + keyAlias);
return cached.key;
}
throw new HsmException("공개키 조회 불가: HSM 장애이며 캐시도 없습니다. alias=" + keyAlias);
}
/**
* HSM KeyStore 에서 AES 대칭키를 반환합니다. 최초 1회만 HSM 통신하고 이후 캐시를 반환합니다.
* HSM 생성 CKA_EXTRACTABLE=true (ctkmu -x 플래그) 생성된 키만 반환됩니다.
*
* [캐싱 전략]
* HSM 에서 가져온 P11SecretKey(PKCS11 핸들 래퍼) 그대로 캐싱하면, HSM Provider
* 재초기화될 세션 무효화로 인해 캐시된 키를 사용한 Cipher 연산이 실패한다.
* 따라서 getEncoded() 바이트를 추출하여 SecretKeySpec(JVM 메모리 ) 으로 변환
* 캐싱한다. encryptAes/decryptAes 이미 JVM 소프트웨어 Cipher 사용하므로,
* HSM Provider 상태와 완전히 독립적으로 동작한다.
*
* HSM 장애 만료된 캐시가 있으면 그것을 반환하여 서비스 연속성을 유지합니다.
*/
public SecretKey getSecretKey(String keyAlias) throws HsmException {
checkReady();
SecretKey cached = secretKeyCache.get(keyAlias);
if (cached != null) {
return cached;
// 1. 유효한 캐시 즉시 반환 (HSM 상태 무관)
CachedKey<SecretKey> cached = secretKeyCache.get(keyAlias);
if (cached != null && !cached.isExpired()) {
return cached.key;
}
try {
KeyStore keyStore = HsmManager.getInstance().getKeyStore();
java.security.Key key = keyStore.getKey(keyAlias, null);
if (!(key instanceof SecretKey)) {
throw new HsmException("AES 키를 찾을 수 없습니다 (CKA_EXTRACTABLE=true 확인 필요). alias=" + keyAlias);
// 2. 캐시 미스 또는 만료 HSM 갱신 시도
if (HsmManager.getInstance().isReady()) {
try {
KeyStore keyStore = HsmManager.getInstance().getKeyStore();
java.security.Key key = keyStore.getKey(keyAlias, null);
if (key == null) {
throw new HsmException("키를 찾을 수 없습니다. alias=" + keyAlias);
}
SecretKey secretKey = (SecretKey) key;
// P11SecretKey SecretKeySpec 변환: HSM Provider 의존성 제거
// getEncoded() null 이면 non-extractable 키이므로 원본 유지
byte[] keyBytes = secretKey.getEncoded();
if (keyBytes != null) {
secretKey = new SecretKeySpec(keyBytes, secretKey.getAlgorithm());
}
secretKeyCache.put(keyAlias, new CachedKey<>(secretKey));
logger.warn("HsmCryptoService] 대칭키 캐시 등록(갱신): alias=" + keyAlias);
return secretKey;
} catch (HsmException e) {
throw e;
} catch (Exception e) {
logger.warn("HsmCryptoService] 대칭키 HSM 조회 실패: " + e.getMessage());
}
SecretKey secretKey = (SecretKey) key;
secretKeyCache.put(keyAlias, secretKey);
logger.warn("HsmCryptoService] 대칭키 캐시 등록: alias=" + keyAlias);
return secretKey;
} catch (HsmException e) {
throw e;
} catch (Exception e) {
throw new HsmException("AES 키 조회 실패: " + e.getMessage(), e);
}
// 3. HSM 조회 불가 만료 캐시 fallback
if (cached != null) {
logger.warn("HsmCryptoService] HSM 장애, 만료 대칭키 캐시 fallback: alias=" + keyAlias);
return cached.key;
}
throw new HsmException("키 조회 불가: HSM 장애이며 캐시도 없습니다. alias=" + keyAlias);
}
/** 캐시를 비웁니다. HSM 키 교체 후 재로드가 필요할 때 호출합니다. */
@@ -254,13 +318,4 @@ public class HsmCryptoService implements PropertyChangeListener {
return decryptAes(secretKey, iv, ciphertext, null);
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
private void checkReady() throws HsmException {
if (!HsmManager.getInstance().isReady()) {
throw new HsmException("HsmManager 가 초기화되지 않았습니다.");
}
}
}
@@ -5,12 +5,23 @@ import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.security.AuthProvider;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.crypto.SecretKey;
import org.springframework.stereotype.Component;
import com.eactive.eai.agent.encryption.EncryptionManager;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
@@ -24,13 +35,29 @@ 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
*
* ---------------------------------------------------------------------
* [세션 누적 방지 / 회로차단 로직 추가]
* 기존 구현은 재로드마다 KeyStore.getInstance(...).load(null, pin) 새로
* 호출하여 PKCS11 세션(C_OpenSession) 계속 누적되고, 결국 HSM 파티션의
* 최대 세션 수를 초과하면서 reload 영구적으로 실패하는 문제가 있었다.
* 이를 방지하기 위해:
* 1) 정상 상황에서는 "기존 keyStore 인스턴스" 다시 load() 하여 세션 재사용
* 2) 실제 조회(probe) 세션이 살아있는지 검증
* 3) 연속 실패가 임계치를 넘으면 Provider 자체를 logout 완전히 재생성
* 4) 재생성마저 실패하면 마지막으로 성공한 keyStore 유지 (서비스 연속성 우선)
* ---------------------------------------------------------------------
*/
@Component
public class HsmManager implements Lifecycle {
@@ -40,13 +67,21 @@ public class HsmManager implements Lifecycle {
private static final String GROUP_NAME = "HSM";
private static final String PROP_CONFIG = "PKCS11_CONFIG";
private static final String PROP_PIN = "PIN";
private static final String PROP_RELOAD_INTERVAL_MINUTES = "RELOAD_INTERVAL_MINUTES";
private Provider pkcs11Provider;
private KeyStore keyStore;
private volatile KeyStore keyStore;
private boolean started;
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
private volatile char[] pin;
// 재로드 주기 ( 단위). 필요시 PropManager로 외부화 가능.
private static long RELOAD_INTERVAL_MINUTES = 1;
private ScheduledExecutorService scheduler;
private ScheduledFuture<?> reloadFuture;
private HsmManager() {
}
@@ -63,6 +98,7 @@ public class HsmManager implements Lifecycle {
try {
init();
startReloadScheduler();
} catch (Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIHSM002"));
}
@@ -72,8 +108,13 @@ public class HsmManager implements Lifecycle {
}
private void init() throws Exception {
String configContent = PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG);
String pin = PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN);
String reloadIntervalStr = PropManager.getInstance().getProperty(GROUP_NAME, PROP_RELOAD_INTERVAL_MINUTES, "10");
RELOAD_INTERVAL_MINUTES = Long.parseLong(reloadIntervalStr);
EncryptionManager encManager = EncryptionManager.getInstance();
String configContent = encManager.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG));
String pinStr = encManager.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN));
if (configContent == null || configContent.trim().isEmpty()) {
logger.warn("HsmManager] PKCS11_CONFIG 가 설정되지 않았습니다. HSM 초기화를 건너뜁니다.");
@@ -90,18 +131,142 @@ public class HsmManager implements Lifecycle {
Security.addProvider(pkcs11Provider);
keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
char[] pinChars = (pin != null) ? pin.toCharArray() : null;
keyStore.load(null, pinChars);
this.pin = (pinStr != null) ? pinStr.toCharArray() : null;
keyStore.load(null, pin);
logger.warn("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());
java.util.Enumeration<String> aliases = keyStore.aliases();
logKeyStore(this.keyStore);
}
private void logKeyStore(KeyStore keyStore) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
java.util.Enumeration<String> aliases = keyStore.aliases();
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) {
logger.warn("HsmManager] HSM - secretKey null {} : [{}]", alias, secretKey);
}
}
}
logger.warn("HsmManager] HSM 키 목록: [" + aliasList + "]");
}
/**
* 별도 스레드에서 주기적으로 KeyStore 다시 로드하여
* HSM 새로 생성/추가된 키를 인식하도록 한다.
*/
private void startReloadScheduler() {
scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "hsm-keystore-reloader");
t.setDaemon(true);
return t;
});
reloadFuture = scheduler.scheduleWithFixedDelay(
this::reloadKeyStoreSafely,
RELOAD_INTERVAL_MINUTES,
RELOAD_INTERVAL_MINUTES,
TimeUnit.MINUTES
);
logger.warn("HsmManager] KeyStore 주기적 재로드 스케줄러 시작. interval=" + RELOAD_INTERVAL_MINUTES + "");
}
/**
* 스케줄러에서 호출되는 래퍼. 예외가 스케줄러 스레드를 죽이지 않도록 반드시 catch 한다.
* (ScheduledExecutorService task 에서 예외가 던져지면 이후 스케줄을 자동으로 중단시킨다)
*/
private void reloadKeyStoreSafely() {
try {
reloadKeyStoreIfNeeded();
} catch (Throwable t) {
logger.warn("HsmManager] KeyStore 주기적 재로드 실패: " + t.getMessage(), t);
}
}
/**
* KeyStore 다시 로드한다. 외부(getSecretKey )에서 미스 발생
* 즉시 재시도용으로 직접 호출할 수도 있다.
*
* 세션 누적 방지를 위해 KeyStore 인스턴스를 만들지 않고,
* 기존 keyStore 객체에 다시 load() 하여 기존 PKCS11 세션을 재사용한다.
* 연속 실패가 임계치를 넘으면 Provider 자체를 재생성한다.
*/
public synchronized void reloadKeyStoreIfNeeded() throws Exception {
if (pkcs11Provider == null) {
return; // HSM 비활성화 상태
}
try {
if (keyStore != null) {
keyStore.load(null, pin);
logger.warn("HsmManager] KeyStore 재로드 완료 (기존 세션 재사용).");
} else {
KeyStore ks = KeyStore.getInstance("PKCS11", pkcs11Provider);
ks.load(null, pin);
this.keyStore = ks;
logger.warn("HsmManager] KeyStore 신규 생성 완료.");
}
logKeyStore(keyStore);
} catch (Exception e) {
logger.warn("HsmManager] KeyStore 재로드 실패:" + e.getMessage(), e);
logger.warn("HsmManager] Provider 전체 재초기화를 시도합니다.");
fullReinitialize();
}
}
/**
* 세션 누적, 네트워크 단절 등으로 일반 재로드가 이상 복구되지 않을
* 기존 세션을 정리하고 Provider 완전히 새로 생성한다.
*
* 신규 Provider/KeyStore 준비가 완전히 성공한 후에만 기존 Provider 제거하고 교체한다.
* 재초기화 예외가 발생하면 기존 Provider keyStore 그대로 유지한다
* (서비스 중단보다 마지막 정상 상태 보존을 우선).
*/
private void fullReinitialize() throws Exception {
Provider oldProvider = this.pkcs11Provider;
try {
// 1. 신규 Provider 인스턴스 생성 (Security 미등록 상태)
String configContent = EncryptionManager.getInstance()
.decryptDBData(PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG));
Provider newProvider = createProvider(configContent.trim().replace("\\n", "\n"));
// 2. 신규 Provider KeyStore 로드 테스트
// KeyStore.getInstance(type, providerInstance) Security 등록 없이도 동작하므로
// 여기서 실패해도 oldProvider/keyStore 변경되지 않은 상태를 유지함
KeyStore ks = KeyStore.getInstance("PKCS11", newProvider);
ks.load(null, pin);
// 3. 신규 연결 성공 기존 Provider 정리 교체
if (oldProvider instanceof AuthProvider) {
try {
((AuthProvider) oldProvider).logout();
} catch (Exception logoutEx) {
logger.warn("HsmManager] 기존 세션 logout 실패(무시하고 진행): " + logoutEx.getMessage());
}
}
Security.removeProvider(oldProvider.getName());
Security.addProvider(newProvider);
this.pkcs11Provider = newProvider;
this.keyStore = ks;
logger.warn("HsmManager] Provider 전체 재초기화 성공.");
} catch (Exception e) {
logger.warn("HsmManager] Provider 전체 재초기화 실패. 이전 keyStore 를 그대로 유지합니다: " + e.getMessage(), e);
throw e;
}
}
/**
@@ -139,7 +304,21 @@ public class HsmManager implements Lifecycle {
}
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
if (reloadFuture != null) {
reloadFuture.cancel(false);
}
if (scheduler != null) {
scheduler.shutdown();
}
if (pkcs11Provider != null) {
if (pkcs11Provider instanceof AuthProvider) {
try {
((AuthProvider) pkcs11Provider).logout();
} catch (Exception e) {
logger.warn("HsmManager] 종료 시 logout 실패(무시): " + e.getMessage());
}
}
Security.removeProvider(pkcs11Provider.getName());
}
pkcs11Provider = null;
@@ -180,4 +359,20 @@ public class HsmManager implements Lifecycle {
public boolean isReady() {
return started && pkcs11Provider != null && keyStore != null;
}
}
/**
* isReady() 달리 실제 HSM 호출로 세션 생존 여부까지 확인하는 헬스체크.
* 모니터링/헬스체크 엔드포인트에서 사용을 권장한다.
*/
public boolean isHealthy() {
if (!isReady()) {
return false;
}
try {
logKeyStore(keyStore);
return true;
} catch (Exception e) {
return false;
}
}
}
@@ -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,6 +1,7 @@
package com.eactive.eai.common.logger;
import com.eactive.eai.common.logger.mapper.HttpAdapterExtraLogMapper;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.data.entity.onl.logger.HttpAdapterExtraLog;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -10,7 +11,9 @@ import org.springframework.stereotype.Service;
@Service
public class HttpLoggingService {
@Autowired
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
@Autowired
private HttpAdapterExtraLogMapper mapper;
@Autowired
@@ -24,6 +27,7 @@ public class HttpLoggingService {
if (EAIDBLogControl.isEnable()) {
try {
dbLogger.save(httpAdapterExtraLog);
logger.debug("inserted to DB");
} catch(Exception e){
String message = e.getMessage();
// 오류 메시지 추가 - Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection
@@ -33,9 +37,11 @@ public class HttpLoggingService {
EAIDBLogControl.setEnable(false);
}
fileLogger.writeFileLog(httpAdapterExtraLog);
logger.debug("wrote to File");
}
} else {
fileLogger.writeFileLog(httpAdapterExtraLog);
logger.debug("wrote to File");
}
}
}
@@ -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());
}
@@ -173,7 +177,7 @@ public class CryptoModuleManager implements Lifecycle {
private KeyDerivationStrategy resolveStrategy(String fqcn) {
return strategyCache.computeIfAbsent(fqcn, key -> {
try {
Class<?> clazz = Class.forName(key);
Class<?> clazz = Class.forName(key.trim());
return (KeyDerivationStrategy) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("전략 클래스 로드 실패: " + key, e);
@@ -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()) {
@@ -1,65 +0,0 @@
package com.eactive.eai.common.security.keyderiv.strategy;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import javax.crypto.SecretKey;
import com.eactive.eai.common.hsm.HsmCryptoService;
import com.eactive.eai.common.security.keyderiv.DerivedKey;
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
import com.eactive.eai.common.util.ApplicationContextProvider;
/**
* HSM 마스터키 + 런타임 컨텍스트값을 연결(concatenate) SHA-256 해싱으로 키를 도출하는 전략.
* SHA-256 출력은 항상 32바이트(AES-256)이므로 별도 keyLength 파라미터가 불필요하다.
*
* key_deriv_params (JSON) 예시:
* {
* "hsmKeyAlias" : "MASTER_KEY_AES",
* "contextKey" : "X-Api-Group-Seq" -- runtimeContext에서 꺼낼 이름
* }
*/
public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy {
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);
String contextKey = required(params, PARAM_CONTEXT_KEY);
SecretKey masterKey = hsmCryptoService().getSecretKey(hsmKeyAlias);
byte[] masterKeyBytes = masterKey.getEncoded();
byte[] contextBytes = runtimeContext.getOrDefault(contextKey, "")
.getBytes(StandardCharsets.UTF_8);
byte[] combined = new byte[masterKeyBytes.length + contextBytes.length];
System.arraycopy(masterKeyBytes, 0, combined, 0, masterKeyBytes.length);
System.arraycopy(contextBytes, 0, combined, masterKeyBytes.length, contextBytes.length);
byte[] derived = MessageDigest.getInstance("SHA-256").digest(combined);
return new DerivedKey(derived, derived);
}
@Override
public String buildCacheKey(String cryptoName, Map<String, String> params, Map<String, String> runtimeContext) {
String contextKey = params.getOrDefault(PARAM_CONTEXT_KEY, "");
String contextValue = runtimeContext.getOrDefault(contextKey, "");
return cryptoName + ":" + contextValue;
}
private String required(Map<String, String> params, String key) {
String value = params.get(key);
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("key_deriv_params 필수값 누락: " + key);
}
return value;
}
private HsmCryptoService hsmCryptoService() {
return ApplicationContextProvider.getContext().getBean(HsmCryptoService.class);
}
}
@@ -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();
@@ -2,24 +2,25 @@ package com.eactive.eai.common.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.core5.http.Header;
import com.eactive.eai.common.logger.HttpAdapterExtraHeaderVo;
import com.eactive.eai.common.logger.HttpAdapterExtraLogVo;
import com.eactive.eai.common.logger.HttpLoggingService;
import com.eactive.eai.common.logger.async.AsyncHttpLoggingPoolManager;
import com.eactive.eai.env.ElinkConfig;
import org.apache.hc.core5.http.Header;
import org.apache.commons.lang3.StringUtils;
public class HttpAdapterExtraLogUtil {
public static final int MAX_HEADER_VALUE_SIZE = 400;
public static final String BODY_FIELD_NAME = "eapim-adapter-send-body";
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
public static boolean isHttpHeaderMode() {
@@ -62,16 +63,24 @@ public class HttpAdapterExtraLogUtil {
httpAdapterExtraLogVo.setHttpMethod(httpMethod);
for (HttpAdapterExtraHeaderVo httpAdapterExtraHeaderVo : headerVoList) {
// String name = httpAdapterExtraHeaderVo.getName();
// if(StringUtils.isNotBlank(name) && "authorization".equals(name.toLowerCase())) {
// httpAdapterExtraHeaderVo.setValue("{hidden}");
// }
String value = httpAdapterExtraHeaderVo.getValue();
if (value == null) {
httpAdapterExtraHeaderVo.setValue(" ");
}
if(StringUtils.isNotBlank(value) && value.length() > MAX_HEADER_VALUE_SIZE) {
value = value.substring(0, 400) + "...";
httpAdapterExtraHeaderVo.setValue(value);
}else if(value == null){
httpAdapterExtraHeaderVo.setValue(" ");
}
}
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;
}
}
@@ -0,0 +1,279 @@
package com.eactive.eai.common.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.json.simple.JSONObject;
/**
* Jackson JsonNode 범용 접근 유틸리티
*
* "." 구분자로 필드 단계를 이동하고, "[n]" 으로 배열 인덱스에 접근하는
* path 표현식을 지원한다.
*
* 사용 :
* JacksonUtil.getText(root, "term_agreements[0].is_agreed")
* JacksonUtil.getBoolean(root, "term_agreements[0].is_agreed", false)
* JacksonUtil.getNode(root, "data.list[2].child[0].name")
*
* 경로 중간에 필드가 없거나, 배열 인덱스가 범위를 벗어나거나,
* 배열이 아닌 노드에 인덱스 접근을 시도하는 경우 모두
* 예외를 던지지 않고 null / 기본값을 반환한다.
*/
public final class JacksonUtil {
/** "fieldName" 또는 "fieldName[0]" 형태의 토큰을 분해하는 패턴 */
private static final Pattern TOKEN_PATTERN = Pattern.compile("([^\\[\\]]*)((?:\\[\\d+\\])*)");
private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private JacksonUtil() {
// 인스턴스화 방지
}
// ------------------------------------------------------------------
// 기본 트리 탐색
// ------------------------------------------------------------------
/**
* Object(JSONObject/String/JsonNode ) JsonNode로 변환한다.
* KakaopayFilter.readTree() 일반화한 버전.
*/
public static JsonNode readTree(Object jsonData, ObjectMapper objectMapper)
throws JsonMappingException, JsonProcessingException {
if (jsonData == null) {
return null;
}
if (jsonData instanceof JsonNode) {
return (JsonNode) jsonData;
}
String jsonStr = null;
if (jsonData instanceof JSONObject) {
jsonStr = ((JSONObject) jsonData).toJSONString();
} else if (jsonData instanceof String) {
jsonStr = (String) jsonData;
} else {
// POJO 등은 writeValueAsString을 통해 변환
jsonStr = objectMapper.writeValueAsString(jsonData);
}
if (jsonStr == null) {
return null;
}
return objectMapper.readTree(jsonStr);
}
public static JsonNode readTree(Object jsonData) throws JsonMappingException, JsonProcessingException {
return readTree(jsonData, OBJECT_MAPPER);
}
public static String writeAsString(JsonNode node) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsString(node);
}
public static ObjectNode createObjectNode() {
return OBJECT_MAPPER.createObjectNode();
}
/**
* path 표현식으로 JsonNode를 탐색한다.
* 경로가 존재하지 않으면 null을 반환한다 (MissingNode가 아닌 진짜 null).
*
* @param root 탐색을 시작할 JsonNode
* @param path : "term_agreements[0].is_agreed", "data.list[2].name"
*/
public static JsonNode getNode(JsonNode root, String path) {
if (root == null || path == null || path.isEmpty()) {
return null;
}
JsonNode current = root;
for (String rawToken : path.split("\\.")) {
if (current == null || current.isMissingNode() || current.isNull()) {
return null;
}
String fieldName = extractFieldName(rawToken);
List<Integer> indices = extractIndices(rawToken);
// 필드명이 있으면 먼저 필드로 이동 ( 문자열이면 현재 노드 유지 - 최상위 배열 접근용)
if (!fieldName.isEmpty()) {
if (!current.has(fieldName)) {
return null;
}
current = current.get(fieldName);
}
// 이어지는 [n][m]... 인덱스를 순서대로 적용
for (Integer idx : indices) {
if (current == null || !current.isArray() || idx < 0 || idx >= current.size()) {
return null;
}
current = current.get(idx);
}
}
return current;
}
// ------------------------------------------------------------------
// 타입별 getter (전부 null-safe, 기본값 지원)
// ------------------------------------------------------------------
public static String getText(JsonNode root, String path) {
return getText(root, path, null);
}
public static String getText(JsonNode root, String path, String defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asText(defaultValue);
}
public static boolean getBoolean(JsonNode root, String path, boolean defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asBoolean(defaultValue);
}
public static int getInt(JsonNode root, String path, int defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asInt(defaultValue);
}
public static long getLong(JsonNode root, String path, long defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asLong(defaultValue);
}
public static double getDouble(JsonNode root, String path, double defaultValue) {
JsonNode node = getNode(root, path);
return (node == null || node.isMissingNode() || node.isNull()) ? defaultValue : node.asDouble(defaultValue);
}
/** path가 가리키는 노드가 실제로 존재하는지 (null/missing이 아닌지) */
public static boolean exists(JsonNode root, String path) {
JsonNode node = getNode(root, path);
return node != null && !node.isMissingNode() && !node.isNull();
}
/** path가 가리키는 노드가 배열일 때 그 크기를 반환, 배열이 아니거나 없으면 -1 */
public static int size(JsonNode root, String path) {
JsonNode node = getNode(root, path);
return (node != null && node.isArray()) ? node.size() : -1;
}
/** path가 가리키는 ArrayNode를 List<JsonNode>로 반환, 없으면 빈 리스트 */
public static List<JsonNode> getList(JsonNode root, String path) {
List<JsonNode> result = new ArrayList<>();
JsonNode node = getNode(root, path);
if (node != null && node.isArray()) {
for (JsonNode item : node) {
result.add(item);
}
}
return result;
}
// ------------------------------------------------------------------
// 설정 (필요 사용 - 존재하는 경로에 대해서만 동작)
// ------------------------------------------------------------------
/**
* path가 가리키는 위치의 텍스트 값을 변경한다.
* 부모 컨테이너(ObjectNode/ArrayNode) 존재해야 하며, 중간 경로가 없으면 false를 반환한다.
* (자동으로 중간 경로를 생성하지는 않음)
*/
public static boolean setText(JsonNode root, String path, String value) {
return setValue(root, path, value);
}
private static boolean setValue(JsonNode root, String path, String value) {
int lastDot = path.lastIndexOf('.');
String parentPath = (lastDot == -1) ? "" : path.substring(0, lastDot);
String lastToken = (lastDot == -1) ? path : path.substring(lastDot + 1);
JsonNode parent = parentPath.isEmpty() ? root : getNode(root, parentPath);
if (parent == null) {
return false;
}
String fieldName = extractFieldName(lastToken);
List<Integer> indices = extractIndices(lastToken);
JsonNode target = parent;
if (!fieldName.isEmpty()) {
if (!target.has(fieldName)) {
return false;
}
target = target.get(fieldName);
}
// 마지막 인덱스 전까지 이동
for (int i = 0; i < indices.size() - 1; i++) {
int idx = indices.get(i);
if (target == null || !target.isArray() || idx < 0 || idx >= target.size()) {
return false;
}
target = target.get(idx);
}
if (!indices.isEmpty()) {
// 배열의 특정 인덱스 교체
int lastIdx = indices.get(indices.size() - 1);
if (target == null || !target.isArray() || lastIdx < 0 || lastIdx >= target.size()) {
return false;
}
((ArrayNode) target).set(lastIdx, value);
return true;
} else {
// 객체 필드 교체 (target은 fieldName으로 이미 이동된 상태이므로, parent 기준 재설정 필요)
if (parent.isObject() && fieldName != null && !fieldName.isEmpty()) {
((ObjectNode) parent).put(fieldName, value);
return true;
}
return false;
}
}
// ------------------------------------------------------------------
// 내부 파싱 헬퍼
// ------------------------------------------------------------------
/** "term_agreements[0]" -> "term_agreements" / "[0]" -> "" */
private static String extractFieldName(String token) {
Matcher m = TOKEN_PATTERN.matcher(token);
if (m.matches()) {
return m.group(1) == null ? "" : m.group(1);
}
return token;
}
/** "term_agreements[0][1]" -> [0, 1] / "term_agreements" -> [] */
private static List<Integer> extractIndices(String token) {
List<Integer> indices = new ArrayList<>();
Matcher m = INDEX_PATTERN.matcher(token);
while (m.find()) {
indices.add(Integer.parseInt(m.group(1)));
}
return indices;
}
}
@@ -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,49 @@
package com.eactive.eai.common.util;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.HttpAdapterExtraLogUtil;
public class RestSendBodyLogUtils {
private static final String PROP_LOG_MAX_DATA_SIZE = "log.max.data.size";
private static final String PROP_GROUP = "RestSendBodyLog";
private RestSendBodyLogUtils() {
throw new IllegalStateException("Utility class");
}
/**
* API ID 어댑터 body 로그 남기는 여부
*
* @param apiId
* @return
*/
public static boolean isBodyLoggingApi(String apiId) {
if (StringUtils.isEmpty(apiId))
return false;
String logging = PropManager.getInstance().getProperty(PROP_GROUP, apiId, "N");
return StringUtils.equals("Y", logging);
}
/**
* body 로그에 남길 만큼 자르기
*
* @param body
* @return
*/
public static String getBodyByMaxSize(String body) {
String maxDataSize = PropManager.getInstance().getProperty(PROP_GROUP, PROP_LOG_MAX_DATA_SIZE);
int imaxDataSize = HttpAdapterExtraLogUtil.MAX_HEADER_VALUE_SIZE;
if (maxDataSize != null)
imaxDataSize = Integer.parseInt(maxDataSize);
if (body.length() > imaxDataSize)
return StringUtils.substring(body, 0, imaxDataSize) + "...";
return body;
}
}
@@ -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);
}
}
@@ -6,6 +6,9 @@ import java.lang.reflect.Method;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ExportException;
import java.security.Security;
import java.util.Arrays;
@@ -24,6 +27,7 @@ import com.eactive.eai.common.dao.Keys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleManager;
import com.eactive.eai.common.logger.async.AsyncHttpLoggingPoolManager;
import com.eactive.eai.common.logger.async.AsyncLoggingPoolManager;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.routing.rmi.RemoteProxy;
@@ -36,7 +40,6 @@ import com.eactive.eai.common.util.ServiceLocator;
import com.eactive.eai.common.util.ServiceLocatorException;
import com.eactive.eai.env.ConfigKeys;
import com.eactive.eai.env.ElinkConfig;
import com.eactive.eai.common.logger.async.AsyncHttpLoggingPoolManager;
/**
* eLink FrameWork이 초기화(Deploy) 실행되어야 작업을 정의
@@ -180,14 +183,25 @@ public class AppInitializer implements InitializingBean, DisposableBean {
this.shutdownWaitIntervalMs = shutdownWaitIntervalMs;
}
private Registry rmiRegistry;
@SuppressWarnings("deprecation")
private void initRmiServer(int registryPort, int servicePort) throws RemoteException {
try {
rmiRegistry = LocateRegistry.createRegistry(registryPort);
} catch (ExportException e) {
// 혹시 이전 정리 실패로 이미 떠있다면 재사용 시도
Logger.getLogger(Logger.LOGGER_DEFAULT)
.warn("Registry already exists on port " + registryPort + ", reusing", e);
rmiRegistry = LocateRegistry.getRegistry(registryPort);
}
rmiServiceExporter.setServiceName("RemoteProxy");
rmiServiceExporter.setService(remoteProxy);
rmiServiceExporter.setServiceInterface(serviceInterface);
rmiServiceExporter.setRegistryPort(registryPort);
rmiServiceExporter.setServicePort(servicePort);
rmiServiceExporter.setAlwaysCreateRegistry(true);
rmiServiceExporter.setAlwaysCreateRegistry(false);
rmiServiceExporter.afterPropertiesSet();
}
@@ -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());
// 거래통제 로그에 로그생성
@@ -1013,7 +1007,14 @@ public class RequestProcessor extends RequestProcessorSupport {
if (logger.isError()) logger.error(guidLogPrefix + " : 거래통제 Log 실패 - " + vo.getRspErrorMsg());
}
return restrictResponse;
AdapterGroupVO adptGrpVO = AdapterManager.getInstance().getAdapterGroupVO(vo.getAdapterGroupName());
if (!Keys.IF_STANDARD.equals(vo.getStdMsgTypeCode()) && StringUtils.equalsAny(adptGrpVO.getType(),
Keys.TYPE_REST, Keys.TYPE_HTTP, Keys.TYPE_HTTP_CUSTOM)) {
throw new HttpStatusException(eaiMsg.getRspErrMsg(), eaiMsg.getRspErrCd(),
HttpStatus.SERVICE_UNAVAILABLE.value());
} else {
return restrictResponse;
}
}
else {
// ExceptionHandler에 의해 에러 응답송신
@@ -1033,9 +1034,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 +1066,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 +1089,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 +1257,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 +1286,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 +1400,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 +1425,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;
@@ -11,6 +11,7 @@ import org.springframework.http.HttpStatus;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.exception.ExceptionHandler;
import com.eactive.eai.common.message.EAIMessage;
@@ -111,7 +112,13 @@ public abstract class RequestProcessorSupport implements Processor
**/
public Object execute(Object message, Properties prop) throws HttpStatusException
{
long msgRcvTm = System.currentTimeMillis();
String receivedTimestamp = prop.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME);
long msgRcvTm = 0L;
if(receivedTimestamp == null)
msgRcvTm = System.currentTimeMillis();
else
msgRcvTm = Long.parseLong(receivedTimestamp);
increaseRcvCount();
local.set(new Long(msgRcvTm));
@@ -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())));
@@ -140,7 +140,7 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
readerMap.clear();
readerMap = null;
// readerMap = null;
started = false;
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
@@ -208,6 +208,10 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
@SuppressWarnings("rawtypes")
private void initReaderFactory(Properties config) {
if (readerMap == null) {
readerMap = new ConcurrentHashMap<>();
}
Class cl = null;
StandardReader reader = null;
@@ -334,6 +338,8 @@ reader.FLAT=com.eactive.eai.message.parser.FlatReader
}
if(logger.isDebug()) logger.debug("mapperClass : {}", mapper.getClass().getCanonicalName());
standardMessage.setBizDataPath(mapper.getPath(InterfaceMapper.BIZ_DATA));
String versionMapperClass = config.getProperty(VERSION_MAPPER);
if(StringUtils.isEmpty(versionMapperClass)) {
versionMapperClass = "com.eactive.eai.message.mapper.DefaultMessageMapper";
@@ -18,7 +18,7 @@ public class DefaultInterfaceMapper implements InterfaceMapper {
itemPathMap = map;
}
protected String getPath(String key) {
public String getPath(String key) {
String path = itemPathMap.get(key);
if(StringUtils.isBlank(path)) {
logger.debug("message-mapping not defined key = {}", key);
@@ -34,7 +34,11 @@ public interface InterfaceMapper extends Serializable {
String RESPONSE_TYPE = "RESPONSE_TYPE";
String SYNC_ASYNC_TYPE = "SYNC_ASYNC_TYPE";
String BIZ_DATA = "BIZ_DATA";
public void initPathMap(HashMap<String, String> map);
public String getPath(String key);
//------------------------------------------------------------
// 표준전문과 Mapping 되는 항목을 모두 정의해야 .
//------------------------------------------------------------
@@ -671,6 +671,8 @@ public abstract class DefaultProcess extends Process {
StandardMessage resStandardMessage = this.resEaiMsg.getStandardMessage();
InterfaceMapper mapper = this.resEaiMsg.getMapper();
String responseType = mapper.getResponseType(resStandardMessage);
// apigw내 오류 없이, 표준헤더내 오류설정된 경우 응답 처리
if(!STDMessageKeys.RESPONSE_TYPE_CODE_N.equals(responseType)){
String inboudnAdapterGroupName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
String inboudnAdapterName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_NAME);
@@ -682,18 +684,24 @@ public abstract class DefaultProcess extends Process {
AdapterVO inboundAdapterVO = AdapterManager.getInstance().getAdapterVO(inboudnAdapterGroupName, inboudnAdapterName);
String errorResponseHandlerClass = AdapterPropManager.getInstance().getProperty(inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
if(StringUtils.isBlank(errorResponseHandlerClass)) {
String errorCode = mapper.getErrorCode(resStandardMessage);
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("MSG.MAIN_MSG.outp_msg_desc"));
this.resEaiMsg.setRspErr("RECEAIINA001", String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
return;
if ((com.eactive.eai.adapter.Keys.IF_STANDARD.equals(this.adptrMsgPtrnCd)
||com.eactive.eai.adapter.Keys.IF_SUBSTANDARD.equals(this.adptrMsgPtrnCd))) {
String errorCode = mapper.getErrorCode(resStandardMessage);
String errorMsg = StringUtils.trim(mapper.getErrorMsg(resStandardMessage));
String errorDesc = StringUtils.trim(resStandardMessage.findItemValue("MSG.MAIN_MSG.outp_msg_desc"));
this.resEaiMsg.setRspErr("RECEAIINA001", String.format("[%s] %s (%s)", errorCode, errorMsg, errorDesc));
return;
} else {
this.resEaiMsg.setRspErr("RECEAIINA001", "비표준 오류 응답 수신");
return;
}
}
logger.info(inboudnAdapterName+", Handle Error Message Class-"+errorResponseHandlerClass);
AdapterErrorMessageHandler adapterErrorMessageHandler = AdapterErrorMessageHandlerFactory.createHandler(errorResponseHandlerClass);
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, resStandardMessage);
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, this.resEaiMsg);
if(responseObj != null) {
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
}
@@ -774,8 +782,9 @@ public abstract class DefaultProcess extends Process {
if(this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP) instanceof Map) {
Map<String, Object> map = (Map<String, Object>) this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP);
this.resObject = map.get(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE);
this.callProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
}
standardMessage.setBizData(this.resObject, this.inboundCharset);
standardMessage.setBizData(this.resObject, this.inboundCharset);
try {
//mapper.nextGuidSeq(standardMessage);
mapper.setSendRecvDivision(standardMessage, STDMessageKeys.SEND_RECV_CD_RECV);
@@ -4,11 +4,17 @@ import java.net.SocketTimeoutException;
import java.util.Properties;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.circuitBreaker.CircuitBreakerManager;
import com.eactive.eai.common.circuitBreaker.type.TypedCircuitBreakerManager;
import com.eactive.eai.util.PropertiesUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.AdapterGroupVO;
@@ -99,7 +105,32 @@ public class HTTPProcess extends DefaultProcess {
this.timeout = iTimeoutValue * 1000;
this.tempProp.put(INTERFACE_TIME_OUT, "" + this.timeout);
try {
this.resObject = HttpSender.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp);
// API별 이용
String apiId = reqEaiMsg.getEAISvcCd();
CircuitBreaker circuitBreaker = TypedCircuitBreakerManager.getInstance().getCircuitBreaker(apiId);
// 디폴트 이용
if(circuitBreaker == null) {
String circuitBreakerUseYn = PropManager.getInstance().getProperty("CircuitBreaker", "useYn");
if ("Y".equals(circuitBreakerUseYn))
circuitBreaker = CircuitBreakerManager.getInstance().getCircuitBreaker(apiId);
}
if (circuitBreaker != null) {
// ObjectMapper 인스턴스 생성
ObjectMapper objectMapper = new ObjectMapper();
// 서킷 브레이커의 메트릭 정보와 상태 정보를 직접 Node로 변환
ObjectNode combinedNode = objectMapper.createObjectNode();
combinedNode.put("state", circuitBreaker.getState().toString());
combinedNode.set("metrics", objectMapper.valueToTree(circuitBreaker.getMetrics()));
logger.info("CircuitBreaker state " + combinedNode.toString());
this.resObject = circuitBreaker.executeCallable(() -> HttpSender
.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp));
} else {
this.resObject = HttpSender.callService(this.adapterGroupName, this.outboundProp, this.reqObject, this.tempProp);
}
// 응답코드에 대한 처리
this.resEaiMsg = setResponseForOutbound(this.resEaiMsg);
if (!com.eactive.eai.adapter.Keys.IF_STANDARD.equals(this.adptrMsgPtrnCd)) {
@@ -1,29 +1,25 @@
package com.eactive.eai.outbound;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.Properties;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.circuitBreaker.CircuitBreakerManager;
import com.eactive.eai.common.circuitBreaker.type.TypedCircuitBreakerManager;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.util.PropertiesUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.initech.core.util.PropertiesManager;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandlerFactory;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.common.EAIKeys;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.circuitBreaker.CircuitBreakerManager;
import com.eactive.eai.common.circuitBreaker.type.TypedCircuitBreakerManager;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.header.HeaderAction;
import com.eactive.eai.common.header.HeaderActionFactory;
@@ -43,14 +39,16 @@ import com.eactive.eai.common.worker.ResponseMap;
import com.eactive.eai.control.HttpSender;
import com.eactive.eai.control.LogSender;
import com.eactive.eai.control.Transform;
import com.eactive.eai.custom.alarm.AlarmStateManager;
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
import com.eactive.eai.custom.alarm.event.AlarmEvent;
import com.eactive.eai.custom.alarm.key.AlarmKey;
import com.eactive.eai.custom.alarm.key.CoreAlarmKey;
import com.eactive.eai.env.ElinkConfig;
import com.eactive.eai.inbound.processor.Processor;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.util.PropertiesUtil;
import com.ext.eai.common.stdmessage.STDMessageKeys;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
public class RESTProcess extends HTTPProcess {
static final long serialVersionUID = 1L;
@@ -100,6 +98,10 @@ public class RESTProcess extends HTTPProcess {
this.timeout = iTimeoutValue * 1000;
this.tempProp.put(INTERFACE_TIME_OUT, "" + this.timeout);
this.tempProp.put("API_SERVICE_CODE", this.reqEaiMsg.getEAISvcCd());
this.tempProp.put("OUT_REQ_EAI_MSG", this.reqEaiMsg);
this.tempProp.put("OUT_REQ_STD_MSG", this.reqEaiMsg.getStandardMessage());
try {
// API별 이용
String apiId = reqEaiMsg.getEAISvcCd();
@@ -148,6 +150,16 @@ public class RESTProcess extends HTTPProcess {
logger.error(guidLogPrefix + rspErrorMsg, hse);
this.resEaiMsg.setRspErr(rspErrorCode,rspErrorMsg);
throw new RuntimeException(rspErrorCode);
} catch (CallNotPermittedException e) {
String[] msgArgs = new String[1];
msgArgs[0] = this.reqEaiMsg.getEAISvcCd();
String rspErrorCode = "RECEAIOHP201";
String rspErrorMsg = ExceptionUtil.make(e, rspErrorCode, msgArgs);
if (logger.isError())
logger.error(guidLogPrefix + rspErrorMsg, e);
this.resEaiMsg.setRspErr(rspErrorCode,rspErrorMsg);
throw new RuntimeException(rspErrorCode);
} catch (Exception e) {
String[] msgArgs = new String[1];
msgArgs[0] = this.reqEaiMsg.getEAISvcCd();
@@ -522,6 +534,7 @@ public class RESTProcess extends HTTPProcess {
String url = StringUtils.removeEnd(simAddr, "/") + "/mockapi/" + this.reqEaiMsg.getEAISvcCd();
this.outboundProp.put(HttpClientAdapterServiceKey.URL, url);
this.tempProp.put("tranType", reqEaiMsg.getTranType());
this.tempProp.remove(HttpAdapterServiceKey.INBOUND_REWRITE_PATH);
logger.debug("simUrl=" + url);
}
@@ -681,5 +694,47 @@ public class RESTProcess extends HTTPProcess {
public void storeMessage() throws Exception {
}
public void setOutboundErrorMessage() throws Exception {
this.logPssSno = TranLogUtil.getResLogSeq(this.svcPssTp, this.svcPssSeq, this.isComp);
boolean setResObject = false;
// 에러에 대한 응답메시지
if(this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP) instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) this.tempProp.get(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP);
this.callProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
// OUTBOUND_PROPERTY_MAP 있는 경우, 타겟서버와 통신은 정상적으로 이루진 경우 처리한다.
AdapterGroupVO outboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
if (outboundAdapterGroupVO != null) {
AdapterVO outboundAdapterVo = null;
if(adapterName != null)
outboundAdapterVo = outboundAdapterGroupVO.getAdapterVO(adapterName);
else
outboundAdapterVo = outboundAdapterGroupVO.nextAdapterVO();
if (outboundAdapterVo != null) {
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
outboundAdapterVo.getPropGroupName(), "ERR_MSG_HANDLER");
if (StringUtils.isNotBlank(errorHandlerClass)) {
if (logger.isInfo())
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
this.resObject = handler.generateOutboundErrorResponseMessage(adapterGroupName, adapterGroupName,
callProp, this.reqObject, this.resEaiMsg);
setResObject = true;
}
}
}
}
if (!setResObject) {
this.resObject = "";
}
StandardMessage standardMessage = this.resEaiMsg.getStandardMessage();
standardMessage.setBizData(this.resObject, this.inboundCharset);
}
}
@@ -25,6 +25,33 @@ public class HexaConverter {
return bytes;
}
public static String binToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
public static byte[] hexToBin(String hexStr) {
String lookup = "0123456789ABCDEF";
int len = hexStr.length();
byte[] bArray = new byte[len / 2];
for (int i = 0; i < bArray.length; i++) {
char highChar = Character.toUpperCase(hexStr.charAt(i * 2));
char lowChar = Character.toUpperCase(hexStr.charAt(i * 2 + 1));
int high = lookup.indexOf(highChar);
int low = lookup.indexOf(lowChar);
bArray[i] = (byte) ((high << 4) | low);
}
return bArray;
}
// public static void main(String[] argv) {
// String str = "TEST\nÇѱÛ";
// String hexStr = HexaConverter.bytesToHexa(str.getBytes());

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