KEYCLOAK-5895 CrossDC: NotSerializableException when opening sessions tab in admin console
This commit is contained in:
parent
94ba85c210
commit
6d91ab674b
3 changed files with 97 additions and 19 deletions
|
@ -56,6 +56,7 @@ import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
|
||||||
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
|
||||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -294,11 +295,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
Stream<UserSessionEntity> stream = cache.entrySet().stream()
|
Stream<UserSessionEntity> stream = cache.entrySet().stream()
|
||||||
.filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
|
.filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
|
||||||
.map(Mappers.userSessionEntity())
|
.map(Mappers.userSessionEntity())
|
||||||
// Filter out client sessions that have been invalidated in the meantime
|
|
||||||
.filter(userSession -> {
|
|
||||||
final UUID clientSessionId = userSession.getAuthenticatedClientSessions().get(clientUuid);
|
|
||||||
return clientSessionId != null && clientSessionCacheDecorated.containsKey(clientSessionId);
|
|
||||||
})
|
|
||||||
.sorted(Comparators.userSessionLastSessionRefresh());
|
.sorted(Comparators.userSessionLastSessionRefresh());
|
||||||
|
|
||||||
if (firstResult > 0) {
|
if (firstResult > 0) {
|
||||||
|
@ -393,19 +389,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
|
||||||
cache = CacheDecorators.skipCacheLoaders(cache);
|
cache = CacheDecorators.skipCacheLoaders(cache);
|
||||||
|
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = getClientSessionCache(offline);
|
|
||||||
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCacheDecorated = CacheDecorators.skipCacheLoaders(clientSessionCache);
|
|
||||||
|
|
||||||
final String clientUuid = client.getId();
|
final String clientUuid = client.getId();
|
||||||
|
|
||||||
return cache.entrySet().stream()
|
return cache.entrySet().stream()
|
||||||
.filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
|
.filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
|
||||||
// Filter out client sessions that have been invalidated in the meantime
|
|
||||||
.map(Mappers.userSessionEntity())
|
|
||||||
.filter(userSession -> {
|
|
||||||
final UUID clientSessionId = userSession.getAuthenticatedClientSessions().get(clientUuid);
|
|
||||||
return clientSessionId != null && clientSessionCacheDecorated.containsKey(clientSessionId);
|
|
||||||
})
|
|
||||||
.count();
|
.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.keycloak.testsuite.crossdc;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
|
@ -457,6 +459,75 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogoutWithAllStartedNodes(
|
||||||
|
@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 every DC
|
||||||
|
startBackendNode(DC.FIRST, 1);
|
||||||
|
startBackendNode(DC.SECOND, 1);
|
||||||
|
|
||||||
|
// Create sessions. Don't include remote stats. Size is smaller because of distributed cache
|
||||||
|
List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
||||||
|
false, cacheDc1Statistics, cacheDc2Statistics, false);
|
||||||
|
|
||||||
|
// Simulate displaying sessions in admin console
|
||||||
|
Retry.execute(() -> {
|
||||||
|
assertTestAppActiveSessionsCount(SESSIONS_COUNT);
|
||||||
|
}, 50, 50);
|
||||||
|
|
||||||
|
|
||||||
|
// Logout realm and check sessions not anymore in admin console
|
||||||
|
getAdminClient().realm(REALM_NAME).logoutAll();
|
||||||
|
|
||||||
|
Retry.execute(() -> {
|
||||||
|
assertTestAppActiveSessionsCount(0);
|
||||||
|
}, 50, 50);
|
||||||
|
|
||||||
|
|
||||||
|
// Login again and check sessions back in
|
||||||
|
responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
||||||
|
false, cacheDc1Statistics, cacheDc2Statistics, false);
|
||||||
|
|
||||||
|
Retry.execute(() -> {
|
||||||
|
assertTestAppActiveSessionsCount(SESSIONS_COUNT);
|
||||||
|
}, 50, 50);
|
||||||
|
|
||||||
|
|
||||||
|
// Logout user and check sessions not anymore in admin console
|
||||||
|
ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").logout();
|
||||||
|
|
||||||
|
Retry.execute(() -> {
|
||||||
|
assertTestAppActiveSessionsCount(0);
|
||||||
|
}, 50, 50);
|
||||||
|
|
||||||
|
// Stop both nodes
|
||||||
|
stopBackendNode(DC.FIRST, 1);
|
||||||
|
stopBackendNode(DC.SECOND, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTestAppActiveSessionsCount(int expectedSessionsCount) {
|
||||||
|
List<Map<String, String>> sessions = getAdminClient().realm(REALM_NAME).getClientSessionStats();
|
||||||
|
|
||||||
|
Optional<Map<String, String>> optional = sessions.stream().filter((Map<String, String> map) -> {
|
||||||
|
return map.get("clientId").equals("test-app");
|
||||||
|
}).findFirst();
|
||||||
|
|
||||||
|
if (expectedSessionsCount == 0) {
|
||||||
|
// No sessions present. Statistics for the client not included
|
||||||
|
Assert.assertFalse(optional.isPresent());
|
||||||
|
} else {
|
||||||
|
Map<String, String> testAppSessions = optional.get();
|
||||||
|
Assert.assertEquals(expectedSessionsCount, Integer.parseInt(testAppSessions.get("active")));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UserSessionRepresentation> userSessions = ApiUtil.findClientByClientId(getAdminClient().realm(REALM_NAME), "test-app").getUserSessions(0, 100);
|
||||||
|
Assert.assertEquals(expectedSessionsCount, userSessions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// AUTH SESSIONS
|
// AUTH SESSIONS
|
||||||
|
|
||||||
|
|
|
@ -140,14 +140,13 @@ public class UserSessionProviderOfflineTest {
|
||||||
// Assert userSession revoked
|
// Assert userSession revoked
|
||||||
testApp = realm.getClientByClientId("test-app");
|
testApp = realm.getClientByClientId("test-app");
|
||||||
thirdparty = realm.getClientByClientId("third-party");
|
thirdparty = realm.getClientByClientId("third-party");
|
||||||
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
|
|
||||||
|
// Still 2 sessions. The count of sessions by client may not be accurate after revoke due the
|
||||||
|
// performance optimizations (the "127.0.0.1" session still has another client "thirdparty" in it)
|
||||||
|
Assert.assertEquals(2, session.sessions().getOfflineSessionsCount(realm, testApp));
|
||||||
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
|
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
|
||||||
|
|
||||||
List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
|
|
||||||
List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
|
List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
|
||||||
Assert.assertEquals(1, testAppSessions.size());
|
|
||||||
Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
|
|
||||||
Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
|
|
||||||
Assert.assertEquals(1, thirdpartySessions.size());
|
Assert.assertEquals(1, thirdpartySessions.size());
|
||||||
Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
|
Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
|
||||||
Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
|
Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
|
||||||
|
@ -160,6 +159,27 @@ public class UserSessionProviderOfflineTest {
|
||||||
clients = sessionManager.findClientsWithOfflineToken(realm, user2);
|
clients = sessionManager.findClientsWithOfflineToken(realm, user2);
|
||||||
Assert.assertEquals(1, clients.size());
|
Assert.assertEquals(1, clients.size());
|
||||||
Assert.assertEquals("test-app", clients.iterator().next().getClientId());
|
Assert.assertEquals("test-app", clients.iterator().next().getClientId());
|
||||||
|
|
||||||
|
// Revoke the second session for user1 too.
|
||||||
|
sessionManager.revokeOfflineToken(user1, thirdparty);
|
||||||
|
|
||||||
|
resetSession();
|
||||||
|
|
||||||
|
testApp = realm.getClientByClientId("test-app");
|
||||||
|
thirdparty = realm.getClientByClientId("third-party");
|
||||||
|
|
||||||
|
// Accurate count now. All sessions of user1 cleared
|
||||||
|
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
|
||||||
|
Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
|
||||||
|
|
||||||
|
List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
|
||||||
|
|
||||||
|
Assert.assertEquals(1, testAppSessions.size());
|
||||||
|
Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
|
||||||
|
Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
|
||||||
|
|
||||||
|
clients = sessionManager.findClientsWithOfflineToken(realm, user1);
|
||||||
|
Assert.assertEquals(0, clients.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue