Outbound Client oAuth Token 기능 개선 및 패치
- 광주은행 내용 적용 - ignite 사용
This commit is contained in:
@@ -13,15 +13,21 @@ import com.eactive.eai.common.lifecycle.Lifecycle;
|
|||||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||||
import com.eactive.eai.common.lifecycle.LifecycleListener;
|
import com.eactive.eai.common.lifecycle.LifecycleListener;
|
||||||
import com.eactive.eai.common.lifecycle.LifecycleSupport;
|
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.ApplicationContextProvider;
|
||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
|
import com.mchange.v1.cachedstore.CachedStore.Manager;
|
||||||
import com.openbanking.eai.common.token.AccessTokenVO;
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : Access 토큰 정보를 DB로 부터 로딩하고 만료시 재발급 정보를 메모리에 관리하는 Manager 클래스
|
* 1. 기능 : Access 토큰 정보를 DB로 부터 로딩하고 만료시 재발급 정보를 메모리에 관리하는 Manager 클래스
|
||||||
@@ -55,13 +61,8 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
/**
|
/**
|
||||||
* 기동 여부
|
* 기동 여부
|
||||||
*/
|
*/
|
||||||
private boolean started;
|
private boolean started;
|
||||||
|
|
||||||
/**
|
|
||||||
* 토큰 정보를 저장하는 collection
|
|
||||||
*/
|
|
||||||
private HashMap<String, AccessTokenVO> tokens;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 인증 정보를 저장하는 Map
|
* 인증 정보를 저장하는 Map
|
||||||
*/
|
*/
|
||||||
@@ -78,7 +79,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
private static final int TOKEN_SCHEDULER_THREAD_POOL_SIZE = 30;
|
private static final int TOKEN_SCHEDULER_THREAD_POOL_SIZE = 30;
|
||||||
|
|
||||||
public AccessTokenManagerByDB() {
|
public AccessTokenManagerByDB() {
|
||||||
tokens = new HashMap<>();
|
|
||||||
scheduledTasks = new ConcurrentHashMap<>();
|
scheduledTasks = new ConcurrentHashMap<>();
|
||||||
outboundOAuthCredentialVos = new HashMap<>();
|
outboundOAuthCredentialVos = new HashMap<>();
|
||||||
runningTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
runningTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||||
@@ -117,7 +117,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
|
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tokens.clear();
|
|
||||||
init();
|
init();
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201"));
|
throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201"));
|
||||||
@@ -168,7 +167,6 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
logger.error("Scheduler shutdown interrupted", e);
|
logger.error("Scheduler shutdown interrupted", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tokens = null;
|
|
||||||
started = false;
|
started = false;
|
||||||
|
|
||||||
if (logger.isWarn()){
|
if (logger.isWarn()){
|
||||||
@@ -240,27 +238,34 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
try {
|
try {
|
||||||
logger.debug("Executing token issuance for adapter group: {}", adapterGroupName);
|
logger.debug("Executing token issuance for adapter group: {}", adapterGroupName);
|
||||||
|
|
||||||
// 현재 토큰 가져오기
|
SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, new Function<AccessTokenVO, AccessTokenVO>() {
|
||||||
AccessTokenVO accessToken = tokens.get(adapterGroupName);
|
|
||||||
|
|
||||||
// 다음 스케줄 시간 계산
|
@Override
|
||||||
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
|
public AccessTokenVO apply(AccessTokenVO accessToken) {
|
||||||
// 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
|
|
||||||
if (accessToken == null) {
|
long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000);
|
||||||
issueToken(credential, false);
|
// // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급
|
||||||
} else if(accessToken.getExpiration() != null) {
|
if (accessToken == null) {
|
||||||
// 토큰이 있고 만료시간이 설정 된 경우
|
return issueToken(credential);
|
||||||
if(accessToken.getExpiration().before(new Date(intervalTime))){
|
} else if(accessToken.getExpiration() != null) {
|
||||||
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
|
// 토큰이 있고 만료시간이 설정 된 경우
|
||||||
issueToken(credential, true);
|
if(accessToken.getExpiration().before(new Date(intervalTime))){
|
||||||
|
// 다음 스케줄 전에 토큰이 만료되는 경우 재발급
|
||||||
|
return issueToken(credential);
|
||||||
|
} else {
|
||||||
|
// 토큰이 아직 유효한 경우
|
||||||
|
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 토큰이 아직 유효한 경우
|
//토큰이 있지만 만료 시간이 없는 경우
|
||||||
logger.debug("Token still valid until next schedule for adapter group: {}, expiration date: {}", adapterGroupName, accessToken.getExpiration());
|
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
|
||||||
|
return accessToken;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
//토큰이 있지만 만료 시간이 없는 경우
|
|
||||||
logger.debug("Token exists but expiration time is null for adapter group: {}", adapterGroupName);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Token issuance completed for adapter group: {}", adapterGroupName);
|
logger.debug("Token issuance completed for adapter group: {}", adapterGroupName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -282,19 +287,24 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo, boolean isTokenExpiredWithinInterval) throws Exception {
|
private AccessTokenVO issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo) {
|
||||||
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
|
String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName();
|
||||||
boolean isExpired = true;
|
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 (tokens.containsKey(adapterGroupName)) {
|
// if (isExpired || isTokenExpiredWithinInterval) {
|
||||||
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);
|
logger.info("Issuing new token for adapter group: {}", adapterGroupName);
|
||||||
tokens.remove(adapterGroupName);
|
// tokens.remove(adapterGroupName);
|
||||||
|
|
||||||
AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
||||||
|
|
||||||
@@ -306,16 +316,23 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type);
|
||||||
if(service != null) {
|
if(service != null) {
|
||||||
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
|
logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName);
|
||||||
AccessTokenVO accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
|
accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo);
|
||||||
this.tokens.put(adapterGroupName, accessToken);
|
|
||||||
logger.info("New token issued successfully for adapter group: {}", adapterGroupName);
|
logger.info("New token issued successfully for adapter group: {}", adapterGroupName);
|
||||||
|
return accessToken;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
|
logger.warn("Token service not found for type: {} and adapter group: {}", type, adapterGroupName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warn("No valid adapter configuration found for adapter group: {}", adapterGroupName);
|
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) {
|
public void stopToken(String adapterGroupName) {
|
||||||
@@ -324,7 +341,7 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
task.cancel(true);
|
task.cancel(true);
|
||||||
logger.warn("Token task stopped for adapter group: {}", adapterGroupName);
|
logger.warn("Token task stopped for adapter group: {}", adapterGroupName);
|
||||||
}
|
}
|
||||||
tokens.remove(adapterGroupName);
|
SessionManager.getInstance().removeOutboundAccessToken(adapterGroupName);
|
||||||
logger.debug("Token removed for adapter group: {}", adapterGroupName);
|
logger.debug("Token removed for adapter group: {}", adapterGroupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,15 +399,28 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
* @return Access 토큰 정보
|
* @return Access 토큰 정보
|
||||||
**/
|
**/
|
||||||
public AccessTokenVO getAccessTokenVO(String adapterGroupName) {
|
public AccessTokenVO getAccessTokenVO(String adapterGroupName) {
|
||||||
AccessTokenVO accessToken = null;
|
AccessTokenVO accessToken = null;
|
||||||
|
|
||||||
try {
|
|
||||||
accessToken = tokens.get(adapterGroupName);
|
|
||||||
} catch(Exception e) {
|
|
||||||
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessToken;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -403,9 +433,9 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
**/
|
**/
|
||||||
public String getAccessToken(String adapterGroupName) {
|
public String getAccessToken(String adapterGroupName) {
|
||||||
String accessToken = null;
|
String accessToken = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
accessToken = tokens.get(adapterGroupName).getAccessToken();
|
accessToken = getAccessTokenVO(adapterGroupName).getAccessToken();
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
|
if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e);
|
||||||
}
|
}
|
||||||
@@ -435,27 +465,29 @@ public class AccessTokenManagerByDB implements Lifecycle {
|
|||||||
* 3. 주의사항
|
* 3. 주의사항
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties, String oldToken) throws Exception {
|
public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties,
|
||||||
|
String oldToken) throws Exception {
|
||||||
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
|
|
||||||
|
|
||||||
// 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다.
|
AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName);
|
||||||
if (accessToken == null || accessToken.isExpired()
|
|
||||||
|| StringUtils.equals(accessToken.getAccessToken(), oldToken)) {
|
// 동시 요청이 발생할 경우 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
tokens.put(adapterGroupName, accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package com.eactive.eai.common.session;
|
package com.eactive.eai.common.session;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.apache.ignite.IgniteCache;
|
||||||
|
|
||||||
import com.eactive.eai.authserver.service.BearerTokenInfo;
|
import com.eactive.eai.authserver.service.BearerTokenInfo;
|
||||||
import com.eactive.eai.common.lifecycle.Lifecycle;
|
import com.eactive.eai.common.lifecycle.Lifecycle;
|
||||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||||
@@ -11,6 +16,7 @@ import com.eactive.eai.common.server.EAIServerManager;
|
|||||||
import com.eactive.eai.common.server.EAIServerVO;
|
import com.eactive.eai.common.server.EAIServerVO;
|
||||||
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
|
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
|
||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 :
|
* 1. 기능 :
|
||||||
@@ -60,6 +66,7 @@ public abstract class SessionManager implements Lifecycle {
|
|||||||
public static final String WEBSOCKER_CAHCE_NAME = "webSocketTimeout";
|
public static final String WEBSOCKER_CAHCE_NAME = "webSocketTimeout";
|
||||||
public static final String TERMINAL_CAHCE_NAME = "terminalSession";
|
public static final String TERMINAL_CAHCE_NAME = "terminalSession";
|
||||||
public static final String CAGATEWAY_TOKEN_CACHE_NAME = "CAGatewayToken";
|
public static final String CAGATEWAY_TOKEN_CACHE_NAME = "CAGatewayToken";
|
||||||
|
public static final String OUTBOUND_ACCESS_TOKEN_CACHE_NAME = "OutBoundAccessToken";
|
||||||
|
|
||||||
public static final String SESSION_MANAGER_GROUPNAME = "RMIInfo";
|
public static final String SESSION_MANAGER_GROUPNAME = "RMIInfo";
|
||||||
public static final String SESSION_MANAGER_CACHETYPE = "cacheType";
|
public static final String SESSION_MANAGER_CACHETYPE = "cacheType";
|
||||||
@@ -204,6 +211,13 @@ public abstract class SessionManager implements Lifecycle {
|
|||||||
|
|
||||||
public abstract void removeCAToken(String key);
|
public abstract void removeCAToken(String key);
|
||||||
|
|
||||||
|
//Outbound Access Token Cache
|
||||||
|
public abstract AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> getNewToken);
|
||||||
|
|
||||||
|
public abstract void removeOutboundAccessToken(String key);
|
||||||
|
|
||||||
|
public abstract void clearOutboundAccessToken();
|
||||||
|
|
||||||
|
|
||||||
// HTTP Cache
|
// HTTP Cache
|
||||||
public abstract SessionVO getHttpLogin(String key);
|
public abstract SessionVO getHttpLogin(String key);
|
||||||
@@ -245,4 +259,5 @@ public abstract class SessionManager implements Lifecycle {
|
|||||||
public abstract void closeManager();
|
public abstract void closeManager();
|
||||||
|
|
||||||
public abstract String getCacheStatus();
|
public abstract String getCacheStatus();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.eactive.eai.common.session;
|
|||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import com.eactive.eai.authserver.service.BearerTokenInfo;
|
import com.eactive.eai.authserver.service.BearerTokenInfo;
|
||||||
import com.eactive.eai.common.ehcache.CustomRMICacheReplicatorFactory;
|
import com.eactive.eai.common.ehcache.CustomRMICacheReplicatorFactory;
|
||||||
@@ -14,6 +16,7 @@ import com.eactive.eai.common.sessioninfo.TerminalInfoDAO;
|
|||||||
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
|
import com.eactive.eai.common.sessioninfo.TerminalInfoVO;
|
||||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||||
import com.eactive.eai.util.HealthCheckManager;
|
import com.eactive.eai.util.HealthCheckManager;
|
||||||
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
|
|
||||||
import net.sf.ehcache.Cache;
|
import net.sf.ehcache.Cache;
|
||||||
import net.sf.ehcache.CacheException;
|
import net.sf.ehcache.CacheException;
|
||||||
@@ -937,4 +940,19 @@ public class SessionManagerForEhcache extends SessionManager {
|
|||||||
public void removeCAToken(String key) {
|
public void removeCAToken(String key) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> getNewToken) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeOutboundAccessToken(String key) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearOutboundAccessToken() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.cache.Cache;
|
import javax.cache.Cache;
|
||||||
import javax.cache.configuration.Factory;
|
import javax.cache.configuration.Factory;
|
||||||
@@ -54,6 +58,7 @@ import com.eactive.eai.common.util.ApplicationContextProvider;
|
|||||||
import com.eactive.eai.common.worker.Future;
|
import com.eactive.eai.common.worker.Future;
|
||||||
import com.eactive.eai.common.worker.ResponseMap;
|
import com.eactive.eai.common.worker.ResponseMap;
|
||||||
import com.eactive.eai.util.HealthCheckManager;
|
import com.eactive.eai.util.HealthCheckManager;
|
||||||
|
import com.openbanking.eai.common.token.AccessTokenVO;
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
|
|
||||||
@@ -66,6 +71,8 @@ import ch.qos.logback.classic.Level;
|
|||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
public class SessionManagerForIgnite extends SessionManager {
|
public class SessionManagerForIgnite extends SessionManager {
|
||||||
|
|
||||||
|
private static final String DISTRIBUTED_TOKEN_LOCK = "DISTRIBUTED_TOKEN_LOCK";
|
||||||
// evictMaster Instance - single socket 관련
|
// evictMaster Instance - single socket 관련
|
||||||
private static IgniteCache<String, String> evictMasterCache = null;
|
private static IgniteCache<String, String> evictMasterCache = null;
|
||||||
// Socket Session cache 정보
|
// Socket Session cache 정보
|
||||||
@@ -76,8 +83,11 @@ public class SessionManagerForIgnite extends SessionManager {
|
|||||||
private static IgniteCache<String, CacheStoreObject> cacheStore = null;
|
private static IgniteCache<String, CacheStoreObject> cacheStore = null;
|
||||||
// CA Gateway Token 관리용
|
// CA Gateway Token 관리용
|
||||||
private static IgniteCache<String, BearerTokenInfo> cacheCAToken = null;
|
private static IgniteCache<String, BearerTokenInfo> cacheCAToken = null;
|
||||||
|
// OutboundAccessToekn 관리용
|
||||||
|
private static IgniteCache<String, AccessTokenVO> cacheOutBoundAccessToken = null;
|
||||||
// webSocket Login cache 정보
|
// webSocket Login cache 정보
|
||||||
private static IgniteCache<String, SessionVO> cacheLogin = null; // key = loginId
|
private static IgniteCache<String, SessionVO> cacheLogin = null; // key = loginId
|
||||||
|
|
||||||
private static IgniteCache<String, String> cacheConvertUserId = null; // 단말ID 를 userID로 변경
|
private static IgniteCache<String, String> cacheConvertUserId = null; // 단말ID 를 userID로 변경
|
||||||
// HTTP Polling 에 대한 timeout 관리용
|
// HTTP Polling 에 대한 timeout 관리용
|
||||||
private static IgniteCache<String, SessionVO> httpLogin = null; // key == uuid
|
private static IgniteCache<String, SessionVO> httpLogin = null; // key == uuid
|
||||||
@@ -392,6 +402,7 @@ public class SessionManagerForIgnite extends SessionManager {
|
|||||||
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, USERID_CAHCE_NAME, 20000, false));
|
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, USERID_CAHCE_NAME, 20000, false));
|
||||||
caches.add(initCache(config, webSocketTimeout, bootstrapAsynchronously, WEBSOCKER_CAHCE_NAME, 20000, false));
|
caches.add(initCache(config, webSocketTimeout, bootstrapAsynchronously, WEBSOCKER_CAHCE_NAME, 20000, false));
|
||||||
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, TERMINAL_CAHCE_NAME, 20000, false));
|
caches.add(initCache(config, sessionTimeout, bootstrapAsynchronously, TERMINAL_CAHCE_NAME, 20000, false));
|
||||||
|
caches.add(initCache(config, 0, bootstrapAsynchronously, OUTBOUND_ACCESS_TOKEN_CACHE_NAME, 1000, false, true)); // transactionMode true
|
||||||
caches.add(initCache(config, 0, bootstrapAsynchronously, CAGATEWAY_TOKEN_CACHE_NAME, 1000, false));
|
caches.add(initCache(config, 0, bootstrapAsynchronously, CAGATEWAY_TOKEN_CACHE_NAME, 1000, false));
|
||||||
|
|
||||||
config.setCacheConfiguration(caches.stream().toArray(CacheConfiguration[]::new));
|
config.setCacheConfiguration(caches.stream().toArray(CacheConfiguration[]::new));
|
||||||
@@ -421,6 +432,7 @@ public class SessionManagerForIgnite extends SessionManager {
|
|||||||
cacheTopic = manager.cache(TOPIC_CAHCE_NAME);
|
cacheTopic = manager.cache(TOPIC_CAHCE_NAME);
|
||||||
cacheStore = manager.cache(STORE_CAHCE_NAME);
|
cacheStore = manager.cache(STORE_CAHCE_NAME);
|
||||||
cacheCAToken = manager.cache(CAGATEWAY_TOKEN_CACHE_NAME);
|
cacheCAToken = manager.cache(CAGATEWAY_TOKEN_CACHE_NAME);
|
||||||
|
cacheOutBoundAccessToken = manager.cache(OUTBOUND_ACCESS_TOKEN_CACHE_NAME);
|
||||||
|
|
||||||
cacheLogin = manager.cache(LOGIN_CAHCE_NAME);
|
cacheLogin = manager.cache(LOGIN_CAHCE_NAME);
|
||||||
httpLogin = manager.cache(HTTP_CAHCE_NAME);
|
httpLogin = manager.cache(HTTP_CAHCE_NAME);
|
||||||
@@ -428,49 +440,56 @@ public class SessionManagerForIgnite extends SessionManager {
|
|||||||
cacheWebSocketTimeout = manager.cache(WEBSOCKER_CAHCE_NAME);
|
cacheWebSocketTimeout = manager.cache(WEBSOCKER_CAHCE_NAME);
|
||||||
cacheTerminal = manager.cache(TERMINAL_CAHCE_NAME);
|
cacheTerminal = manager.cache(TERMINAL_CAHCE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs,
|
||||||
|
String bootstrapAsynchronously, String name, int maxsize, boolean useCacheWriteModeAsync,
|
||||||
|
boolean transactionMode) {
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
logger.debug(logPrefix + " create " + name + " cache expiryDurationSecs=" + expiryDurationSecs);
|
||||||
|
}
|
||||||
|
// near-cache
|
||||||
|
NearCacheConfiguration nearConfiguration = new NearCacheConfiguration();
|
||||||
|
LruEvictionPolicy nearEvictionPolicy = new LruEvictionPolicy();
|
||||||
|
nearEvictionPolicy.setMaxSize(maxsize);
|
||||||
|
nearConfiguration.setNearEvictionPolicy(nearEvictionPolicy);
|
||||||
|
|
||||||
|
CacheConfiguration cacheConfig = new CacheConfiguration(name);
|
||||||
|
|
||||||
|
cacheConfig.setCacheMode(CacheMode.PARTITIONED);
|
||||||
|
|
||||||
|
// ATOMIC,TRANSACTIONAL
|
||||||
|
if (transactionMode) {
|
||||||
|
cacheConfig.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
|
||||||
|
} else {
|
||||||
|
cacheConfig.setAtomicityMode(CacheAtomicityMode.ATOMIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the number of backups (replicas) for each partition : default(0)
|
||||||
|
cacheConfig.setBackups(1);
|
||||||
|
// Enable reading from backups to improve read consistency : default(true)
|
||||||
|
cacheConfig.setReadFromBackup(true);
|
||||||
|
|
||||||
|
// FULL_SYNC/FULL_ASYNC/PRIMARY_SYNC(default)
|
||||||
|
if (useCacheWriteModeAsync) {
|
||||||
|
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_ASYNC);
|
||||||
|
} else {
|
||||||
|
// cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
|
||||||
|
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiryDurationSecs > 0) {
|
||||||
|
cacheConfig.setExpiryPolicyFactory(
|
||||||
|
ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, expiryDurationSecs)));
|
||||||
|
} else {
|
||||||
|
cacheConfig.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(Duration.ETERNAL));
|
||||||
|
}
|
||||||
|
cacheConfig.setNearConfiguration(nearConfiguration);
|
||||||
|
return cacheConfig;
|
||||||
|
}
|
||||||
|
|
||||||
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs, String bootstrapAsynchronously,
|
private CacheConfiguration initCache(IgniteConfiguration config, int expiryDurationSecs, String bootstrapAsynchronously,
|
||||||
String name, int maxsize, boolean useCacheWriteModeAsync) {
|
String name, int maxsize, boolean useCacheWriteModeAsync) {
|
||||||
if (logger.isDebug()) {
|
return initCache(config, expiryDurationSecs, bootstrapAsynchronously, name, maxsize, useCacheWriteModeAsync, false);
|
||||||
logger.debug(logPrefix + " create " + name + " cache expiryDurationSecs=" + expiryDurationSecs);
|
|
||||||
}
|
|
||||||
// near-cache
|
|
||||||
NearCacheConfiguration nearConfiguration = new NearCacheConfiguration();
|
|
||||||
LruEvictionPolicy nearEvictionPolicy = new LruEvictionPolicy();
|
|
||||||
nearEvictionPolicy.setMaxSize(maxsize);
|
|
||||||
nearConfiguration.setNearEvictionPolicy(nearEvictionPolicy);
|
|
||||||
|
|
||||||
CacheConfiguration cacheConfig = new CacheConfiguration(name);
|
|
||||||
// REPLICATED,PARTITIONED(default)
|
|
||||||
// cacheConfig.setCacheMode(CacheMode.REPLICATED);
|
|
||||||
cacheConfig.setCacheMode(CacheMode.PARTITIONED);
|
|
||||||
|
|
||||||
// ATOMIC,TRANSACTIONAL
|
|
||||||
cacheConfig.setAtomicityMode(CacheAtomicityMode.ATOMIC);
|
|
||||||
|
|
||||||
// Set the number of backups (replicas) for each partition : default(0)
|
|
||||||
cacheConfig.setBackups(1);
|
|
||||||
// Enable reading from backups to improve read consistency : default(true)
|
|
||||||
cacheConfig.setReadFromBackup(true);
|
|
||||||
|
|
||||||
// FULL_SYNC/FULL_ASYNC/PRIMARY_SYNC(default)
|
|
||||||
if (useCacheWriteModeAsync) {
|
|
||||||
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_ASYNC);
|
|
||||||
} else {
|
|
||||||
// cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
|
|
||||||
cacheConfig.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(expiryDurationSecs > 0) {
|
|
||||||
cacheConfig
|
|
||||||
.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, expiryDurationSecs)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cacheConfig
|
|
||||||
.setExpiryPolicyFactory( ModifiedExpiryPolicy.factoryOf(Duration.ETERNAL) );
|
|
||||||
}
|
|
||||||
cacheConfig.setNearConfiguration(nearConfiguration);
|
|
||||||
return cacheConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocket Login 관련
|
// WebSocket Login 관련
|
||||||
@@ -868,5 +887,114 @@ public class SessionManagerForIgnite extends SessionManager {
|
|||||||
cacheCAToken.remove(key);
|
cacheCAToken.remove(key);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void sleepQuietly(long millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// 인터럽트 상태 복구 (중요)
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessTokenVO getOutboundAccessToken(String key, Function<AccessTokenVO, AccessTokenVO> tokenSupplier) {
|
||||||
|
// 1. 먼저 캐시에서 조회 (락 없이 빠르게)
|
||||||
|
AccessTokenVO token = cacheOutBoundAccessToken.get(key);
|
||||||
|
|
||||||
|
// 2. 만료되었거나 없다면 락 획득 시도
|
||||||
|
if (token == null || token.isExpired()) {
|
||||||
|
Lock lock = cacheOutBoundAccessToken.lock(DISTRIBUTED_TOKEN_LOCK); // Ignite 분산 락
|
||||||
|
|
||||||
|
boolean acquired = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock");
|
||||||
|
acquired = lock.tryLock(60, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
if ( acquired ) {
|
||||||
|
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock success=>"+acquired);
|
||||||
|
// 3. 락 획득 후 다시 확인 (그 사이 다른 인스턴스가 갱신했을 수 있음)
|
||||||
|
token = cacheOutBoundAccessToken.get(key);
|
||||||
|
|
||||||
|
logger.debug(String.format("getting token in ignite=%s", token));
|
||||||
|
|
||||||
|
if (token == null || token.isExpired()) {
|
||||||
|
// 4. 실제로 비어있을 때만 외부 API 호출 (I/O 발생)
|
||||||
|
token = tokenSupplier.apply(token);
|
||||||
|
|
||||||
|
logger.debug(String.format("getting token is null or expired borrow new token =%s", token));
|
||||||
|
|
||||||
|
cacheOutBoundAccessToken.put(key, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock fail=>"+acquired);
|
||||||
|
}
|
||||||
|
}catch(Throwable e) {
|
||||||
|
logger.error("occuring exception in getOutboundAccessToken", e);
|
||||||
|
}finally {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if( acquired ) {
|
||||||
|
lock.unlock();
|
||||||
|
logger.debug("calling func getOutboundAccessToken = distributed ignite unlock");
|
||||||
|
}
|
||||||
|
}catch(Throwable e) {
|
||||||
|
logger.error("occuring exception in getOutboundAccessToken unlock fail.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeOutboundAccessToken(String key) {
|
||||||
|
|
||||||
|
Lock lock = cacheOutBoundAccessToken.lock(DISTRIBUTED_TOKEN_LOCK); // Ignite 분산 락
|
||||||
|
|
||||||
|
boolean acquired = false;
|
||||||
|
try {
|
||||||
|
|
||||||
|
logger.debug("calling func removeOutboundAccessToken = distributed ignite trylock");
|
||||||
|
acquired = lock.tryLock(60, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
if( acquired ) {
|
||||||
|
if (logger.isDebug()) {
|
||||||
|
|
||||||
|
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock success=>"
|
||||||
|
+ acquired);
|
||||||
|
|
||||||
|
logger.debug("calling func removeOutboundAccessToken Remove OutBoundAccessToken key - " + key);
|
||||||
|
}
|
||||||
|
cacheOutBoundAccessToken.remove(key);
|
||||||
|
}else {
|
||||||
|
logger.debug("calling func getOutboundAccessToken = distributed ignite trylock fail=>"+acquired);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("occuring exception in removeOutboundAccessToken", e);
|
||||||
|
} finally {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (acquired) {
|
||||||
|
lock.unlock();
|
||||||
|
logger.debug("calling func removeOutboundAccessToken = distributed ignite unlock");
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("occuring exception in removeOutboundAccessToken unlock fail.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearOutboundAccessToken() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user