From 265a30ccf3fea67aa66947d61669bace1584aa54 Mon Sep 17 00:00:00 2001 From: curry772 Date: Mon, 18 May 2026 10:58:10 +0900 Subject: [PATCH] =?UTF-8?q?Outbound=20Client=20oAuth=20Token=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=8C=A8=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 광주은행 내용 적용 - ignite 사용 --- .../authoutbound/AccessTokenManagerByDB.java | 178 +++++++++------ .../eai/common/session/SessionManager.java | 15 ++ .../session/SessionManagerForEhcache.java | 18 ++ .../session/SessionManagerForIgnite.java | 208 ++++++++++++++---- 4 files changed, 306 insertions(+), 113 deletions(-) diff --git a/src/main/java/com/eactive/eai/common/authoutbound/AccessTokenManagerByDB.java b/src/main/java/com/eactive/eai/common/authoutbound/AccessTokenManagerByDB.java index 6383fe0..7fe4818 100644 --- a/src/main/java/com/eactive/eai/common/authoutbound/AccessTokenManagerByDB.java +++ b/src/main/java/com/eactive/eai/common/authoutbound/AccessTokenManagerByDB.java @@ -13,15 +13,21 @@ 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 클래스 @@ -55,13 +61,8 @@ public class AccessTokenManagerByDB implements Lifecycle { /** * 기동 여부 */ - private boolean started; - - /** - * 토큰 정보를 저장하는 collection - */ - private HashMap tokens; - + private boolean started; + /** * 인증 정보를 저장하는 Map */ @@ -78,7 +79,6 @@ public class AccessTokenManagerByDB implements Lifecycle { private static final int TOKEN_SCHEDULER_THREAD_POOL_SIZE = 30; public AccessTokenManagerByDB() { - tokens = new HashMap<>(); scheduledTasks = new ConcurrentHashMap<>(); outboundOAuthCredentialVos = new HashMap<>(); runningTasks = Collections.newSetFromMap(new ConcurrentHashMap<>()); @@ -117,7 +117,6 @@ public class AccessTokenManagerByDB implements Lifecycle { lifecycle.fireLifecycleEvent(STARTING_EVENT, this); try { - tokens.clear(); init(); } catch(Exception e) { throw new LifecycleException(ExceptionUtil.getErrorCode(e,"RECEAICPM201")); @@ -168,7 +167,6 @@ public class AccessTokenManagerByDB implements Lifecycle { logger.error("Scheduler shutdown interrupted", e); } - this.tokens = null; started = false; if (logger.isWarn()){ @@ -240,27 +238,34 @@ public class AccessTokenManagerByDB implements Lifecycle { try { logger.debug("Executing token issuance for adapter group: {}", adapterGroupName); - // 현재 토큰 가져오기 - AccessTokenVO accessToken = tokens.get(adapterGroupName); + SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, new Function() { - // 다음 스케줄 시간 계산 - long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000); - // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급 - if (accessToken == null) { - issueToken(credential, false); - } else if(accessToken.getExpiration() != null) { - // 토큰이 있고 만료시간이 설정 된 경우 - if(accessToken.getExpiration().before(new Date(intervalTime))){ - // 다음 스케줄 전에 토큰이 만료되는 경우 재발급 - issueToken(credential, true); + @Override + public AccessTokenVO apply(AccessTokenVO accessToken) { + + long intervalTime = System.currentTimeMillis() + (credential.getIntervalSec() * 1000); +// // 토큰이 없거나, 다음 스케줄 시간 전에 만료될 경우 재발급 + if (accessToken == null) { + return issueToken(credential); + } else if(accessToken.getExpiration() != null) { + // 토큰이 있고 만료시간이 설정 된 경우 + 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 { - // 토큰이 아직 유효한 경우 - 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); } catch (Exception e) { @@ -282,19 +287,24 @@ public class AccessTokenManagerByDB implements Lifecycle { } } - private void issueToken(OutboundOAuthCredentialVo outboundOAuthCredentialVo, boolean isTokenExpiredWithinInterval) throws Exception { - String adapterGroupName = outboundOAuthCredentialVo.getAdapterGroupName(); - boolean isExpired = true; + 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 (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) { +// if (isExpired || isTokenExpiredWithinInterval) { + + logger.info("Issuing new token for adapter group: {}", adapterGroupName); - tokens.remove(adapterGroupName); +// tokens.remove(adapterGroupName); AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName); @@ -306,16 +316,23 @@ public class AccessTokenManagerByDB implements Lifecycle { HttpClientAccessTokenServiceByDB service = HttpClientAccessTokenServiceFactoryByDB.createFactory(type); if(service != null) { logger.debug("Executing token service of type: {} for adapter group: {}", type, adapterGroupName); - AccessTokenVO accessToken = (AccessTokenVO) service.execute(adapterGroupName, properties, outboundOAuthCredentialVo); - this.tokens.put(adapterGroupName, accessToken); + 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) { @@ -324,7 +341,7 @@ public class AccessTokenManagerByDB implements Lifecycle { task.cancel(true); logger.warn("Token task stopped for adapter group: {}", adapterGroupName); } - tokens.remove(adapterGroupName); + SessionManager.getInstance().removeOutboundAccessToken(adapterGroupName); logger.debug("Token removed for adapter group: {}", adapterGroupName); } @@ -382,15 +399,28 @@ public class AccessTokenManagerByDB implements Lifecycle { * @return Access 토큰 정보 **/ public AccessTokenVO getAccessTokenVO(String adapterGroupName) { - AccessTokenVO accessToken = null; - - try { - accessToken = tokens.get(adapterGroupName); - } catch(Exception e) { - if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e); - } + AccessTokenVO accessToken = null; - return accessToken; + try { + accessToken = SessionManager.getInstance().getOutboundAccessToken(adapterGroupName, + new Function() { + + @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) { String accessToken = null; - + try { - accessToken = tokens.get(adapterGroupName).getAccessToken(); + accessToken = getAccessTokenVO(adapterGroupName).getAccessToken(); } catch(Exception e) { if (logger.isError()) logger.error("토큰을 찾을 수 없습니다. ["+adapterGroupName+"] ", e); } @@ -435,27 +465,29 @@ public class AccessTokenManagerByDB implements Lifecycle { * 3. 주의사항 * **/ - public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties, String oldToken) throws Exception { - - AccessTokenVO accessToken = getAccessTokenVO(adapterGroupName); + public synchronized AccessTokenVO retryAccessTokenVO(String adapterGroupName, Properties properties, + String oldToken) throws Exception { - // 동시 요청이 발생할 경우 synchronized 처리를 했기때문에 토큰을 다시 한번 체크한다. - if (accessToken == null || accessToken.isExpired() - || StringUtils.equals(accessToken.getAccessToken(), oldToken)) { + 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); + } + } - 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; - } + } } diff --git a/src/main/java/com/eactive/eai/common/session/SessionManager.java b/src/main/java/com/eactive/eai/common/session/SessionManager.java index 1136743..f3e948d 100644 --- a/src/main/java/com/eactive/eai/common/session/SessionManager.java +++ b/src/main/java/com/eactive/eai/common/session/SessionManager.java @@ -1,5 +1,10 @@ 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.common.lifecycle.Lifecycle; 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.sessioninfo.TerminalInfoVO; import com.eactive.eai.common.util.Logger; +import com.openbanking.eai.common.token.AccessTokenVO; /** * 1. 기능 : @@ -60,6 +66,7 @@ public abstract class SessionManager implements Lifecycle { public static final String WEBSOCKER_CAHCE_NAME = "webSocketTimeout"; public static final String TERMINAL_CAHCE_NAME = "terminalSession"; 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_CACHETYPE = "cacheType"; @@ -204,6 +211,13 @@ public abstract class SessionManager implements Lifecycle { public abstract void removeCAToken(String key); + //Outbound Access Token Cache + public abstract AccessTokenVO getOutboundAccessToken(String key, Function getNewToken); + + public abstract void removeOutboundAccessToken(String key); + + public abstract void clearOutboundAccessToken(); + // HTTP Cache public abstract SessionVO getHttpLogin(String key); @@ -245,4 +259,5 @@ public abstract class SessionManager implements Lifecycle { public abstract void closeManager(); public abstract String getCacheStatus(); + } diff --git a/src/main/java/com/eactive/eai/common/session/SessionManagerForEhcache.java b/src/main/java/com/eactive/eai/common/session/SessionManagerForEhcache.java index e23b465..b488677 100644 --- a/src/main/java/com/eactive/eai/common/session/SessionManagerForEhcache.java +++ b/src/main/java/com/eactive/eai/common/session/SessionManagerForEhcache.java @@ -2,6 +2,8 @@ package com.eactive.eai.common.session; import java.util.Properties; 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.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.util.ApplicationContextProvider; import com.eactive.eai.util.HealthCheckManager; +import com.openbanking.eai.common.token.AccessTokenVO; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheException; @@ -937,4 +940,19 @@ public class SessionManagerForEhcache extends SessionManager { public void removeCAToken(String key) { throw new UnsupportedOperationException(); } + + @Override + public AccessTokenVO getOutboundAccessToken(String key, Function getNewToken) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeOutboundAccessToken(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearOutboundAccessToken() { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/com/eactive/eai/common/session/SessionManagerForIgnite.java b/src/main/java/com/eactive/eai/common/session/SessionManagerForIgnite.java index e5c13b1..064bff9 100644 --- a/src/main/java/com/eactive/eai/common/session/SessionManagerForIgnite.java +++ b/src/main/java/com/eactive/eai/common/session/SessionManagerForIgnite.java @@ -4,6 +4,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; 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.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.ResponseMap; import com.eactive.eai.util.HealthCheckManager; +import com.openbanking.eai.common.token.AccessTokenVO; import ch.qos.logback.classic.Level; @@ -66,6 +71,8 @@ import ch.qos.logback.classic.Level; //------------------------------------------------------------------------------------ public class SessionManagerForIgnite extends SessionManager { + + private static final String DISTRIBUTED_TOKEN_LOCK = "DISTRIBUTED_TOKEN_LOCK"; // evictMaster Instance - single socket 관련 private static IgniteCache evictMasterCache = null; // Socket Session cache 정보 @@ -76,8 +83,11 @@ public class SessionManagerForIgnite extends SessionManager { private static IgniteCache cacheStore = null; // CA Gateway Token 관리용 private static IgniteCache cacheCAToken = null; + // OutboundAccessToekn 관리용 + private static IgniteCache cacheOutBoundAccessToken = null; // webSocket Login cache 정보 private static IgniteCache cacheLogin = null; // key = loginId + private static IgniteCache cacheConvertUserId = null; // 단말ID 를 userID로 변경 // HTTP Polling 에 대한 timeout 관리용 private static IgniteCache 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, webSocketTimeout, bootstrapAsynchronously, WEBSOCKER_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)); config.setCacheConfiguration(caches.stream().toArray(CacheConfiguration[]::new)); @@ -421,6 +432,7 @@ public class SessionManagerForIgnite extends SessionManager { cacheTopic = manager.cache(TOPIC_CAHCE_NAME); cacheStore = manager.cache(STORE_CAHCE_NAME); cacheCAToken = manager.cache(CAGATEWAY_TOKEN_CACHE_NAME); + cacheOutBoundAccessToken = manager.cache(OUTBOUND_ACCESS_TOKEN_CACHE_NAME); cacheLogin = manager.cache(LOGIN_CAHCE_NAME); httpLogin = manager.cache(HTTP_CAHCE_NAME); @@ -428,49 +440,56 @@ public class SessionManagerForIgnite extends SessionManager { cacheWebSocketTimeout = manager.cache(WEBSOCKER_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, String name, int maxsize, boolean useCacheWriteModeAsync) { - 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); - // 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; + return initCache(config, expiryDurationSecs, bootstrapAsynchronously, name, maxsize, useCacheWriteModeAsync, false); } // WebSocket Login 관련 @@ -868,5 +887,114 @@ public class SessionManagerForIgnite extends SessionManager { cacheCAToken.remove(key); } + + + private void sleepQuietly(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + // 인터럽트 상태 복구 (중요) + Thread.currentThread().interrupt(); + } + } + + public AccessTokenVO getOutboundAccessToken(String key, Function 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() { + + + } }