Persistent sessions code also for offline sessions (#28319)
Persistent sessions code also for offline sessions Closes #28318 Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
fd97072a62
commit
b4cfebd8d5
13 changed files with 131 additions and 141 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -309,6 +309,10 @@ jobs:
|
||||||
if: needs.conditional.outputs.ci-store == 'true'
|
if: needs.conditional.outputs.ci-store == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 150
|
timeout-minutes: 150
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
variant: ["persistent-user-sessions,persistent-user-sessions-no-cache", "persistent-user-sessions"]
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
@ -316,11 +320,11 @@ jobs:
|
||||||
name: Integration test setup
|
name: Integration test setup
|
||||||
uses: ./.github/actions/integration-test-setup
|
uses: ./.github/actions/integration-test-setup
|
||||||
|
|
||||||
- name: Run base tests
|
- name: Run base tests without cache
|
||||||
run: |
|
run: |
|
||||||
TESTS=`testsuite/integration-arquillian/tests/base/testsuites/suite.sh persistent-sessions`
|
TESTS=`testsuite/integration-arquillian/tests/base/testsuites/suite.sh persistent-sessions`
|
||||||
echo "Tests: $TESTS"
|
echo "Tests: $TESTS"
|
||||||
./mvnw test ${{ env.SUREFIRE_RETRY }} -Pauth-server-quarkus -Dauth.server.features=persistent-user-sessions,persistent-user-sessions-no-cache -Dtest=$TESTS -pl testsuite/integration-arquillian/tests/base 2>&1 | misc/log/trimmer.sh
|
./mvnw test ${{ env.SUREFIRE_RETRY }} -Pauth-server-quarkus -Dauth.server.features=${{ matrix.variant }} -Dtest=$TESTS -pl testsuite/integration-arquillian/tests/base 2>&1 | misc/log/trimmer.sh
|
||||||
|
|
||||||
- name: Upload JVM Heapdumps
|
- name: Upload JVM Heapdumps
|
||||||
if: always()
|
if: always()
|
||||||
|
|
|
@ -114,9 +114,9 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
protected final RemoteCacheInvoker remoteCacheInvoker;
|
protected final RemoteCacheInvoker remoteCacheInvoker;
|
||||||
protected final InfinispanKeyGenerator keyGenerator;
|
protected final InfinispanKeyGenerator keyGenerator;
|
||||||
|
|
||||||
protected final SessionFunction offlineSessionCacheEntryLifespanAdjuster;
|
protected final SessionFunction<UserSessionEntity> offlineSessionCacheEntryLifespanAdjuster;
|
||||||
|
|
||||||
protected final SessionFunction offlineClientSessionCacheEntryLifespanAdjuster;
|
protected final SessionFunction<AuthenticatedClientSessionEntity> offlineClientSessionCacheEntryLifespanAdjuster;
|
||||||
|
|
||||||
public PersistentUserSessionProvider(KeycloakSession session,
|
public PersistentUserSessionProvider(KeycloakSession session,
|
||||||
RemoteCacheInvoker remoteCacheInvoker,
|
RemoteCacheInvoker remoteCacheInvoker,
|
||||||
|
@ -206,12 +206,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
|
||||||
final UUID clientSessionId;
|
final UUID clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSession.getId(), client.getId());
|
||||||
if (userSession.isOffline()) {
|
|
||||||
clientSessionId = keyGenerator.generateKeyUUID(session, clientSessionCache);
|
|
||||||
} else {
|
|
||||||
clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSession.getId(), client.getId());
|
|
||||||
}
|
|
||||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||||
entity.setRealmId(realm.getId());
|
entity.setRealmId(realm.getId());
|
||||||
entity.setClientId(client.getId());
|
entity.setClientId(client.getId());
|
||||||
|
@ -244,7 +239,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
|
||||||
clientSessionUpdateTx.addTask(clientSessionId, createClientSessionTask, entity, persistenceState);
|
clientSessionUpdateTx.addTask(clientSessionId, createClientSessionTask, entity, persistenceState);
|
||||||
|
|
||||||
SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(client.getId(), clientSessionId);
|
SessionUpdateTask<UserSessionEntity> registerClientSessionTask = new RegisterClientSessionTask(client.getId(), clientSessionId);
|
||||||
userSessionUpdateTx.addTask(userSession.getId(), registerClientSessionTask);
|
userSessionUpdateTx.addTask(userSession.getId(), registerClientSessionTask);
|
||||||
|
|
||||||
return adapter;
|
return adapter;
|
||||||
|
@ -797,18 +792,17 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
return getUserSessionsStream(realm, client, first, max, true);
|
return getUserSessionsStream(realm, client, first, max, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline) {
|
public void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline) {
|
||||||
if (persistentUserSessions == null || persistentUserSessions.isEmpty()) {
|
if (persistentUserSessions == null || persistentUserSessions.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
persistentUserSessions.forEach(userSessionModel -> importUserSession(userSessionModel, offline));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionEntityWrapper<UserSessionEntity> importUserSession(UserSessionModel persistentUserSession, boolean offline) {
|
||||||
Map<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
Map<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsById = new HashMap<>();
|
||||||
|
|
||||||
Map<String, SessionEntityWrapper<UserSessionEntity>> sessionsById = persistentUserSessions.stream()
|
|
||||||
.map((UserSessionModel persistentUserSession) -> {
|
|
||||||
|
|
||||||
UserSessionEntity userSessionEntityToImport = createUserSessionEntityInstance(persistentUserSession);
|
UserSessionEntity userSessionEntityToImport = createUserSessionEntityInstance(persistentUserSession);
|
||||||
|
|
||||||
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : persistentUserSession.getAuthenticatedClientSessions().entrySet()) {
|
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : persistentUserSession.getAuthenticatedClientSessions().entrySet()) {
|
||||||
|
@ -828,68 +822,41 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
clientSessions.put(clientUUID, clientSessionToImport.getId());
|
clientSessions.put(clientUUID, clientSessionToImport.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return userSessionEntityToImport;
|
SessionEntityWrapper<UserSessionEntity> wrappedUserSessionEntity = new SessionEntityWrapper<>(userSessionEntityToImport);
|
||||||
})
|
|
||||||
.map(SessionEntityWrapper::new)
|
Map<String, SessionEntityWrapper<UserSessionEntity>> sessionsById =
|
||||||
.collect(Collectors.toMap(sessionEntityWrapper -> sessionEntityWrapper.getEntity().getId(), Function.identity()));
|
Stream.of(wrappedUserSessionEntity).collect(Collectors.toMap(sessionEntityWrapper -> sessionEntityWrapper.getEntity().getId(), Function.identity()));
|
||||||
|
|
||||||
// Directly put all entities to the infinispan cache
|
// Directly put all entities to the infinispan cache
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = CacheDecorators.skipCacheLoadersIfRemoteStoreIsEnabled(getCache(offline));
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = CacheDecorators.skipCacheLoadersIfRemoteStoreIsEnabled(getCache(offline));
|
||||||
|
|
||||||
boolean importWithExpiration = sessionsById.size() == 1;
|
sessionsById = importSessionsWithExpiration(sessionsById, cache,
|
||||||
if (importWithExpiration) {
|
|
||||||
importSessionsWithExpiration(sessionsById, cache,
|
|
||||||
offline ? offlineSessionCacheEntryLifespanAdjuster : SessionTimeouts::getUserSessionLifespanMs,
|
offline ? offlineSessionCacheEntryLifespanAdjuster : SessionTimeouts::getUserSessionLifespanMs,
|
||||||
offline ? SessionTimeouts::getOfflineSessionMaxIdleMs : SessionTimeouts::getUserSessionMaxIdleMs);
|
offline ? SessionTimeouts::getOfflineSessionMaxIdleMs : SessionTimeouts::getUserSessionMaxIdleMs);
|
||||||
} else {
|
|
||||||
Retry.executeWithBackoff((int iteration) -> {
|
if (sessionsById.isEmpty()) {
|
||||||
cache.putAll(sessionsById);
|
return null;
|
||||||
}, 10, 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// put all entities to the remoteCache (if exists)
|
// put all entities to the remoteCache (if exists)
|
||||||
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
|
RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cache);
|
||||||
if (remoteCache != null) {
|
if (remoteCache != null) {
|
||||||
Map<String, SessionEntityWrapper<UserSessionEntity>> sessionsByIdForTransport = sessionsById.values().stream()
|
Map<String, SessionEntityWrapper<UserSessionEntity>> sessionsByIdForTransport = Stream.of(wrappedUserSessionEntity)
|
||||||
.map(SessionEntityWrapper::forTransport)
|
.map(SessionEntityWrapper::forTransport)
|
||||||
.collect(Collectors.toMap(sessionEntityWrapper -> sessionEntityWrapper.getEntity().getId(), Function.identity()));
|
.collect(Collectors.toMap(sessionEntityWrapper -> sessionEntityWrapper.getEntity().getId(), Function.identity()));
|
||||||
|
|
||||||
if (importWithExpiration) {
|
|
||||||
importSessionsWithExpiration(sessionsByIdForTransport, remoteCache,
|
importSessionsWithExpiration(sessionsByIdForTransport, remoteCache,
|
||||||
offline ? offlineSessionCacheEntryLifespanAdjuster : SessionTimeouts::getUserSessionLifespanMs,
|
offline ? offlineSessionCacheEntryLifespanAdjuster : SessionTimeouts::getUserSessionLifespanMs,
|
||||||
offline ? SessionTimeouts::getOfflineSessionMaxIdleMs : SessionTimeouts::getUserSessionMaxIdleMs);
|
offline ? SessionTimeouts::getOfflineSessionMaxIdleMs : SessionTimeouts::getUserSessionMaxIdleMs);
|
||||||
} else {
|
|
||||||
Retry.executeWithBackoff((int iteration) -> {
|
|
||||||
|
|
||||||
try {
|
|
||||||
remoteCache.putAll(sessionsByIdForTransport);
|
|
||||||
} catch (HotRodClientException re) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debugf(re, "Failed to put import %d sessions to remoteCache. Iteration '%s'. Will try to retry the task",
|
|
||||||
sessionsByIdForTransport.size(), iteration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rethrow the exception. Retry will take care of handle the exception and eventually retry the operation.
|
|
||||||
throw re;
|
|
||||||
}
|
|
||||||
|
|
||||||
}, 10, 10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import client sessions
|
// Import client sessions
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessCache =
|
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessCache =
|
||||||
CacheDecorators.skipCacheLoadersIfRemoteStoreIsEnabled(offline ? offlineClientSessionCache : clientSessionCache);
|
CacheDecorators.skipCacheLoadersIfRemoteStoreIsEnabled(offline ? offlineClientSessionCache : clientSessionCache);
|
||||||
|
|
||||||
if (importWithExpiration) {
|
|
||||||
importSessionsWithExpiration(clientSessionsById, clientSessCache,
|
importSessionsWithExpiration(clientSessionsById, clientSessCache,
|
||||||
offline ? offlineClientSessionCacheEntryLifespanAdjuster : SessionTimeouts::getClientSessionLifespanMs,
|
offline ? offlineClientSessionCacheEntryLifespanAdjuster : SessionTimeouts::getClientSessionLifespanMs,
|
||||||
offline ? SessionTimeouts::getOfflineClientSessionMaxIdleMs : SessionTimeouts::getClientSessionMaxIdleMs);
|
offline ? SessionTimeouts::getOfflineClientSessionMaxIdleMs : SessionTimeouts::getClientSessionMaxIdleMs);
|
||||||
} else {
|
|
||||||
Retry.executeWithBackoff((int iteration) -> {
|
|
||||||
clientSessCache.putAll(clientSessionsById);
|
|
||||||
}, 10, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// put all entities to the remoteCache (if exists)
|
// put all entities to the remoteCache (if exists)
|
||||||
RemoteCache remoteCacheClientSessions = InfinispanUtil.getRemoteCache(clientSessCache);
|
RemoteCache remoteCacheClientSessions = InfinispanUtil.getRemoteCache(clientSessCache);
|
||||||
|
@ -898,38 +865,22 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
.map(SessionEntityWrapper::forTransport)
|
.map(SessionEntityWrapper::forTransport)
|
||||||
.collect(Collectors.toMap(sessionEntityWrapper -> sessionEntityWrapper.getEntity().getId(), Function.identity()));
|
.collect(Collectors.toMap(sessionEntityWrapper -> sessionEntityWrapper.getEntity().getId(), Function.identity()));
|
||||||
|
|
||||||
if (importWithExpiration) {
|
|
||||||
importSessionsWithExpiration(sessionsByIdForTransport, remoteCacheClientSessions,
|
importSessionsWithExpiration(sessionsByIdForTransport, remoteCacheClientSessions,
|
||||||
offline ? offlineClientSessionCacheEntryLifespanAdjuster : SessionTimeouts::getClientSessionLifespanMs,
|
offline ? offlineClientSessionCacheEntryLifespanAdjuster : SessionTimeouts::getClientSessionLifespanMs,
|
||||||
offline ? SessionTimeouts::getOfflineClientSessionMaxIdleMs : SessionTimeouts::getClientSessionMaxIdleMs);
|
offline ? SessionTimeouts::getOfflineClientSessionMaxIdleMs : SessionTimeouts::getClientSessionMaxIdleMs);
|
||||||
} else {
|
|
||||||
Retry.executeWithBackoff((int iteration) -> {
|
|
||||||
|
|
||||||
try {
|
|
||||||
remoteCacheClientSessions.putAll(sessionsByIdForTransport);
|
|
||||||
} catch (HotRodClientException re) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debugf(re, "Failed to put import %d client sessions to remoteCache. Iteration '%s'. Will try to retry the task",
|
|
||||||
sessionsByIdForTransport.size(), iteration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rethrow the exception. Retry will take care of handle the exception and eventually retry the operation.
|
return sessionsById.entrySet().stream().findFirst().map(Map.Entry::getValue).orElse(null);
|
||||||
throw re;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, 10, 10);
|
private <T extends SessionEntity, K> Map<K, SessionEntityWrapper<T>> importSessionsWithExpiration(Map<K, SessionEntityWrapper<T>> sessionsById,
|
||||||
}
|
BasicCache<K, SessionEntityWrapper<T>> cache, SessionFunction<T> lifespanMsCalculator,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends SessionEntity> void importSessionsWithExpiration(Map<? extends Object, SessionEntityWrapper<T>> sessionsById,
|
|
||||||
BasicCache cache, SessionFunction<T> lifespanMsCalculator,
|
|
||||||
SessionFunction<T> maxIdleTimeMsCalculator) {
|
SessionFunction<T> maxIdleTimeMsCalculator) {
|
||||||
sessionsById.forEach((id, sessionEntityWrapper) -> {
|
return sessionsById.entrySet().stream().map(entry -> {
|
||||||
|
|
||||||
T sessionEntity = sessionEntityWrapper.getEntity();
|
T sessionEntity = entry.getValue().getEntity();
|
||||||
RealmModel currentRealm = session.realms().getRealm(sessionEntity.getRealmId());
|
RealmModel currentRealm = session.realms().getRealm(sessionEntity.getRealmId());
|
||||||
ClientModel client = sessionEntityWrapper.getClientIfNeeded(currentRealm);
|
ClientModel client = entry.getValue().getClientIfNeeded(currentRealm);
|
||||||
long lifespan = lifespanMsCalculator.apply(currentRealm, client, sessionEntity);
|
long lifespan = lifespanMsCalculator.apply(currentRealm, client, sessionEntity);
|
||||||
long maxIdle = maxIdleTimeMsCalculator.apply(currentRealm, client, sessionEntity);
|
long maxIdle = maxIdleTimeMsCalculator.apply(currentRealm, client, sessionEntity);
|
||||||
|
|
||||||
|
@ -939,7 +890,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
Retry.executeWithBackoff((int iteration) -> {
|
Retry.executeWithBackoff((int iteration) -> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cache.put(id, sessionEntityWrapper, lifespan, TimeUnit.MILLISECONDS, maxIdle, TimeUnit.MILLISECONDS);
|
cache.putIfAbsent(entry.getKey(), entry.getValue(), lifespan, TimeUnit.MILLISECONDS, maxIdle, TimeUnit.MILLISECONDS);
|
||||||
} catch (HotRodClientException re) {
|
} catch (HotRodClientException re) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debugf(re, "Failed to put import %d sessions to remoteCache. Iteration '%s'. Will try to retry the task",
|
log.debugf(re, "Failed to put import %d sessions to remoteCache. Iteration '%s'. Will try to retry the task",
|
||||||
|
@ -952,10 +903,13 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
|
|
||||||
}, 10, 10);
|
}, 10, 10);
|
||||||
} else {
|
} else {
|
||||||
cache.put(id, sessionEntityWrapper, lifespan, TimeUnit.MILLISECONDS, maxIdle, TimeUnit.MILLISECONDS);
|
cache.putIfAbsent(entry.getKey(), entry.getValue(), lifespan, TimeUnit.MILLISECONDS, maxIdle, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
});
|
}).filter(Objects::nonNull).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserSessionEntity createUserSessionEntityInstance(UserSessionModel userSession) {
|
private UserSessionEntity createUserSessionEntityInstance(UserSessionModel userSession) {
|
||||||
|
@ -1021,7 +975,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
AuthenticatedClientSessionStore clientSessions = sessionToImportInto.getEntity().getAuthenticatedClientSessions();
|
AuthenticatedClientSessionStore clientSessions = sessionToImportInto.getEntity().getAuthenticatedClientSessions();
|
||||||
clientSessions.put(clientSession.getClient().getId(), clientSessionId);
|
clientSessions.put(clientSession.getClient().getId(), clientSessionId);
|
||||||
|
|
||||||
SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId);
|
SessionUpdateTask<UserSessionEntity> registerClientSessionTask = new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId);
|
||||||
userSessionUpdateTx.addTask(sessionToImportInto.getId(), registerClientSessionTask);
|
userSessionUpdateTx.addTask(sessionToImportInto.getId(), registerClientSessionTask);
|
||||||
|
|
||||||
return new AuthenticatedClientSessionAdapter(session, this, entity, clientSession.getClient(), sessionToImportInto, clientSessionUpdateTx, offline);
|
return new AuthenticatedClientSessionAdapter(session, this, entity, clientSession.getClient(), sessionToImportInto, clientSessionUpdateTx, offline);
|
||||||
|
@ -1030,12 +984,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
|
|
||||||
private AuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(String userSessionId, AuthenticatedClientSessionModel clientSession,
|
private AuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(String userSessionId, AuthenticatedClientSessionModel clientSession,
|
||||||
String realmId, String clientId, boolean offline) {
|
String realmId, String clientId, boolean offline) {
|
||||||
final UUID clientSessionId;
|
final UUID clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSessionId, clientId);
|
||||||
if (offline) {
|
|
||||||
clientSessionId = keyGenerator.generateKeyUUID(session, clientSessionCache);
|
|
||||||
} else {
|
|
||||||
clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSessionId, clientId);
|
|
||||||
}
|
|
||||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||||
entity.setRealmId(realmId);
|
entity.setRealmId(realmId);
|
||||||
|
|
||||||
|
@ -1084,7 +1033,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
clientSessionUpdateTx.addTask(clientSession.getId(), null, clientSession, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
clientSessionUpdateTx.addTask(clientSession.getId(), null, clientSession, UserSessionModel.SessionPersistenceState.PERSISTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionTx.get(realm, userSessionEntity.getId());
|
return userSessionUpdateTx.get(userSessionEntity.getId());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1128,6 +1077,7 @@ public class PersistentUserSessionProvider implements UserSessionProvider, Sessi
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UUID createClientSessionUUID(String userSessionId, String clientId) {
|
public static UUID createClientSessionUUID(String userSessionId, String clientId) {
|
||||||
|
// This allows creating a UUID that is constant even if the entry is reloaded from the database
|
||||||
return UUID.nameUUIDFromBytes((userSessionId + clientId).getBytes(StandardCharsets.UTF_8));
|
return UUID.nameUUIDFromBytes((userSessionId + clientId).getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,16 +104,7 @@ public class ClientSessionPersistentChangelogBasedTransaction extends Persistent
|
||||||
|
|
||||||
private AuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(String userSessionId, AuthenticatedClientSessionModel clientSession,
|
private AuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(String userSessionId, AuthenticatedClientSessionModel clientSession,
|
||||||
String realmId, String clientId) {
|
String realmId, String clientId) {
|
||||||
UUID clientSessionId = null;
|
UUID clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSessionId, clientId);
|
||||||
if (clientSession.getId() != null) {
|
|
||||||
clientSessionId = UUID.fromString(clientSession.getId());
|
|
||||||
} else {
|
|
||||||
if (offline) {
|
|
||||||
clientSessionId = keyGenerator.generateKeyUUID(kcSession, cache);
|
|
||||||
} else {
|
|
||||||
clientSessionId = PersistentUserSessionProvider.createClientSessionUUID(userSessionId, clientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(clientSessionId);
|
||||||
entity.setRealmId(realmId);
|
entity.setRealmId(realmId);
|
||||||
|
|
|
@ -38,6 +38,8 @@ import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
|
||||||
import org.keycloak.connections.infinispan.InfinispanUtil;
|
import org.keycloak.connections.infinispan.InfinispanUtil;
|
||||||
|
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +75,7 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
|
||||||
public void addTask(K key, SessionUpdateTask<V> task) {
|
public void addTask(K key, SessionUpdateTask<V> task) {
|
||||||
SessionUpdatesList<V> myUpdates = updates.get(key);
|
SessionUpdatesList<V> myUpdates = updates.get(key);
|
||||||
if (myUpdates == null) {
|
if (myUpdates == null) {
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) && (Objects.equals(cacheName, USER_SESSION_CACHE_NAME) || Objects.equals(cacheName, CLIENT_SESSION_CACHE_NAME))) {
|
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) && (Objects.equals(cacheName, USER_SESSION_CACHE_NAME) || Objects.equals(cacheName, CLIENT_SESSION_CACHE_NAME) || Objects.equals(cacheName, OFFLINE_USER_SESSION_CACHE_NAME) || Objects.equals(cacheName, OFFLINE_CLIENT_SESSION_CACHE_NAME))) {
|
||||||
throw new IllegalStateException("Can't load from cache");
|
throw new IllegalStateException("Can't load from cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,11 @@ import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||||
|
|
||||||
public class JpaChangesPerformer<K, V extends SessionEntity> implements SessionChangesPerformer<K, V> {
|
public class JpaChangesPerformer<K, V extends SessionEntity> implements SessionChangesPerformer<K, V> {
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
@ -69,8 +74,8 @@ public class JpaChangesPerformer<K, V extends SessionEntity> implements SessionC
|
||||||
|
|
||||||
private TriConsumer<KeycloakSession, Map.Entry<K, SessionUpdatesList<V>>, MergedUpdate<V>> processor() {
|
private TriConsumer<KeycloakSession, Map.Entry<K, SessionUpdatesList<V>>, MergedUpdate<V>> processor() {
|
||||||
return switch (cacheName) {
|
return switch (cacheName) {
|
||||||
case "sessions", "offlineSessions" -> this::processUserSessionUpdate;
|
case USER_SESSION_CACHE_NAME, OFFLINE_USER_SESSION_CACHE_NAME -> this::processUserSessionUpdate;
|
||||||
case "clientSessions", "offlineClientSessions" -> this::processClientSessionUpdate;
|
case CLIENT_SESSION_CACHE_NAME, OFFLINE_CLIENT_SESSION_CACHE_NAME -> this::processClientSessionUpdate;
|
||||||
default -> throw new IllegalStateException("Unexpected value: " + cacheName);
|
default -> throw new IllegalStateException("Unexpected value: " + cacheName);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||||
|
|
||||||
public class PersistentSessionsChangelogBasedTransaction<K, V extends SessionEntity> extends InfinispanChangelogBasedTransaction<K, V> {
|
public class PersistentSessionsChangelogBasedTransaction<K, V extends SessionEntity> extends InfinispanChangelogBasedTransaction<K, V> {
|
||||||
|
@ -45,7 +47,8 @@ public class PersistentSessionsChangelogBasedTransaction<K, V extends SessionEnt
|
||||||
throw new IllegalStateException("Persistent user sessions are not enabled");
|
throw new IllegalStateException("Persistent user sessions are not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) && (cache.getName().equals(USER_SESSION_CACHE_NAME) || cache.getName().equals(CLIENT_SESSION_CACHE_NAME))) {
|
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) &&
|
||||||
|
(cache.getName().equals(USER_SESSION_CACHE_NAME) || cache.getName().equals(CLIENT_SESSION_CACHE_NAME) || cache.getName().equals(OFFLINE_USER_SESSION_CACHE_NAME) || cache.getName().equals(OFFLINE_CLIENT_SESSION_CACHE_NAME))) {
|
||||||
changesPerformers = List.of(
|
changesPerformers = List.of(
|
||||||
new JpaChangesPerformer<>(session, cache.getName(), offline)
|
new JpaChangesPerformer<>(session, cache.getName(), offline)
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,6 +35,8 @@ import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||||
|
|
||||||
public class UserSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<String, UserSessionEntity> {
|
public class UserSessionPersistentChangelogBasedTransaction extends PersistentSessionsChangelogBasedTransaction<String, UserSessionEntity> {
|
||||||
|
@ -48,7 +50,7 @@ public class UserSessionPersistentChangelogBasedTransaction extends PersistentSe
|
||||||
SessionUpdatesList<UserSessionEntity> myUpdates = updates.get(key);
|
SessionUpdatesList<UserSessionEntity> myUpdates = updates.get(key);
|
||||||
if (myUpdates == null) {
|
if (myUpdates == null) {
|
||||||
SessionEntityWrapper<UserSessionEntity> wrappedEntity = null;
|
SessionEntityWrapper<UserSessionEntity> wrappedEntity = null;
|
||||||
if (!((Objects.equals(cache.getName(), USER_SESSION_CACHE_NAME) || Objects.equals(cache.getName(), CLIENT_SESSION_CACHE_NAME)) && Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE))) {
|
if (!((Objects.equals(cache.getName(), USER_SESSION_CACHE_NAME) || Objects.equals(cache.getName(), CLIENT_SESSION_CACHE_NAME) || Objects.equals(cache.getName(), OFFLINE_USER_SESSION_CACHE_NAME) || Objects.equals(cache.getName(), OFFLINE_CLIENT_SESSION_CACHE_NAME)) && Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE))) {
|
||||||
wrappedEntity = cache.get(key);
|
wrappedEntity = cache.get(key);
|
||||||
}
|
}
|
||||||
if (wrappedEntity == null) {
|
if (wrappedEntity == null) {
|
||||||
|
@ -111,15 +113,12 @@ public class UserSessionPersistentChangelogBasedTransaction extends PersistentSe
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) && (cache.getName().equals(USER_SESSION_CACHE_NAME) || cache.getName().equals(CLIENT_SESSION_CACHE_NAME))) {
|
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) && (cache.getName().equals(USER_SESSION_CACHE_NAME) || cache.getName().equals(CLIENT_SESSION_CACHE_NAME) || cache.getName().equals(OFFLINE_USER_SESSION_CACHE_NAME) || cache.getName().equals(OFFLINE_CLIENT_SESSION_CACHE_NAME))) {
|
||||||
return ((PersistentUserSessionProvider) kcSession.getProvider(UserSessionProvider.class)).wrapPersistentEntity(persistentUserSession.getRealm(), offline, persistentUserSession);
|
return ((PersistentUserSessionProvider) kcSession.getProvider(UserSessionProvider.class)).wrapPersistentEntity(persistentUserSession.getRealm(), offline, persistentUserSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debugf("Attempting to import user-session for sessionId=%s offline=%s", sessionId, offline);
|
LOG.debugf("Attempting to import user-session for sessionId=%s offline=%s", sessionId, offline);
|
||||||
kcSession.sessions().importUserSessions(Collections.singleton(persistentUserSession), offline);
|
SessionEntityWrapper<UserSessionEntity> ispnUserSessionEntity = ((PersistentUserSessionProvider) kcSession.getProvider(UserSessionProvider.class)).importUserSession(persistentUserSession, offline);;
|
||||||
LOG.debugf("user-session imported, trying another lookup for sessionId=%s offline=%s", sessionId, offline);
|
|
||||||
|
|
||||||
SessionEntityWrapper<UserSessionEntity> ispnUserSessionEntity = cache.get(sessionId);
|
|
||||||
|
|
||||||
if (ispnUserSessionEntity != null) {
|
if (ispnUserSessionEntity != null) {
|
||||||
LOG.debugf("user-session found after import for sessionId=%s offline=%s", sessionId, offline);
|
LOG.debugf("user-session found after import for sessionId=%s offline=%s", sessionId, offline);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
-->
|
-->
|
||||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||||
|
|
||||||
<changeSet author="keycloak" id="25.0.0-xxx">
|
<changeSet author="keycloak" id="25.0.0-28265-tables">
|
||||||
<addColumn tableName="OFFLINE_USER_SESSION">
|
<addColumn tableName="OFFLINE_USER_SESSION">
|
||||||
<!-- length(broker_session_id) + length(realm_id) <= 1700 for mssql -->
|
<!-- length(broker_session_id) + length(realm_id) <= 1700 for mssql -->
|
||||||
<column name="BROKER_SESSION_ID" type="VARCHAR(1024)" />
|
<column name="BROKER_SESSION_ID" type="VARCHAR(1024)" />
|
||||||
|
@ -26,6 +26,13 @@
|
||||||
<addColumn tableName="OFFLINE_CLIENT_SESSION">
|
<addColumn tableName="OFFLINE_CLIENT_SESSION">
|
||||||
<column name="VERSION" type="INT" defaultValueNumeric="0" />
|
<column name="VERSION" type="INT" defaultValueNumeric="0" />
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
<modifySql dbms="mssql">
|
||||||
|
<!-- ensure that existing rows also get the new values on mssql -->
|
||||||
|
<!-- https://github.com/liquibase/liquibase/issues/4644 -->
|
||||||
|
<replace replace="DEFAULT 0" with="DEFAULT 0 WITH VALUES" />
|
||||||
|
</modifySql>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="keycloak" id="25.0.0-28265-index-creation">
|
||||||
<createIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_BY_LAST_SESSION_REFRESH" >
|
<createIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_BY_LAST_SESSION_REFRESH" >
|
||||||
<!-- optimize this index for range queries for expire sessions -->
|
<!-- optimize this index for range queries for expire sessions -->
|
||||||
<!-- it should also distribute hot segments across realms and online/offline -->
|
<!-- it should also distribute hot segments across realms and online/offline -->
|
||||||
|
@ -33,17 +40,14 @@
|
||||||
<column name="OFFLINE_FLAG" />
|
<column name="OFFLINE_FLAG" />
|
||||||
<column name="LAST_SESSION_REFRESH" />
|
<column name="LAST_SESSION_REFRESH" />
|
||||||
</createIndex>
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
<changeSet author="keycloak" id="25.0.0-28265-index-cleanup">
|
||||||
<dropIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_CREATEDON" />
|
<dropIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_CREATEDON" />
|
||||||
<dropIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_PRELOAD" />
|
<dropIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_PRELOAD" />
|
||||||
<dropIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_BY_USERSESS" />
|
<dropIndex tableName="OFFLINE_USER_SESSION" indexName="IDX_OFFLINE_USS_BY_USERSESS" />
|
||||||
<dropIndex tableName="OFFLINE_CLIENT_SESSION" indexName="IDX_OFFLINE_CSS_PRELOAD" />
|
<dropIndex tableName="OFFLINE_CLIENT_SESSION" indexName="IDX_OFFLINE_CSS_PRELOAD" />
|
||||||
<modifySql dbms="mssql">
|
|
||||||
<!-- ensure that existing rows also get the new values on mssql -->
|
|
||||||
<!-- https://github.com/liquibase/liquibase/issues/4644 -->
|
|
||||||
<replace replace="DEFAULT 0" with="DEFAULT 0 WITH VALUES" />
|
|
||||||
</modifySql>
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
<changeSet author="keycloak" id="25.0.0-xxx-2-mysql">
|
<changeSet author="keycloak" id="25.0.0-28265-index-2-mysql">
|
||||||
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
|
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
|
||||||
<or>
|
<or>
|
||||||
<dbms type="mysql"/>
|
<dbms type="mysql"/>
|
||||||
|
@ -57,7 +61,7 @@
|
||||||
<column name="REALM_ID" />
|
<column name="REALM_ID" />
|
||||||
</createIndex>
|
</createIndex>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
<changeSet author="keycloak" id="25.0.0-xxx-2-not-mysql">
|
<changeSet author="keycloak" id="25.0.0-28265-index-2-not-mysql">
|
||||||
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
|
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
|
||||||
<not>
|
<not>
|
||||||
<or>
|
<or>
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import org.infinispan.client.hotrod.impl.ConfigurationProperties;
|
import org.infinispan.client.hotrod.impl.ConfigurationProperties;
|
||||||
|
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||||
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
|
import org.infinispan.configuration.cache.PersistenceConfigurationBuilder;
|
||||||
import org.infinispan.configuration.global.GlobalConfiguration;
|
import org.infinispan.configuration.global.GlobalConfiguration;
|
||||||
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
|
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
|
||||||
|
@ -41,6 +42,7 @@ import org.jgroups.protocols.TCP_NIO2;
|
||||||
import org.jgroups.protocols.UDP;
|
import org.jgroups.protocols.UDP;
|
||||||
import org.jgroups.util.TLS;
|
import org.jgroups.util.TLS;
|
||||||
import org.jgroups.util.TLSClientAuth;
|
import org.jgroups.util.TLSClientAuth;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
@ -55,7 +57,10 @@ import static org.keycloak.config.CachingOptions.CACHE_REMOTE_HOST_PROPERTY;
|
||||||
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PASSWORD_PROPERTY;
|
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PASSWORD_PROPERTY;
|
||||||
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PORT_PROPERTY;
|
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PORT_PROPERTY;
|
||||||
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_USERNAME_PROPERTY;
|
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_USERNAME_PROPERTY;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.DISTRIBUTED_REPLICATED_CACHE_NAMES;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.DISTRIBUTED_REPLICATED_CACHE_NAMES;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||||
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||||
import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.SCRAM_SHA_512;
|
import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.SCRAM_SHA_512;
|
||||||
|
|
||||||
|
@ -103,11 +108,22 @@ public class CacheManagerFactory {
|
||||||
private DefaultCacheManager startCacheManager() {
|
private DefaultCacheManager startCacheManager() {
|
||||||
ConfigurationBuilderHolder builder = new ParserRegistry().parse(config);
|
ConfigurationBuilderHolder builder = new ParserRegistry().parse(config);
|
||||||
|
|
||||||
if (builder.getNamedConfigurationBuilders().get(USER_SESSION_CACHE_NAME).clustering().cacheMode().isClustered()) {
|
if (builder.getNamedConfigurationBuilders().entrySet().stream().anyMatch(c -> c.getValue().clustering().cacheMode().isClustered())) {
|
||||||
configureTransportStack(builder);
|
configureTransportStack(builder);
|
||||||
configureRemoteStores(builder);
|
configureRemoteStores(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DISTRIBUTED_REPLICATED_CACHE_NAMES.forEach(cacheName -> {
|
||||||
|
if (!Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) && Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) &&
|
||||||
|
(cacheName.equals(USER_SESSION_CACHE_NAME) || cacheName.equals(CLIENT_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_USER_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_CLIENT_SESSION_CACHE_NAME))) {
|
||||||
|
ConfigurationBuilder configurationBuilder = builder.getNamedConfigurationBuilders().get(cacheName);
|
||||||
|
if (configurationBuilder.memory().maxSize() == null && configurationBuilder.memory().maxCount() == -1) {
|
||||||
|
logger.infof("Persistent user sessions enabled and no memory limit found in configuration. Setting max entries for %s to 10000 entries", cacheName);
|
||||||
|
configurationBuilder.memory().maxCount(10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (metricsEnabled) {
|
if (metricsEnabled) {
|
||||||
builder.getGlobalConfigurationBuilder().addModule(MicrometerMeterRegisterConfigurationBuilder.class);
|
builder.getGlobalConfigurationBuilder().addModule(MicrometerMeterRegisterConfigurationBuilder.class);
|
||||||
builder.getGlobalConfigurationBuilder().module(MicrometerMeterRegisterConfigurationBuilder.class).meterRegistry(Metrics.globalRegistry);
|
builder.getGlobalConfigurationBuilder().module(MicrometerMeterRegisterConfigurationBuilder.class).meterRegistry(Metrics.globalRegistry);
|
||||||
|
@ -223,6 +239,11 @@ public class CacheManagerFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
DISTRIBUTED_REPLICATED_CACHE_NAMES.forEach(cacheName -> {
|
DISTRIBUTED_REPLICATED_CACHE_NAMES.forEach(cacheName -> {
|
||||||
|
if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE) &&
|
||||||
|
(cacheName.equals(USER_SESSION_CACHE_NAME) || cacheName.equals(CLIENT_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_USER_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_CLIENT_SESSION_CACHE_NAME))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PersistenceConfigurationBuilder persistenceCB = builder.getNamedConfigurationBuilders().get(cacheName).persistence();
|
PersistenceConfigurationBuilder persistenceCB = builder.getNamedConfigurationBuilders().get(cacheName).persistence();
|
||||||
|
|
||||||
//if specified via command line -> cannot be defined in the xml file
|
//if specified via command line -> cannot be defined in the xml file
|
||||||
|
|
|
@ -198,7 +198,11 @@ public interface UserSessionProvider extends Provider {
|
||||||
*/
|
*/
|
||||||
Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, ClientModel client, Integer firstResult, Integer maxResults);
|
Stream<UserSessionModel> getOfflineUserSessionsStream(RealmModel realm, ClientModel client, Integer firstResult, Integer maxResults);
|
||||||
|
|
||||||
/** Triggered by persister during pre-load. It imports authenticatedClientSessions too **/
|
/** Triggered by persister during pre-load. It imports authenticatedClientSessions too.
|
||||||
|
*
|
||||||
|
* @deprecated Deprecated as offline session preloading was removed in KC25. This method will be removed in KC27.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline);
|
void importUserSessions(Collection<UserSessionModel> persistentUserSessions, boolean offline);
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
|
@ -20,3 +20,7 @@ RefreshTokenTest
|
||||||
OfflineTokenTest
|
OfflineTokenTest
|
||||||
AccessTokenTest
|
AccessTokenTest
|
||||||
LogoutTest
|
LogoutTest
|
||||||
|
ClientStorageTest
|
||||||
|
UserInfoTest
|
||||||
|
LightWeightAccessTokenTest
|
||||||
|
TokenIntrospectionTest
|
|
@ -335,6 +335,7 @@ public class OfflineSessionPersistenceTest extends KeycloakModelTest {
|
||||||
private String createOfflineClientSession(String offlineUserSessionId, String clientId) {
|
private String createOfflineClientSession(String offlineUserSessionId, String clientId) {
|
||||||
return withRealm(realmId, (session, realm) -> {
|
return withRealm(realmId, (session, realm) -> {
|
||||||
UserSessionModel offlineUserSession = session.sessions().getOfflineUserSession(realm, offlineUserSessionId);
|
UserSessionModel offlineUserSession = session.sessions().getOfflineUserSession(realm, offlineUserSessionId);
|
||||||
|
assertThat("Can't retrieve offline session for " + offlineUserSessionId, offlineUserSession, Matchers.notNullValue());
|
||||||
ClientModel client = session.clients().getClientById(realm, clientId);
|
ClientModel client = session.clients().getClientById(realm, clientId);
|
||||||
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, offlineUserSession);
|
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, offlineUserSession);
|
||||||
return session.sessions().createOfflineClientSession(clientSession, offlineUserSession).getId();
|
return session.sessions().createOfflineClientSession(clientSession, offlineUserSession).getId();
|
||||||
|
|
|
@ -481,6 +481,8 @@ public class UserSessionProviderOfflineModelTest extends KeycloakModelTest {
|
||||||
public void testOfflineSessionLifespanOverride() {
|
public void testOfflineSessionLifespanOverride() {
|
||||||
// skip the test for CrossDC
|
// skip the test for CrossDC
|
||||||
Assume.assumeFalse(Objects.equals(CONFIG.scope("connectionsInfinispan.default").get("remoteStoreEnabled"), "true"));
|
Assume.assumeFalse(Objects.equals(CONFIG.scope("connectionsInfinispan.default").get("remoteStoreEnabled"), "true"));
|
||||||
|
// As we don't put things in the embedded cache, the test will fail as the number of entries will always be 0.
|
||||||
|
Assume.assumeFalse(Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS_NO_CACHE));
|
||||||
|
|
||||||
createOfflineSessions("user1", 2, new LinkedList<>(), new LinkedList<>());
|
createOfflineSessions("user1", 2, new LinkedList<>(), new LinkedList<>());
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue