Compare commits

..

4 Commits

Author SHA1 Message Date
curry772 c118c88dce 어댑터별 apikey 헤더명 동적 적용 2026-07-20 11:12:13 +09:00
curry772 28a4becf1c threshhold 및 threshholdpersecond 값 null 체크 추가 2026-07-20 11:10:56 +09:00
curry772 8339efc752 상태코드 출력포맷 변경 2026-07-20 11:10:21 +09:00
curry772 61c9aa9864 REST, HTTP 어댑터 재처리 기능 보완 2026-07-13 13:25:38 +09:00
7 changed files with 75 additions and 42 deletions
@@ -57,7 +57,7 @@ public class HttpStatusException extends Exception {
public String getCode() {
if (StringUtils.isBlank(code)) {
return String.format("Http Status: %d", status);
return String.format("HttCd:%d", status);
} else {
return code;
}
@@ -88,10 +88,17 @@ public class DeadlineAwareRetryExecutor {
if (!policy.shouldRetryOnStatus(request, status)) {
// 성공 또는 재시도 불필요 → 호출부에서 close 책임
log.debug("non-retryable response status={}, attempt={}", status, attempt + 1);
log.info("non-retryable response status={}, attempt={}", status, attempt + 1);
return response;
}
// ④ 마지막 시도였으면 종료
if (attempt >= policy.getMaxRetries()) {
// 최대 재처리에 도달하여, 재시도하지 않고, 응답 객체를 리턴한다.
log.info("최대 재시도 횟수 도달 response status={}, attempt={}", status, attempt + 1);
return response;
}
// 재시도 대상
log.warn("retryable status={}, attempt={}/{}",
status, attempt + 1, policy.getMaxRetries());
@@ -127,9 +134,6 @@ public class DeadlineAwareRetryExecutor {
}
}
// ④ 마지막 시도였으면 바로 종료
if (attempt >= policy.getMaxRetries()) break;
// ⑤ 백오프 대기 전 남은 시간 재확인
long remainingBeforeWait = totalDeadlineMs - (System.currentTimeMillis() - startTime);
if (remainingBeforeWait <= policy.getBackoffMs()) {
@@ -171,10 +175,6 @@ public class DeadlineAwareRetryExecutor {
log.info("RequestConfig = {}", config);
}
private boolean isSuccess(int status) {
return status >= 200 && status < 300;
}
private void closeQuietly(CloseableHttpResponse response) {
try {
response.close();
@@ -14,6 +14,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHost;
@@ -135,7 +136,15 @@ public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupp
byte[] responseMessage = null;
String responseString = "";
try (CloseableHttpResponse response = (CloseableHttpResponse) this.client.execute(method, context)) {
DeadlineAwareRetryExecutor executor =
new DeadlineAwareRetryExecutor((CloseableHttpClient)this.client,
vo.getConnectionTimeout(),
vo.getTimeout());
// Properties에서 RetryPolicy 로드
RetryPolicy policy = RetryPolicy.from(prop); // 설정 로드 방식에 맞게
try (CloseableHttpResponse response = (CloseableHttpResponse) executor.execute(method, context, policy)) {
status = response.getCode();
responseMessage = EntityUtils.toByteArray(response.getEntity());
responseString = new String(responseMessage, vo.getEncode());
@@ -65,7 +65,7 @@ public class RetryPolicy {
return new RetryPolicy(getInt(props, RETRY_MAX_RETRIES, 0), getLong(props, RETRY_BACKOFF_MS, 1000L), codes,
getBoolean(props, RETRY_ON_CONNECT_FAIL, false), getBoolean(props, RETRY_ON_TIMEOUT, false),
getBoolean(props, RETRY_IDEMPOTENT_ONLY, true));
getBoolean(props, RETRY_IDEMPOTENT_ONLY, false));
}
// ----------------------------------------------------------------
@@ -85,4 +85,8 @@ public interface HttpAdapterServiceKey {
//응답 처리 용 표준 전문 오브젝트
static final String STANDARD_MESSAGE_OBJECT = "STANDARD_MESSAGE_OBJECT";
// 어댑터별 인증 키 헤더 이름
static final String ADAPTER_TOKEN_HEADER_NAME = "ADAPTER_TOKEN_HEADER_NAME";
static final String ADAPTER_APIKEY_HEADER_NAME = "ADAPTER_APIKEY_HEADER_NAME";
}
@@ -1,5 +1,26 @@
package com.eactive.eai.adapter.http.dynamic.filter;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.authserver.service.BearerTokenInfo;
//import com.eactive.eai.authserver.vo.BearerTokenInfo;
@@ -13,7 +34,6 @@ import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.session.SessionManager;
import com.eactive.eai.common.stdmessage.STDMessageManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.StringUtil;
import com.eactive.eai.inbound.action.ActionFactory;
import com.eactive.eai.inbound.action.RequestAction;
import com.eactive.eai.inbound.processor.Processor;
@@ -25,27 +45,6 @@ import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.util.IOUtils;
import com.nimbusds.jwt.SignedJWT;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
public class ApiAuthFilter implements HttpAdapterFilter {
static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
@@ -124,7 +123,7 @@ public class ApiAuthFilter implements HttpAdapterFilter {
switch (eaiMessage.getAuthType()){
case "oauth":
try {
String token = extractBearerToken(request);
String token = extractBearerToken(request, prop);
SignedJWT signedJWT = SignedJWT.parse(token);
if (signedJWT.getJWTClaimsSet().getExpirationTime() == null) {
@@ -182,8 +181,23 @@ public class ApiAuthFilter implements HttpAdapterFilter {
}
break;
case "api_key":
String apiKeyName = PropManager.getInstance().getProperty("ApiConfig", "api.key.name", "x-api-key");
String apiKey = request.getHeader(apiKeyName);
// 기관별로 API Key 헤더명이 다를 수 있어, 어댑터별 설정(ADAPTER_TOKEN_HEADER_NAME)을 우선 사용하고
// 없으면 ApiConfig 전역 설정(api.key.name)을 사용. 콤마로 다중 헤더명 지정 시 순서대로 값이 있는 헤더를 사용
String apiKeyNameConf = prop.getProperty(HttpAdapterServiceKey.ADAPTER_APIKEY_HEADER_NAME);
if (StringUtils.isBlank(apiKeyNameConf)) {
apiKeyNameConf = PropManager.getInstance().getProperty("ApiConfig", "api.key.name", "x-api-key");
}
String[] apiKeyNames = org.springframework.util.StringUtils.tokenizeToStringArray(apiKeyNameConf, ",");
String apiKeyName = null;
String apiKey = null;
for (String name : apiKeyNames) {
String value = request.getHeader(name);
if (StringUtils.isNotBlank(value)) {
apiKeyName = name;
apiKey = value.trim();
break;
}
}
if(apiKey == null){
// QueryString으로 전달된 access_token 파라미터 확인
String apiKeyParamName = PropManager.getInstance().getProperty("ApiConfig", "api.key.param.name", "x-api-key");
@@ -191,14 +205,14 @@ public class ApiAuthFilter implements HttpAdapterFilter {
if (StringUtils.isNotBlank(queryApiKey)) {
apiKey = queryApiKey.trim();
} else {
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Invalid or missing API key \""+apiKeyName+"\" in Http Header");
throw new JwtAuthException(ERROR_AUTHENTICATION_FAIL, "Invalid or missing API key \""+apiKeyNameConf+"\" in Http Header");
}
}
prop.setProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, apiKey);
isPassScope = true;
break;
case "ca":
String token = extractBearerToken(request);
String token = extractBearerToken(request, prop);
BearerTokenInfo bearerTokenInfo = SessionManager.getInstance().getCAToken(token);
if ( bearerTokenInfo != null ) {
if( bearerTokenInfo.isExpired() ) {
@@ -319,8 +333,13 @@ public class ApiAuthFilter implements HttpAdapterFilter {
return false;
}
public static String extractBearerToken(HttpServletRequest request) throws JwtAuthException {
String tokenHeaderName = PropManager.getInstance().getProperty("ApiConfig", "token.header.name", "Authorization");
public static String extractBearerToken(HttpServletRequest request, Properties prop) throws JwtAuthException {
// 기관별로 토큰 헤더명이 다를 수 있어, 어댑터별 설정(ADAPTER_APPKEY_HEADER_NAME)을 우선 사용하고
// 없으면 ApiConfig 전역 설정(token.header.name)을 사용
String tokenHeaderName = prop != null ? prop.getProperty(HttpAdapterServiceKey.ADAPTER_TOKEN_HEADER_NAME) : null;
if (StringUtils.isBlank(tokenHeaderName)) {
tokenHeaderName = PropManager.getInstance().getProperty("ApiConfig", "token.header.name", "Authorization");
}
String authorization = request.getHeader(tokenHeaderName);
if (StringUtils.isBlank(authorization)) {
// QueryString으로 전달된 access_token 파라미터 확인
@@ -104,8 +104,9 @@ public class InflowControlDAO extends BaseDAO {
for (InflowControl inflowControl : inflowControls) {
InflowTargetVO vo = new InflowTargetVO();
vo.setName(inflowControl.getId().getName());
vo.setThreshold(inflowControl.getThreshold());
vo.setThresholdPerSecond(inflowControl.getThresholdpersecond());
vo.setThreshold(inflowControl.getThreshold() == null ? 0 : inflowControl.getThreshold());
vo.setThresholdPerSecond(
inflowControl.getThresholdpersecond() == null ? 0 : inflowControl.getThresholdpersecond());
vo.setThresholdTimeUnit(inflowControl.getThresholdtimeunit());
vo.setActivate(!"0".equals(inflowControl.getUseyn()));
targetList.put(vo.getName(), vo);