Skip to content

Commit

Permalink
KEYCLOAK-5895 CrossDC: NotSerializableException when opening sessions…
Browse files Browse the repository at this point in the history
… tab in admin console
  • Loading branch information
mposolda authored and hmlnarik committed Nov 23, 2017
1 parent 94ba85c commit 6d91ab6
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
import org.keycloak.models.utils.SessionTimeoutHelper;

import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -294,11 +295,6 @@ protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientM
Stream<UserSessionEntity> stream = cache.entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
.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());

if (firstResult > 0) {
Expand Down Expand Up @@ -393,19 +389,10 @@ protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolea
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
cache = CacheDecorators.skipCacheLoaders(cache);

Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = getClientSessionCache(offline);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCacheDecorated = CacheDecorators.skipCacheLoaders(clientSessionCache);

final String clientUuid = client.getId();

return cache.entrySet().stream()
.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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import javax.ws.rs.NotFoundException;
Expand Down Expand Up @@ -457,6 +459,75 @@ public void testLogoutUserWithFailover(
}


@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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,13 @@ public void testOfflineSessionsCrud() {
// Assert userSession revoked
testApp = realm.getClientByClientId("test-app");
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));

List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 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("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
Expand All @@ -160,6 +159,27 @@ public void testOfflineSessionsCrud() {
clients = sessionManager.findClientsWithOfflineToken(realm, user2);
Assert.assertEquals(1, clients.size());
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
Expand Down

0 comments on commit 6d91ab6

Please sign in to comment.