KEYCLOAK-5747 Ensure refreshToken doesn't need to send request to the other DC. Other fixes and polishing
This commit is contained in:
parent
61c5a332b4
commit
bd1072d2eb
31 changed files with 876 additions and 277 deletions
|
@ -304,12 +304,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
Configuration replicationEvictionCacheConfiguration = replicationConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationEvictionCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||
counterConfigBuilder.invocationBatching().enable()
|
||||
.transaction().transactionMode(TransactionMode.TRANSACTIONAL);
|
||||
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
|
||||
|
||||
long realmRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
|
||||
realmRevisionsMaxEntries = realmRevisionsMaxEntries > 0
|
||||
? 2 * realmRevisionsMaxEntries
|
||||
|
|
|
@ -24,16 +24,16 @@ import java.util.Set;
|
|||
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.changes.ClientSessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask.CacheOperation;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask.CrossDCMessageStatus;
|
||||
import org.keycloak.models.sessions.infinispan.changes.Tasks;
|
||||
import org.keycloak.models.sessions.infinispan.changes.UserSessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshChecker;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import java.util.UUID;
|
||||
|
@ -43,25 +43,31 @@ import java.util.UUID;
|
|||
*/
|
||||
public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
|
||||
|
||||
private final KeycloakSession kcSession;
|
||||
private final InfinispanUserSessionProvider provider;
|
||||
private AuthenticatedClientSessionEntity entity;
|
||||
private final ClientModel client;
|
||||
private final InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx;
|
||||
private final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
|
||||
private UserSessionModel userSession;
|
||||
private boolean offline;
|
||||
|
||||
public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client,
|
||||
UserSessionModel userSession,
|
||||
public AuthenticatedClientSessionAdapter(KeycloakSession kcSession, InfinispanUserSessionProvider provider,
|
||||
AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionModel userSession,
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx) {
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx, boolean offline) {
|
||||
if (userSession == null) {
|
||||
throw new NullPointerException("userSession must not be null");
|
||||
}
|
||||
|
||||
this.kcSession = kcSession;
|
||||
this.provider = provider;
|
||||
this.entity = entity;
|
||||
this.userSession = userSession;
|
||||
this.client = client;
|
||||
this.userSessionUpdateTx = userSessionUpdateTx;
|
||||
this.clientSessionUpdateTx = clientSessionUpdateTx;
|
||||
this.offline = offline;
|
||||
}
|
||||
|
||||
private void update(UserSessionUpdateTask task) {
|
||||
|
@ -141,6 +147,18 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
|||
public void runUpdate(AuthenticatedClientSessionEntity entity) {
|
||||
entity.setTimestamp(timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<AuthenticatedClientSessionEntity> sessionWrapper) {
|
||||
return new LastSessionRefreshChecker(provider.getLastSessionRefreshStore(), provider.getOfflineLastSessionRefreshStore())
|
||||
.shouldSaveClientSessionToRemoteCache(kcSession, client.getRealm(), sessionWrapper, userSession, offline, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "setTimestamp(" + timestamp + ')';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
update(task);
|
||||
|
|
|
@ -38,8 +38,6 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
|||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask.CacheOperation;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask.CrossDCMessageStatus;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
|
@ -56,6 +54,7 @@ import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
|
|||
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
|
||||
import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
|
||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
@ -164,8 +163,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(false);
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(false);
|
||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession,
|
||||
userSessionUpdateTx, clientSessionUpdateTx);
|
||||
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(session, this, entity, client, userSession, userSessionUpdateTx, clientSessionUpdateTx, false);
|
||||
|
||||
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
||||
clientSessionUpdateTx.addTask(clientSessionId, createClientSessionTask, entity);
|
||||
|
@ -446,17 +444,18 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
private void removeExpiredUserSessions(RealmModel realm) {
|
||||
int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
|
||||
int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout();
|
||||
int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout() - SessionTimeoutHelper.PERIODIC_CLEANER_IDLE_TIMEOUT_WINDOW_SECONDS;
|
||||
|
||||
FuturesHelper futures = new FuturesHelper();
|
||||
|
||||
// Each cluster node cleanups just local sessions, which are those owned by itself (+ few more taking l1 cache into account)
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(sessionCache);
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(offlineClientSessionCache);
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(clientSessionCache);
|
||||
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
||||
|
||||
final AtomicInteger userSessionsSize = new AtomicInteger();
|
||||
final AtomicInteger clientSessionsSize = new AtomicInteger();
|
||||
|
||||
// Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate
|
||||
localCacheStoreIgnore
|
||||
|
@ -474,6 +473,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
futures.addTask(future);
|
||||
|
||||
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
|
||||
clientSessionsSize.incrementAndGet();
|
||||
Future f = localClientSessionCache.removeAsync(clientSessionId);
|
||||
futures.addTask(f);
|
||||
});
|
||||
|
@ -483,12 +483,13 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
futures.waitForAllToFinish();
|
||||
|
||||
log.debugf("Removed %d expired user sessions for realm '%s'", userSessionsSize.get(), realm.getName());
|
||||
log.debugf("Removed %d expired user sessions and %d expired client sessions for realm '%s'", userSessionsSize.get(),
|
||||
clientSessionsSize.get(), realm.getName());
|
||||
}
|
||||
|
||||
private void removeExpiredOfflineUserSessions(RealmModel realm) {
|
||||
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
|
||||
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
|
||||
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout() - SessionTimeoutHelper.PERIODIC_CLEANER_IDLE_TIMEOUT_WINDOW_SECONDS;
|
||||
|
||||
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(offlineSessionCache);
|
||||
|
@ -501,6 +502,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
|
||||
|
||||
final AtomicInteger userSessionsSize = new AtomicInteger();
|
||||
final AtomicInteger clientSessionsSize = new AtomicInteger();
|
||||
|
||||
// Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate
|
||||
localCacheStoreIgnore
|
||||
|
@ -517,6 +519,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
Future future = localCache.removeAsync(userSessionEntity.getId());
|
||||
futures.addTask(future);
|
||||
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
|
||||
clientSessionsSize.incrementAndGet();
|
||||
Future f = localClientSessionCache.removeAsync(clientSessionId);
|
||||
futures.addTask(f);
|
||||
});
|
||||
|
@ -533,7 +536,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
futures.waitForAllToFinish();
|
||||
|
||||
log.debugf("Removed %d expired offline user sessions for realm '%s'", userSessionsSize.get(), realm.getName());
|
||||
log.debugf("Removed %d expired offline user sessions and %d expired offline client sessions for realm '%s'",
|
||||
userSessionsSize.get(), clientSessionsSize.get(), realm.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -712,7 +716,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, boolean offline) {
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
|
||||
return entity != null ? new AuthenticatedClientSessionAdapter(entity, client, userSession, userSessionUpdateTx, clientSessionUpdateTx) : null;
|
||||
return entity != null ? new AuthenticatedClientSessionAdapter(session,this, entity, client, userSession, userSessionUpdateTx, clientSessionUpdateTx, offline) : null;
|
||||
}
|
||||
|
||||
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
||||
|
@ -762,7 +766,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(true);
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(true);
|
||||
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession, userSessionUpdateTx, clientSessionUpdateTx);
|
||||
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession, userSessionUpdateTx, clientSessionUpdateTx, true);
|
||||
|
||||
// update timestamp to current time
|
||||
offlineClientSession.setTimestamp(Time.currentTime());
|
||||
|
@ -831,7 +835,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
// Handle client sessions
|
||||
if (importAuthenticatedClientSessions) {
|
||||
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
|
||||
importClientSession(importedSession, clientSession, userSessionUpdateTx, clientSessionUpdateTx);
|
||||
importClientSession(importedSession, clientSession, userSessionUpdateTx, clientSessionUpdateTx, offline);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -841,7 +845,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
|
||||
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter sessionToImportInto, AuthenticatedClientSessionModel clientSession,
|
||||
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx) {
|
||||
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
|
||||
boolean offline) {
|
||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
|
||||
entity.setRealmId(sessionToImportInto.getRealm().getId());
|
||||
final UUID clientSessionId = entity.getId();
|
||||
|
@ -864,7 +869,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId);
|
||||
userSessionUpdateTx.addTask(sessionToImportInto.getId(), registerClientSessionTask);
|
||||
|
||||
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), sessionToImportInto, userSessionUpdateTx, clientSessionUpdateTx);
|
||||
return new AuthenticatedClientSessionAdapter(session,this, entity, clientSession.getClient(), sessionToImportInto, userSessionUpdateTx, clientSessionUpdateTx, offline);
|
||||
}
|
||||
|
||||
private static class RegisterClientSessionTask implements SessionUpdateTask<UserSessionEntity> {
|
||||
|
|
|
@ -212,7 +212,8 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionsCache = ispn.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
|
||||
boolean sessionsRemoteCache = checkRemoteCache(session, sessionsCache, (RealmModel realm) -> {
|
||||
return realm.getSsoSessionIdleTimeout() * 1000;
|
||||
// We won't write to the remoteCache during token refresh, so the timeout needs to be longer.
|
||||
return realm.getSsoSessionMaxLifespan() * 1000;
|
||||
});
|
||||
|
||||
if (sessionsRemoteCache) {
|
||||
|
@ -221,7 +222,8 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
|
|||
|
||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsCache = ispn.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
|
||||
checkRemoteCache(session, clientSessionsCache, (RealmModel realm) -> {
|
||||
return realm.getSsoSessionIdleTimeout() * 1000;
|
||||
// We won't write to the remoteCache during token refresh, so the timeout needs to be longer.
|
||||
return realm.getSsoSessionMaxLifespan() * 1000;
|
||||
});
|
||||
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = ispn.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
|
||||
|
|
|
@ -212,7 +212,7 @@ public class UserSessionAdapter implements UserSessionModel {
|
|||
@Override
|
||||
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
|
||||
return new LastSessionRefreshChecker(provider.getLastSessionRefreshStore(), provider.getOfflineLastSessionRefreshStore())
|
||||
.getCrossDCMessageStatus(UserSessionAdapter.this.session, UserSessionAdapter.this.realm, sessionWrapper, offline, lastSessionRefresh);
|
||||
.shouldSaveUserSessionToRemoteCache(UserSessionAdapter.this.session, UserSessionAdapter.this.realm, sessionWrapper, offline, lastSessionRefresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,11 +17,16 @@
|
|||
|
||||
package org.keycloak.models.sessions.infinispan.changes.sessions;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.AuthenticatedClientSessionAdapter;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
/**
|
||||
|
@ -41,7 +46,72 @@ public class LastSessionRefreshChecker {
|
|||
}
|
||||
|
||||
|
||||
public SessionUpdateTask.CrossDCMessageStatus getCrossDCMessageStatus(KeycloakSession kcSession, RealmModel realm, SessionEntityWrapper<UserSessionEntity> sessionWrapper, boolean offline, int newLastSessionRefresh) {
|
||||
public SessionUpdateTask.CrossDCMessageStatus shouldSaveUserSessionToRemoteCache(
|
||||
KeycloakSession kcSession, RealmModel realm, SessionEntityWrapper<UserSessionEntity> sessionWrapper, boolean offline, int newLastSessionRefresh) {
|
||||
|
||||
SessionUpdateTask.CrossDCMessageStatus baseChecks = baseChecks(kcSession, realm ,offline);
|
||||
if (baseChecks != null) {
|
||||
return baseChecks;
|
||||
}
|
||||
|
||||
String userSessionId = sessionWrapper.getEntity().getId();
|
||||
|
||||
if (offline) {
|
||||
Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE);
|
||||
if (lsrr == null) {
|
||||
lsrr = sessionWrapper.getEntity().getStarted();
|
||||
}
|
||||
|
||||
if (lsrr + (realm.getOfflineSessionIdleTimeout() / 2) <= newLastSessionRefresh) {
|
||||
logger.debugf("We are going to write remotely userSession %s. Remote last session refresh: %d, New last session refresh: %d",
|
||||
userSessionId, lsrr, newLastSessionRefresh);
|
||||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debugf("Skip writing last session refresh to the remoteCache. Session %s newLastSessionRefresh %d", userSessionId, newLastSessionRefresh);
|
||||
}
|
||||
|
||||
LastSessionRefreshStore storeToUse = offline ? offlineStore : store;
|
||||
storeToUse.putLastSessionRefresh(kcSession, userSessionId, realm.getId(), newLastSessionRefresh);
|
||||
|
||||
return SessionUpdateTask.CrossDCMessageStatus.NOT_NEEDED;
|
||||
}
|
||||
|
||||
|
||||
public SessionUpdateTask.CrossDCMessageStatus shouldSaveClientSessionToRemoteCache(
|
||||
KeycloakSession kcSession, RealmModel realm, SessionEntityWrapper<AuthenticatedClientSessionEntity> sessionWrapper, UserSessionModel userSession, boolean offline, int newTimestamp) {
|
||||
|
||||
SessionUpdateTask.CrossDCMessageStatus baseChecks = baseChecks(kcSession, realm ,offline);
|
||||
if (baseChecks != null) {
|
||||
return baseChecks;
|
||||
}
|
||||
|
||||
UUID clientSessionId = sessionWrapper.getEntity().getId();
|
||||
|
||||
if (offline) {
|
||||
Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(AuthenticatedClientSessionEntity.LAST_TIMESTAMP_REMOTE);
|
||||
if (lsrr == null) {
|
||||
lsrr = userSession.getStarted();
|
||||
}
|
||||
|
||||
if (lsrr + (realm.getOfflineSessionIdleTimeout() / 2) <= newTimestamp) {
|
||||
logger.debugf("We are going to write remotely for clientSession %s. Remote timestamp: %d, New timestamp: %d",
|
||||
clientSessionId, lsrr, newTimestamp);
|
||||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debugf("Skip writing timestamp to the remoteCache. ClientSession %s timestamp %d", clientSessionId, newTimestamp);
|
||||
}
|
||||
|
||||
return SessionUpdateTask.CrossDCMessageStatus.NOT_NEEDED;
|
||||
}
|
||||
|
||||
|
||||
private SessionUpdateTask.CrossDCMessageStatus baseChecks(KeycloakSession kcSession, RealmModel realm, boolean offline) {
|
||||
// revokeRefreshToken always writes everything to remoteCache immediately
|
||||
if (realm.isRevokeRefreshToken()) {
|
||||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||
|
@ -53,29 +123,13 @@ public class LastSessionRefreshChecker {
|
|||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||
}
|
||||
|
||||
// Received the message from the other DC that we should update the lastSessionRefresh in local cluster
|
||||
Boolean ignoreRemoteCacheUpdate = (Boolean) kcSession.getAttribute(LastSessionRefreshListener.IGNORE_REMOTE_CACHE_UPDATE);
|
||||
if (ignoreRemoteCacheUpdate != null && ignoreRemoteCacheUpdate) {
|
||||
return SessionUpdateTask.CrossDCMessageStatus.NOT_NEEDED;
|
||||
}
|
||||
|
||||
Integer lsrr = sessionWrapper.getLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE);
|
||||
if (lsrr == null) {
|
||||
logger.debugf("Not available lsrr note on user session %s.", sessionWrapper.getEntity().getId());
|
||||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||
}
|
||||
|
||||
int idleTimeout = offline ? realm.getOfflineSessionIdleTimeout() : realm.getSsoSessionIdleTimeout();
|
||||
|
||||
if (lsrr + (idleTimeout / 2) <= newLastSessionRefresh) {
|
||||
logger.debugf("We are going to write remotely. Remote last session refresh: %d, New last session refresh: %d", (int) lsrr, newLastSessionRefresh);
|
||||
return SessionUpdateTask.CrossDCMessageStatus.SYNC;
|
||||
}
|
||||
|
||||
logger.debugf("Skip writing last session refresh to the remoteCache. Session %s newLastSessionRefresh %d", sessionWrapper.getEntity().getId(), newLastSessionRefresh);
|
||||
|
||||
storeToUse.putLastSessionRefresh(kcSession, sessionWrapper.getEntity().getId(), realm.getId(), newLastSessionRefresh);
|
||||
|
||||
return SessionUpdateTask.CrossDCMessageStatus.NOT_NEEDED;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -79,9 +79,9 @@ public class LastSessionRefreshListener implements ClusterListener {
|
|||
KeycloakModelUtils.runJobInTransaction(sessionFactory, (kcSession) -> {
|
||||
|
||||
RealmModel realm = kcSession.realms().getRealm(realmId);
|
||||
UserSessionModel userSession = kcSession.sessions().getUserSession(realm, sessionId);
|
||||
UserSessionModel userSession = offline ? kcSession.sessions().getOfflineUserSession(realm, sessionId) : kcSession.sessions().getUserSession(realm, sessionId);
|
||||
if (userSession == null) {
|
||||
logger.debugf("User session %s not available on node %s", sessionId, myAddress);
|
||||
logger.debugf("User session '%s' not available on node '%s' offline '%b'", sessionId, myAddress, offline);
|
||||
} else {
|
||||
// Update just if lastSessionRefresh from event is bigger than ours
|
||||
if (lastSessionRefresh > userSession.getLastSessionRefresh()) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.common.util.Time;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
|
||||
/**
|
||||
|
@ -30,15 +31,19 @@ import org.keycloak.timer.TimerProvider;
|
|||
*/
|
||||
public class LastSessionRefreshStoreFactory {
|
||||
|
||||
// Timer interval. The store will be checked every 5 seconds whether the message with stored lastSessionRefreshes
|
||||
// Timer interval. The store will be checked every 5 seconds whether the message with stored lastSessionRefreshes should be sent
|
||||
public static final long DEFAULT_TIMER_INTERVAL_MS = 5000;
|
||||
|
||||
// Max interval between messages. It means that when message is sent to second DC, then another message will be sent at least after 60 seconds.
|
||||
public static final int DEFAULT_MAX_INTERVAL_BETWEEN_MESSAGES_SECONDS = 60;
|
||||
public static final int DEFAULT_MAX_INTERVAL_BETWEEN_MESSAGES_SECONDS = SessionTimeoutHelper.PERIODIC_TASK_INTERVAL_SECONDS;
|
||||
|
||||
// Max count of lastSessionRefreshes. It count of lastSessionRefreshes reach this value, the message is sent to second DC
|
||||
// Max count of lastSessionRefreshes. If count of lastSessionRefreshes reach this value, the message is sent to second DC
|
||||
public static final int DEFAULT_MAX_COUNT = 100;
|
||||
|
||||
// Name of periodic tasks to send events to the other DCs
|
||||
public static final String LSR_PERIODIC_TASK_NAME = "lastSessionRefreshes";
|
||||
public static final String LSR_OFFLINE_PERIODIC_TASK_NAME = "lastSessionRefreshes-offline";
|
||||
|
||||
|
||||
public LastSessionRefreshStore createAndInit(KeycloakSession kcSession, Cache<String, SessionEntityWrapper<UserSessionEntity>> cache, boolean offline) {
|
||||
return createAndInit(kcSession, cache, DEFAULT_TIMER_INTERVAL_MS, DEFAULT_MAX_INTERVAL_BETWEEN_MESSAGES_SECONDS, DEFAULT_MAX_COUNT, offline);
|
||||
|
@ -46,7 +51,7 @@ public class LastSessionRefreshStoreFactory {
|
|||
|
||||
|
||||
public LastSessionRefreshStore createAndInit(KeycloakSession kcSession, Cache<String, SessionEntityWrapper<UserSessionEntity>> cache, long timerIntervalMs, int maxIntervalBetweenMessagesSeconds, int maxCount, boolean offline) {
|
||||
String eventKey = offline ? "lastSessionRefreshes-offline" : "lastSessionRefreshes";
|
||||
String eventKey = offline ? LSR_OFFLINE_PERIODIC_TASK_NAME : LSR_PERIODIC_TASK_NAME;
|
||||
LastSessionRefreshStore store = createStoreInstance(maxIntervalBetweenMessagesSeconds, maxCount, eventKey);
|
||||
|
||||
// Register listener
|
||||
|
|
|
@ -27,6 +27,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import org.infinispan.commons.marshall.Externalizer;
|
||||
import org.infinispan.commons.marshall.MarshallUtil;
|
||||
import org.infinispan.commons.marshall.SerializeWith;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -37,6 +39,11 @@ import java.util.UUID;
|
|||
@SerializeWith(AuthenticatedClientSessionEntity.ExternalizerImpl.class)
|
||||
public class AuthenticatedClientSessionEntity extends SessionEntity {
|
||||
|
||||
public static final Logger logger = Logger.getLogger(AuthenticatedClientSessionEntity.class);
|
||||
|
||||
// Metadata attribute, which contains the last timestamp available on remoteCache. Used in decide whether we need to write to remoteCache (DC) or not
|
||||
public static final String LAST_TIMESTAMP_REMOTE = "lstr";
|
||||
|
||||
private String authMethod;
|
||||
private String redirectUri;
|
||||
private volatile int timestamp;
|
||||
|
@ -157,6 +164,31 @@ public class AuthenticatedClientSessionEntity extends SessionEntity {
|
|||
return id != null ? id.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionEntityWrapper mergeRemoteEntityWithLocalEntity(SessionEntityWrapper localEntityWrapper) {
|
||||
int timestampRemote = getTimestamp();
|
||||
|
||||
SessionEntityWrapper entityWrapper;
|
||||
if (localEntityWrapper == null) {
|
||||
entityWrapper = new SessionEntityWrapper<>(this);
|
||||
} else {
|
||||
AuthenticatedClientSessionEntity localClientSession = (AuthenticatedClientSessionEntity) localEntityWrapper.getEntity();
|
||||
|
||||
// local timestamp should always contain the bigger
|
||||
if (timestampRemote < localClientSession.getTimestamp()) {
|
||||
setTimestamp(localClientSession.getTimestamp());
|
||||
}
|
||||
|
||||
entityWrapper = new SessionEntityWrapper<>(localEntityWrapper.getLocalMetadata(), this);
|
||||
}
|
||||
|
||||
entityWrapper.putLocalMetadataNoteInt(LAST_TIMESTAMP_REMOTE, timestampRemote);
|
||||
|
||||
logger.debugf("Updating client session entity %s. timestamp=%d, timestampRemote=%d", getId(), getTimestamp(), timestampRemote);
|
||||
|
||||
return entityWrapper;
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<AuthenticatedClientSessionEntity> {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -219,7 +219,7 @@ public class UserSessionEntity extends SessionEntity {
|
|||
|
||||
entityWrapper.putLocalMetadataNoteInt(LAST_SESSION_REFRESH_REMOTE, lsrRemote);
|
||||
|
||||
logger.debugf("Updating session entity. lastSessionRefresh=%d, lastSessionRefreshRemote=%d", getLastSessionRefresh(), lsrRemote);
|
||||
logger.debugf("Updating session entity '%s'. lastSessionRefresh=%d, lastSessionRefreshRemote=%d", getId(), getLastSessionRefresh(), lsrRemote);
|
||||
|
||||
return entityWrapper;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.remotestore;
|
|||
|
||||
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.common.util.Time;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -34,9 +33,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
|
||||
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -78,8 +75,8 @@ public class RemoteCacheInvoker {
|
|||
|
||||
long loadedMaxIdleTimeMs = context.maxIdleTimeLoader.getMaxIdleTimeMs(realm);
|
||||
|
||||
// Double the timeout to ensure that entry won't expire on remoteCache in case that write of some entities to remoteCache is postponed (eg. userSession.lastSessionRefresh)
|
||||
final long maxIdleTimeMs = loadedMaxIdleTimeMs * 2;
|
||||
// Increase the timeout to ensure that entry won't expire on remoteCache in case that write of some entities to remoteCache is postponed (eg. userSession.lastSessionRefresh)
|
||||
final long maxIdleTimeMs = loadedMaxIdleTimeMs + 1800000;
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Running task '%s' on remote cache '%s' . Key is '%s'", operation, cacheName, key);
|
||||
|
@ -115,7 +112,6 @@ public class RemoteCacheInvoker {
|
|||
remoteCache.put(key, sessionWrapper.forTransport(), task.getLifespanMs(), TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
||||
break;
|
||||
case ADD_IF_ABSENT:
|
||||
final int currentTime = Time.currentTime();
|
||||
SessionEntityWrapper<V> existing = remoteCache
|
||||
.withFlags(Flag.FORCE_RETURN_VALUE)
|
||||
.putIfAbsent(key, sessionWrapper.forTransport(), -1, TimeUnit.MILLISECONDS, maxIdleMs, TimeUnit.MILLISECONDS);
|
||||
|
@ -123,8 +119,6 @@ public class RemoteCacheInvoker {
|
|||
logger.debugf("Existing entity in remote cache for key: %s . Will update it", key);
|
||||
|
||||
replace(remoteCache, task.getLifespanMs(), maxIdleMs, key, task);
|
||||
} else {
|
||||
sessionWrapper.putLocalMetadataNoteInt(UserSessionEntity.LAST_SESSION_REFRESH_REMOTE, currentTime);
|
||||
}
|
||||
break;
|
||||
case REPLACE:
|
||||
|
|
|
@ -76,6 +76,7 @@ public class ConcurrencyJDGSessionsCacheTest {
|
|||
|
||||
private static final UUID CLIENT_1_UUID = UUID.randomUUID();
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache1 = createManager(1).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
|
||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache2 = createManager(2).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
|
||||
|
@ -187,7 +188,6 @@ public class ConcurrencyJDGSessionsCacheTest {
|
|||
", successfulListenerWrites: " + successfulListenerWrites.get() + ", successfulListenerWrites2: " + successfulListenerWrites2.get() +
|
||||
", failedReplaceCounter: " + failedReplaceCounter.get() + ", failedReplaceCounter2: " + failedReplaceCounter2.get());
|
||||
|
||||
|
||||
System.out.println("remoteCache1.notes: " + ((UserSessionEntity) remoteCache1.get("123")).getNotes().size() );
|
||||
System.out.println("remoteCache2.notes: " + ((UserSessionEntity) remoteCache2.get("123")).getNotes().size() );
|
||||
|
||||
|
|
|
@ -156,12 +156,13 @@ public class InfinispanKeyStorageProviderTest {
|
|||
|
||||
protected Cache<String, PublicKeysEntry> getKeysCache() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(true);
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(true).enabled(true);
|
||||
|
||||
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
|
||||
|
||||
ConfigurationBuilder cb = new ConfigurationBuilder();
|
||||
cb.eviction().strategy(EvictionStrategy.LRU).type(EvictionType.COUNT).size(InfinispanConnectionProvider.KEYS_CACHE_DEFAULT_MAX);
|
||||
cb.jmxStatistics().enabled(true);
|
||||
Configuration cfg = cb.build();
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.KEYS_CACHE_NAME, cfg);
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.utils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SessionTimeoutHelper {
|
||||
|
||||
|
||||
/**
|
||||
* Interval specifies maximum time, for which the "userSession.lastSessionRefresh" may contain stale value.
|
||||
*
|
||||
* For example, if there are 2 datacenters and sessionRefresh will happen on DC1, then the message about the updated lastSessionRefresh may
|
||||
* be sent to the DC2 later (EG. Some periodic thread will send the updated lastSessionRefresh times in batches with 60 seconds delay).
|
||||
*/
|
||||
public static final int PERIODIC_TASK_INTERVAL_SECONDS = 60;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum time difference, which will be still tolerated when checking userSession idle timeout.
|
||||
*
|
||||
* For example, if there are 2 datacenters and sessionRefresh happened on DC1, then we still want to tolerate some timeout on DC2 due the
|
||||
* fact that lastSessionRefresh of current userSession may be updated later from DC1.
|
||||
*
|
||||
* See {@link #PERIODIC_TASK_INTERVAL_SECONDS}
|
||||
*/
|
||||
public static final int IDLE_TIMEOUT_WINDOW_SECONDS = 120;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum time difference, which will be still tolerated when checking userSession idle timeout with periodic cleaner threads.
|
||||
*
|
||||
* Just the sessions, with the timeout bigger than this value are considered really time-outed and can be garbage-collected (Considering the cross-dc
|
||||
* environment and the fact that some session updates on different DC can be postponed and seen on current DC with some delay).
|
||||
*
|
||||
* See {@link #PERIODIC_TASK_INTERVAL_SECONDS} and {@link #IDLE_TIMEOUT_WINDOW_SECONDS}
|
||||
*/
|
||||
public static final int PERIODIC_CLEANER_IDLE_TIMEOUT_WINDOW_SECONDS = 180;
|
||||
}
|
|
@ -28,6 +28,21 @@ public interface TimerProvider extends Provider {
|
|||
|
||||
public void scheduleTask(ScheduledTask scheduledTask, long intervalMillis, String taskName);
|
||||
|
||||
public void cancelTask(String taskName);
|
||||
|
||||
/**
|
||||
* Cancel task and return the details about it, so it can be eventually restored later
|
||||
*
|
||||
* @param taskName
|
||||
* @return existing task or null if task under this name doesn't exist
|
||||
*/
|
||||
public TimerTaskContext cancelTask(String taskName);
|
||||
|
||||
|
||||
interface TimerTaskContext {
|
||||
|
||||
Runnable getRunnable();
|
||||
|
||||
long getIntervalMillis();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ public class TokenManager {
|
|||
if (userSession != null) {
|
||||
|
||||
// Revoke timeouted offline userSession
|
||||
if (userSession.getLastSessionRefresh() < Time.currentTime() - realm.getOfflineSessionIdleTimeout()) {
|
||||
if (!AuthenticationManager.isOfflineSessionValid(realm, userSession)) {
|
||||
sessionManager.revokeOfflineUserSession(userSession);
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Offline session not active", "Offline session not active");
|
||||
}
|
||||
|
@ -282,9 +282,8 @@ public class TokenManager {
|
|||
clusterStartupTime != validation.clientSession.getTimestamp()) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||
}
|
||||
}
|
||||
|
||||
if (realm.isRevokeRefreshToken()) {
|
||||
|
||||
if (!refreshToken.getId().equals(validation.clientSession.getCurrentRefreshToken())) {
|
||||
validation.clientSession.setCurrentRefreshToken(refreshToken.getId());
|
||||
validation.clientSession.setCurrentRefreshTokenUseCount(0);
|
||||
|
@ -296,8 +295,6 @@ public class TokenManager {
|
|||
"Maximum allowed refresh token reuse exceeded");
|
||||
}
|
||||
validation.clientSession.setCurrentRefreshTokenUseCount(currentCount + 1);
|
||||
} else {
|
||||
validation.clientSession.setCurrentRefreshToken(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.jose.jws.AlgorithmType;
|
|||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocol.Error;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
|
@ -107,7 +108,11 @@ public class AuthenticationManager {
|
|||
}
|
||||
int currentTime = Time.currentTime();
|
||||
int max = userSession.getStarted() + realm.getSsoSessionMaxLifespan();
|
||||
return userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
|
||||
|
||||
// Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
|
||||
int maxIdle = realm.getSsoSessionIdleTimeout() + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
|
||||
|
||||
return userSession.getLastSessionRefresh() + maxIdle > currentTime && max > currentTime;
|
||||
}
|
||||
|
||||
public static boolean isOfflineSessionValid(RealmModel realm, UserSessionModel userSession) {
|
||||
|
@ -116,7 +121,11 @@ public class AuthenticationManager {
|
|||
return false;
|
||||
}
|
||||
int currentTime = Time.currentTime();
|
||||
return userSession.getLastSessionRefresh() + realm.getOfflineSessionIdleTimeout() > currentTime;
|
||||
|
||||
// Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
|
||||
int maxIdle = realm.getOfflineSessionIdleTimeout() + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
|
||||
|
||||
return userSession.getLastSessionRefresh() + maxIdle > currentTime;
|
||||
}
|
||||
|
||||
public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
|
||||
|
|
|
@ -334,7 +334,7 @@ public class KeycloakApplication extends Application {
|
|||
TimerProvider timer = session.getProvider(TimerProvider.class);
|
||||
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
|
||||
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens");
|
||||
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
|
||||
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, ClearExpiredUserSessions.TASK_NAME);
|
||||
new UserStorageSyncManager().bootstrapPeriodic(sessionFactory, timer);
|
||||
} finally {
|
||||
session.close();
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.keycloak.timer.ScheduledTask;
|
|||
*/
|
||||
public class ClearExpiredUserSessions implements ScheduledTask {
|
||||
|
||||
public static final String TASK_NAME = "ClearExpiredUserSessions";
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
UserSessionProvider sessions = session.sessions();
|
||||
|
|
|
@ -52,10 +52,11 @@ public class BasicTimerProvider implements TimerProvider {
|
|||
}
|
||||
};
|
||||
|
||||
TimerTask existingTask = factory.putTask(taskName, task);
|
||||
TimerTaskContextImpl taskContext = new TimerTaskContextImpl(runnable, task, intervalMillis);
|
||||
TimerTaskContextImpl existingTask = factory.putTask(taskName, taskContext);
|
||||
if (existingTask != null) {
|
||||
logger.debugf("Existing timer task '%s' found. Cancelling it", taskName);
|
||||
existingTask.cancel();
|
||||
existingTask.timerTask.cancel();
|
||||
}
|
||||
|
||||
logger.debugf("Starting task '%s' with interval '%d'", taskName, intervalMillis);
|
||||
|
@ -69,12 +70,14 @@ public class BasicTimerProvider implements TimerProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void cancelTask(String taskName) {
|
||||
TimerTask existingTask = factory.removeTask(taskName);
|
||||
public TimerTaskContext cancelTask(String taskName) {
|
||||
TimerTaskContextImpl existingTask = factory.removeTask(taskName);
|
||||
if (existingTask != null) {
|
||||
logger.debugf("Cancelling task '%s'", taskName);
|
||||
existingTask.cancel();
|
||||
existingTask.timerTask.cancel();
|
||||
}
|
||||
|
||||
return existingTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -35,7 +35,7 @@ public class BasicTimerProviderFactory implements TimerProviderFactory {
|
|||
|
||||
private Timer timer;
|
||||
|
||||
private ConcurrentMap<String, TimerTask> scheduledTasks = new ConcurrentHashMap<String, TimerTask>();
|
||||
private ConcurrentMap<String, TimerTaskContextImpl> scheduledTasks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public TimerProvider create(KeycloakSession session) {
|
||||
|
@ -63,11 +63,11 @@ public class BasicTimerProviderFactory implements TimerProviderFactory {
|
|||
return "basic";
|
||||
}
|
||||
|
||||
protected TimerTask putTask(String taskName, TimerTask task) {
|
||||
protected TimerTaskContextImpl putTask(String taskName, TimerTaskContextImpl task) {
|
||||
return scheduledTasks.put(taskName, task);
|
||||
}
|
||||
|
||||
protected TimerTask removeTask(String taskName) {
|
||||
protected TimerTaskContextImpl removeTask(String taskName) {
|
||||
return scheduledTasks.remove(taskName);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.timer.basic;
|
||||
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class TimerTaskContextImpl implements TimerProvider.TimerTaskContext {
|
||||
|
||||
private final Runnable runnable;
|
||||
final TimerTask timerTask;
|
||||
private final long intervalMillis;
|
||||
|
||||
public TimerTaskContextImpl(Runnable runnable, TimerTask timerTask, long intervalMillis) {
|
||||
this.runnable = runnable;
|
||||
this.timerTask = timerTask;
|
||||
this.intervalMillis = intervalMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getRunnable() {
|
||||
return runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIntervalMillis() {
|
||||
return intervalMillis;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStoreFactory;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||
|
@ -49,6 +50,7 @@ import org.keycloak.representations.idm.EventRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.testsuite.components.TestProvider;
|
||||
import org.keycloak.testsuite.components.TestProviderFactory;
|
||||
|
@ -63,6 +65,7 @@ import org.keycloak.testsuite.runonserver.ModuleUtil;
|
|||
import org.keycloak.testsuite.runonserver.FetchOnServer;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServer;
|
||||
import org.keycloak.testsuite.runonserver.SerializationUtil;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
|
@ -83,21 +86,24 @@ import java.util.Date;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class TestingResourceProvider implements RealmResourceProvider {
|
||||
|
||||
private KeycloakSession session;
|
||||
private final KeycloakSession session;
|
||||
private final Map<String, TimerProvider.TimerTaskContext> suspendedTimerTasks;
|
||||
|
||||
@Override
|
||||
public Object getResource() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestingResourceProvider(KeycloakSession session) {
|
||||
public TestingResourceProvider(KeycloakSession session, Map<String, TimerProvider.TimerTaskContext> suspendedTimerTasks) {
|
||||
this.session = session;
|
||||
this.suspendedTimerTasks = suspendedTimerTasks;
|
||||
}
|
||||
|
||||
@POST
|
||||
|
@ -134,9 +140,9 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
}
|
||||
|
||||
@GET
|
||||
@Path("/get-user-session")
|
||||
@Path("/get-last-session-refresh")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Integer getLastSessionRefresh(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
|
||||
public Integer getLastSessionRefresh(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId, @QueryParam("offline") boolean offline) {
|
||||
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(name);
|
||||
|
@ -144,7 +150,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
throw new NotFoundException("Realm not found");
|
||||
}
|
||||
|
||||
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
|
||||
UserSessionModel sessionModel = offline ? session.sessions().getOfflineUserSession(realm, sessionId) : session.sessions().getUserSession(realm, sessionId);
|
||||
if (sessionModel == null) {
|
||||
throw new NotFoundException("Session not found");
|
||||
}
|
||||
|
@ -673,6 +679,41 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
System.setProperty("java.security.krb5.conf", krb5ConfFile);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/suspend-periodic-tasks")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response suspendPeriodicTasks() {
|
||||
suspendTask(ClearExpiredUserSessions.TASK_NAME);
|
||||
suspendTask(LastSessionRefreshStoreFactory.LSR_PERIODIC_TASK_NAME);
|
||||
suspendTask(LastSessionRefreshStoreFactory.LSR_OFFLINE_PERIODIC_TASK_NAME);
|
||||
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
private void suspendTask(String taskName) {
|
||||
TimerProvider.TimerTaskContext taskContext = session.getProvider(TimerProvider.class).cancelTask(taskName);
|
||||
|
||||
if (taskContext != null) {
|
||||
suspendedTimerTasks.put(taskName, taskContext);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/restore-periodic-tasks")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response restorePeriodicTasks() {
|
||||
TimerProvider timer = session.getProvider(TimerProvider.class);
|
||||
|
||||
for (Map.Entry<String, TimerProvider.TimerTaskContext> task : suspendedTimerTasks.entrySet()) {
|
||||
timer.schedule(task.getValue().getRunnable(), task.getValue().getIntervalMillis(), task.getKey());
|
||||
}
|
||||
|
||||
suspendedTimerTasks.clear();
|
||||
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
|
||||
@POST
|
||||
@Path("/run-on-server")
|
||||
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||
|
|
|
@ -17,20 +17,26 @@
|
|||
|
||||
package org.keycloak.testsuite.rest;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.resource.RealmResourceProviderFactory;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class TestingResourceProviderFactory implements RealmResourceProviderFactory {
|
||||
|
||||
private Map<String, TimerProvider.TimerTaskContext> suspendedTimerTasks = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public RealmResourceProvider create(KeycloakSession session) {
|
||||
return new TestingResourceProvider(session);
|
||||
return new TestingResourceProvider(session, suspendedTimerTasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,6 +34,8 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -180,9 +182,9 @@ public interface TestingResource {
|
|||
void removeUserSessions(@QueryParam("realm") final String realm);
|
||||
|
||||
@GET
|
||||
@Path("/get-user-session")
|
||||
@Path("/get-last-session-refresh")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Integer getLastSessionRefresh(@QueryParam("realm") final String realm, @QueryParam("session") final String sessionId);
|
||||
Integer getLastSessionRefresh(@QueryParam("realm") final String realm, @QueryParam("session") final String sessionId, @QueryParam("offline") boolean offline);
|
||||
|
||||
@POST
|
||||
@Path("/remove-expired")
|
||||
|
@ -254,6 +256,17 @@ public interface TestingResource {
|
|||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void setKrb5ConfFile(@QueryParam("krb5-conf-file") String krb5ConfFile);
|
||||
|
||||
@POST
|
||||
@Path("/suspend-periodic-tasks")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response suspendPeriodicTasks();
|
||||
|
||||
|
||||
@POST
|
||||
@Path("/restore-periodic-tasks")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Response restorePeriodicTasks();
|
||||
|
||||
@POST
|
||||
@Path("/run-on-server")
|
||||
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.common.util.Time;
|
|||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -315,7 +316,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
|
|||
demoRealmRep.setSsoSessionIdleTimeout(1);
|
||||
testRealmResource().update(demoRealmRep);
|
||||
|
||||
pause(2000);
|
||||
// Needs to add some additional time due the tolerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
|
||||
setAdapterAndServerTimeOffset(2 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
|
||||
productPortal.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
|
@ -343,7 +345,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
|
|||
demoRealmRep.setSsoSessionIdleTimeout(1);
|
||||
testRealmResource().update(demoRealmRep);
|
||||
|
||||
pause(2000);
|
||||
// Needs to add some additional time due the tolerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
|
||||
setAdapterAndServerTimeOffset(2 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
|
||||
productPortal.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
|
|
|
@ -101,6 +101,23 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest
|
|||
|
||||
}
|
||||
|
||||
// Disable periodic tasks in cross-dc tests. It's needed to have some scenarios more stable.
|
||||
@Before
|
||||
public void suspendPeriodicTasks() {
|
||||
backendTestingClients.values().stream().forEach((KeycloakTestingClient testingClient) -> {
|
||||
testingClient.testing().suspendPeriodicTasks();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void restorePeriodicTasks() {
|
||||
backendTestingClients.values().stream().forEach((KeycloakTestingClient testingClient) -> {
|
||||
testingClient.testing().restorePeriodicTasks();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void importTestRealms() {
|
||||
enableOnlyFirstNodeInFirstDc();
|
||||
|
|
|
@ -19,14 +19,29 @@ package org.keycloak.testsuite.crossdc;
|
|||
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.container.test.api.TargetsContainer;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||
import org.keycloak.testsuite.rest.representation.RemoteCacheStats;
|
||||
import org.keycloak.testsuite.arquillian.InfinispanStatistics;
|
||||
import org.keycloak.testsuite.arquillian.annotation.JmxInfinispanCacheStatistics;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
/**
|
||||
|
@ -34,8 +49,40 @@ import org.keycloak.testsuite.util.OAuthClient;
|
|||
*/
|
||||
public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
|
||||
|
||||
@Deployment(name = "dc0")
|
||||
@TargetsContainer(QUALIFIER_AUTH_SERVER_DC_0_NODE_1)
|
||||
public static WebArchive deployDC0() {
|
||||
return RunOnServerDeployment.create(
|
||||
LastSessionRefreshCrossDCTest.class,
|
||||
AbstractAdminCrossDCTest.class,
|
||||
AbstractCrossDCTest.class,
|
||||
AbstractTestRealmKeycloakTest.class,
|
||||
KeycloakTestingClient.class,
|
||||
InfinispanStatistics.class
|
||||
);
|
||||
}
|
||||
|
||||
@Deployment(name = "dc1")
|
||||
@TargetsContainer(QUALIFIER_AUTH_SERVER_DC_1_NODE_1)
|
||||
public static WebArchive deployDC1() {
|
||||
return RunOnServerDeployment.create(
|
||||
LastSessionRefreshCrossDCTest.class,
|
||||
AbstractAdminCrossDCTest.class,
|
||||
AbstractCrossDCTest.class,
|
||||
AbstractTestRealmKeycloakTest.class,
|
||||
KeycloakTestingClient.class,
|
||||
InfinispanStatistics.class
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRevokeRefreshToken() {
|
||||
public void testRevokeRefreshToken(@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc1Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc2Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc1Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc2Stats
|
||||
|
||||
) {
|
||||
// Enable revokeRefreshToken
|
||||
RealmRepresentation realmRep = testRealm().toRepresentation();
|
||||
realmRep.setRevokeRefreshToken(true);
|
||||
|
@ -44,6 +91,19 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
// Enable second DC
|
||||
enableDcOnLoadBalancer(DC.SECOND);
|
||||
|
||||
sessionCacheDc1Stats.reset();
|
||||
sessionCacheDc2Stats.reset();
|
||||
clientSessionCacheDc1Stats.reset();
|
||||
clientSessionCacheDc2Stats.reset();
|
||||
|
||||
// Get statistics
|
||||
AtomicLong sessionStoresDc1 = new AtomicLong(getStores(sessionCacheDc1Stats));
|
||||
AtomicLong sessionStoresDc2 = new AtomicLong(getStores(sessionCacheDc2Stats));
|
||||
AtomicLong clientSessionStoresDc1 = new AtomicLong(getStores(clientSessionCacheDc1Stats));
|
||||
AtomicLong clientSessionStoresDc2 = new AtomicLong(getStores(clientSessionCacheDc2Stats));
|
||||
AtomicInteger lsrDc1 = new AtomicInteger(-1);
|
||||
AtomicInteger lsrDc2 = new AtomicInteger(-1);
|
||||
|
||||
// Login
|
||||
OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
|
||||
String code = response1.getCode();
|
||||
|
@ -53,44 +113,33 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
String refreshToken1 = tokenResponse.getRefreshToken();
|
||||
|
||||
|
||||
// Get statistics
|
||||
int lsr00 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId);
|
||||
int lsr10 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId);
|
||||
int lsrr0 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId);
|
||||
log.infof("lsr00: %d, lsr10: %d, lsrr0: %d", lsr00, lsr10, lsrr0);
|
||||
|
||||
Assert.assertEquals(lsr00, lsr10);
|
||||
Assert.assertEquals(lsr00, lsrr0);
|
||||
// Assert statistics - sessions created on both DCs and created on remoteCaches too
|
||||
assertStatistics("After session created", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, true, false);
|
||||
|
||||
|
||||
// Set time offset to some point in future. TODO This won't be needed once we have single-use cache based solution for refresh tokens
|
||||
setTimeOffset(10);
|
||||
|
||||
// refresh token on DC0
|
||||
// refresh token on DC1
|
||||
disableDcOnLoadBalancer(DC.SECOND);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken2 = tokenResponse.getRefreshToken();
|
||||
|
||||
// Assert times changed on DC0, DC1 and remoteCache
|
||||
Retry.execute(() -> {
|
||||
int lsr01 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId);
|
||||
int lsr11 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId);
|
||||
int lsrr1 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId);
|
||||
log.infof("lsr01: %d, lsr11: %d, lsrr1: %d", lsr01, lsr11, lsrr1);
|
||||
// Assert statistics - sessions updated on both DCs and on remoteCaches too
|
||||
assertStatistics("After time offset 10", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, true, false);
|
||||
|
||||
Assert.assertEquals(lsr01, lsr11);
|
||||
Assert.assertEquals(lsr01, lsrr1);
|
||||
Assert.assertTrue(lsr01 > lsr00);
|
||||
}, 50, 50);
|
||||
|
||||
// try refresh with old token on DC1. It should fail.
|
||||
// try refresh with old token on DC2. It should fail.
|
||||
disableDcOnLoadBalancer(DC.FIRST);
|
||||
enableDcOnLoadBalancer(DC.SECOND);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
Assert.assertNull("Expecting no access token present", tokenResponse.getAccessToken());
|
||||
Assert.assertNotNull(tokenResponse.getError());
|
||||
|
||||
// try refresh with new token on DC1. It should pass.
|
||||
// try refresh with new token on DC2. It should pass.
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken2, "password");
|
||||
Assert.assertNotNull(tokenResponse.getAccessToken());
|
||||
Assert.assertNull(tokenResponse.getError());
|
||||
|
@ -103,12 +152,35 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
|
||||
@Test
|
||||
public void testLastSessionRefreshUpdate() {
|
||||
// Disable DC1 on loadbalancer
|
||||
public void testLastSessionRefreshUpdate(@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc1Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc2Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc1Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc2Stats
|
||||
|
||||
) {
|
||||
|
||||
// TODO:mposolda Disable periodic cleaner now on all Keycloak nodes. Make sure it's re-enabled after finish
|
||||
|
||||
// Ensure to remove all current sessions and offline sessions
|
||||
setTimeOffset(10000000);
|
||||
getTestingClientForStartedNodeInDc(0).testing("test").removeExpired("test");
|
||||
setTimeOffset(0);
|
||||
|
||||
sessionCacheDc1Stats.reset();
|
||||
sessionCacheDc2Stats.reset();
|
||||
clientSessionCacheDc1Stats.reset();
|
||||
clientSessionCacheDc2Stats.reset();
|
||||
|
||||
// Disable DC2 on loadbalancer
|
||||
disableDcOnLoadBalancer(DC.SECOND);
|
||||
|
||||
// Get statistics
|
||||
int stores0 = getRemoteCacheStats(0).getGlobalStores();
|
||||
AtomicLong sessionStoresDc1 = new AtomicLong(getStores(sessionCacheDc1Stats));
|
||||
AtomicLong sessionStoresDc2 = new AtomicLong(getStores(sessionCacheDc2Stats));
|
||||
AtomicLong clientSessionStoresDc1 = new AtomicLong(getStores(clientSessionCacheDc1Stats));
|
||||
AtomicLong clientSessionStoresDc2 = new AtomicLong(getStores(clientSessionCacheDc2Stats));
|
||||
AtomicInteger lsrDc1 = new AtomicInteger(-1);
|
||||
AtomicInteger lsrDc2 = new AtomicInteger(-1);
|
||||
|
||||
// Login
|
||||
OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
|
||||
|
@ -118,121 +190,264 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
String sessionId = oauth.verifyToken(tokenResponse.getAccessToken()).getSessionState();
|
||||
String refreshToken1 = tokenResponse.getRefreshToken();
|
||||
|
||||
// Assert statistics - sessions created on both DCs and created on remoteCaches too
|
||||
assertStatistics("After session created", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, true, false);
|
||||
|
||||
// Get statistics
|
||||
this.suiteContext.getDcAuthServerBackendsInfo().get(0).stream()
|
||||
.filter(ContainerInfo::isStarted).findFirst().get();
|
||||
|
||||
AtomicInteger stores1 = new AtomicInteger(-1);
|
||||
Retry.execute(() -> {
|
||||
stores1.set(getRemoteCacheStats(0).getGlobalStores());
|
||||
log.infof("stores0=%d, stores1=%d", stores0, stores1.get());
|
||||
Assert.assertTrue(stores1.get() > stores0);
|
||||
}, 50, 50);
|
||||
|
||||
int lsr00 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId);
|
||||
int lsr10 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId);
|
||||
Assert.assertEquals(lsr00, lsr10);
|
||||
|
||||
// Set time offset to some point in future.
|
||||
setTimeOffset(10);
|
||||
|
||||
// refresh token on DC0
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken2 = tokenResponse.getRefreshToken();
|
||||
|
||||
// assert that hotrod statistics were NOT updated
|
||||
AtomicInteger stores2 = new AtomicInteger(-1);
|
||||
|
||||
// TODO: not sure why stores2 < stores1 at first run. Probably should be replaced with JMX statistics
|
||||
Retry.execute(() -> {
|
||||
stores2.set(getRemoteCacheStats(0).getGlobalStores());
|
||||
log.infof("stores1=%d, stores2=%d", stores1.get(), stores2.get());
|
||||
Assert.assertEquals(stores1.get(), stores2.get());
|
||||
}, 50, 50);
|
||||
|
||||
// assert that lastSessionRefresh on DC0 updated, but on DC1 still the same
|
||||
AtomicInteger lsr01 = new AtomicInteger(-1);
|
||||
AtomicInteger lsr11 = new AtomicInteger(-1);
|
||||
Retry.execute(() -> {
|
||||
lsr01.set(getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId));
|
||||
lsr11.set(getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId));
|
||||
log.infof("lsr01: %d, lsr11: %d", lsr01.get(), lsr11.get());
|
||||
Assert.assertTrue(lsr01.get() > lsr00);
|
||||
}, 50, 100);
|
||||
Assert.assertEquals(lsr10, lsr11.get());
|
||||
|
||||
// assert that lastSessionRefresh still the same on remoteCache
|
||||
int lsrr1 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId);
|
||||
Assert.assertEquals(lsr00, lsrr1);
|
||||
log.infof("lsrr1: %d", lsrr1);
|
||||
|
||||
// setTimeOffset to greater value
|
||||
// Set time offset
|
||||
setTimeOffset(100);
|
||||
|
||||
// refresh token
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken3 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken3);
|
||||
|
||||
// assert that lastSessionRefresh on both DC0 and DC1 was updated, but on remoteCache still the same
|
||||
AtomicInteger lsr02 = new AtomicInteger(-1);
|
||||
AtomicInteger lsr12 = new AtomicInteger(-1);
|
||||
AtomicInteger lsrr2 = new AtomicInteger(-1);
|
||||
AtomicInteger stores3 = new AtomicInteger(-1);
|
||||
Retry.execute(() -> {
|
||||
lsr02.set(getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId));
|
||||
lsr12.set(getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId));
|
||||
log.infof("lsr02: %d, lsr12: %d", lsr02.get(), lsr12.get());
|
||||
Assert.assertEquals(lsr02.get(), lsr12.get());
|
||||
Assert.assertTrue(lsr02.get() > lsr01.get());
|
||||
Assert.assertTrue(lsr12.get() > lsr11.get());
|
||||
// Assert statistics - sessions updated on both DC1 and DC2. RemoteCaches not updated
|
||||
assertStatistics("After refresh at time 100", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, false, false);
|
||||
|
||||
lsrr2.set(getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId));
|
||||
log.infof("lsrr2: %d", lsrr2.get());
|
||||
Assert.assertEquals(lsrr1, lsrr2.get());
|
||||
|
||||
// assert that hotrod statistics were NOT updated on DC0
|
||||
stores3.set(getRemoteCacheStats(0).getGlobalStores());
|
||||
log.infof("stores2=%d, stores3=%d", stores2.get(), stores3.get());
|
||||
Assert.assertEquals(stores2.get(), stores3.get());
|
||||
}, 50, 100);
|
||||
// Set time offset
|
||||
setTimeOffset(110);
|
||||
|
||||
// Increase time offset even more
|
||||
setTimeOffset(1500);
|
||||
|
||||
// refresh token
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
Assert.assertNull("Error: " + tokenResponse.getError() + ", error description: " + tokenResponse.getErrorDescription(), tokenResponse.getError());
|
||||
Assert.assertNotNull(tokenResponse.getRefreshToken());
|
||||
String refreshToken2 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken2);
|
||||
|
||||
// assert that lastSessionRefresh updated everywhere including remoteCache
|
||||
AtomicInteger lsr03 = new AtomicInteger(-1);
|
||||
AtomicInteger lsr13 = new AtomicInteger(-1);
|
||||
AtomicInteger lsrr3 = new AtomicInteger(-1);
|
||||
AtomicInteger stores4 = new AtomicInteger(-1);
|
||||
Retry.execute(() -> {
|
||||
lsr03.set(getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId));
|
||||
lsr13.set(getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId));
|
||||
log.infof("lsr03: %d, lsr13: %d", lsr03.get(), lsr13.get());
|
||||
Assert.assertEquals(lsr03.get(), lsr13.get());
|
||||
Assert.assertTrue(lsr03.get() > lsr02.get());
|
||||
Assert.assertTrue(lsr13.get() > lsr12.get());
|
||||
// Assert statistics - sessions updated just on DC1.
|
||||
// Update of DC2 is postponed (It's just 10 seconds since last message). RemoteCaches not updated
|
||||
assertStatistics("After refresh at time 110", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, false, false, false);
|
||||
|
||||
lsrr3.set(getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId));
|
||||
log.infof("lsrr3: %d", lsrr3.get());
|
||||
Assert.assertTrue(lsrr3.get() > lsrr2.get());
|
||||
|
||||
// assert that hotrod statistics were NOT updated on DC0
|
||||
stores4.set(getRemoteCacheStats(0).getGlobalStores());
|
||||
log.infof("stores3=%d, stores4=%d", stores3.get(), stores4.get());
|
||||
Assert.assertTrue(stores4.get() > stores3.get());
|
||||
}, 50, 100);
|
||||
// 31 minutes after "100". Session should be still valid and not yet expired (RefreshToken will be invalid due the expiration on the JWT itself. Hence not testing refresh here)
|
||||
setTimeOffset(1960);
|
||||
|
||||
boolean sessionValid = getTestingClientForStartedNodeInDc(1).server("test").fetch((KeycloakSession session) -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
|
||||
return AuthenticationManager.isSessionValid(realm, userSession);
|
||||
}, Boolean.class);
|
||||
|
||||
Assert.assertTrue(sessionValid);
|
||||
|
||||
getTestingClientForStartedNodeInDc(1).testing("test").removeExpired("test");
|
||||
|
||||
// Assert statistics - nothing was updated. No refresh happened and nothing was cleared during "removeExpired"
|
||||
assertStatistics("After checking valid at time 1960", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, false, false, false, false);
|
||||
|
||||
|
||||
// 35 minutes after "100". Session not valid and will be expired by the cleaner
|
||||
setTimeOffset(2200);
|
||||
|
||||
sessionValid = getTestingClientForStartedNodeInDc(1).server("test").fetch((KeycloakSession session) -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
|
||||
return AuthenticationManager.isSessionValid(realm, userSession);
|
||||
}, Boolean.class);
|
||||
|
||||
Assert.assertFalse(sessionValid);
|
||||
|
||||
getTestingClientForStartedNodeInDc(1).testing("test").removeExpired("test");
|
||||
|
||||
// Session should be removed on both DCs
|
||||
try {
|
||||
getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId, false);
|
||||
Assert.fail("It wasn't expected to find the session " + sessionId);
|
||||
} catch (NotFoundException nfe) {
|
||||
// Expected
|
||||
}
|
||||
try {
|
||||
getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId, false);
|
||||
Assert.fail("It wasn't expected to find the session " + sessionId);
|
||||
} catch (NotFoundException nfe) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private RemoteCacheStats getRemoteCacheStats(int dcIndex) {
|
||||
return getTestingClientForStartedNodeInDc(dcIndex).testing("test")
|
||||
.cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME)
|
||||
.getRemoteCacheStats();
|
||||
@Test
|
||||
public void testOfflineSessionsLastSessionRefreshUpdate(@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc1Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics sessionCacheDc2Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc1Stats,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientSessionCacheDc2Stats
|
||||
|
||||
) throws Exception {
|
||||
|
||||
// TODO:mposolda Disable periodic cleaner now on all Keycloak nodes. Make sure it's re-enabled after finish
|
||||
|
||||
// Ensure to remove all current sessions and offline sessions
|
||||
setTimeOffset(10000000);
|
||||
getTestingClientForStartedNodeInDc(0).testing("test").removeExpired("test");
|
||||
setTimeOffset(0);
|
||||
|
||||
sessionCacheDc1Stats.reset();
|
||||
sessionCacheDc2Stats.reset();
|
||||
clientSessionCacheDc1Stats.reset();
|
||||
clientSessionCacheDc2Stats.reset();
|
||||
|
||||
// Disable DC2 on loadbalancer
|
||||
disableDcOnLoadBalancer(DC.SECOND);
|
||||
|
||||
// Get statistics
|
||||
AtomicLong sessionStoresDc1 = new AtomicLong(getStores(sessionCacheDc1Stats));
|
||||
AtomicLong sessionStoresDc2 = new AtomicLong(getStores(sessionCacheDc2Stats));
|
||||
AtomicLong clientSessionStoresDc1 = new AtomicLong(getStores(clientSessionCacheDc1Stats));
|
||||
AtomicLong clientSessionStoresDc2 = new AtomicLong(getStores(clientSessionCacheDc2Stats));
|
||||
AtomicInteger lsrDc1 = new AtomicInteger(-1);
|
||||
AtomicInteger lsrDc2 = new AtomicInteger(-1);
|
||||
|
||||
// Login
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
OAuthClient.AuthorizationEndpointResponse response1 = oauth.doLogin("test-user@localhost", "password");
|
||||
String code = response1.getCode();
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
Assert.assertNotNull(tokenResponse.getAccessToken());
|
||||
String sessionId = oauth.verifyToken(tokenResponse.getAccessToken()).getSessionState();
|
||||
String refreshToken1 = tokenResponse.getRefreshToken();
|
||||
|
||||
// Assert statistics - sessions created on both DCs and created on remoteCaches too
|
||||
assertStatistics("After session created", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, true, true);
|
||||
|
||||
|
||||
// Set time offset
|
||||
setTimeOffset(100);
|
||||
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken3 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken3);
|
||||
|
||||
// Assert statistics - sessions updated on both DC1 and DC2. RemoteCaches not updated
|
||||
assertStatistics("After refresh at time 100", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, false, true);
|
||||
|
||||
|
||||
|
||||
// Set time offset
|
||||
setTimeOffset(110);
|
||||
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken2 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken2);
|
||||
|
||||
// Assert statistics - sessions updated just on DC1.
|
||||
// Update of DC2 is postponed (It's just 10 seconds since last message). RemoteCaches not updated
|
||||
assertStatistics("After refresh at time 110", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, false, false, true);
|
||||
|
||||
|
||||
// Set time offset to 20 days
|
||||
setTimeOffset(1728000);
|
||||
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken4 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken4);
|
||||
|
||||
// Assert statistics - sessions updated on both DC1 and DC2. RemoteCaches updated as well.
|
||||
assertStatistics("After refresh at time 1728000", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, true, true);
|
||||
|
||||
// Set time offset to 30 days
|
||||
setTimeOffset(2592000);
|
||||
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken5 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken5);
|
||||
|
||||
// Assert statistics - sessions updated on both DC1 and DC2. RemoteCaches won't be updated now due it's just 10 days from the last remoteCache update
|
||||
assertStatistics("After refresh at time 2592000", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, false, true);
|
||||
|
||||
// Set time offset to 40 days
|
||||
setTimeOffset(3456000);
|
||||
|
||||
// refresh token on DC1
|
||||
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
|
||||
String refreshToken6 = tokenResponse.getRefreshToken();
|
||||
Assert.assertNotNull(refreshToken6);
|
||||
|
||||
// Assert statistics - sessions updated on both DC1 and DC2. RemoteCaches will be updated too due it's 20 days from the last remoteCache update
|
||||
assertStatistics("After refresh at time 3456000", sessionId, sessionCacheDc1Stats, sessionCacheDc2Stats, clientSessionCacheDc1Stats, clientSessionCacheDc2Stats,
|
||||
sessionStoresDc1, sessionStoresDc2, clientSessionStoresDc1, clientSessionStoresDc2,
|
||||
lsrDc1, lsrDc2, true, true, true, true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void assertStatistics(String messagePrefix, String sessionId,
|
||||
InfinispanStatistics sessionCacheDc1Stats, InfinispanStatistics sessionCacheDc2Stats, InfinispanStatistics clientSessionCacheDc1Stats, InfinispanStatistics clientSessionCacheDc2Stats,
|
||||
AtomicLong sessionStoresDc1, AtomicLong sessionStoresDc2, AtomicLong clientSessionStoresDc1, AtomicLong clientSessionStoresDc2,
|
||||
AtomicInteger lsrDc1, AtomicInteger lsrDc2,
|
||||
boolean expectedUpdatedLsrDc1, boolean expectedUpdatedLsrDc2, boolean expectedUpdatedRemoteCache, boolean offline) {
|
||||
Retry.execute(() -> {
|
||||
long newSessionStoresDc1 = getStores(sessionCacheDc1Stats);
|
||||
long newSessionStoresDc2 = getStores(sessionCacheDc2Stats);
|
||||
long newClientSessionStoresDc1 = getStores(clientSessionCacheDc1Stats);
|
||||
long newClientSessionStoresDc2 = getStores(clientSessionCacheDc2Stats);
|
||||
|
||||
int newLsrDc1 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId, offline);
|
||||
int newLsrDc2 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId, offline);
|
||||
|
||||
log.infof(messagePrefix + ": sessionStoresDc1: %d, sessionStoresDc2: %d, clientSessionStoresDc1: %d, clientSessionStoresDc2: %d, lsrDc1: %d, lsrDc2: %d",
|
||||
newSessionStoresDc1, newSessionStoresDc2, newClientSessionStoresDc1, newClientSessionStoresDc2, newLsrDc1, newLsrDc2);
|
||||
|
||||
// Check lastSessionRefresh updated on DC1
|
||||
if (expectedUpdatedLsrDc1) {
|
||||
Assert.assertThat(newLsrDc1, Matchers.greaterThan(lsrDc1.get()));
|
||||
} else {
|
||||
Assert.assertEquals(newLsrDc1, lsrDc1.get());
|
||||
}
|
||||
|
||||
// Check lastSessionRefresh updated on DC2
|
||||
if (expectedUpdatedLsrDc2) {
|
||||
Assert.assertThat(newLsrDc2, Matchers.greaterThan(lsrDc2.get()));
|
||||
} else {
|
||||
Assert.assertEquals(newLsrDc2, lsrDc2.get());
|
||||
}
|
||||
|
||||
// Check store statistics updated on JDG side
|
||||
if (expectedUpdatedRemoteCache) {
|
||||
Assert.assertThat(newSessionStoresDc1, Matchers.greaterThan(sessionStoresDc1.get()));
|
||||
Assert.assertThat(newSessionStoresDc2, Matchers.greaterThan(sessionStoresDc2.get()));
|
||||
Assert.assertThat(newClientSessionStoresDc1, Matchers.greaterThan(clientSessionStoresDc1.get()));
|
||||
Assert.assertThat(newClientSessionStoresDc2, Matchers.greaterThan(clientSessionStoresDc2.get()));
|
||||
} else {
|
||||
Assert.assertEquals(newSessionStoresDc1, sessionStoresDc1.get());
|
||||
Assert.assertEquals(newSessionStoresDc2, sessionStoresDc2.get());
|
||||
Assert.assertEquals(newClientSessionStoresDc1, clientSessionStoresDc1.get());
|
||||
Assert.assertEquals(newClientSessionStoresDc2, clientSessionStoresDc2.get());
|
||||
}
|
||||
|
||||
// Update counter references
|
||||
sessionStoresDc1.set(newSessionStoresDc1);
|
||||
sessionStoresDc2.set(newSessionStoresDc2);
|
||||
clientSessionStoresDc1.set(newClientSessionStoresDc1);
|
||||
clientSessionStoresDc2.set(newClientSessionStoresDc2);
|
||||
lsrDc1.set(newLsrDc1);
|
||||
lsrDc2.set(newLsrDc2);
|
||||
}, 50, 50);
|
||||
|
||||
}
|
||||
|
||||
private long getStores(InfinispanStatistics cacheStats) {
|
||||
return (long) cacheStats.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_STORES);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
private int sessions01;
|
||||
private int sessions02;
|
||||
private int clientSessions01;
|
||||
private int clientSessions02;
|
||||
private int remoteSessions01;
|
||||
private int remoteSessions02;
|
||||
|
||||
|
@ -102,12 +104,12 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
@Test
|
||||
public void testRealmRemoveSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientCacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientCacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientCacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientCacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
// log.infof("Sleeping!");
|
||||
// Thread.sleep(10000000);
|
||||
|
@ -118,13 +120,13 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
getAdminClient().realm(REALM_NAME).remove();
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches
|
||||
assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
// Return last used accessTokenResponse
|
||||
private List<OAuthClient.AccessTokenResponse> createInitialSessions(String cacheName, boolean offline, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics, boolean includeRemoteStats) throws Exception {
|
||||
private List<OAuthClient.AccessTokenResponse> createInitialSessions(String cacheName, String clientSessionsCacheName, boolean offline, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics, boolean includeRemoteStats) throws Exception {
|
||||
|
||||
// Enable second DC
|
||||
enableDcOnLoadBalancer(DC.SECOND);
|
||||
|
@ -132,9 +134,12 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
// Check sessions count before test
|
||||
sessions01 = getTestingClientForStartedNodeInDc(0).testing().cache(cacheName).size();
|
||||
sessions02 = getTestingClientForStartedNodeInDc(1).testing().cache(cacheName).size();
|
||||
clientSessions01 = getTestingClientForStartedNodeInDc(0).testing().cache(clientSessionsCacheName).size();
|
||||
clientSessions02 = getTestingClientForStartedNodeInDc(1).testing().cache(clientSessionsCacheName).size();
|
||||
remoteSessions01 = (Integer) cacheDc1Statistics.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
|
||||
remoteSessions02 = (Integer) cacheDc2Statistics.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
|
||||
log.infof("Before creating sessions: sessions01: %d, sessions02: %d, remoteSessions01: %d, remoteSessions02: %d", sessions01, sessions02, remoteSessions01, remoteSessions02);
|
||||
log.infof("Before creating sessions: sessions01: %d, sessions02: %d, remoteSessions01: %d, remoteSessions02: %d, clientSessions01: %d, clientSessions02: %d",
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, clientSessions01, clientSessions02);
|
||||
|
||||
// Create 20 user sessions
|
||||
oauth.realm(REALM_NAME);
|
||||
|
@ -152,9 +157,12 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
Retry.execute(() -> {
|
||||
int sessions11 = getTestingClientForStartedNodeInDc(0).testing().cache(cacheName).size();
|
||||
int sessions12 = getTestingClientForStartedNodeInDc(1).testing().cache(cacheName).size();
|
||||
int clientSessions11 = getTestingClientForStartedNodeInDc(0).testing().cache(clientSessionsCacheName).size();
|
||||
int clientSessions12 = getTestingClientForStartedNodeInDc(1).testing().cache(clientSessionsCacheName).size();
|
||||
int remoteSessions11 = (Integer) cacheDc1Statistics.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
|
||||
int remoteSessions12 = (Integer) cacheDc2Statistics.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
|
||||
log.infof("After creating sessions: sessions11: %d, sessions12: %d, remoteSessions11: %d, remoteSessions12: %d", sessions11, sessions12, remoteSessions11, remoteSessions12);
|
||||
log.infof("After creating sessions: sessions11: %d, sessions12: %d, remoteSessions11: %d, remoteSessions12: %d, clientSessions11: %d, clientSessions12: %d",
|
||||
sessions11, sessions12, remoteSessions11, remoteSessions12, clientSessions11, clientSessions12);
|
||||
|
||||
Assert.assertEquals(sessions11, sessions01 + SESSIONS_COUNT);
|
||||
Assert.assertEquals(sessions12, sessions02 + SESSIONS_COUNT);
|
||||
|
@ -169,11 +177,14 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
}
|
||||
|
||||
|
||||
private void assertStatisticsExpected(String messagePrefix, String cacheName, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics, InfinispanStatistics channelStatisticsCrossDc,
|
||||
int sessions1Expected, int sessions2Expected, int remoteSessions1Expected, int remoteSessions2Expected, boolean checkSomeMessagesSentBetweenDCs) {
|
||||
private void assertStatisticsExpected(String messagePrefix, String cacheName, String clientSessionsCacheName,
|
||||
InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics, InfinispanStatistics channelStatisticsCrossDc,
|
||||
int sessions1Expected, int sessions2Expected, int clientSessions1Expected, int clientSessions2Expected, int remoteSessions1Expected, int remoteSessions2Expected, boolean checkSomeMessagesSentBetweenDCs) {
|
||||
Retry.execute(() -> {
|
||||
int sessions1 = getTestingClientForStartedNodeInDc(0).testing().cache(cacheName).size();
|
||||
int sessions2 = getTestingClientForStartedNodeInDc(1).testing().cache(cacheName).size();
|
||||
int clientSessions1 = getTestingClientForStartedNodeInDc(0).testing().cache(clientSessionsCacheName).size();
|
||||
int clientSessions2 = getTestingClientForStartedNodeInDc(1).testing().cache(clientSessionsCacheName).size();
|
||||
int remoteSessions1 = (Integer) cacheDc1Statistics.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
|
||||
int remoteSessions2 = (Integer) cacheDc2Statistics.getSingleStatistics(InfinispanStatistics.Constants.STAT_CACHE_NUMBER_OF_ENTRIES);
|
||||
long messagesCount = (Long) channelStatisticsCrossDc.getSingleStatistics(InfinispanStatistics.Constants.STAT_CHANNEL_SENT_MESSAGES);
|
||||
|
@ -181,6 +192,8 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
Assert.assertEquals(sessions1, sessions1Expected);
|
||||
Assert.assertEquals(sessions2, sessions2Expected);
|
||||
Assert.assertEquals(clientSessions1, clientSessions1Expected);
|
||||
Assert.assertEquals(clientSessions2, clientSessions2Expected);
|
||||
Assert.assertEquals(remoteSessions1, remoteSessions1Expected);
|
||||
Assert.assertEquals(remoteSessions2, remoteSessions2Expected);
|
||||
|
||||
|
@ -195,11 +208,11 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
@Test
|
||||
public void testRealmRemoveOfflineSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME,true, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
channelStatisticsCrossDc.reset();
|
||||
|
||||
|
@ -207,18 +220,18 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
getAdminClient().realm(REALM_NAME).remove();
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLogoutAllInRealm(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
channelStatisticsCrossDc.reset();
|
||||
|
||||
|
@ -226,18 +239,18 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
getAdminClient().realm(REALM_NAME).logoutAll();
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After realm logout", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After realm logout", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPeriodicExpiration(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
public void testPeriodicExpirationSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true).get(SESSIONS_COUNT - 1);
|
||||
OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,false, cacheDc1Statistics, cacheDc2Statistics, true).get(SESSIONS_COUNT - 1);
|
||||
|
||||
// Assert I am able to refresh
|
||||
OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password");
|
||||
|
@ -250,8 +263,9 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME);
|
||||
|
||||
// Nothing yet expired. It may happen that no message sent between DCs
|
||||
assertStatisticsExpected("After remove expired - 1", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01 + SESSIONS_COUNT, sessions02 + SESSIONS_COUNT, remoteSessions01 + SESSIONS_COUNT, remoteSessions02 + SESSIONS_COUNT, false);
|
||||
assertStatisticsExpected("After remove expired - 1", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01 + SESSIONS_COUNT, sessions02 + SESSIONS_COUNT, clientSessions01 + SESSIONS_COUNT, clientSessions02 + SESSIONS_COUNT,
|
||||
remoteSessions01 + SESSIONS_COUNT, remoteSessions02 + SESSIONS_COUNT, false);
|
||||
|
||||
|
||||
// Set time offset
|
||||
|
@ -269,8 +283,55 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME);
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After remove expired - 2", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After remove expired - 2", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPeriodicExpirationOfflineSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME,
|
||||
true, cacheDc1Statistics, cacheDc2Statistics, true).get(SESSIONS_COUNT - 1);
|
||||
|
||||
// Assert I am able to refresh
|
||||
OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password");
|
||||
Assert.assertNotNull(refreshResponse.getRefreshToken());
|
||||
Assert.assertNull(refreshResponse.getError());
|
||||
|
||||
channelStatisticsCrossDc.reset();
|
||||
|
||||
// Remove expired in DC0
|
||||
getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME);
|
||||
|
||||
// Nothing yet expired. It may happen that no message sent between DCs
|
||||
assertStatisticsExpected("After remove expired - 1", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME,
|
||||
cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01 + SESSIONS_COUNT, sessions02 + SESSIONS_COUNT, clientSessions01 + SESSIONS_COUNT, clientSessions02 + SESSIONS_COUNT,
|
||||
remoteSessions01 + SESSIONS_COUNT, remoteSessions02 + SESSIONS_COUNT, false);
|
||||
|
||||
|
||||
// Set time offset
|
||||
setTimeOffset(10000000);
|
||||
|
||||
// Assert I am not able to refresh anymore
|
||||
refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password");
|
||||
Assert.assertNull(refreshResponse.getRefreshToken());
|
||||
Assert.assertNotNull(refreshResponse.getError());
|
||||
|
||||
|
||||
channelStatisticsCrossDc.reset();
|
||||
|
||||
// Remove expired in DC0
|
||||
getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME);
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After remove expired - 2", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME,
|
||||
cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -278,10 +339,10 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
@Test
|
||||
public void testUserRemoveSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
// log.infof("Sleeping!");
|
||||
// Thread.sleep(10000000);
|
||||
|
@ -293,17 +354,17 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After user remove", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After user remove", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUserRemoveOfflineSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME,true, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
// log.infof("Sleeping!");
|
||||
// Thread.sleep(10000000);
|
||||
|
@ -315,18 +376,19 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After user remove", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After user remove", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME,
|
||||
cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLogoutUser(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
channelStatisticsCrossDc.reset();
|
||||
|
||||
|
@ -336,29 +398,33 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
getAdminClient().realm(REALM_NAME).deleteSession(userSession.getId());
|
||||
|
||||
// Just one session expired.
|
||||
assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01 + SESSIONS_COUNT - 1, sessions02 + SESSIONS_COUNT - 1, remoteSessions01 + SESSIONS_COUNT - 1, remoteSessions02 + SESSIONS_COUNT - 1, true);
|
||||
assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
||||
cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01 + SESSIONS_COUNT - 1, sessions02 + SESSIONS_COUNT - 1, clientSessions01 + SESSIONS_COUNT - 1, clientSessions02 + SESSIONS_COUNT - 1,
|
||||
remoteSessions01 + SESSIONS_COUNT - 1, remoteSessions02 + SESSIONS_COUNT - 1, true);
|
||||
|
||||
// Logout all sessions for user now
|
||||
user.logout();
|
||||
|
||||
// Assert sessions removed on node1 and node2 and on remote caches.
|
||||
assertStatisticsExpected("After user logout", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, remoteSessions01, remoteSessions02, true);
|
||||
assertStatisticsExpected("After user logout", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
||||
cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
|
||||
sessions01, sessions02, clientSessions01, clientSessions02, remoteSessions01, remoteSessions02, true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLogoutUserWithFailover(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
// Start node2 on first DC
|
||||
startBackendNode(DC.FIRST, 1);
|
||||
|
||||
// Don't include remote stats. Size is smaller because of distributed cache
|
||||
List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, false);
|
||||
List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
||||
false, cacheDc1Statistics, cacheDc2Statistics, false);
|
||||
|
||||
// Kill node2 now. Around 10 sessions (half of SESSIONS_COUNT) will be lost on Keycloak side. But not on infinispan side
|
||||
stopBackendNode(DC.FIRST, 1);
|
||||
|
@ -396,8 +462,8 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
@Test
|
||||
public void testPeriodicExpirationAuthSessions(
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
createInitialAuthSessions();
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
|
@ -533,7 +534,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
String refreshId = oauth.verifyRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||
|
||||
int last = testingClient.testing().getLastSessionRefresh("test", sessionId);
|
||||
int last = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
|
||||
|
||||
setTimeOffset(2);
|
||||
|
||||
|
@ -544,7 +545,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
assertEquals(200, tokenResponse.getStatusCode());
|
||||
|
||||
int next = testingClient.testing().getLastSessionRefresh("test", sessionId);
|
||||
int next = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
|
||||
|
||||
Assert.assertNotEquals(last, next);
|
||||
|
||||
|
@ -555,7 +556,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
setTimeOffset(4);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
|
||||
next = testingClient.testing().getLastSessionRefresh("test", sessionId);
|
||||
next = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
|
||||
|
||||
// lastSEssionRefresh should be updated because access code lifespan is higher than sso idle timeout
|
||||
Assert.assertThat(next, allOf(greaterThan(last), lessThan(last + 50)));
|
||||
|
@ -564,7 +565,8 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
RealmManager.realm(realmResource).ssoSessionIdleTimeout(1);
|
||||
|
||||
events.clear();
|
||||
setTimeOffset(6);
|
||||
// Needs to add some additional time due the tollerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
|
||||
setTimeOffset(6 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
|
||||
// test idle timeout
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -315,8 +316,8 @@ public class AdapterTestStrategy extends ExternalResource {
|
|||
session.getTransactionManager().commit();
|
||||
session.close();
|
||||
|
||||
Time.setOffset(2);
|
||||
|
||||
// Needs to add some additional time due the tolerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
|
||||
Time.setOffset(2 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
|
||||
// test SSO
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/product-portal");
|
||||
|
@ -350,7 +351,8 @@ public class AdapterTestStrategy extends ExternalResource {
|
|||
session.getTransactionManager().commit();
|
||||
session.close();
|
||||
|
||||
Time.setOffset(2);
|
||||
// Needs to add some additional time due the tolerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
|
||||
Time.setOffset(2 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("demo");
|
||||
|
|
Loading…
Reference in a new issue