Merge branch 'jenkins_with_weblogic' of ssh://git@192.168.240.178:18081/eapim/eapim-online.git into jenkins_with_weblogic
This commit is contained in:
@@ -22,6 +22,17 @@
|
||||
<url-pattern>/mgr/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<filter>
|
||||
<filter-name>ApiRequestBodyFilter</filter-name>
|
||||
<filter-class>com.eactive.eai.adapter.controller.ApiRequestBodyFilter</filter-class>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>ApiRequestBodyFilter</filter-name>
|
||||
<url-pattern>/api/*</url-pattern> <!-- 필요한 URL 패턴 -->
|
||||
<url-pattern>/mapi/*</url-pattern> <!-- 필요한 URL 패턴 -->
|
||||
</filter-mapping>
|
||||
|
||||
|
||||
<context-param>
|
||||
<param-name>logbackDisableServletContainerInitializer</param-name>
|
||||
<param-value>true</param-value>
|
||||
|
||||
@@ -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/**` 엔드포인트 매핑<br>- TSEAIHS04 테이블에서 어댑터 정보 조회<br>- HttpDynamicInAdapterManager로 어댑터 찾기<br>- ApiAdapterService.callApi() 호출 |
|
||||
|
||||
### 2. ApiAdapterService
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `eapim-online/src/main/java/com/eactive/eai/adapter/service/` |
|
||||
| 역할 | Inbound 서비스 레이어 |
|
||||
| 상속 | `HttpAdapterServiceSupport` |
|
||||
| 주요 기능 | - HTTP Method별 요청 파싱<br>- Inbound 암/복호화 (AES128, AES256 등)<br>- 부모 클래스 `service()` 호출 |
|
||||
|
||||
### 3. HttpAdapterServiceSupport
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/dynamic/` |
|
||||
| 역할 | Inbound 어댑터 추상 클래스 |
|
||||
| 주요 기능 | - `service()` 메소드: 핵심 디스패처<br>- Pre-Filter / Post-Filter 실행<br>- StandardMessage 생성<br>- RequestDispatcher.handle() 호출 |
|
||||
|
||||
### 4. RequestDispatcher
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/` |
|
||||
| 역할 | 요청 분배자 |
|
||||
| 주요 기능 | - Processor 인스턴스 생성/획득<br>- `processor.execute()` 호출<br>- AdapterGroup 상태 관리 (동시 처리 수 증감) |
|
||||
|
||||
### 5. RequestProcessorSupport / RequestProcessor
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/inbound/processor/` |
|
||||
| 역할 | 요청 처리 프로세서 |
|
||||
| 관계 | `RequestProcessor extends RequestProcessorSupport` |
|
||||
| 주요 기능 | - `execute()` → `hookup()` 호출<br>- StandardMessage → EAIMessage 변환<br>- 유량제어 (RateLimiter)<br>- SYNC/ASYNC 분기 처리<br>- FlowRouter.process() 호출 |
|
||||
|
||||
### 6. IFRouter
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/common/routing/` |
|
||||
| 역할 | FlowControl → Outbound 라우터 |
|
||||
| 주요 기능 | - Outbound Process로 라우팅<br>- Local/Remote 호출 판단<br>- FailOver 처리<br>- TAS(시뮬레이터) 거래 처리<br>- ElinkESBProcessProxy 호출 |
|
||||
|
||||
### 7. DefaultProcess / RESTProcess
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/outbound/` |
|
||||
| 역할 | Outbound 처리 프로세스 |
|
||||
| 관계 | `RESTProcess extends HTTPProcess extends DefaultProcess` |
|
||||
| 주요 기능 (DefaultProcess) | - `flow()`: 핵심 처리 흐름<br>- 송신 전/후 처리<br>- 메시지 변환 (Transform)<br>- 로그 처리 |
|
||||
| 주요 기능 (RESTProcess) | - `outboundCtrlCallService()`: HttpSender 호출<br>- REST 특화 처리<br>- 표준/비표준 메시지 처리 |
|
||||
|
||||
### 8. HttpSender
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/control/` |
|
||||
| 역할 | HTTP 송신 제어 |
|
||||
| 주요 기능 | - ElinkAdapterFactory에서 HTTP Adapter 획득<br>- `adapter.callService()` 호출<br>- SocketTimeout, HttpStatusException 처리 |
|
||||
|
||||
### 9. HttpClient5AdapterServiceRest
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/client/impl/` |
|
||||
| 역할 | 실제 HTTP 클라이언트 구현체 |
|
||||
| 주요 기능 | - Apache HttpClient 5 사용<br>- GET/POST/PUT/DELETE 메소드 처리<br>- OAuth2 토큰 관리 (발급/갱신)<br>- Outbound 암/복호화 (AES128, AES256, AES256GCM 등)<br>- Forward Proxy 지원<br>- 응답 헤더 릴레이 |
|
||||
|
||||
---
|
||||
|
||||
## 클래스 관계도
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 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
|
||||
@@ -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<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>();
|
||||
|
||||
// 컨텍스트 정리 (필수)
|
||||
public static void clear();
|
||||
|
||||
// 비동기 전달용
|
||||
public static Map<String, Object> getAll(); // 복사본 반환
|
||||
public static void restore(Map<String, Object> 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<String, Object> transactionContextTransfer;
|
||||
|
||||
public Map<String, Object> getTransactionContextTransfer();
|
||||
public void setTransactionContextTransfer(Map<String, Object> 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<Map<String,Object>>│◄── 직접 저장소 (Lazy Init)
|
||||
│ └───────────────────────────────┘ │
|
||||
│ + clear() │
|
||||
│ + getAll() → Map 복사본 │
|
||||
│ + restore(Map) │
|
||||
│ + setSeedSalt() / getSeedSalt() │
|
||||
│ + setEncryptionKey() / ... │
|
||||
└───────────────┬─────────────────────┘
|
||||
│ 비동기 전달 시
|
||||
│ getAll() → 복사
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ elink-online-common │
|
||||
├─────────────────────────────────────┤
|
||||
│ EAIMessage │
|
||||
│ - transactionContextTransfer │◄── JMS 전달 전용 (직접 사용 금지!)
|
||||
│ : Map<String,Object> │ Serializable
|
||||
│ │
|
||||
│ [송신] FlowRouter에서 설정 │
|
||||
│ [수신] ElinkESBProcessProxy에서 │
|
||||
│ restore() 후 null 초기화 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
+1
-1
Submodule elink-online-common updated: 8d21eba5e8...51db6c88cf
+1
-1
Submodule elink-online-core updated: 0f148e997e...75e3d3451c
+1
-1
Submodule elink-online-core-jpa updated: 2cd9c29d4d...ea932d1fdd
+1
-1
Submodule elink-online-emsclient updated: f68423b40e...7cd000f692
+1
-1
Submodule elink-online-transformer updated: c3e2de482b...dd68924d78
Binary file not shown.
@@ -60,7 +60,7 @@ 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, "/");
|
||||
|
||||
//** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다
|
||||
String methodAndUri = servletRequest.getMethod() + "|" + apiUri;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Object> {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||
Class<? extends HttpMessageConverter<?>> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String, Object> map = new HashMap<>();
|
||||
map.put(encryptedJsonKey, encryptedStrMessage);
|
||||
JSONObject json = new JSONObject();
|
||||
json.putAll(map);
|
||||
return json.toJSONString();
|
||||
//return encryptedMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<String, String> req) {
|
||||
// String fileId = req.get("fileid");
|
||||
//
|
||||
// String token = tokenService.generateFileToken(fileId);
|
||||
//
|
||||
// Map<String, String> 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<String> scopeSet) throws JwtAuthException {
|
||||
if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
|
||||
throw new JwtAuthException("invalid_client", "Bad client credentials(client_secret is empty or not match)");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> getScopeSet() {
|
||||
return scopeSet;
|
||||
}
|
||||
|
||||
public void setScopeSet(Set<String> scopeSet) {
|
||||
this.scopeSet = scopeSet;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expiresAt;
|
||||
}
|
||||
}
|
||||
@@ -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<String, BearerTokenInfo> CATokenStore = new ConcurrentHashMap<>();
|
||||
|
||||
// File 토큰 저장소
|
||||
private final Map<String, String> fileTokenStore = new ConcurrentHashMap<>();
|
||||
|
||||
// // FileTransfer 토큰 저장소
|
||||
// private final Map<String, String> fileTransferTokenStore = new ConcurrentHashMap<>();
|
||||
|
||||
public String generateCAToken(String clientId, Long expiresIn, Set<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -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) {
|
||||
|
||||
+13
-4
@@ -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) {
|
||||
|
||||
@@ -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시간 설정
|
||||
|
||||
+74
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
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);
|
||||
inStack.clear();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user