KEYCLOAK-5895 CrossDC: NotSerializableException when opening sessions tab in admin console

This commit is contained in:
mposolda 2017-11-23 16:38:24 +01:00 committed by Hynek Mlnařík
parent 94ba85c210
commit 6d91ab674b
3 changed files with 97 additions and 19 deletions

View file

@ -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();
} }

View file

@ -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

View file

@ -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