Files
elink-online-common/src/main/java/com/eactive/eai/common/authoutbound/AccessTokenManagerByDB.java
T
2026-07-09 14:08:11 +09:00

493 lines
17 KiB
Java

package com.eactive.eai.common.authoutbound;
import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.authoutbound.OutboundOAuthCredentialDao;
import com.eactive.eai.authoutbound.OutboundOAuthCredentialVo;
import com.eactive.eai.authoutbound.client.HttpClientAccessTokenServiceByDB;
import com.eactive.eai.authoutbound.client.HttpClientAccessTokenServiceFactoryByDB;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.session.SessionManager;
import com.eactive.eai.common.session.SessionManagerForIgnite;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.mchange.v1.cachedstore.CachedStore.Manager;
import com.openbanking.eai.common.token.AccessTokenVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 1. 기능 : Access 토큰 정보를 DB로 부터 로딩하고 만료시 재발급 정보를 메모리에 관리하는 Manager 클래스
* 2. 처리 개요 : DB 프로퍼티 정보에 저장된 Access 토큰 정보를 메모리에 로딩하거나 재발급 관리한다.
* * -
* 3. 주의사항
*
* @author :
* @version : v 1.0.0
* @see : 관련 기능을 참조
* @since :
* :
*/
@Component
public class AccessTokenManagerByDB implements Lifecycle {
/**
* Default Logger
*/
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
/**
* AccessTokenManager Single Instance
*/
private static AccessTokenManagerByDB instance = new AccessTokenManagerByDB();
/**
* LifecyleSupport object
*/
private LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* 기동 여부
*/
private boolean started;
/**
* 인증 정보를 저장하는 Map
*/
private Map<String, OutboundOAuthCredentialVo> outboundOAuthCredentialVos;
@Autowired
OutboundOAuthCredentialDao outboundOAuthCredentialDao;
private ScheduledExecutorService scheduler;
private Map<String, ScheduledFuture<?>> scheduledTasks;
private Set<String> runningTasks;
private static final long TASK_TIMEOUT_SECONDS = 30;
private static final int TOKEN_SCHEDULER_THREAD_POOL_SIZE = 30;
public AccessTokenManagerByDB() {
scheduledTasks = new ConcurrentHashMap<>();
outboundOAuthCredentialVos = new HashMap<>();
runningTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
scheduler = Executors.newScheduledThreadPool(TOKEN_SCHEDULER_THREAD_POOL_SIZE);
}
/**
* 1. 기능 : AccessTokenManager Singleton Object를 반환하는 getter method
* 2. 처리 개요 : AccessTokenManager Singleton Object를 반환한다.
* -
* 3. 주의사항
*
* @param
* @return
* @exception
**/
public static AccessTokenManagerByDB getInstance() {
return ApplicationContextProvider.getContext().getBean(AccessTokenManagerByDB.class);
}
/**
* 1. 기능 : AccessTokenManager의 초기화 여부를 반환하는 getter 메서드
* 2. 처리 개요 : AccessTokenManager의 초기화 여부를 반환한다.
* 3. 주의사항
*
* @return 초기화 여부
**/
public boolean isStarted() {
return this.started;
}
public void start() throws LifecycleException {
if (started)
throw new LifecycleException("RECEAICPM201");
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
init();
} catch(Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201"));
}
started = true;
if (logger.isWarn()){
logger.warn("Service started successfully");
}
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
private void init() throws Exception {
List<OutboundOAuthCredentialVo> OutboundOAuthCredentialVos = outboundOAuthCredentialDao.getAllOutboundOAuthCredentials();
logger.info("Initializing with {} credential configurations", OutboundOAuthCredentialVos.size());
for (OutboundOAuthCredentialVo outboundOAuthCredentialVo : OutboundOAuthCredentialVos) {
startToken(outboundOAuthCredentialVo);
outboundOAuthCredentialVos.put(outboundOAuthCredentialVo.getAdapterGroupName(), outboundOAuthCredentialVo);
}
}
public void stop() throws LifecycleException {
if (!started)
throw new LifecycleException("RECEAICPM203");
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
// 모든 스케줄된 작업 중지
scheduledTasks.forEach((name, future) -> {
future.cancel(true);
logger.warn("Task stopped for adapter group: {}", name);
});
scheduledTasks.clear();
// 실행 중인 작업 세트 클리어
runningTasks.clear();
// 스케줄러 종료
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
logger.warn("Forced shutdown of scheduler after timeout");
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
logger.error("Scheduler shutdown interrupted", e);
}
started = false;
if (logger.isWarn()){
logger.warn("Service stopped successfully");
}
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
public synchronized void refresh(OutboundOAuthCredentialVo outboundOAuthCredentialVo) throws Exception {
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
logger.info("Starting refresh for adapter group: {}", adapterGroupName);
// 기존 스케줄러 작업 취소
stopToken(adapterGroupName);
// 인증 정보 갱신
outboundOAuthCredentialVos.put(adapterGroupName, outboundOAuthCredentialVo);
if("N".equals(outboundOAuthCredentialVo.getUseYn())) {
logger.info("Token task disabled for adapter group: {}", adapterGroupName);
return;
}
// Y인 경우 새로운 스케줄러 작업 시작
logger.info("Restarting token task for adapter group: {}", adapterGroupName);
startToken(outboundOAuthCredentialVo);
}
public void startToken(final OutboundOAuthCredentialVo outboundOAuthCredentialVo) {
if (!"Y".equals(outboundOAuthCredentialVo.getUseYn())) {
logger.debug("Token task not started - disabled for adapter group: {}",
outboundOAuthCredentialVo.getAdapterGroupName());
return;
}
final String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
if (scheduledTasks.containsKey(adapterGroupName)) {
logger.warn("Token task not started - already scheduled for adapter group: {}", adapterGroupName);
return;
}
long interval = outboundOAuthCredentialVo.getIntervalSec() > 0 ?
outboundOAuthCredentialVo.getIntervalSec() : 24 * 60 * 60;
logger.info("Starting token task for adapter group: {} with interval: {} seconds",
adapterGroupName, interval);
Runnable periodicTask = () -> executeTokenTask(adapterGroupName, outboundOAuthCredentialVo);
ScheduledFuture<?> scheduledTask = scheduler.scheduleAtFixedRate(
periodicTask,
0, // 즉시 첫 실행
interval,
TimeUnit.SECONDS
);
scheduledTasks.put(adapterGroupName, scheduledTask);
}
private void executeTokenTask(String adapterGroupName, OutboundOAuthCredentialVo credential) {
if (!runningTasks.add(adapterGroupName)) {
logger.info("Task skipped - already running for adapter group: {}", adapterGroupName);
return;
}
logger.debug("Starting token task for adapter group: {}", adapterGroupName);
try {
Future<?> future = scheduler.submit(() -> {
try {
logger.debug("Executing token issuance for adapter group: {}", adapterGroupName);
SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, new Function<AccessTokenVO, AccessTokenVO>() {
@Override
public AccessTokenVO apply(AccessTokenVO accessToken) {
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
// // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
if (accessToken == null) {
logger.debug("Token not exists for adapter group: {}", adapterGroupName);
return issueToken(credential);
} else if(accessToken.getExpiration() != null) {
// 토큰이 있고 만료시간이 설정 된 경우
if(accessToken.getExpiration().before(new Date(intervalTime))){
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
logger.debug("Token expired : {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
return issueToken(credential);
} else {
// 토큰이 아직 유효한 경우
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
return accessToken;
}
} else {
//토큰이 있지만 만료 시간이 없는 경우
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
return accessToken;
}
}
});
logger.debug("Token issuance completed for adapter group: {}", adapterGroupName);
} catch (Exception e) {
logger.error("Token issuance failed for adapter group: {}", adapterGroupName, e);
}
});
try {
future.get(TASK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
logger.error("Task timed out for adapter group: {}", adapterGroupName);
future.cancel(true);
} catch (Exception e) {
logger.error("Task execution failed for adapter group: {}", adapterGroupName, e);
}
} finally {
runningTasks.remove(adapterGroupName);
logger.debug("Token task completed for adapter group: {}", adapterGroupName);
}
}
private AccessTokenVO issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo) {
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
AccessTokenVO accessToken = null;
try {
// boolean isExpired = true;
// if (tokens.containsKey(adapterGroupName)) {
// AccessTokenVO accessToken = tokens.get(adapterGroupName);
// isExpired = accessToken.isExpired();
// }
// logger.debug("Token status for adapter group: {}, expired: {}, isTokenExpiredWithinInterval: {}", adapterGroupName, isExpired, isTokenExpiredWithinInterval);
// if (isExpired || isTokenExpiredWithinInterval) {
logger.info("Issuing new token for adapter group: {}", adapterGroupName);
// tokens.remove(adapterGroupName);
AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
if (gvo != null && gvo.getAdapters().hasNext()) {
AdapterVO avo = gvo.getAdapters().next();
Properties properties = AdapterPropManager.getInstance().getProperties(avo.getPropGroupName());
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
if(service != null) {
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
logger.info("New token issued successfully for adapter group: {}", adapterGroupName);
return accessToken;
} else {
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
}
} else {
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
}
} catch (Exception e) {
logger.error("Task execution failed for adapter group: {}", adapterGroupName, e);
}
return accessToken;
}
public void stopToken(String adapterGroupName) {
ScheduledFuture<?> task = scheduledTasks.remove(adapterGroupName);
if (task != null) {
task.cancel(true);
logger.warn("Token task stopped for adapter group: {}", adapterGroupName);
}
SessionManager.getInstance().removeOutboundAccessToken(adapterGroupName);
logger.debug("Token removed for adapter group: {}", adapterGroupName);
}
public void refresh(String adapterGroupName) throws Exception {
refresh(outboundOAuthCredentialDao.getOutboundOAuthCredential(adapterGroupName));
}
/**
* 1. 기능 : LifecycleListener를 등록하는 메서드
* 2. 처리 개요 : LifecycleListener를 등록한다.
* 3. 주의사항
*
* @param listener LifecycleEvent를 수신한 LifecycleListener
**/
public void addLifecycleListener(LifecycleListener listener)
{
lifecycle.addLifecycleListener(listener);
}
/**
* 1. 기능 : 등록된 LifecycleListener 리스트를 반환하는 메서드
* 2. 처리 개요 : 등록된 LifecycleListener 리스트를 반환하다.
* 3. 주의사항
*
* @return 등록된 LifecycleListener 리스트
**/
public LifecycleListener[] findLifecycleListeners()
{
return lifecycle.findLifecycleListeners();
}
/**
* 1. 기능 : 등록된 LifecycleListener를 삭제하는 메서드
* 2. 처리 개요 : 파라미터의 LifecycleListener를 삭제한다.
* 3. 주의사항
*
* @param listener 삭제할 LifecycleListener
**/
public void removeLifecycleListener(LifecycleListener listener)
{
lifecycle.removeLifecycleListener(listener);
}
public boolean isOAuthCredentialRegistered(String adapterGroupName){
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
return outboundOAuthCredentialVo != null && "Y".equals(outboundOAuthCredentialVo.getUseYn());
}
/**
* 1. 기능 : Access 토큰 정보를 반환하는 메서드
* 2. 처리 개요 : Access 토큰 정보를 반환한다.
* -
* 3. 주의사항
*
* @return Access 토큰 정보
**/
public AccessTokenVO getAccessTokenVO(String adapterGroupName) {
AccessTokenVO accessToken = null;
try {
accessToken = SessionManager.getInstance().getOutboundAccessToken(adapterGroupName,
new Function<AccessTokenVO, AccessTokenVO>() {
@Override
public AccessTokenVO apply(AccessTokenVO t) {
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos
.get(adapterGroupName);
return issueToken(outboundOAuthCredentialVo);
}
});
} catch (Exception e) {
if (logger.isError())
logger.error("토큰을 찾을 수 없습니다. [" + adapterGroupName + "] ", e);
}
return accessToken;
}
/**
* 1. 기능 : Access 토큰 값를 반환하는 메서드
* 2. 처리 개요 : Access 토큰 값를 반환한다.
* -
* 3. 주의사항
*
* @return accessToken 값
**/
public String getAccessToken(String adapterGroupName) {
String accessToken = null;
try {
accessToken = getAccessTokenVO(adapterGroupName).getAccessToken();
} catch(Exception e) {
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
}
return accessToken;
}
/**
* 1. 기능 : Access 토큰를 삭제하는 메서드
* 2. 처리 개요 : Access 토큰를 삭제한다.
* -
* 3. 주의사항
*
**/
public void removeAccessTokenVO(String adapterGroupName) {
// 스케줄러 작업 중지 및 토큰 제거
stopToken(adapterGroupName);
// OAuth 인증 정보 제거
outboundOAuthCredentialVos.remove(adapterGroupName);
}
/**
* 1. 기능 : Access 토큰를 재발급하는 메서드
* 2. 처리 개요 : Access 토큰를 재발급한다.
* -
* 3. 주의사항
*
**/
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties,
String oldToken) throws Exception {
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
// 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다.
if (accessToken == null || accessToken.isExpired()
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
if (isOAuthCredentialRegistered(adapterGroupName) == false) {
throw new Exception(
"There is no OAuthCredentialRegistered information, or whether to use it is 'N'.");
}
String type = properties.getProperty("ADAPTER_TOKEN_ISSUING_CLIENT_TYPE");
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
OutboundOAuthCredentialVo outboundOAuthCredentialVo = outboundOAuthCredentialVos.get(adapterGroupName);
if (service != null) {
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties,
outboundOAuthCredentialVo);
}
}
return accessToken;
}
}