From 2f29ca6d8dc57385f47b01c7af2fcc6235fdde33 Mon Sep 17 00:00:00 2001 From: yunjy-hp Date: Wed, 7 Jan 2026 16:41:25 +0900 Subject: [PATCH 01/15] =?UTF-8?q?=ED=91=9C=EC=A4=80=EC=A0=84=EB=AC=B8=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=ED=8F=AC=EB=A9=A7=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?8=EC=9E=90=EB=A6=AC=20->=209=EC=9E=90=EB=A6=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eai/custom/message/StandardMessageCoordinatorKJB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorKJB.java b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorKJB.java index 437fa14..f0bb6fc 100644 --- a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorKJB.java +++ b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorKJB.java @@ -87,7 +87,7 @@ public class StandardMessageCoordinatorKJB extends DefaultStandardMessageCoordin //전문요청일자 standardMessage.setData("Common.BizProc_Info.MESG_DMAN_DT", DatetimeUtil.getCurrentDate()); //전문요청시간 - standardMessage.setData("Common.BizProc_Info.MESG_DMAN_TKTM", DatetimeUtil.getTime8()); + standardMessage.setData("Common.BizProc_Info.MESG_DMAN_TKTM", DatetimeUtil.getFormattedDate("HHmmssSSS")); //2012-07-24 임의 로 영업일자에 system시간 설정 From 95b83c29eebdbd88c3aff57598fd6caea86b20e7 Mon Sep 17 00:00:00 2001 From: yunjy-hp Date: Wed, 7 Jan 2026 16:44:34 +0900 Subject: [PATCH 02/15] =?UTF-8?q?ElinkTransactionContext=20=EC=9E=91?= =?UTF-8?q?=EC=97=85(=EB=B3=80=ED=99=98=20=ED=95=A8=EC=88=98=EC=97=90=20?= =?UTF-8?q?=EA=B0=92=20=EC=A0=84=EB=8B=AC=20=EC=B2=B4=EA=B3=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/API_TRANSACTION_FLOW.md | 500 ++++++++++++++++++ docs/ELINK_TRANSACTION_CONTEXT.md | 300 +++++++++++ .../controller/ApiAdapterController.java | 6 +- 3 files changed, 804 insertions(+), 2 deletions(-) create mode 100644 docs/API_TRANSACTION_FLOW.md create mode 100644 docs/ELINK_TRANSACTION_CONTEXT.md diff --git a/docs/API_TRANSACTION_FLOW.md b/docs/API_TRANSACTION_FLOW.md new file mode 100644 index 0000000..6605510 --- /dev/null +++ b/docs/API_TRANSACTION_FLOW.md @@ -0,0 +1,500 @@ +# API 거래 플로우 정리 + +## 전체 플로우 다이어그램 + +``` +[Client Request] + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 1. ApiAdapterController (/api/**, /mapi/**) │ +│ - HTTP 요청 수신, 어댑터 정보 조회 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 2. ApiAdapterService │ +│ - 요청 파싱 (GET/POST/PUT/DELETE) │ +│ - 암/복호화 처리 (AES128, AES256 등) │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 3. HttpAdapterServiceSupport.service() │ +│ - Pre/Post Filter 실행 │ +│ - RequestDispatcher 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 4. RequestDispatcher.handle() │ +│ - Processor 인스턴스 생성/획득 │ +│ - Adapter 상태 관리 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 5. RequestProcessor.hookup() │ +│ - EAIMessage 생성 (StandardMessage → EAIMessage) │ +│ - 유량제어, 라우팅 판단 │ +│ - FlowRouter.process() 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 6. IFRouter.process() │ +│ - Outbound 라우팅 │ +│ - FailOver 처리 │ +│ - ElinkESBProcessProxy 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 7. RESTProcess (extends DefaultProcess) │ +│ - flow() 메소드 실행 │ +│ - 송신 변환, 로그, 응답 처리 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 8. HttpSender.callService() │ +│ - ElinkAdapter 획득 │ +│ - Adapter 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 9. HttpClient5AdapterServiceRest.execute() │ +│ - 실제 HTTP 통신 (Apache HttpClient 5) │ +│ - OAuth 토큰 관리 │ +│ - 암/복호화 처리 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +[Target System] +``` + +--- + +## 각 클래스 설명 + +### 1. ApiAdapterController + +| 항목 | 내용 | +|------|------| +| 위치 | `eapim-online/src/main/java/com/eactive/eai/adapter/controller/` | +| 역할 | REST API 진입점 (Spring Controller) | +| 주요 기능 | - `/api/**`, `/mapi/**` 엔드포인트 매핑
- TSEAIHS04 테이블에서 어댑터 정보 조회
- HttpDynamicInAdapterManager로 어댑터 찾기
- ApiAdapterService.callApi() 호출 | + +### 2. ApiAdapterService + +| 항목 | 내용 | +|------|------| +| 위치 | `eapim-online/src/main/java/com/eactive/eai/adapter/service/` | +| 역할 | Inbound 서비스 레이어 | +| 상속 | `HttpAdapterServiceSupport` | +| 주요 기능 | - HTTP Method별 요청 파싱
- Inbound 암/복호화 (AES128, AES256 등)
- 부모 클래스 `service()` 호출 | + +### 3. HttpAdapterServiceSupport + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/dynamic/` | +| 역할 | Inbound 어댑터 추상 클래스 | +| 주요 기능 | - `service()` 메소드: 핵심 디스패처
- Pre-Filter / Post-Filter 실행
- StandardMessage 생성
- RequestDispatcher.handle() 호출 | + +### 4. RequestDispatcher + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/` | +| 역할 | 요청 분배자 | +| 주요 기능 | - Processor 인스턴스 생성/획득
- `processor.execute()` 호출
- AdapterGroup 상태 관리 (동시 처리 수 증감) | + +### 5. RequestProcessorSupport / RequestProcessor + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/inbound/processor/` | +| 역할 | 요청 처리 프로세서 | +| 관계 | `RequestProcessor extends RequestProcessorSupport` | +| 주요 기능 | - `execute()` → `hookup()` 호출
- StandardMessage → EAIMessage 변환
- 유량제어 (RateLimiter)
- SYNC/ASYNC 분기 처리
- FlowRouter.process() 호출 | + +### 6. IFRouter + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/common/routing/` | +| 역할 | FlowControl → Outbound 라우터 | +| 주요 기능 | - Outbound Process로 라우팅
- Local/Remote 호출 판단
- FailOver 처리
- TAS(시뮬레이터) 거래 처리
- ElinkESBProcessProxy 호출 | + +### 7. DefaultProcess / RESTProcess + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/outbound/` | +| 역할 | Outbound 처리 프로세스 | +| 관계 | `RESTProcess extends HTTPProcess extends DefaultProcess` | +| 주요 기능 (DefaultProcess) | - `flow()`: 핵심 처리 흐름
- 송신 전/후 처리
- 메시지 변환 (Transform)
- 로그 처리 | +| 주요 기능 (RESTProcess) | - `outboundCtrlCallService()`: HttpSender 호출
- REST 특화 처리
- 표준/비표준 메시지 처리 | + +### 8. HttpSender + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/control/` | +| 역할 | HTTP 송신 제어 | +| 주요 기능 | - ElinkAdapterFactory에서 HTTP Adapter 획득
- `adapter.callService()` 호출
- SocketTimeout, HttpStatusException 처리 | + +### 9. HttpClient5AdapterServiceRest + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/client/impl/` | +| 역할 | 실제 HTTP 클라이언트 구현체 | +| 주요 기능 | - Apache HttpClient 5 사용
- GET/POST/PUT/DELETE 메소드 처리
- OAuth2 토큰 관리 (발급/갱신)
- Outbound 암/복호화 (AES128, AES256, AES256GCM 등)
- Forward Proxy 지원
- 응답 헤더 릴레이 | + +--- + +## 클래스 관계도 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ eapim-online (프로젝트) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ ApiAdapterController ──uses──▶ ApiAdapterService │ +│ │ │ +│ │ extends │ +│ ▼ │ +└──────────────────────────────────────┼──────────────────────────────────┘ + │ +┌──────────────────────────────────────┼──────────────────────────────────┐ +│ elink-online-common (서브모듈) │ +├──────────────────────────────────────┼──────────────────────────────────┤ +│ ▼ │ +│ HttpAdapterServiceSupport │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ RequestDispatcher ──creates──▶ RequestProcessor │ +│ │ │ +│ │ extends │ +│ ▼ │ +│ RequestProcessorSupport │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ FlowRouter ────────────────▶ IFRouter │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ RESTProcess ◀──extends── HTTPProcess │ +│ │ │ │ +│ │ extends │ extends │ +│ ▼ ▼ │ +│ DefaultProcess ◀────────────┘ │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ HttpSender │ +│ │ │ +│ │ uses │ +│ ▼ │ +│ ElinkAdapterFactory │ +│ │ │ +│ │ creates │ +│ ▼ │ +│ HttpClient5AdapterServiceRest │ +│ │ │ +│ │ uses │ +│ ▼ │ +│ Apache HttpClient 5 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 메시지 변환 흐름 + +``` +[HTTP Request Body] + ↓ + StandardMessage (표준전문) + ↓ + EAIMessage (내부 전문) + ↓ + Transform (변환) + ↓ + [Target System Request] + ↓ + [Target System Response] + ↓ + Transform (역변환) + ↓ + EAIMessage + ↓ + StandardMessage + ↓ + [HTTP Response Body] +``` + +--- + +## 주요 처리 패턴 + +| 패턴 | 설명 | 처리 위치 | +|------|------|-----------| +| **SS (Sync-Sync)** | 동기 요청 → 동기 응답 | RequestProcessor, RESTProcess | +| **SA (Sync-Async)** | 동기 요청 → 비동기 응답 (Topic 대기) | RESTProcess.getTopicProperty() | +| **AS (Async-Sync)** | 비동기 요청 → 동기 응답 | DefaultProcess.isAsyncSyncPattern() | +| **AA (Async-Async)** | 비동기 요청 → 비동기 응답 | RequestProcessor | + +--- + +## 비동기(Async) 처리 플로우 + +### 비동기 전체 흐름 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ [송신 스레드 - 요청 수신] │ +│ │ +│ RequestProcessor.hookup() │ +│ ├─ ROUTING_TYPE = "QUEUE" 판단 │ +│ │ │ +│ └─ FlowRouter.process() │ +│ └─ JMSSender.sendToQueue(eaiMessage, ...) │ +│ └─ EAIMessage 직렬화 → JMS Queue 전송 │ +│ │ │ +│ ← 즉시 응답 반환 (202 Accepted 등) │ +└─────────────────────────┼───────────────────────────────────────────────┘ + │ + [JMS Queue] + (ActiveMQ / WebLogic JMS / WebSphere MQ) + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ [수신 스레드 - QueueConsumer] │ +│ │ +│ DefaultQueueConsumer.run() │ +│ ├─ consumer.receive() - 메시지 수신 대기 │ +│ │ │ +│ ├─ EAIMessage 역직렬화 │ +│ │ └─ transactionContext Map 복원 │ +│ │ │ +│ ├─ ElinkTransactionContext.initialize(...) │ +│ │ │ +│ └─ ElinkESBProcessProxy.callElinkESBProcess() │ +│ └─ DefaultProcess / RESTProcess 호출 │ +│ └─ 실제 대외기관 호출 │ +│ │ +│ (finally) ElinkTransactionContext.clear() │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Queue 송신 (FlowRouter → JMSSender) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ FlowRouter.process() │ +│ │ │ +│ ├─ route_type == "QUEUE" 확인 │ +│ │ │ +│ ├─ RoutingVO에서 asyncRoutingPath(Queue 이름) 조회 │ +│ │ │ +│ ├─ Properties에 추가 정보 설정 │ +│ │ ├─ EAIBWKCLS (업무그룹코드) │ +│ │ ├─ EAISVCCD (서비스코드) │ +│ │ └─ OSIDINSTCD (대외기관코드) │ +│ │ │ +│ └─ JMSSender.sendToQueue(eaiMessage, factory, queue, prop) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ JMSSender.sendToQueue() │ +│ │ │ +│ ├─ ElinkConfig.isUseInternalQueue() 확인 │ +│ │ │ +│ ├─ [내부 Queue] AmqQueueProducer.sendMessage() │ +│ │ └─ ActiveMQ 내장 브로커 사용 (vm://localhost) │ +│ │ │ +│ └─ [외부 Queue] sendToJmsQueue() │ +│ ├─ JmsServiceLocator에서 QueueConnectionFactory 획득 │ +│ ├─ QueueConnection → QueueSession → QueueSender 생성 │ +│ ├─ ObjectMessage 생성 │ +│ │ ├─ msg.setObject(eaiMessage) ← Serializable │ +│ │ └─ msg.setStringProperty(...) ← Properties 복사 │ +│ └─ qsender.send(msg) │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Queue Consumer 수신 및 처리 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ QueueConsumerService (Spring Bean) │ +│ │ │ +│ ├─ afterPropertiesSet() → startConsumer() │ +│ │ │ +│ ├─ ActiveMQConnectionFactory 생성 │ +│ │ └─ setTrustAllPackages(true) ← 역직렬화 허용 │ +│ │ │ +│ ├─ ExecutorService (FixedThreadPool) 생성 │ +│ │ │ +│ └─ DefaultQueueConsumer[maxThread] 생성 및 submit │ +│ └─ 각 Consumer가 별도 스레드에서 실행 │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Consumer.run() [Loop] (DefaultQueueConsumer / FCQueueReceiver) │ +│ │ │ +│ ├─ while(run) { │ +│ │ message = consumer.receive(2000) ← 2초 타임아웃 │ +│ │ │ +│ │ if(message == null) continue; │ +│ │ │ +│ │ // ObjectMessage 검증 및 EAIMessage 역직렬화 │ +│ │ omsg = (ObjectMessage) message; │ +│ │ eaiMessage = (EAIMessage) omsg.getObject(); │ +│ │ │ +│ │ // RoutingVO에서 Process 경로 조회 │ +│ │ routingVO = RoutingManager.getRoutingVO(flwCntlRtnNm); │ +│ │ serviceURI = routingVO.getSyncRoutingPath(); // BaseFCProcess │ +│ │ │ +│ │ // Process 호출 (ElinkESBProcessProxy 내부에서 컨텍스트 관리) │ +│ │ ElinkESBProcessProxy.callElinkESBProcess(serviceURI, ...) │ +│ │ } │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ ElinkESBProcessProxy.callElinkESBProcess() │ +│ │ │ +│ ├─ [조건 충족: ASYNC && !outbound] → 컨텍스트 초기화 │ +│ │ ElinkTransactionContext.initialize(eaiMessage.getTransactionContext()) +│ │ │ +│ ├─ BaseFCProcess.callService() │ +│ │ └─ ElinkESBProcessProxy (RESTProcess) ← 조건 미충족, 초기화 안함 │ +│ │ └─ RESTProcess 실행 │ +│ │ │ +│ └─ finally { ElinkTransactionContext.clear() } ← 조건부 해제 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 비동기 관련 클래스 + +| 클래스 | 위치 | 역할 | +|--------|------|------| +| `FlowRouter` | `elink-online-common/.../routing/` | ROUTING_TYPE에 따라 CALL/QUEUE/TOPIC 분기 | +| `JMSSender` | `elink-online-common/.../util/` | JMS Queue/Topic 메시지 송신 | +| `AmqQueueProducer` | `elink-online-common/.../activemq/` | ActiveMQ 내장 브로커용 Producer | + +#### JMS Provider별 Consumer 구현체 + +| Provider | Consumer | 비고 | +|----------|----------|------| +| **ActiveMQ** | `flowcontrol/activemq/DefaultQueueConsumer.java` | 내장 브로커 (vm://localhost) | +| **JEUS** | `flowcontrol/jeus/DefaultQueueConsumer.java` | JNDI Lookup | +| **WebSphere** | `flowcontrol/websphere/DefaultQueueConsumer.java` | JNDI Lookup | +| **WebLogic** | `flowcontrol/jms/FCQueueReceiver.java` | JNDI Lookup (weblogic.jndi.WLInitialContextFactory) | + +> `ElinkTransactionContext` 초기화/해제는 Consumer가 아닌 `ElinkESBProcessProxy`에서 조건부로 처리됩니다. + +### Queue 연결 방식 + +| 방식 | 설정 | 사용 환경 | +|------|------|-----------| +| **내장 브로커** | `vm://localhost` | 단일 인스턴스, 테스트 환경 | +| **TCP 연결** | `tcp://host:61617` | 분산 환경, 다중 인스턴스 | +| **외부 JMS** | JNDI Lookup | WebLogic, WebSphere MQ 등 | + +### 환경별 Consumer 설정 (Spring XML) + +``` +WebContent/WEB-INF/ +├─ applicationContext.xml ← 메인 설정 +│ ├─ import: mq/applicationContext-activemq.xml +│ └─ import: mq/applicationContext-websphere.xml +│ +└─ mq/ + ├─ applicationContext-activemq.xml ← ActiveMQ (현재 활성화) + │ └─ flowcontrol.activemq.QueueConsumerService + │ └─ uri: vm://localhost (내장 브로커) + │ + └─ applicationContext-websphere.xml ← WebSphere MQ (주석처리 = 비활성화) + └─ flowcontrol.websphere.QueueConsumerService + └─ 외부 WebSphere MQ 연결 +``` + +> **Note**: WebLogic/JEUS 환경에서 외부 JMS 사용 시 해당 XML 파일 추가 또는 수정 필요. +> 현재 프로젝트는 **ActiveMQ 내장 브로커**를 기본으로 사용. + +### TransactionContext 비동기 전달 + +``` +[송신 스레드] [수신 스레드] + │ │ + ├─ eaiMsg.transactionContext │ + │ └─ {"SEED_SALT": "abc123", ...} │ + │ │ + ▼ │ +┌──────────────┐ │ +│ Serialize │ ──── JMS Queue ────▶ │ +│ (EAIMessage) │ │ +└──────────────┘ │ + ▼ + ┌──────────────────┐ + │ Deserialize │ + │ (EAIMessage) │ + │ └─ context 복원 │ + └──────────────────┘ + │ + ▼ + ElinkESBProcessProxy + └─ 조건부 initialize() + │ + ElinkTransactionContext + .getSeedSalt() → "abc123" +``` + +> **Note**: `EAIMessage.transactionContext`는 `Serializable`이므로 JMS Queue를 통해 전달되어도 데이터가 유지됩니다. +> `ElinkTransactionContext.initialize()`는 `ElinkESBProcessProxy`에서 ASYNC 거래 && !outbound 조건일 때만 호출됩니다. + +--- + +## 암호화 지원 (HttpClient5AdapterServiceRest) + +| 알고리즘 | 용도 | Key 추출 방식 | +|----------|------|---------------| +| AES128-TOSS | 토스 연동 | clientSecret[22:38] | +| AES128-TOGETHER | 투게더 연동 | clientSecret[44:60] | +| AES256 | 일반 | clientSecret[10:42] | +| AES256-KAKAO | 카카오 연동 | IV/Key 별도 설정 | +| AES256-NICEON | 나이스지키미 연동 | Adapter Token 기반 | +| AES256GCM | GCM 모드 | 동적 Key 생성 | + +--- + +## 관련 파일 경로 + +### eapim-online (메인 프로젝트) +- `src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java` +- `src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java` + +### elink-online-common (서브모듈) + +#### 동기 처리 +- `src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceSupport.java` +- `src/main/java/com/eactive/eai/adapter/RequestDispatcher.java` +- `src/main/java/com/eactive/eai/inbound/processor/RequestProcessorSupport.java` +- `src/main/java/com/eactive/eai/inbound/processor/RequestProcessor.java` +- `src/main/java/com/eactive/eai/common/routing/IFRouter.java` +- `src/main/java/com/eactive/eai/outbound/DefaultProcess.java` +- `src/main/java/com/eactive/eai/outbound/RESTProcess.java` +- `src/main/java/com/eactive/eai/control/HttpSender.java` +- `src/main/java/com/eactive/eai/adapter/http/client/impl/HttpClient5AdapterServiceRest.java` + +#### 비동기 처리 (Queue) +- `src/main/java/com/eactive/eai/common/routing/FlowRouter.java` - 라우팅 분기 (CALL/QUEUE/TOPIC) +- `src/main/java/com/eactive/eai/common/routing/ElinkESBProcessProxy.java` - Process 호출 (비동기 컨텍스트 관리) +- `src/main/java/com/eactive/eai/common/util/JMSSender.java` - Queue/Topic 송신 +- `src/main/java/com/eactive/eai/common/activemq/AmqQueueProducer.java` - ActiveMQ Producer +- `src/main/java/com/eactive/eai/flowcontrol/activemq/QueueConsumerService.java` - ActiveMQ Consumer 서비스 +- `src/main/java/com/eactive/eai/flowcontrol/activemq/DefaultQueueConsumer.java` - ActiveMQ Consumer +- `src/main/java/com/eactive/eai/flowcontrol/jeus/QueueConsumerService.java` - JEUS Consumer 서비스 +- `src/main/java/com/eactive/eai/flowcontrol/jeus/DefaultQueueConsumer.java` - JEUS Consumer +- `src/main/java/com/eactive/eai/flowcontrol/websphere/QueueConsumerService.java` - WebSphere Consumer 서비스 +- `src/main/java/com/eactive/eai/flowcontrol/websphere/DefaultQueueConsumer.java` - WebSphere Consumer +- `src/main/java/com/eactive/eai/flowcontrol/jms/FCQueueReceiver.java` - WebLogic JMS Consumer diff --git a/docs/ELINK_TRANSACTION_CONTEXT.md b/docs/ELINK_TRANSACTION_CONTEXT.md new file mode 100644 index 0000000..12bb0a7 --- /dev/null +++ b/docs/ELINK_TRANSACTION_CONTEXT.md @@ -0,0 +1,300 @@ +# ElinkTransactionContext 아키텍처 + +## 개요 + +`ElinkTransactionContext`는 트랜잭션(거래) 범위에서 컨텍스트 변수를 관리하기 위한 ThreadLocal 기반 유틸리티입니다. JEP 함수 등 EAIMessage나 Properties에 직접 접근할 수 없는 클래스에서 트랜잭션 범위 변수에 접근할 수 있도록 합니다. + +--- + +## 핵심 설계 원칙 + +1. **ThreadLocal이 직접 저장소**: EAIMessage의 Map을 참조하지 않고 ThreadLocal에 직접 저장 +2. **Lazy Initialization**: 최초 put 시 Map 자동 생성 (명시적 초기화 불필요) +3. **clear()는 필수**: 스레드 풀 환경에서 메모리 누수 방지 +4. **비동기 전달용 필드 분리**: `transactionContextTransfer`는 JMS 전달 전용 + +--- + +## 구성 요소 + +### 1. ElinkTransactionContext (저장소) + +```java +// ElinkTransactionContext.java (elink-online-core) +public class ElinkTransactionContext { + private static final ThreadLocal> THREAD_LOCAL = new ThreadLocal<>(); + + // 컨텍스트 정리 (필수) + public static void clear(); + + // 비동기 전달용 + public static Map getAll(); // 복사본 반환 + public static void restore(Map ctx); // 복원 + + // 명시적 접근자 (Lazy init - 최초 put 시 Map 생성) + public static void setSeedSalt(String seedSalt); + public static String getSeedSalt(); + public static void setEncryptionKey(String encryptionKey); + public static String getEncryptionKey(); +} +``` + +- **위치**: `elink-online-core` +- **역할**: ThreadLocal 기반 트랜잭션 변수 저장소 +- **특징**: + - 명시적 초기화 불필요 (Lazy init) + - `clear()`는 필수 (finally 블록에서 호출) + +### 2. EAIMessage.transactionContextTransfer (전달용) + +```java +// EAIMessage.java (elink-online-common) +/** + * [주의] 비동기 JMS 전달 전용 - 직접 사용 금지! + */ +private Map transactionContextTransfer; + +public Map getTransactionContextTransfer(); +public void setTransactionContextTransfer(Map ctx); +``` + +- **위치**: `elink-online-common` +- **역할**: 비동기 거래 시 JMS를 통한 컨텍스트 전달 +- **특징**: + - `Serializable` - ObjectMessage로 직렬화 + - **직접 데이터 저장 용도로 사용 금지** + - 복원 후 즉시 null로 초기화 + +--- + +## 처리 흐름 + +### 동기 (Sync) 처리 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ApiAdapterController.callApi() │ +│ │ │ +│ ├─ try { │ +│ │ service.callApi() │ +│ │ └─ RequestProcessor.hookup() │ +│ │ │ │ +│ │ ├─ [컨텍스트 사용 가능 - Lazy Init] ───────────────── │ +│ │ │ ElinkTransactionContext.setSeedSalt("value") │ +│ │ │ └─ 최초 호출 시 Map 자동 생성 │ +│ │ │ │ +│ │ ├─ FlowRouter.process() │ +│ │ │ └─ ElinkESBProcessProxy → Process 호출 │ +│ │ │ │ +│ │ ├─ [반환 후에도 컨텍스트 사용 가능] ✓ │ +│ │ │ │ +│ │ └─ 응답 반환 │ +│ │ } │ +│ └─ finally { │ +│ ElinkTransactionContext.clear() ← 최상위 진입점에서 해제 │ +│ } │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 비동기 (Async) 처리 - Queue 사용 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ [송신 스레드] │ +│ │ +│ ApiAdapterController.callApi() │ +│ │ │ +│ ├─ try { │ +│ │ RequestProcessor.hookup() │ +│ │ ├─ ElinkTransactionContext.setSeedSalt("value") │ +│ │ │ └─ ThreadLocal에 직접 저장 │ +│ │ │ │ +│ │ └─ FlowRouter.process() │ +│ │ ├─ [컨텍스트 복사] ───────────────────────────────────── │ +│ │ │ eaiMessage.setTransactionContextTransfer( │ +│ │ │ ElinkTransactionContext.getAll() │ +│ │ │ ) │ +│ │ └─ JMSSender.sendToQueue(eaiMessage, ...) │ +│ │ └─ EAIMessage 직렬화 (transactionContextTransfer 포함)│ +│ │ } │ +│ └─ finally { ElinkTransactionContext.clear() } │ +│ │ │ +└──────────────────────────┼──────────────────────────────────────────────┘ + │ + [JMS Queue] + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ [수신 스레드 - Consumer] │ +│ │ +│ FCQueueReceiver / DefaultQueueConsumer │ +│ │ │ +│ ├─ EAIMessage 역직렬화 (transactionContextTransfer 복원) │ +│ │ │ +│ └─ ElinkESBProcessProxy.callElinkESBProcess(BaseFCProcess, ...) │ +│ │ │ +│ ├─ [조건 충족: ASYNC && !outbound] │ +│ │ ├─ ElinkTransactionContext.restore( │ +│ │ │ eaiMessage.getTransactionContextTransfer() │ +│ │ │ ) │ +│ │ └─ eaiMessage.setTransactionContextTransfer(null) ← 즉시 초기화 +│ │ │ +│ ├─ BaseFCProcess.callService() │ +│ │ └─ ElinkESBProcessProxy(..., RESTProcess, ...) │ +│ │ └─ [조건 미충족: outbound] → 복원 안함 │ +│ │ └─ RESTProcess 호출 │ +│ │ └─ ElinkTransactionContext.getSeedSalt() │ +│ │ └─ 송신 스레드에서 설정한 값 조회 ✓ │ +│ │ │ +│ └─ finally { ElinkTransactionContext.clear() } │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 초기화/해제 위치 + +| 위치 | 역할 | 비고 | +|------|------|------| +| `ApiAdapterController.callApi()` | 해제 (finally) | 동기 - 최상위 진입점 | +| `RequestProcessorSupport.execute()` | 해제 (finally) | 동기 - 안전망 (이중 해제) | +| `FlowRouter.process()` | 컨텍스트 복사 | 비동기 송신 전 | +| `ElinkESBProcessProxy.callElinkESBProcess()` | 복원/해제 (조건부) | 비동기 수신 시 | + +### ElinkESBProcessProxy 복원 조건 + +```java +boolean shouldManageContext = eaiMessage != null + && EAIMessageKeys.ASYNC_SVC.equals(eaiMessage.getSvcTsmtUsgTp()) // ASYNC 거래 + && (serviceURI == null || !serviceURI.startsWith("com.eactive.eai.outbound")); // outbound가 아닐 때 + +if (shouldManageContext) { + ElinkTransactionContext.restore(eaiMessage.getTransactionContextTransfer()); + eaiMessage.setTransactionContextTransfer(null); // 복원 후 즉시 초기화 +} +``` + +- **ASYNC 거래**: 동기 거래는 Lazy init으로 자동 처리 +- **outbound가 아닐 때**: BaseFCProcess 호출 시에만 복원, RESTProcess 호출 시에는 이미 복원된 상태 + +> **Note**: `clear()`는 멱등(idempotent)하므로 여러 번 호출되어도 안전합니다. + +--- + +## 사용 예시 + +### 값 설정 (RequestProcessor, DefaultProcess 등) + +```java +// SeedSalt 설정 (최초 호출 시 Map 자동 생성) +ElinkTransactionContext.setSeedSalt("o123y498172"); + +// EncryptionKey 설정 +ElinkTransactionContext.setEncryptionKey("myKey123"); +``` + +### 값 조회 (JEP 함수, Transform 등) + +```java +// SeedSalt 조회 +String seedSalt = ElinkTransactionContext.getSeedSalt(); + +// EncryptionKey 조회 +String encryptionKey = ElinkTransactionContext.getEncryptionKey(); +``` + +--- + +## 변수 추가 방법 + +새로운 트랜잭션 변수가 필요한 경우 `ElinkTransactionContext`에 접근자 메소드 추가: + +```java +// ElinkTransactionContext.java + +// 1. Key 상수 추가 +private static final String KEY_NEW_VARIABLE = "NEW_VARIABLE"; + +// 2. Setter 추가 (private put 헬퍼 사용) +public static void setNewVariable(String value) { + put(KEY_NEW_VARIABLE, value); +} + +// 3. Getter 추가 (private get 헬퍼 사용) +public static String getNewVariable() { + return get(KEY_NEW_VARIABLE); +} +``` + +--- + +## 주의사항 + +### Thread Pool 환경 + +- `ApiAdapterController.callApi()` finally에서 `clear()` 호출 +- `ElinkESBProcessProxy.callElinkESBProcess()` finally에서 조건부 `clear()` 호출 +- Thread 재사용 시 이전 트랜잭션 데이터 누수 방지 + +### 비동기 Queue 처리 + +- `EAIMessage.transactionContextTransfer`는 **전달 전용** (직접 접근 금지) +- `FlowRouter`에서 송신 전 `getAll()`로 복사 +- `ElinkESBProcessProxy`에서 `restore()` 후 즉시 null 초기화 + +### transactionContextTransfer 직접 사용 금지 + +```java +// ❌ 잘못된 사용 +eaiMessage.getTransactionContextTransfer().put("key", "value"); + +// ✓ 올바른 사용 +ElinkTransactionContext.setSeedSalt("value"); +``` + +--- + +## 관련 파일 + +| 파일 | 위치 | 역할 | +|------|------|------| +| `ElinkTransactionContext.java` | `elink-online-core` | ThreadLocal 컨텍스트 저장소 | +| `EAIMessage.java` | `elink-online-common` | transactionContextTransfer (전달용) | +| `ApiAdapterController.java` | `eapim-online` | 해제 (동기 최상위 진입점) | +| `FlowRouter.java` | `elink-online-common` | 비동기 송신 전 컨텍스트 복사 | +| `ElinkESBProcessProxy.java` | `elink-online-common` | 복원/해제 (비동기 - 조건부) | +| `RequestProcessorSupport.java` | `elink-online-common` | 해제 (동기 안전망) | + +--- + +## 클래스 다이어그램 + +``` +┌─────────────────────────────────────┐ +│ elink-online-core │ +├─────────────────────────────────────┤ +│ ElinkTransactionContext │ +│ ┌───────────────────────────────┐ │ +│ │ ThreadLocal>│◄── 직접 저장소 (Lazy Init) +│ └───────────────────────────────┘ │ +│ + clear() │ +│ + getAll() → Map 복사본 │ +│ + restore(Map) │ +│ + setSeedSalt() / getSeedSalt() │ +│ + setEncryptionKey() / ... │ +└───────────────┬─────────────────────┘ + │ 비동기 전달 시 + │ getAll() → 복사 + ▼ +┌─────────────────────────────────────┐ +│ elink-online-common │ +├─────────────────────────────────────┤ +│ EAIMessage │ +│ - transactionContextTransfer │◄── JMS 전달 전용 (직접 사용 금지!) +│ : Map │ Serializable +│ │ +│ [송신] FlowRouter에서 설정 │ +│ [수신] ElinkESBProcessProxy에서 │ +│ restore() 후 null 초기화 │ +└─────────────────────────────────────┘ +``` diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java index 11feef8..d118539 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java @@ -60,8 +60,10 @@ public class ApiAdapterController implements HttpAdapterServiceKey { String apiUri = servletRequest.getRequestURI(); // /api/v1/public/getUserInfo.svc apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath()); - apiUri = StringUtils.removeEnd(apiUri, "/"); - + apiUri = StringUtils.removeEnd(apiUri, "/"); +// TODO: 샘플 주석 작업 완료 후 제거 요망 +// String salt = servletRequest.getHeader("salt"); +// ElinkTransactionContext.setSeedSalt(salt); //** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다 String methodAndUri = servletRequest.getMethod() + "|" + apiUri; HttpDynamicInAdapterUri adptUri = null; From 4c5a9efcc4adfab9af87ccce5af3b34b80407f97 Mon Sep 17 00:00:00 2001 From: cho Date: Thu, 8 Jan 2026 11:12:10 +0900 Subject: [PATCH 03/15] =?UTF-8?q?CA=20Token=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- elink-online-common | 2 +- elink-online-core | 2 +- elink-online-core-jpa | 2 +- elink-online-emsclient | 2 +- elink-online-transformer | 2 +- .../custom/BearerTokenContoller.java | 17 +--- .../authserver/custom/BearerTokenFilter.java | 77 --------------- .../authserver/custom/BearerTokenInfo.java | 49 ---------- .../authserver/custom/BearerTokenService.java | 97 ------------------- 9 files changed, 6 insertions(+), 244 deletions(-) delete mode 100644 src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java delete mode 100644 src/main/java/com/eactive/eai/authserver/custom/BearerTokenInfo.java delete mode 100644 src/main/java/com/eactive/eai/authserver/custom/BearerTokenService.java diff --git a/elink-online-common b/elink-online-common index 8d21eba..7d5fb8a 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 8d21eba5e8dc25d11ad315aa4559566cd3623bba +Subproject commit 7d5fb8afe4383d91c0061cbc8697c96f98a6e186 diff --git a/elink-online-core b/elink-online-core index 0f148e9..75e3d34 160000 --- a/elink-online-core +++ b/elink-online-core @@ -1 +1 @@ -Subproject commit 0f148e997ec808e01a013baa8ca7b0053a69fe22 +Subproject commit 75e3d3451c0965ad7bbf88a469211afb75f3dc5f diff --git a/elink-online-core-jpa b/elink-online-core-jpa index 2cd9c29..ea932d1 160000 --- a/elink-online-core-jpa +++ b/elink-online-core-jpa @@ -1 +1 @@ -Subproject commit 2cd9c29d4df228c1ffd7ed6471c8245510b19213 +Subproject commit ea932d1fddecd1f1fa8fc52959bcc6cfb455434a diff --git a/elink-online-emsclient b/elink-online-emsclient index f68423b..7cd000f 160000 --- a/elink-online-emsclient +++ b/elink-online-emsclient @@ -1 +1 @@ -Subproject commit f68423b40ecb5076990739f09f87ec62f7fd17ae +Subproject commit 7cd000f6927095a87d622c921ffe944a3802f3d7 diff --git a/elink-online-transformer b/elink-online-transformer index c3e2de4..c3234b6 160000 --- a/elink-online-transformer +++ b/elink-online-transformer @@ -1 +1 @@ -Subproject commit c3e2de482be0fa692a5133cf77e0e3dc86357b6e +Subproject commit c3234b623479a675ef869bddfc83318373539ac7 diff --git a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java index d3d547f..44e32de 100644 --- a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java +++ b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenContoller.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; +import com.eactive.eai.authserver.service.BearerTokenService; import com.eactive.eai.authserver.service.OAuth2Manager; import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.MessageUtil; @@ -85,22 +86,6 @@ public class BearerTokenContoller { } } -// // 서비스용 Bearer Token 발급 -// @PostMapping("/service") -// public ResponseEntity issueFileToken(@RequestBody Map req) { -// String fileId = req.get("fileid"); -// -// String token = tokenService.generateFileToken(fileId); -// -// Map tokenMap = new HashMap<>(); -// tokenMap.put("token_type", "Bearer"); -// tokenMap.put("file_token", token); -// tokenMap.put("expires_in", "599"); -// tokenMap.put("expires_on", "1575593514"); -// tokenMap.put("resource", fileId); -// -// return ResponseEntity.ok(tokenMap); -// } private void veryfyClient(ClientDetails clientDetails, String clientSecret, Set scopeSet) throws JwtAuthException { if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) { throw new JwtAuthException("invalid_client", "Bad client credentials(client_secret is empty or not match)"); diff --git a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java deleted file mode 100644 index 5ccb70c..0000000 --- a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenFilter.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.eactive.eai.authserver.custom; - -import java.io.IOException; -import java.util.Arrays; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONObject; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; -import com.eactive.eai.common.util.Logger; -import com.eactive.eai.common.util.MessageUtil; - -@Component -public class BearerTokenFilter extends OncePerRequestFilter{ - - public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); - private final BearerTokenService tokenService; - - public static final String ERROR_AUTHENTICATION_FAIL = "E.AUTHENTICATION_FAIL"; - public static final String ERROR_AUTHORIZATION_FAIL = "E.AUTHORIZATION_FAIL"; - public static final String ERROR_TOKEN_EXPIRED = "E.TOKEN_EXPIRED"; - - public BearerTokenFilter(BearerTokenService tokenService) { - this.tokenService = tokenService; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws IOException, ServletException { - try { - String auth = request.getHeader("Authorization"); - - if (auth != null && auth.startsWith("Bearer ")) { - String token = auth.substring(7); - - String clientId = null; - String fileId = null; - - if (tokenService.validateCA(token)) { - clientId = tokenService.getClientIdFromCA(token); - } else { - throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Access token is invalid or expired"); - } - -// if (fileId != null) { -// UsernamePasswordAuthenticationToken authentication = -// new UsernamePasswordAuthenticationToken(fileId, null, Arrays.asList()); -// SecurityContextHolder.getContext().setAuthentication(authentication); -// } - } - - filterChain.doFilter(request, response); - } catch (JwtAuthException jae) { - logger.debug(jae.getMessage()); - String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, jae.getMessage()); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write(errorJson); - } catch (Exception e) { - logger.error(e.getMessage()); - String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR, e.getMessage()); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write(errorJson); - } - } -} - diff --git a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenInfo.java b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenInfo.java deleted file mode 100644 index e5a37fb..0000000 --- a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenInfo.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.eactive.eai.authserver.custom; - -import java.util.Set; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class BearerTokenInfo { - @JsonProperty("client_id") - private String clientId; - private Long expiresIn; - private Long expiresAt; - private Set scopeSet; - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public Long getExpiresIn() { - return expiresIn; - } - - public void setExpiresIn(Long expiresIn) { - this.expiresIn = expiresIn; - } - - public Long getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(Long expiresIn) { - this.expiresAt = System.currentTimeMillis() + (expiresIn * 1000); - } - - public Set getScopeSet() { - return scopeSet; - } - - public void setScopeSet(Set scopeSet) { - this.scopeSet = scopeSet; - } - - public boolean isExpired() { - return System.currentTimeMillis() > expiresAt; - } -} diff --git a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenService.java b/src/main/java/com/eactive/eai/authserver/custom/BearerTokenService.java deleted file mode 100644 index 3b01368..0000000 --- a/src/main/java/com/eactive/eai/authserver/custom/BearerTokenService.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.eactive.eai.authserver.custom; - -import java.security.SecureRandom; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import org.springframework.stereotype.Service; - -@Service -public class BearerTokenService { - - // CA 토큰 저장소 - private final Map CATokenStore = new ConcurrentHashMap<>(); - - // File 토큰 저장소 - private final Map fileTokenStore = new ConcurrentHashMap<>(); - -// // FileTransfer 토큰 저장소 -// private final Map fileTransferTokenStore = new ConcurrentHashMap<>(); - - public String generateCAToken(String clientId, Long expiresIn, Set scopeSet) { - String token = UUID.randomUUID().toString(); - - BearerTokenInfo CATokenInfo = new BearerTokenInfo(); - CATokenInfo.setClientId(clientId); - CATokenInfo.setExpiresIn(expiresIn); - CATokenInfo.setExpiresAt(expiresIn); - CATokenInfo.setScopeSet(scopeSet); - - CATokenStore.put(token, CATokenInfo); - - return token; - } - - public String generateFileToken(String fileId) { - //String token = UUID.randomUUID().toString().replace("-", ""); - String token = generateTokenString(987); - fileTokenStore.put(token, fileId); - return token; - } - -// public String generateFileTransferToken(String fileId) { -// //String token = UUID.randomUUID().toString().replace("-", ""); -// String token = generateTokenString(654); -// fileTransferTokenStore.put(token, fileId); -// return token; -// } - - public boolean validateCA(String token) { - BearerTokenInfo CATokenInfo = CATokenStore.get(token); - if (CATokenInfo == null) return false; - - if (CATokenInfo.isExpired()) { - CATokenStore.remove(token); - return false; - } - return CATokenStore.containsKey(token); - } - - public boolean validateFile(String token) { - return fileTokenStore.containsKey(token); - } - -// public boolean validateFileTransfer(String token) { -// return fileTransferTokenStore.containsKey(token); -// } - - public String getClientIdFromCA(String token) { - BearerTokenInfo CATokenInfo = CATokenStore.get(token); - return CATokenInfo.getClientId(); - } - - public String getFileIdFromFile(String token) { - return fileTokenStore.get(token); - } - -// public String getFileIdFromFileTransfer(String token) { -// return fileTransferTokenStore.get(token); -// } - - private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - private static final SecureRandom secureRandom = new SecureRandom(); - - public static String generateTokenString(int length) { - // Java 8의 IntStream과 Collectors.joining을 사용한 효율적인 방법 - return IntStream.range(0, length) - .map(i -> secureRandom.nextInt(CHARACTERS.length())) - .mapToObj(CHARACTERS::charAt) - .map(String::valueOf) - .collect(Collectors.joining()); - } -} - From daa86517d804964c2503ac493b2a4fc6e4e4a439 Mon Sep 17 00:00:00 2001 From: cho Date: Thu, 8 Jan 2026 14:11:59 +0900 Subject: [PATCH 04/15] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eai/authserver/custom/SecurityConfig.java | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 src/main/java/com/eactive/eai/authserver/custom/SecurityConfig.java diff --git a/src/main/java/com/eactive/eai/authserver/custom/SecurityConfig.java b/src/main/java/com/eactive/eai/authserver/custom/SecurityConfig.java deleted file mode 100644 index f2a6183..0000000 --- a/src/main/java/com/eactive/eai/authserver/custom/SecurityConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.eactive.eai.authserver.custom; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -// -//@Configuration -//@EnableWebSecurity -//public class SecurityConfig { -// -// private final BearerTokenFilter tokenFilter; -// -// public SecurityConfig(BearerTokenFilter tokenFilter) { -// this.tokenFilter = tokenFilter; -// } -// -// @Bean -// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { -// -// http -// .csrf().disable() -// .authorizeRequests() -// .antMatchers("/auth/token/**").permitAll() -// .anyRequest().authenticated() -// .and() -// .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); -// -// return http.build(); -// } -//} -@Configuration -@EnableWebSecurity -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - private final BearerTokenFilter tokenFilter; - - @Autowired - public SecurityConfig(BearerTokenFilter tokenFilter) { - this.tokenFilter = tokenFilter; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - - http - .csrf().disable() - .authorizeRequests() - .antMatchers("/auth/token/**").permitAll() - .anyRequest().authenticated() - .and() - .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); - } -} \ No newline at end of file From c789e82e0e2a63516983edf57a075479324eb7ce Mon Sep 17 00:00:00 2001 From: yunjy-hp Date: Thu, 8 Jan 2026 15:13:29 +0900 Subject: [PATCH 05/15] =?UTF-8?q?ElinkTransactionContext=EC=97=90=20?= =?UTF-8?q?=EC=B5=9C=EC=B4=88=EC=9A=94=EC=B2=AD=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EA=B0=80=EC=A0=B8=EB=8B=A4?= =?UTF-8?q?=20=EC=93=B0=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../userdefined/SubStringRequestMessage.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java diff --git a/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java b/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java new file mode 100644 index 0000000..b02b4b5 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java @@ -0,0 +1,44 @@ +package com.eactive.eai.custom.transformer.function.userdefined; + +import com.eactive.eai.common.context.ElinkTransactionContext; +import org.apache.commons.lang.StringUtils; +import org.nfunk.jep.ParseException; +import org.nfunk.jep.function.PostfixMathCommand; + +import java.util.Stack; + +public class SubStringRequestMessage extends PostfixMathCommand { + public SubStringRequestMessage() { + numberOfParameters = -1; + } + + public void run(Stack inStack) throws ParseException { + checkStack(inStack); + + String bizData = ElinkTransactionContext.getRequestBizData(); + + Object p1 = inStack.pop(); + Object p2 = inStack.pop(); + + int start = toInt(p2); + int end = toInt(p1); + + String resultStr = bizData.substring(start, end); + + if(inStack.size() > 0){ + inStack.pop(); + } + + inStack.push(resultStr); + } + + public static int toInt(Object obj) { + if (obj instanceof Number) { + return ((Number) obj).intValue(); + } + if (obj instanceof String) { + return Integer.parseInt(((String) obj).trim()); + } + throw new IllegalArgumentException("Cannot convert to int: " + obj); + } +} From 3b2097eb8c0f114c6532549945485f12e3711798 Mon Sep 17 00:00:00 2001 From: yunjy-hp Date: Thu, 8 Jan 2026 15:15:25 +0900 Subject: [PATCH 06/15] =?UTF-8?q?clear=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../function/userdefined/SubStringRequestMessage.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java b/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java index b02b4b5..add30f3 100644 --- a/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java +++ b/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/SubStringRequestMessage.java @@ -24,10 +24,7 @@ public class SubStringRequestMessage extends PostfixMathCommand { int end = toInt(p1); String resultStr = bizData.substring(start, end); - - if(inStack.size() > 0){ - inStack.pop(); - } + inStack.clear(); inStack.push(resultStr); } From 70a8adf236a86662c6d405ffab05d98b3f66969a Mon Sep 17 00:00:00 2001 From: jaewohong Date: Fri, 9 Jan 2026 16:16:30 +0900 Subject: [PATCH 07/15] =?UTF-8?q?security=20libs=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- elink-online-transformer | 2 +- libs/security-1.0.0.jar | Bin 0 -> 19023 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 libs/security-1.0.0.jar diff --git a/elink-online-transformer b/elink-online-transformer index c3234b6..dd68924 160000 --- a/elink-online-transformer +++ b/elink-online-transformer @@ -1 +1 @@ -Subproject commit c3234b623479a675ef869bddfc83318373539ac7 +Subproject commit dd68924d78c70c0baf37970dd658e7accc72e260 diff --git a/libs/security-1.0.0.jar b/libs/security-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..2e46994ff3b220be263c8e8b7c9e267b2d9e08af GIT binary patch literal 19023 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 literal 0 HcmV?d00001 From 746fd44fd6d905578cf7d653ecf5f611a7acb4b6 Mon Sep 17 00:00:00 2001 From: jaewohong Date: Fri, 9 Jan 2026 17:30:44 +0900 Subject: [PATCH 08/15] =?UTF-8?q?seed=20=EC=95=94=ED=98=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApiAdapterController.java | 4 +- .../adapter/service/ApiAdapterService.java | 191 +++++++++--------- 2 files changed, 98 insertions(+), 97 deletions(-) diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java index d118539..213cc3a 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java @@ -61,9 +61,7 @@ public class ApiAdapterController implements HttpAdapterServiceKey { // /api/v1/public/getUserInfo.svc apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath()); apiUri = StringUtils.removeEnd(apiUri, "/"); -// TODO: 샘플 주석 작업 완료 후 제거 요망 -// String salt = servletRequest.getHeader("salt"); -// ElinkTransactionContext.setSeedSalt(salt); + //** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다 String methodAndUri = servletRequest.getMethod() + "|" + apiUri; HttpDynamicInAdapterUri adptUri = null; diff --git a/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java b/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java index 1f7be85..14acfa9 100644 --- a/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java +++ b/src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import org.springframework.security.oauth2.provider.ClientDetails; import com.nimbusds.jwt.SignedJWT; +import com.eactive.eai.common.context.ElinkTransactionContext; @Service @@ -68,6 +69,7 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { private static final String FILE_GROUP_NAME = "image-file"; private static final String UPLOAD_ROOT_PATH = "UPLOAD_ROOT_PATH"; public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id"; // jwhong + private boolean encryptResponseApply; // inbound에 대한 응답 암호화 여부 flag public String callApi(HttpServletRequest request, HttpServletResponse response, Properties httpProp, AdapterGroupVO adapterGroupVO, AdapterVO adapterVO, Properties transactionProp) throws Exception { int traceLevel = 0; @@ -124,8 +126,11 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { transactionProp.put(INBOUND_TOKEN, inboundToken); transactionProp.put(ENCRYPT_AES256_IV, httpProp.getProperty(ENCRYPT_AES256_IV, "")); //jwhong transactionProp.put(ENCRYPT_AES256_KEY, httpProp.getProperty(ENCRYPT_AES256_KEY, "")); //jwhong + + // SEED 컬럼암호하 시 Key로 사용함 + String seedkey = getHeaders(request).getOrDefault("x-obp-partnercode", "").toString(); + ElinkTransactionContext.setSeedKey(seedkey); - try { traceLevel = Integer.parseInt(traceLevelTemp); } catch (Exception e) { @@ -317,29 +322,10 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { result = mapper.writeValueAsString(rootNode); } } - - // KJBank는 요청에 대한 응답 (Sync응답, Aync 에 대한 Ack응답) 에 대하여 암호화 하지 않는다. 무조건 안한다. 따라서 아래부분은 구현은 했지만 사용하지 않는다. - //result = doPostEncryption(result, transactionProp); // jwhong Encrypt - // ASYNC 인 경우 광주은행은 RETURN 해 주는 값들이 수정되어야 한다. JWHONG - /* - if ("ASYN".equals(syncAsyncType) && MessageType.JSON.equals(adptMsgType) && StringUtils.isNotBlank(headerGroupName)) { - ObjectMapper mapper = new ObjectMapper(); - ObjectNode rootNode = (ObjectNode) mapper.readTree(result); - JsonNode headerGroup = rootNode.get(headerGroupName); - if(headerGroup != null) { - for(Iterator it = headerGroup.fieldNames(); it.hasNext();) { - String name = it.next(); - String value = headerGroup.get(name).asText(); - response.addHeader(name, value); - } - rootNode.remove(headerGroupName); - result = mapper.writeValueAsString(rootNode); - } - } - */ - - // KJBank는 요청에 대한 응답 (Sync응답, Aync 에 대한 Ack응답) 에 대하여 암호화 하지 않는다. 무조건 안한다. 따라서 아래부분은 구현은 했지만 사용하지 않는다. - //result = doPostEncryption(result, transactionProp); // jwhong Encrypt + + // KJBank는 요청에 대한 응답 (Sync응답, Aync 에 대한 Ack응답) 에 대하여 암호화 하지 않는다. 무조건 안한다. 따라서 아래부분은 구현은 했지만 사용하지 않는다. + encryptResponseApply = StringUtils.equalsIgnoreCase(httpProp.getProperty("ENCRYPT_RESPONSE_APPLY", "N"), "Y"); + result = doPostEncryption(result, transactionProp, request); // jwhong Encrypt return result; } @@ -382,7 +368,7 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { decryptedMessage = doInboundPreDecrypt(eaiBody, decryptAlgorithm,clientSecret,secretAES256Iv, secretAES256Key ); if (decryptedMessage == null) { - throw new Exception("[HttpClient5AdapterServiceRest] Decrypt Error : Invalid encrypted message"); + throw new Exception("[ApiAdapterService] Decrypt Error : Invalid encrypted message"); } @@ -393,19 +379,26 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { // jwhong encrypt - private String doPostEncryption(String eaiBody, Properties transactionProp) { + private String doPostEncryption(String eaiBody, Properties transactionProp, HttpServletRequest request) throws Exception { + + if ( !encryptResponseApply) { + return eaiBody; + } + String decryptAlgorithm = transactionProp.getProperty(ENCRYPT_ALGORITHM, ""); String xElinkClientId = transactionProp.getProperty(HEADER_NAME_CLIENT_ID, ""); // 이 값이 OAuth의 client id 값이다 . http header 에 포함되어 온다. jwhong String encryptBaseKeyType = transactionProp.getProperty(ENCRYPT_BASE_TYPE); - String inboundToken = transactionProp.getProperty(INBOUND_TOKEN, ""); + String inboundToken = ""; String secretAES256Iv = transactionProp.getProperty("ENCRYPT_AES256_IV"); String secretAES256Key = transactionProp.getProperty("ENCRYPT_AES256_KEY"); - - + if ( decryptAlgorithm == "" ) { // 복호화 대상이 아님 return eaiBody; } + + inboundToken = JwtTokenExtractor(request); + xElinkClientId = JwtClientIdExtractor(inboundToken); // token에서 client id를 추출함 // 암호화 key를 token 내용으로 할지 client secret 내용으로 할지 결정함. adapter property에 정의함 // ENCRYPT_ALGORITHM 을 설정했는데 ENCRYPT_BASE_TYPE을 설정안하면 Decrypt 수행시 Exception 발생함 @@ -421,12 +414,16 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { } } - byte[] decryptedMessage = null; - decryptedMessage = doInboundPostEncrypt(eaiBody, decryptAlgorithm,clientSecret,secretAES256Iv, secretAES256Key ); + String encryptedMessage = null; + encryptedMessage = doInboundPostEncrypt(eaiBody, decryptAlgorithm,clientSecret,secretAES256Iv, secretAES256Key ); - String resultMessage = new String(decryptedMessage, StandardCharsets.UTF_8 ); - return resultMessage; - //return new String(decryptedMessage, StandardCharsets.UTF_8 ); + if (encryptedMessage == null) { + throw new Exception("[ApiAdapterService] Encrypt Error : Invalid PlainText message"); + } + + return encryptedMessage; + //String resultMessage = new String(encryptedMessage, StandardCharsets.UTF_8 ); + //return resultMessage; } @@ -466,7 +463,7 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { eaiStringBody = extractBodyMsg(decryptAlgorithm, eaiStringBody); if (eaiStringBody == null) { - throw new Exception("[HttpClient5AdapterServiceRest] Cannot decrypt Error : input is plain text"); + throw new Exception("[ApiAdapterService] Cannot decrypt Error : input is plain text"); } //test source @@ -568,28 +565,7 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { String decryptedJsonKey = ""; String decryptedBodyMessage = ""; - switch (decryptAlgorithm) { - case "AES128": - case "AES128-TOSS": - decryptedJsonKey = "preScreeningRequest"; - break; - case "AES128-TOGETHER": - decryptedJsonKey = "obpTxData"; - break; - case "AES256": - decryptedJsonKey = "preScreeningRequest"; - break; - case "AES256-KAKAO": - decryptedJsonKey = "encrypted_data"; - break; - case "AES256-TOSS": - decryptedJsonKey = "encryptedData"; - break; - case "AES256GCM": - break; - default: - break; - } + decryptedJsonKey = getJsonKey(decryptAlgorithm); try { JSONParser parser = new JSONParser(); @@ -603,74 +579,95 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { return decryptedBodyMessage; } - - private byte[] doInboundPostEncrypt(String eaiStringBody, String encryptAlgorithm, String clientSecretKey, String secretAES256Iv, String secretAES256Key) { - - byte[] encryptedMessage = null; - //String eaiStringBody = new String(eaiBody, StandardCharsets.UTF_8); - - //test source - logger.debug("Base64 문자열: " + eaiStringBody); - logger.debug("Base64 문자열 길이: " + eaiStringBody.length()); - logger.debug("Base64 문자열 끝: " + eaiStringBody.substring(eaiStringBody.length() - 10)); + + private String getJsonKey(String Algorithm) { - // Base64 디코딩 테스트 - try { - byte[] decoded = Base64.getDecoder().decode(eaiStringBody); - logger.debug("디코딩 성공, 길이: " + decoded.length); - } catch (IllegalArgumentException e) { - logger.debug("Base64 디코딩 실패: " + e.getMessage()); - } - // end test source + String jsonKey = ""; + + switch (Algorithm) { + case "AES128": + case "AES128-TOSS": + jsonKey = "preScreeningRequest"; + break; + case "AES128-TOGETHER": + jsonKey = "obpTxData"; + break; + case "AES256": + jsonKey = "preScreeningRequest"; + break; + case "AES256-KAKAO": + jsonKey = "encrypted_data"; + break; + case "AES256-TOSS": + jsonKey = "encryptedData"; + break; + case "AES256-NICEON": + jsonKey = "enc_data"; + break; + case "AES256GCM": + break; + default: + break; + } + return jsonKey; + } + private String doInboundPostEncrypt(String eaiStringBody, String encryptAlgorithm, String clientSecretKey, String secretAES256Iv, String secretAES256Key) { + + //byte[] encryptedMessage = null; + String encryptedStrMessage = null; + String encryptedJsonKey = ""; + + encryptedJsonKey = getJsonKey(encryptAlgorithm); + switch (encryptAlgorithm) { case "AES128": case "AES128-TOSS": try { String key128 = clientSecretKey.substring(22,38); //togetherEncoder 경우는 substring(44,60) 이네?? - String decryptedStrMessage = AESCipher.encrypt(eaiStringBody, key128); - encryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + encryptedStrMessage = AESCipher.encrypt(eaiStringBody, key128); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); - logger.error("HttpClientAdapterServiceRest] AES128 Decryption error=" + e.getMessage()); + logger.error("HttpClientAdapterServiceRest] AES128 Encryption error=" + e.getMessage()); } break; case "AES128-TOGETHER": try { String key128 = clientSecretKey.substring(44,60); //togetherEncoder 경우는 substring(44,60) 이네?? - String decryptedStrMessage = AESCipher.encrypt(eaiStringBody, key128); - encryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + encryptedStrMessage = AESCipher.encrypt(eaiStringBody, key128); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); - logger.error("HttpClientAdapterServiceRest] AES128 Decryption error=" + e.getMessage()); + logger.error("HttpClientAdapterServiceRest] AES128 Encryption error=" + e.getMessage()); } break; case "AES256": try { String key = clientSecretKey.substring(10,42); - String decryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, key); - encryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + encryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, key); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); - logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage()); + logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage()); } break; case "AES256-KAKAO": try { - String decryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, secretAES256Iv, secretAES256Key); - encryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + encryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, secretAES256Iv, secretAES256Key); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); - logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage()); + logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage()); } break; case "AES256-TOSS": try { - String decryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, secretAES256Iv, secretAES256Key); - encryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + encryptedStrMessage = AES256Cipher.encrypt(eaiStringBody, secretAES256Iv, secretAES256Key); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); - logger.error("HttpClientAdapterServiceRest] AES256 Decryption error=" + e.getMessage()); + logger.error("HttpClientAdapterServiceRest] AES256 Encryption error=" + e.getMessage()); } break; @@ -680,8 +677,8 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { AES256GCMCipher cipher = new AES256GCMCipher(key); byte[] aad = "metadata".getBytes(); - String decryptedStrMessage = cipher.encrypt(eaiStringBody , aad); - encryptedMessage = decryptedStrMessage.getBytes(StandardCharsets.UTF_8); + encryptedStrMessage = cipher.encrypt(eaiStringBody , aad); + //encryptedMessage = encryptedStrMessage.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); @@ -690,7 +687,8 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { break; default: try { - encryptedMessage = eaiStringBody.getBytes(); + encryptedStrMessage = eaiStringBody; + //encryptedMessage = eaiStringBody.getBytes(); } catch (Exception e) { e.printStackTrace(); logger.error("HttpClientAdapterServiceRest] doOutboundPostFilter Array copy error=" + e.getMessage()); @@ -698,7 +696,12 @@ public class ApiAdapterService extends HttpAdapterServiceSupport { break; } - return encryptedMessage; + Map map = new HashMap<>(); + map.put(encryptedJsonKey, encryptedStrMessage); + JSONObject json = new JSONObject(); + json.putAll(map); + return json.toJSONString(); + //return encryptedMessage; } From b05ecb2ba9aaa1b67de39fdf62f749381f7b2b14 Mon Sep 17 00:00:00 2001 From: cho Date: Sat, 10 Jan 2026 13:56:12 +0900 Subject: [PATCH 09/15] =?UTF-8?q?inbound=EC=97=90=20json=20=EC=A0=84?= =?UTF-8?q?=EB=AC=B8=EC=9D=BC=20=EB=95=8C,=20=EC=B5=9C=EC=83=81=EC=9C=84?= =?UTF-8?q?=EC=97=90=20Array('[]')=ED=83=80=EC=9E=85=EC=9D=B4=20=EB=B0=94?= =?UTF-8?q?=EB=A1=9C=20=EC=98=AC=EB=95=8C,=20=EB=B3=80=ED=99=98=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=B4,=20KJB=5FROOTLESS=5FARR?= =?UTF-8?q?AY=20=EB=9D=BC=EB=8A=94=20=ED=82=A4=EC=9D=98=20Object=EB=A5=BC?= =?UTF-8?q?=20warraped=20=ED=95=84=ED=84=B0=EC=B2=98=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/WEB-INF/web.xml | 11 +++ .../controller/ApiRequestBodyFilter.java | 85 +++++++++++++++++++ .../adapter/controller/ApiResponseAdvice.java | 52 ++++++++++++ .../CachedBodyHttpServletRequest.java | 51 +++++++++++ 4 files changed, 199 insertions(+) create mode 100644 src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java create mode 100644 src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java create mode 100644 src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 0b2219d..6074ebf 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -21,6 +21,17 @@ /management/* /mgr/* + + + ApiRequestBodyFilter + com.eactive.eai.adapter.controller.ApiRequestBodyFilter + + + ApiRequestBodyFilter + /api/* + /mapi/* + + logbackDisableServletContainerInitializer diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java new file mode 100644 index 0000000..aa7ec3e --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java @@ -0,0 +1,85 @@ +package com.eactive.eai.adapter.controller; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.Charset; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class ApiRequestBodyFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + // 1. 필터 대상 + if (!isTarget(request)) { + filterChain.doFilter(request, response); + return; + } + + // 2. 요청 전문 읽기 + String body = readBody(request); + if (body == null || body.isEmpty()) { + filterChain.doFilter(request, response); + return; + } + + String modifiedBody = body; + + try { + Object json = JSONValue.parse(body); + + // 3. 예제: ROOTLESS ARRAY 보정 + if (json instanceof JSONArray) { + JSONObject wrap = new JSONObject(); + wrap.put("KJB_ROOTLESS_ARRAY", json); + modifiedBody = wrap.toJSONString(); + } + + } catch (Exception e) { + // 변조 실패 → 원본 유지 + modifiedBody = body; + } + + byte[] bytes = modifiedBody.getBytes(request.getCharacterEncoding() != null ? request.getCharacterEncoding() : Charset.defaultCharset().name() ); + + // 5. Wrapper로 재주입 + HttpServletRequest wrapped = + new CachedBodyHttpServletRequest(request, bytes); + + filterChain.doFilter(wrapped, response); + } + + private boolean isTarget(HttpServletRequest request) { + String uri = request.getRequestURI(); + + + return ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) + && (uri.startsWith("/api") || uri.startsWith("/mapi") ) && !uri.startsWith("/mapi/oauth2/token"); + } + + private String readBody(HttpServletRequest request) throws IOException { + StringBuilder sb = new StringBuilder(); + try (BufferedReader br = request.getReader()) { + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + } + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java new file mode 100644 index 0000000..4827f88 --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java @@ -0,0 +1,52 @@ +package com.eactive.eai.adapter.controller; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice +public class ApiResponseAdvice implements ResponseBodyAdvice { + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + // TODO Auto-generated method stub + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, + Class> selectedConverterType, ServerHttpRequest request, + ServerHttpResponse response) { + + if (body instanceof String) { + + return removeRootlessArrayKeyFormResponseBody((String) body); + } + + return body; + } + + String removeRootlessArrayKeyFormResponseBody(String body) { + try { + Object root = JSONValue.parse(body); + + if (root instanceof JSONObject && ((JSONObject) root).containsKey("KJB_ROOTLESS_ARRAY")) { + JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("KJB_ROOTLESS_ARRAY"); + return jsonArray.toJSONString(); + } else { + return body; + } + } catch (Exception e) { + return body; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java b/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java new file mode 100644 index 0000000..c3c0f9e --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java @@ -0,0 +1,51 @@ +package com.eactive.eai.adapter.controller; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { + + private final byte[] cachedBody; + + public CachedBodyHttpServletRequest(HttpServletRequest request, byte[] body) { + super(request); + this.cachedBody = body; + } + + @Override + public ServletInputStream getInputStream() { + ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody); + + return new ServletInputStream() { + @Override + public int read() { + return bais.read(); + } + + @Override + public boolean isFinished() { + return bais.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + } From 50a1ae727ddcc6e0c0537e1248df508d8f142c76 Mon Sep 17 00:00:00 2001 From: cho Date: Sat, 10 Jan 2026 18:58:51 +0900 Subject: [PATCH 10/15] KJB_ROOTLESS_ARRAY -> kjbrootlessarray --- .../eactive/eai/adapter/controller/ApiRequestBodyFilter.java | 2 +- .../com/eactive/eai/adapter/controller/ApiResponseAdvice.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java index aa7ec3e..fcad778 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java @@ -45,7 +45,7 @@ public class ApiRequestBodyFilter extends OncePerRequestFilter { // 3. 예제: ROOTLESS ARRAY 보정 if (json instanceof JSONArray) { JSONObject wrap = new JSONObject(); - wrap.put("KJB_ROOTLESS_ARRAY", json); + wrap.put("kjbrootlessarray", json); modifiedBody = wrap.toJSONString(); } diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java index 4827f88..6fdf3cb 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java @@ -37,8 +37,8 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { try { Object root = JSONValue.parse(body); - 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("kjbrootlessarray")) { + JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("kjbrootlessarray"); return jsonArray.toJSONString(); } else { return body; From 71a5a9464dd574dfb2b56ece7df0a7936c36fac7 Mon Sep 17 00:00:00 2001 From: cho Date: Sat, 10 Jan 2026 19:35:27 +0900 Subject: [PATCH 11/15] =?UTF-8?q?ROOTLESSARRAY=20=EC=9B=90=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/WEB-INF/web.xml | 10 --- .../controller/ApiRequestBodyFilter.java | 85 ------------------- .../adapter/controller/ApiResponseAdvice.java | 52 ------------ .../CachedBodyHttpServletRequest.java | 51 ----------- 4 files changed, 198 deletions(-) delete mode 100644 src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java delete mode 100644 src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java delete mode 100644 src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 6074ebf..6eec29d 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -22,16 +22,6 @@ /mgr/* - - ApiRequestBodyFilter - com.eactive.eai.adapter.controller.ApiRequestBodyFilter - - - ApiRequestBodyFilter - /api/* - /mapi/* - - logbackDisableServletContainerInitializer diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java deleted file mode 100644 index fcad778..0000000 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.eactive.eai.adapter.controller; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.charset.Charset; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -@Component -public class ApiRequestBodyFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) - throws ServletException, IOException { - - // 1. 필터 대상 - if (!isTarget(request)) { - filterChain.doFilter(request, response); - return; - } - - // 2. 요청 전문 읽기 - String body = readBody(request); - if (body == null || body.isEmpty()) { - filterChain.doFilter(request, response); - return; - } - - String modifiedBody = body; - - try { - Object json = JSONValue.parse(body); - - // 3. 예제: ROOTLESS ARRAY 보정 - if (json instanceof JSONArray) { - JSONObject wrap = new JSONObject(); - wrap.put("kjbrootlessarray", json); - modifiedBody = wrap.toJSONString(); - } - - } catch (Exception e) { - // 변조 실패 → 원본 유지 - modifiedBody = body; - } - - byte[] bytes = modifiedBody.getBytes(request.getCharacterEncoding() != null ? request.getCharacterEncoding() : Charset.defaultCharset().name() ); - - // 5. Wrapper로 재주입 - HttpServletRequest wrapped = - new CachedBodyHttpServletRequest(request, bytes); - - filterChain.doFilter(wrapped, response); - } - - private boolean isTarget(HttpServletRequest request) { - String uri = request.getRequestURI(); - - - return ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) - && (uri.startsWith("/api") || uri.startsWith("/mapi") ) && !uri.startsWith("/mapi/oauth2/token"); - } - - private String readBody(HttpServletRequest request) throws IOException { - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = request.getReader()) { - String line; - while ((line = br.readLine()) != null) { - sb.append(line); - } - } - return sb.toString(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java deleted file mode 100644 index 6fdf3cb..0000000 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.eactive.eai.adapter.controller; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.springframework.core.MethodParameter; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; - -@RestControllerAdvice -public class ApiResponseAdvice implements ResponseBodyAdvice { - - @Override - public boolean supports(MethodParameter returnType, Class> converterType) { - // TODO Auto-generated method stub - return true; - } - - @Override - public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, - Class> selectedConverterType, ServerHttpRequest request, - ServerHttpResponse response) { - - if (body instanceof String) { - - return removeRootlessArrayKeyFormResponseBody((String) body); - } - - return body; - } - - String removeRootlessArrayKeyFormResponseBody(String body) { - try { - Object root = JSONValue.parse(body); - - if (root instanceof JSONObject && ((JSONObject) root).containsKey("kjbrootlessarray")) { - JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("kjbrootlessarray"); - return jsonArray.toJSONString(); - } else { - return body; - } - } catch (Exception e) { - return body; - } - - } - -} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java b/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java deleted file mode 100644 index c3c0f9e..0000000 --- a/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.eactive.eai.adapter.controller; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; - -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; - -public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { - - private final byte[] cachedBody; - - public CachedBodyHttpServletRequest(HttpServletRequest request, byte[] body) { - super(request); - this.cachedBody = body; - } - - @Override - public ServletInputStream getInputStream() { - ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody); - - return new ServletInputStream() { - @Override - public int read() { - return bais.read(); - } - - @Override - public boolean isFinished() { - return bais.available() == 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setReadListener(ReadListener readListener) { - } - }; - } - - @Override - public BufferedReader getReader() { - return new BufferedReader(new InputStreamReader(getInputStream())); - } - } From 2974668ba768c5b081ac386c9d7ada4813b91584 Mon Sep 17 00:00:00 2001 From: "Yunsam.Eo" Date: Mon, 12 Jan 2026 10:41:32 +0900 Subject: [PATCH 12/15] =?UTF-8?q?=EA=B8=88=EA=B2=B0=EC=9B=90=20=EA=B1=B0?= =?UTF-8?q?=EB=9E=98=EA=B5=AC=EB=B6=84=EB=B2=88=ED=98=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EB=A1=9C=EC=A7=81=20=EC=98=A4=EB=A5=98=EB=B3=B4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http/client/filter/KFTCFaceFilter.java | 14 +++++++++++++- .../http/client/filter/KFTCP2PFilter.java | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCFaceFilter.java b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCFaceFilter.java index d2b7d4f..78e3c70 100644 --- a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCFaceFilter.java +++ b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCFaceFilter.java @@ -16,6 +16,7 @@ import com.eactive.eai.adapter.AdapterManager; import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; import com.eactive.eai.common.property.PropGroupVO; import com.eactive.eai.common.property.PropManager; +import com.eactive.eai.common.server.EAIServerManager; import com.eactive.eai.common.util.Logger; @@ -28,6 +29,12 @@ public class KFTCFaceFilter implements HttpClientAdapterFilter, HttpAdapterServi public static final String B_ORG_CODE = "org_code"; public static final String B_TRANSACTION_ID = "transaction_id"; public static final String B_REQUEST_DATETIME = "request_datetime"; + public static String instId = null; + + static { + EAIServerManager eaiServerManager = EAIServerManager.getInstance(); + instId = eaiServerManager.getInstId(); + } @SuppressWarnings({ "unchecked" }) @Override @@ -47,7 +54,12 @@ public class KFTCFaceFilter implements HttpClientAdapterFilter, HttpAdapterServi } String request_datetime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); - String transaction_id = orgCode + request_datetime + "1" + RandomStringUtils.random(4, true, true).toUpperCase(); + String reqDate = new SimpleDateFormat("yyMMdd").format(new Date()); + String reqTime = new SimpleDateFormat("HHmmss").format(new Date()); + // 기관코드(3) + 요청일자(6) -- 금결원 표준화 항목, 고정 9자리. + // 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 기관별 생성 거래고유번호(11자리) + // 기관코드(3) + 요청일자(6) + 요청시간(6) + 인스턴스ID(2) + RandomString(3) -- 20자리. + String transaction_id = orgCode + reqDate + reqTime + instId + RandomStringUtils.random(3, true, true).toUpperCase(); Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER"); if (filterHeaders == null) { diff --git a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCP2PFilter.java b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCP2PFilter.java index 92c0f36..270b2e6 100644 --- a/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCP2PFilter.java +++ b/src/main/java/com/eactive/eai/custom/kjb/adapter/http/client/filter/KFTCP2PFilter.java @@ -11,6 +11,7 @@ import org.apache.commons.lang3.RandomStringUtils; import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey; import com.eactive.eai.common.property.PropGroupVO; import com.eactive.eai.common.property.PropManager; +import com.eactive.eai.common.server.EAIServerManager; import com.eactive.eai.common.util.Logger; @@ -22,21 +23,29 @@ public class KFTCP2PFilter implements HttpClientAdapterFilter, HttpAdapterServic public static final String H_ORG_CODE = "org_code"; public static final String H_TRX_NO = "api_trx_no"; public static final String H_TRX_DTM = "api_trx_dtm"; + public static String instId = null; + + static { + EAIServerManager eaiServerManager = EAIServerManager.getInstance(); + instId = eaiServerManager.getInstId(); + } @Override public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message, Properties tempProp) throws Exception { logger.debug("KFTCP2PFilter] PreFilter Processing Start!!"); - String orgCode = "034"; // 광주은행 행코드 + String orgCode = "D210400012"; // 광주은행 개발 기관코드(운영: K210800020) PropGroupVO propGroupVo = PropManager.getInstance().getPropGroupVO(KJB_HEADER); if (propGroupVo != null) { orgCode = propGroupVo.getProperty(PROP_PREFIX + "." + H_ORG_CODE); } - - String apiTrxDtm = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); - String apiTrxNo = orgCode + apiTrxDtm + "1" + RandomStringUtils.random(5, true, true).toUpperCase(); + + String apiTrxDtm = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); + String apiTrxTm = new SimpleDateFormat("HHmmss").format(new Date()); + // 기관코드(10) + 인스턴스ID(2) + 일시(6) + RandomString(2) -- 총 20자리 + String apiTrxNo = orgCode + instId + apiTrxTm + RandomStringUtils.random(2, true, true).toUpperCase(); Properties filterHeaders = (Properties) tempProp.get("FILTERHEADER"); if (filterHeaders == null) { From e7823da985bdf1b4847531450b08c6d80eaa3e30 Mon Sep 17 00:00:00 2001 From: cho Date: Mon, 12 Jan 2026 15:20:45 +0900 Subject: [PATCH 13/15] =?UTF-8?q?RootlessArray=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebContent/WEB-INF/web.xml | 10 +++ elink-online-common | 2 +- .../controller/ApiRequestBodyFilter.java | 85 +++++++++++++++++++ .../adapter/controller/ApiResponseAdvice.java | 52 ++++++++++++ .../CachedBodyHttpServletRequest.java | 51 +++++++++++ 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java create mode 100644 src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java create mode 100644 src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 6eec29d..1265260 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -21,6 +21,16 @@ /management/* /mgr/* + + + ApiRequestBodyFilter + com.eactive.eai.adapter.controller.ApiRequestBodyFilter + + + ApiRequestBodyFilter + /api/* + /mapi/* + diff --git a/elink-online-common b/elink-online-common index 7d5fb8a..51db6c8 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 7d5fb8afe4383d91c0061cbc8697c96f98a6e186 +Subproject commit 51db6c88cfc2d827f90141ceeef0d798b7d2abf3 diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java new file mode 100644 index 0000000..aa7ec3e --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java @@ -0,0 +1,85 @@ +package com.eactive.eai.adapter.controller; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.Charset; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class ApiRequestBodyFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + // 1. 필터 대상 + if (!isTarget(request)) { + filterChain.doFilter(request, response); + return; + } + + // 2. 요청 전문 읽기 + String body = readBody(request); + if (body == null || body.isEmpty()) { + filterChain.doFilter(request, response); + return; + } + + String modifiedBody = body; + + try { + Object json = JSONValue.parse(body); + + // 3. 예제: ROOTLESS ARRAY 보정 + if (json instanceof JSONArray) { + JSONObject wrap = new JSONObject(); + wrap.put("KJB_ROOTLESS_ARRAY", json); + modifiedBody = wrap.toJSONString(); + } + + } catch (Exception e) { + // 변조 실패 → 원본 유지 + modifiedBody = body; + } + + byte[] bytes = modifiedBody.getBytes(request.getCharacterEncoding() != null ? request.getCharacterEncoding() : Charset.defaultCharset().name() ); + + // 5. Wrapper로 재주입 + HttpServletRequest wrapped = + new CachedBodyHttpServletRequest(request, bytes); + + filterChain.doFilter(wrapped, response); + } + + private boolean isTarget(HttpServletRequest request) { + String uri = request.getRequestURI(); + + + return ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) + && (uri.startsWith("/api") || uri.startsWith("/mapi") ) && !uri.startsWith("/mapi/oauth2/token"); + } + + private String readBody(HttpServletRequest request) throws IOException { + StringBuilder sb = new StringBuilder(); + try (BufferedReader br = request.getReader()) { + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + } + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java new file mode 100644 index 0000000..4827f88 --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiResponseAdvice.java @@ -0,0 +1,52 @@ +package com.eactive.eai.adapter.controller; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice +public class ApiResponseAdvice implements ResponseBodyAdvice { + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + // TODO Auto-generated method stub + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, + Class> selectedConverterType, ServerHttpRequest request, + ServerHttpResponse response) { + + if (body instanceof String) { + + return removeRootlessArrayKeyFormResponseBody((String) body); + } + + return body; + } + + String removeRootlessArrayKeyFormResponseBody(String body) { + try { + Object root = JSONValue.parse(body); + + if (root instanceof JSONObject && ((JSONObject) root).containsKey("KJB_ROOTLESS_ARRAY")) { + JSONArray jsonArray = (JSONArray) ((JSONObject) root).get("KJB_ROOTLESS_ARRAY"); + return jsonArray.toJSONString(); + } else { + return body; + } + } catch (Exception e) { + return body; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java b/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java new file mode 100644 index 0000000..c3c0f9e --- /dev/null +++ b/src/main/java/com/eactive/eai/adapter/controller/CachedBodyHttpServletRequest.java @@ -0,0 +1,51 @@ +package com.eactive.eai.adapter.controller; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { + + private final byte[] cachedBody; + + public CachedBodyHttpServletRequest(HttpServletRequest request, byte[] body) { + super(request); + this.cachedBody = body; + } + + @Override + public ServletInputStream getInputStream() { + ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody); + + return new ServletInputStream() { + @Override + public int read() { + return bais.read(); + } + + @Override + public boolean isFinished() { + return bais.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + } From 264ffbb19bcf5159473b475b1567c23a897bd3de Mon Sep 17 00:00:00 2001 From: yunjy-hp Date: Mon, 12 Jan 2026 17:03:30 +0900 Subject: [PATCH 14/15] =?UTF-8?q?=EB=B3=80=ED=99=98=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/사용자정의함수.md | 70 ++++++++++++++++++ .../JsonPathExtractRequestMessage.java | 74 +++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 docs/사용자정의함수.md create mode 100644 src/main/java/com/eactive/eai/custom/transformer/function/userdefined/JsonPathExtractRequestMessage.java diff --git a/docs/사용자정의함수.md b/docs/사용자정의함수.md new file mode 100644 index 0000000..7656ed2 --- /dev/null +++ b/docs/사용자정의함수.md @@ -0,0 +1,70 @@ +# 사용자 정의 변환 함수 + +## 1. substringrequestmessage + +최초요청데이터를 substring + +| 항목 | 값 | +|------|-----| +| 변환함수명 | substringrequestmessage | +| 변환함수반환유형ID | [2101] STRING | +| 변환함수유형ID | [2302] GENERIC | +| 변환함수생성클래스 | com.eactive.eai.custom.transformer.function.userdefined.SubStringRequestMessage | + +### 파라미터 + +| 일련번호 | 파라미터명 | 유형 | 설명 | +|---------|-----------|------|------| +| 1 | start | NUMBER | 시작 위치 | +| 2 | end | NUMBER | 종료 위치 | + +### 사용 예시 + +``` +substringrequestmessage(AGW_TESTCASE019S1_TGTR.status,3,7) +``` + +**샘플 메시지:** +``` +ABCDEFGHIJK +``` + +**예상 결과:** +``` +DEFG +``` + +--- + +## 2. jsonpathextractrequestmessage + +최초요청(100)이 JSON 일 경우 특정 PATH의 데이터를 추출 하는 함수 + +| 항목 | 값 | +|------|-----| +| 변환함수명 | jsonpathextractrequestmessage | +| 변환함수반환유형ID | [2116] OBJECT | +| 변환함수유형ID | [2302] GENERIC | +| 변환함수생성클래스 | com.eactive.eai.custom.transformer.function.userdefined.JsonPathExtractRequestMessage | + +### 파라미터 + +| 일련번호 | 파라미터명 | 유형 | 설명 | +|---------|-----------|------|------| +| 1 | jsonPath | STRING | 대상 PATH | + +### 사용 예시 + +``` +jsonpathextractrequestmessage(AGW_TESTCASE022S1_TGTR.transactionId,"$.group1.field2") +``` + +**샘플 메시지:** +```json +{"group1":{"field1":"value1","field2":"value2"},"field3":"value3"} +``` + +**예상 결과:** +``` +value2 +``` diff --git a/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/JsonPathExtractRequestMessage.java b/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/JsonPathExtractRequestMessage.java new file mode 100644 index 0000000..4447414 --- /dev/null +++ b/src/main/java/com/eactive/eai/custom/transformer/function/userdefined/JsonPathExtractRequestMessage.java @@ -0,0 +1,74 @@ +package com.eactive.eai.custom.transformer.function.userdefined; + +import com.eactive.eai.common.context.ElinkTransactionContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; +import com.jayway.jsonpath.spi.json.JacksonJsonProvider; +import org.nfunk.jep.ParseException; +import org.nfunk.jep.function.PostfixMathCommand; + +import java.util.Stack; + +public class JsonPathExtractRequestMessage extends PostfixMathCommand { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public JsonPathExtractRequestMessage() { + numberOfParameters = -1; + } + + public void run(Stack inStack) throws ParseException { + checkStack(inStack); + + String bizData = ElinkTransactionContext.getRequestBizData(); + + Object p1 = inStack.pop(); + String path = String.valueOf(p1); + + inStack.clear(); + + try { + // Jackson을 JsonPath의 JSON 프로바이더로 설정 + Configuration config = Configuration.builder() + .jsonProvider(new JacksonJsonNodeJsonProvider()) + .build(); + + JsonNode resultNode = JsonPath.using(config).parse(bizData).read(path); + + if (resultNode == null) { + inStack.push(null); + } else if (resultNode.isValueNode()) { + // 단순 값(문자열, 숫자, boolean 등)은 텍스트로 반환 + inStack.push(resultNode.asText()); + } else { + // 객체/배열은 JsonNode 그대로 반환 + inStack.push(resultNode); + } + } catch (Exception e) { + throw new ParseException("JsonPath extract error: " + e.getMessage()); + } + } + + private JsonNode getNodeAtPath(JsonNode rootNode, String path) { + String[] tokens = path.split("/"); + JsonNode currentNode = rootNode; + + for (String token : tokens) { + if (token.isEmpty()) continue; + + if (token.matches("\\d+")) { + currentNode = currentNode.get(Integer.parseInt(token)); + } else { + currentNode = currentNode.get(token); + } + + if (currentNode == null) { + return null; + } + } + return currentNode; + } +} From 38f17c91f8b1ef8144d51efa633a2a33348b7b51 Mon Sep 17 00:00:00 2001 From: cho Date: Mon, 12 Jan 2026 17:22:07 +0900 Subject: [PATCH 15/15] =?UTF-8?q?Component=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=B6=88=ED=95=84=EC=9A=94=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eactive/eai/adapter/controller/ApiRequestBodyFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java index aa7ec3e..4e13781 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiRequestBodyFilter.java @@ -15,7 +15,7 @@ import org.json.simple.JSONValue; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -@Component + public class ApiRequestBodyFilter extends OncePerRequestFilter { @Override