Skip to content

Commit

Permalink
KEYCLOAK-5747 Ensure refreshToken doesn't need to send request to the…
Browse files Browse the repository at this point in the history
… other DC. Other fixes and polishing
  • Loading branch information
mposolda authored and hmlnarik committed Nov 22, 2017
1 parent 61c5a33 commit bd1072d
Show file tree
Hide file tree
Showing 31 changed files with 874 additions and 275 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,6 @@ protected void initEmbedded() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@

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;
Expand All @@ -43,25 +43,31 @@
*/
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) {
Expand Down Expand Up @@ -141,6 +147,18 @@ public void setTimestamp(int timestamp) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
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;
Expand All @@ -56,6 +54,7 @@
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;
Expand Down Expand Up @@ -164,8 +163,7 @@ public AuthenticatedClientSessionModel createClientSession(RealmModel realm, Cli

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);
Expand Down Expand Up @@ -446,17 +444,18 @@ public void removeExpired(RealmModel realm) {

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
Expand All @@ -474,6 +473,7 @@ public void accept(UserSessionEntity userSessionEntity) {
futures.addTask(future);

userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
clientSessionsSize.incrementAndGet();
Future f = localClientSessionCache.removeAsync(clientSessionId);
futures.addTask(f);
});
Expand All @@ -483,12 +483,13 @@ public void accept(UserSessionEntity userSessionEntity) {

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);
Expand All @@ -501,6 +502,7 @@ private void removeExpiredOfflineUserSessions(RealmModel realm) {
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
Expand All @@ -517,6 +519,7 @@ public void accept(UserSessionEntity userSessionEntity) {
Future future = localCache.removeAsync(userSessionEntity.getId());
futures.addTask(future);
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
clientSessionsSize.incrementAndGet();
Future f = localClientSessionCache.removeAsync(clientSessionId);
futures.addTask(f);
});
Expand All @@ -533,7 +536,8 @@ public void accept(UserSessionEntity userSessionEntity) {

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
Expand Down Expand Up @@ -712,7 +716,7 @@ UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offl
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) {
Expand Down Expand Up @@ -762,7 +766,7 @@ public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedC

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());
Expand Down Expand Up @@ -831,7 +835,7 @@ public UserSessionAdapter importUserSession(UserSessionModel userSession, boolea
// Handle client sessions
if (importAuthenticatedClientSessions) {
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
importClientSession(importedSession, clientSession, userSessionUpdateTx, clientSessionUpdateTx);
importClientSession(importedSession, clientSession, userSessionUpdateTx, clientSessionUpdateTx, offline);
}
}

Expand All @@ -841,7 +845,8 @@ public UserSessionAdapter importUserSession(UserSessionModel userSession, boolea

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();
Expand All @@ -864,7 +869,7 @@ private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter
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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ protected void checkRemoteCaches(KeycloakSession session) {

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) {
Expand All @@ -221,7 +222,8 @@ protected void checkRemoteCaches(KeycloakSession session) {

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public void runUpdate(UserSessionEntity entity) {
@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
Expand Down
Loading

0 comments on commit bd1072d

Please sign in to comment.