493 lines
17 KiB
Java
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;
|
|
}
|
|
}
|