Merge pull request #4610 from hmlnarik/KEYCLOAK-5745-Extract-client-sessions-from-user-sessions

KEYCLOAK-5745 Separate user and client sessions in infinispan
This commit is contained in:
Hynek Mlnařík 2017-10-26 13:09:06 +02:00 committed by GitHub
commit 248da4687a
61 changed files with 919 additions and 443 deletions

View file

@ -91,6 +91,8 @@
<local-cache name="sessions"/> <local-cache name="sessions"/>
<local-cache name="authenticationSessions"/> <local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/> <local-cache name="offlineSessions"/>
<local-cache name="clientSessions"/>
<local-cache name="offlineClientSessions"/>
<local-cache name="loginFailures"/> <local-cache name="loginFailures"/>
<local-cache name="authorization"> <local-cache name="authorization">
<eviction max-entries="10000" strategy="LRU"/> <eviction max-entries="10000" strategy="LRU"/>

View file

@ -398,4 +398,16 @@ if (outcome == success) of /profile=$clusteredProfile/subsystem=jsf/:read-resour
end-try end-try
end-if end-if
if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/:read-resource
echo Adding distributed-cache=offlineClientSessions to keycloak cache container...
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/:add(mode=SYNC,owners=1)
echo
end-if
if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/:read-resource
echo Adding distributed-cache=clientSessions to keycloak cache container...
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/:add(mode=SYNC,owners=1)
echo
end-if
echo *** End Migration of /profile=$clusteredProfile *** echo *** End Migration of /profile=$clusteredProfile ***

View file

@ -361,4 +361,16 @@ if (outcome == success) of /profile=$standaloneProfile/subsystem=jsf/:read-resou
end-try end-try
end-if end-if
if (outcome == failed) of /profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=clientSessions/:read-resource
echo Adding local-cache=clientSessions to keycloak cache container...
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=clientSessions/:add(indexing=NONE,start=LAZY)
echo
end-if
if (outcome == failed) of /profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=offlineClientSessions/:read-resource
echo Adding local-cache=offlineClientSessions to keycloak cache container...
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=offlineClientSessions/:add(indexing=NONE,start=LAZY)
echo
end-if
echo *** End Migration of /profile=$standaloneProfile *** echo *** End Migration of /profile=$standaloneProfile ***

View file

@ -382,4 +382,16 @@ if (outcome == success) of /subsystem=jsf/:read-resource
echo echo
end-if end-if
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/:read-resource
echo Adding distributed-cache=clientSessions to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/:add(mode=SYNC,owners=1)
echo
end-if
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/:read-resource
echo Adding distributed-cache=offlineClientSessions to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/:add(mode=SYNC,owners=1)
echo
end-if
echo *** End Migration *** echo *** End Migration ***

View file

@ -350,4 +350,16 @@ if (outcome == success) of /subsystem=jsf/:read-resource
echo echo
end-if end-if
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-cache=offlineClientSessions/:read-resource
echo Adding local-cache=offlineClientSessions to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/local-cache=offlineClientSessions/:add(indexing=NONE,start=LAZY)
echo
end-if
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-cache=clientSessions/:read-resource
echo Adding local-cache=clientSessions to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/local-cache=clientSessions/:add(indexing=NONE,start=LAZY)
echo
end-if
echo *** End Migration *** echo *** End Migration ***

View file

@ -9,6 +9,8 @@ embed-server --server-config=standalone-ha.xml
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add() /subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=10000,strategy=LRU) /subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=10000,strategy=LRU)

View file

@ -188,7 +188,8 @@ Keycloak servers setup
</distributed-cache> </distributed-cache>
``` ```
3.6) Same for `offlineSessions`, `loginFailures`, and `actionTokens` caches (the only difference from `sessions` cache is that `cache` property value are different): 3.6) Same for `offlineSessions`, `clientSessions`, `offlineClientSessions`, `loginFailures`, and `actionTokens` caches (the only difference
from `sessions` cache is that `cache` property value are different):
```xml ```xml
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"> <distributed-cache name="offlineSessions" mode="SYNC" owners="1">
@ -198,6 +199,20 @@ Keycloak servers setup
</remote-store> </remote-store>
</distributed-cache> </distributed-cache>
<distributed-cache name="clientSessions" mode="SYNC" owners="1">
<remote-store cache="clientSessions" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
<property name="rawValues">true</property>
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
</remote-store>
</distributed-cache>
<distributed-cache name="offlineClientSessions" mode="SYNC" owners="1">
<remote-store cache="offlineClientSessions" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
<property name="rawValues">true</property>
<property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
</remote-store>
</distributed-cache>
<distributed-cache name="loginFailures" mode="SYNC" owners="1"> <distributed-cache name="loginFailures" mode="SYNC" owners="1">
<remote-store cache="loginFailures" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true"> <remote-store cache="loginFailures" remote-servers="remote-cache" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">
<property name="rawValues">true</property> <property name="rawValues">true</property>

View file

@ -245,18 +245,34 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (jdgEnabled) { if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder(); sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase); sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.SESSION_CACHE_NAME, true); configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, true);
} }
Configuration sessionCacheConfiguration = sessionConfigBuilder.build(); Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration); cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, sessionCacheConfiguration);
if (jdgEnabled) { if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder(); sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase); sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true); configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, true);
} }
sessionCacheConfiguration = sessionConfigBuilder.build(); sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration); cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, sessionCacheConfiguration);
if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, true);
}
sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, sessionCacheConfiguration);
if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder();
sessionConfigBuilder.read(sessionCacheConfigurationBase);
configureRemoteCacheStore(sessionConfigBuilder, async, InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME, true);
}
sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME, sessionCacheConfiguration);
if (jdgEnabled) { if (jdgEnabled) {
sessionConfigBuilder = new ConfigurationBuilder(); sessionConfigBuilder = new ConfigurationBuilder();
@ -269,8 +285,10 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfigurationBase); cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfigurationBase);
// Retrieve caches to enforce rebalance // Retrieve caches to enforce rebalance
cacheManager.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME, true); cacheManager.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true); cacheManager.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, true); cacheManager.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true); cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);

View file

@ -33,8 +33,10 @@ public interface InfinispanConnectionProvider extends Provider {
String USER_REVISIONS_CACHE_NAME = "userRevisions"; String USER_REVISIONS_CACHE_NAME = "userRevisions";
int USER_REVISIONS_CACHE_DEFAULT_MAX = 100000; int USER_REVISIONS_CACHE_DEFAULT_MAX = 100000;
String SESSION_CACHE_NAME = "sessions"; String USER_SESSION_CACHE_NAME = "sessions";
String OFFLINE_SESSION_CACHE_NAME = "offlineSessions"; String CLIENT_SESSION_CACHE_NAME = "clientSessions";
String OFFLINE_USER_SESSION_CACHE_NAME = "offlineSessions";
String OFFLINE_CLIENT_SESSION_CACHE_NAME = "offlineClientSessions";
String LOGIN_FAILURE_CACHE_NAME = "loginFailures"; String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
String AUTHENTICATION_SESSIONS_CACHE_NAME = "authenticationSessions"; String AUTHENTICATION_SESSIONS_CACHE_NAME = "authenticationSessions";
String WORK_CACHE_NAME = "work"; String WORK_CACHE_NAME = "work";

View file

@ -28,10 +28,15 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction; import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.changes.UserSessionClientSessionUpdateTask; 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.UserSessionUpdateTask;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.UUID;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -40,56 +45,48 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
private AuthenticatedClientSessionEntity entity; private AuthenticatedClientSessionEntity entity;
private final ClientModel client; private final ClientModel client;
private final InfinispanUserSessionProvider provider; private final InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx;
private final InfinispanChangelogBasedTransaction updateTx; private final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
private UserSessionAdapter userSession; private UserSessionModel userSession;
public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client,
UserSessionModel userSession,
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx) {
if (userSession == null) {
throw new NullPointerException("userSession must not be null");
}
public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionAdapter userSession,
InfinispanUserSessionProvider provider, InfinispanChangelogBasedTransaction updateTx) {
this.provider = provider;
this.entity = entity; this.entity = entity;
this.client = client;
this.updateTx = updateTx;
this.userSession = userSession; this.userSession = userSession;
this.client = client;
this.userSessionUpdateTx = userSessionUpdateTx;
this.clientSessionUpdateTx = clientSessionUpdateTx;
} }
private void update(UserSessionUpdateTask task) { private void update(UserSessionUpdateTask task) {
updateTx.addTask(userSession.getId(), task); userSessionUpdateTx.addTask(userSession.getId(), task);
} }
private void update(ClientSessionUpdateTask task) {
clientSessionUpdateTx.addTask(entity.getId(), task);
}
/**
* Detaches the client session from its user session.
* <p>
* <b>This method does not delete the client session from user session records, it only removes the client session.</b>
* The list of client sessions within user session is updated lazily for performance reasons.
*/
@Override @Override
public void setUserSession(UserSessionModel userSession) { public void detachFromUserSession() {
String clientUUID = client.getId(); // Intentionally do not remove the clientUUID from the user session, invalid session is handled
// as nonexistent in org.keycloak.models.sessions.infinispan.UserSessionAdapter.getAuthenticatedClientSessions()
this.userSession = null;
// Dettach userSession SessionUpdateTask<AuthenticatedClientSessionEntity> removeTask = Tasks.removeSync();
if (userSession == null) {
UserSessionUpdateTask task = new UserSessionUpdateTask() {
@Override clientSessionUpdateTx.addTask(entity.getId(), removeTask);
public void runUpdate(UserSessionEntity sessionEntity) {
sessionEntity.getAuthenticatedClientSessions().remove(clientUUID);
}
};
update(task);
this.userSession = null;
} else {
this.userSession = (UserSessionAdapter) userSession;
UserSessionUpdateTask task = new UserSessionUpdateTask() {
@Override
public void runUpdate(UserSessionEntity sessionEntity) {
AuthenticatedClientSessionEntity current = sessionEntity.getAuthenticatedClientSessions().putIfAbsent(clientUUID, entity);
if (current != null) {
// It may happen when 2 concurrent HTTP requests trying SSO login against same client
entity = current;
}
}
};
update(task);
}
} }
@Override @Override
@ -104,10 +101,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setRedirectUri(String uri) { public void setRedirectUri(String uri) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setRedirectUri(uri); entity.setRedirectUri(uri);
} }
@ -138,19 +135,12 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setTimestamp(int timestamp) { public void setTimestamp(int timestamp) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setTimestamp(timestamp); entity.setTimestamp(timestamp);
} }
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
// We usually update lastSessionRefresh at the same time. That would handle it.
return CrossDCMessageStatus.NOT_NEEDED;
}
}; };
update(task); update(task);
@ -163,19 +153,12 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) { public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setCurrentRefreshTokenUseCount(currentRefreshTokenUseCount); entity.setCurrentRefreshTokenUseCount(currentRefreshTokenUseCount);
} }
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
// We usually update lastSessionRefresh at the same time. That would handle it.
return CrossDCMessageStatus.NOT_NEEDED;
}
}; };
update(task); update(task);
@ -188,19 +171,12 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setCurrentRefreshToken(String currentRefreshToken) { public void setCurrentRefreshToken(String currentRefreshToken) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setCurrentRefreshToken(currentRefreshToken); entity.setCurrentRefreshToken(currentRefreshToken);
} }
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
// We usually update lastSessionRefresh at the same time. That would handle it.
return CrossDCMessageStatus.NOT_NEEDED;
}
}; };
update(task); update(task);
@ -213,10 +189,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setAction(String action) { public void setAction(String action) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setAction(action); entity.setAction(action);
} }
@ -232,10 +208,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setProtocol(String method) { public void setProtocol(String method) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setAuthMethod(method); entity.setAuthMethod(method);
} }
@ -251,10 +227,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setRoles(Set<String> roles) { public void setRoles(Set<String> roles) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setRoles(roles); // TODO not thread-safe. But we will remove setRoles anyway...? entity.setRoles(roles); // TODO not thread-safe. But we will remove setRoles anyway...?
} }
@ -270,10 +246,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setProtocolMappers(Set<String> protocolMappers) { public void setProtocolMappers(Set<String> protocolMappers) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.setProtocolMappers(protocolMappers); // TODO not thread-safe. But we will remove setProtocolMappers anyway...? entity.setProtocolMappers(protocolMappers); // TODO not thread-safe. But we will remove setProtocolMappers anyway...?
} }
@ -289,10 +265,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void setNote(String name, String value) { public void setNote(String name, String value) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.getNotes().put(name, value); entity.getNotes().put(name, value);
} }
@ -303,10 +279,10 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override @Override
public void removeNote(String name) { public void removeNote(String name) {
UserSessionClientSessionUpdateTask task = new UserSessionClientSessionUpdateTask(client.getId()) { ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
@Override @Override
protected void runClientSessionUpdate(AuthenticatedClientSessionEntity entity) { public void runUpdate(AuthenticatedClientSessionEntity entity) {
entity.getNotes().remove(name); entity.getNotes().remove(name);
} }

View file

@ -23,7 +23,6 @@ import org.infinispan.context.Flag;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -33,12 +32,16 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider; import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider; import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.changes.Tasks;
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStore; import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStore;
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker; import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction; import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
import org.keycloak.models.sessions.infinispan.changes.SessionUpdateTask; 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.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity; import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey; import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@ -58,8 +61,10 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -75,10 +80,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache; protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache;
protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache; protected final Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache;
protected final Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache;
protected final Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache;
protected final Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache; protected final Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache;
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx; protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> sessionTx;
protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx; protected final InfinispanChangelogBasedTransaction<String, UserSessionEntity> offlineSessionTx;
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionTx;
protected final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> offlineClientSessionTx;
protected final InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> loginFailuresTx; protected final InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> loginFailuresTx;
protected final SessionEventsSenderTransaction clusterEventsSenderTx; protected final SessionEventsSenderTransaction clusterEventsSenderTx;
@ -92,17 +101,23 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
LastSessionRefreshStore offlineLastSessionRefreshStore, LastSessionRefreshStore offlineLastSessionRefreshStore,
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache, Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionCache,
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache, Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionCache,
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache,
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionCache,
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache) { Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailureCache) {
this.session = session; this.session = session;
this.sessionCache = sessionCache; this.sessionCache = sessionCache;
this.clientSessionCache = clientSessionCache;
this.offlineSessionCache = offlineSessionCache; this.offlineSessionCache = offlineSessionCache;
this.offlineClientSessionCache = offlineClientSessionCache;
this.loginFailureCache = loginFailureCache; this.loginFailureCache = loginFailureCache;
this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCache, remoteCacheInvoker); this.sessionTx = new InfinispanChangelogBasedTransaction<>(session, sessionCache, remoteCacheInvoker);
this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionCache, remoteCacheInvoker); this.offlineSessionTx = new InfinispanChangelogBasedTransaction<>(session, offlineSessionCache, remoteCacheInvoker);
this.clientSessionTx = new InfinispanChangelogBasedTransaction<>(session, clientSessionCache, remoteCacheInvoker);
this.offlineClientSessionTx = new InfinispanChangelogBasedTransaction<>(session, offlineClientSessionCache, remoteCacheInvoker);
this.loginFailuresTx = new InfinispanChangelogBasedTransaction<>(session, InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, loginFailureCache, remoteCacheInvoker); this.loginFailuresTx = new InfinispanChangelogBasedTransaction<>(session, loginFailureCache, remoteCacheInvoker);
this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session); this.clusterEventsSenderTx = new SessionEventsSenderTransaction(session);
@ -112,6 +127,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx); session.getTransactionManager().enlistAfterCompletion(clusterEventsSenderTx);
session.getTransactionManager().enlistAfterCompletion(sessionTx); session.getTransactionManager().enlistAfterCompletion(sessionTx);
session.getTransactionManager().enlistAfterCompletion(offlineSessionTx); session.getTransactionManager().enlistAfterCompletion(offlineSessionTx);
session.getTransactionManager().enlistAfterCompletion(clientSessionTx);
session.getTransactionManager().enlistAfterCompletion(offlineClientSessionTx);
session.getTransactionManager().enlistAfterCompletion(loginFailuresTx); session.getTransactionManager().enlistAfterCompletion(loginFailuresTx);
} }
@ -123,6 +140,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return offline ? offlineSessionTx : sessionTx; return offline ? offlineSessionTx : sessionTx;
} }
protected Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> getClientSessionCache(boolean offline) {
return offline ? offlineClientSessionCache : clientSessionCache;
}
protected InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> getClientSessionTransaction(boolean offline) {
return offline ? offlineClientSessionTx : clientSessionTx;
}
protected LastSessionRefreshStore getLastSessionRefreshStore() { protected LastSessionRefreshStore getLastSessionRefreshStore() {
return lastSessionRefreshStore; return lastSessionRefreshStore;
} }
@ -134,10 +159,20 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override @Override
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) { public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(); AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setRealmId(realm.getId());
final UUID clientSessionId = entity.getId();
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(false);
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(false);
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession,
userSessionUpdateTx, clientSessionUpdateTx);
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
clientSessionUpdateTx.addTask(clientSessionId, createClientSessionTask, entity);
SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(client.getId(), clientSessionId);
userSessionUpdateTx.addTask(userSession.getId(), registerClientSessionTask);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> updateTx = getTransaction(false);
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, updateTx);
adapter.setUserSession(userSession);
return adapter; return adapter;
} }
@ -147,25 +182,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setId(id); entity.setId(id);
updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId); updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
SessionUpdateTask<UserSessionEntity> createSessionTask = new SessionUpdateTask<UserSessionEntity>() { SessionUpdateTask<UserSessionEntity> createSessionTask = Tasks.addIfAbsentSync();
@Override
public void runUpdate(UserSessionEntity session) {
}
@Override
public CacheOperation getOperation(UserSessionEntity session) {
return CacheOperation.ADD_IF_ABSENT;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
sessionTx.addTask(id, createSessionTask, entity); sessionTx.addTask(id, createSessionTask, entity);
return wrap(realm, entity, false); return wrap(realm, entity, false);
@ -228,6 +245,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return resultSessions; return resultSessions;
} }
@Override
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline) {
AuthenticatedClientSessionEntity entity = getClientSessionEntity(clientSessionId, offline);
return wrap(userSession, client, entity, offline);
}
private AuthenticatedClientSessionEntity getClientSessionEntity(UUID id, boolean offline) {
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> tx = getClientSessionTransaction(offline);
SessionEntityWrapper<AuthenticatedClientSessionEntity> entityWrapper = tx.get(id);
return entityWrapper == null ? null : entityWrapper.getEntity();
}
@Override @Override
public List<UserSessionModel> getUserSessions(final RealmModel realm, UserModel user) { public List<UserSessionModel> getUserSessions(final RealmModel realm, UserModel user) {
return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), false); return getUserSessions(realm, UserSessionPredicate.create(realm.getId()).user(user.getId()), false);
@ -256,12 +286,21 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) { protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
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();
Stream<UserSessionEntity> stream = cache.entrySet().stream() Stream<UserSessionEntity> stream = cache.entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId())) .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) {
@ -356,8 +395,19 @@ 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();
return cache.entrySet().stream() return cache.entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId())) .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();
} }
@ -402,28 +452,38 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
// Each cluster node cleanups just local sessions, which are those owned by itself (+ few more taking l1 cache into account) // 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<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(sessionCache);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(offlineClientSessionCache);
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache); Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
final AtomicInteger userSessionsSize = new AtomicInteger();
// Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate // Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate
localCacheStoreIgnore localCacheStoreIgnore
.entrySet() .entrySet()
.stream() .stream()
.filter(UserSessionPredicate.create(realm.getId()).expired(expired, expiredRefresh)) .filter(UserSessionPredicate.create(realm.getId()).expired(expired, expiredRefresh))
.map(Mappers.sessionId()) .map(Mappers.userSessionEntity())
.forEach(new Consumer<String>() { .forEach(new Consumer<UserSessionEntity>() {
@Override @Override
public void accept(String sessionId) { public void accept(UserSessionEntity userSessionEntity) {
Future future = localCache.removeAsync(sessionId); userSessionsSize.incrementAndGet();
Future future = localCache.removeAsync(userSessionEntity.getId());
futures.addTask(future); futures.addTask(future);
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
Future f = localClientSessionCache.removeAsync(clientSessionId);
futures.addTask(f);
});
} }
}); });
futures.waitForAllToFinish(); futures.waitForAllToFinish();
log.debugf("Removed %d expired user sessions for realm '%s'", futures.size(), realm.getName()); log.debugf("Removed %d expired user sessions for realm '%s'", userSessionsSize.get(), realm.getName());
} }
private void removeExpiredOfflineUserSessions(RealmModel realm) { private void removeExpiredOfflineUserSessions(RealmModel realm) {
@ -432,6 +492,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account) // 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); Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(offlineSessionCache);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(offlineClientSessionCache);
UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).expired(null, expiredOffline); UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).expired(null, expiredOffline);
@ -439,6 +500,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache); Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
final AtomicInteger userSessionsSize = new AtomicInteger();
// Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate // Ignore remoteStore for stream iteration. But we will invoke remoteStore for userSession removal propagate
localCacheStoreIgnore localCacheStoreIgnore
.entrySet() .entrySet()
@ -449,8 +512,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override @Override
public void accept(UserSessionEntity userSessionEntity) { public void accept(UserSessionEntity userSessionEntity) {
userSessionsSize.incrementAndGet();
Future future = localCache.removeAsync(userSessionEntity.getId()); Future future = localCache.removeAsync(userSessionEntity.getId());
futures.addTask(future); futures.addTask(future);
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
Future f = localClientSessionCache.removeAsync(clientSessionId);
futures.addTask(f);
});
// TODO:mposolda can be likely optimized to delete all expired at one step // TODO:mposolda can be likely optimized to delete all expired at one step
persister.removeUserSession( userSessionEntity.getId(), true); persister.removeUserSession( userSessionEntity.getId(), true);
@ -464,7 +533,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
futures.waitForAllToFinish(); futures.waitForAllToFinish();
log.debugf("Removed %d expired offline user sessions for realm '%s'", futures.size(), realm.getName()); log.debugf("Removed %d expired offline user sessions for realm '%s'", userSessionsSize.get(), realm.getName());
} }
@Override @Override
@ -484,28 +553,39 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(cache); Cache<String, SessionEntityWrapper<UserSessionEntity>> localCache = CacheDecorators.localCache(cache);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = getClientSessionCache(offline);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> localClientSessionCache = CacheDecorators.localCache(clientSessionCache);
Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache); Cache<String, SessionEntityWrapper<UserSessionEntity>> localCacheStoreIgnore = CacheDecorators.skipCacheLoaders(localCache);
final AtomicInteger userSessionsSize = new AtomicInteger();
localCacheStoreIgnore localCacheStoreIgnore
.entrySet() .entrySet()
.stream() .stream()
.filter(SessionPredicate.create(realmId)) .filter(SessionPredicate.create(realmId))
.map(Mappers.sessionId()) .map(Mappers.userSessionEntity())
.forEach(new Consumer<String>() { .forEach(new Consumer<UserSessionEntity>() {
@Override @Override
public void accept(String sessionId) { public void accept(UserSessionEntity userSessionEntity) {
userSessionsSize.incrementAndGet();
// Remove session from remoteCache too. Use removeAsync for better perf // Remove session from remoteCache too. Use removeAsync for better perf
Future future = localCache.removeAsync(sessionId); Future future = localCache.removeAsync(userSessionEntity.getId());
futures.addTask(future); futures.addTask(future);
userSessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> {
Future f = localClientSessionCache.removeAsync(clientSessionId);
futures.addTask(f);
});
} }
}); });
futures.waitForAllToFinish(); futures.waitForAllToFinish();
log.debugf("Removed %d sessions in realm %s. Offline: %b", (Object) futures.size(), realmId, offline); log.debugf("Removed %d sessions in realm %s. Offline: %b", (Object) userSessionsSize.get(), realmId, offline);
} }
@Override @Override
@ -528,25 +608,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setRealmId(realm.getId()); entity.setRealmId(realm.getId());
entity.setUserId(userId); entity.setUserId(userId);
SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = new SessionUpdateTask<LoginFailureEntity>() { SessionUpdateTask<LoginFailureEntity> createLoginFailureTask = Tasks.addIfAbsentSync();
@Override
public void runUpdate(LoginFailureEntity session) {
}
@Override
public CacheOperation getOperation(LoginFailureEntity session) {
return CacheOperation.ADD_IF_ABSENT;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
loginFailuresTx.addTask(key, createLoginFailureTask, entity); loginFailuresTx.addTask(key, createLoginFailureTask, entity);
return wrap(key, entity); return wrap(key, entity);
@ -554,25 +616,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override @Override
public void removeUserLoginFailure(RealmModel realm, String userId) { public void removeUserLoginFailure(RealmModel realm, String userId) {
SessionUpdateTask<LoginFailureEntity> removeTask = new SessionUpdateTask<LoginFailureEntity>() { SessionUpdateTask<LoginFailureEntity> removeTask = Tasks.removeSync();
@Override
public void runUpdate(LoginFailureEntity entity) {
}
@Override
public CacheOperation getOperation(LoginFailureEntity entity) {
return CacheOperation.REMOVE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<LoginFailureEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask); loginFailuresTx.addTask(new LoginFailureKey(realm.getId(), userId), removeTask);
} }
@ -648,28 +692,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
} }
protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) { protected void removeUserSession(UserSessionEntity sessionEntity, boolean offline) {
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline); InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
SessionUpdateTask<UserSessionEntity> removeTask = new SessionUpdateTask<UserSessionEntity>() { sessionEntity.getAuthenticatedClientSessions().forEach((clientUUID, clientSessionId) -> clientSessionUpdateTx.addTask(clientSessionId, Tasks.removeSync()));
SessionUpdateTask<UserSessionEntity> removeTask = Tasks.removeSync();
@Override userSessionUpdateTx.addTask(sessionEntity.getId(), removeTask);
public void runUpdate(UserSessionEntity entity) {
}
@Override
public CacheOperation getOperation(UserSessionEntity entity) {
return CacheOperation.REMOVE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
tx.addTask(sessionEntity.getId(), removeTask);
} }
InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> getLoginFailuresTx() { InfinispanChangelogBasedTransaction<LoginFailureKey, LoginFailureEntity> getLoginFailuresTx() {
@ -677,8 +704,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
} }
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) { UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline); InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
return entity != null ? new UserSessionAdapter(session, this, tx, realm, entity, offline) : null; InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
return entity != null ? new UserSessionAdapter(session, this, userSessionUpdateTx, clientSessionUpdateTx, realm, entity, offline) : null;
}
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;
} }
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) { UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
@ -726,7 +760,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession : UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId()); getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession, getTransaction(true)); InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(true);
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(true);
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession, userSessionUpdateTx, clientSessionUpdateTx);
// update timestamp to current time // update timestamp to current time
offlineClientSession.setTimestamp(Time.currentTime()); offlineClientSession.setTimestamp(Time.currentTime());
@ -776,7 +812,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setIpAddress(userSession.getIpAddress()); entity.setIpAddress(userSession.getIpAddress());
entity.setLoginUsername(userSession.getLoginUsername()); entity.setLoginUsername(userSession.getLoginUsername());
entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes()); entity.setNotes(userSession.getNotes() == null ? new ConcurrentHashMap<>() : userSession.getNotes());
entity.setAuthenticatedClientSessions(new ConcurrentHashMap<>()); entity.setAuthenticatedClientSessions(new AuthenticatedClientSessionStore());
entity.setRememberMe(userSession.isRememberMe()); entity.setRememberMe(userSession.isRememberMe());
entity.setState(userSession.getState()); entity.setState(userSession.getState());
entity.setUser(userSession.getUser().getId()); entity.setUser(userSession.getUser().getId());
@ -784,35 +820,18 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(userSession.getStarted()); entity.setStarted(userSession.getStarted());
entity.setLastSessionRefresh(userSession.getLastSessionRefresh()); entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
InfinispanChangelogBasedTransaction<String, UserSessionEntity> tx = getTransaction(offline); SessionUpdateTask<UserSessionEntity> importTask = Tasks.addIfAbsentSync();
userSessionUpdateTx.addTask(userSession.getId(), importTask, entity);
SessionUpdateTask importTask = new SessionUpdateTask<UserSessionEntity>() {
@Override
public void runUpdate(UserSessionEntity session) {
}
@Override
public CacheOperation getOperation(UserSessionEntity session) {
return CacheOperation.ADD_IF_ABSENT;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
tx.addTask(userSession.getId(), importTask, entity);
UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline); UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline);
// Handle client sessions // Handle client sessions
if (importAuthenticatedClientSessions) { if (importAuthenticatedClientSessions) {
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) { for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
importClientSession(importedSession, clientSession, tx); importClientSession(importedSession, clientSession, userSessionUpdateTx, clientSessionUpdateTx);
} }
} }
@ -820,9 +839,12 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
} }
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession, private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter sessionToImportInto, AuthenticatedClientSessionModel clientSession,
InfinispanChangelogBasedTransaction<String, UserSessionEntity> updateTx) { InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity(); AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setRealmId(sessionToImportInto.getRealm().getId());
final UUID clientSessionId = entity.getId();
entity.setAction(clientSession.getAction()); entity.setAction(clientSession.getAction());
entity.setAuthMethod(clientSession.getProtocol()); entity.setAuthMethod(clientSession.getProtocol());
@ -833,33 +855,43 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setRoles(clientSession.getRoles()); entity.setRoles(clientSession.getRoles());
entity.setTimestamp(clientSession.getTimestamp()); entity.setTimestamp(clientSession.getTimestamp());
SessionUpdateTask<AuthenticatedClientSessionEntity> createClientSessionTask = Tasks.addIfAbsentSync();
clientSessionUpdateTx.addTask(entity.getId(), createClientSessionTask, entity);
Map<String, AuthenticatedClientSessionEntity> clientSessions = importedUserSession.getEntity().getAuthenticatedClientSessions(); AuthenticatedClientSessionStore clientSessions = sessionToImportInto.getEntity().getAuthenticatedClientSessions();
clientSessions.put(clientSession.getClient().getId(), clientSessionId);
clientSessions.put(clientSession.getClient().getId(), entity); SessionUpdateTask registerClientSessionTask = new RegisterClientSessionTask(clientSession.getClient().getId(), clientSessionId);
userSessionUpdateTx.addTask(sessionToImportInto.getId(), registerClientSessionTask);
SessionUpdateTask importTask = new SessionUpdateTask<UserSessionEntity>() { return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), sessionToImportInto, userSessionUpdateTx, clientSessionUpdateTx);
}
@Override private static class RegisterClientSessionTask implements SessionUpdateTask<UserSessionEntity> {
public void runUpdate(UserSessionEntity session) {
Map<String, AuthenticatedClientSessionEntity> clientSessions = session.getAuthenticatedClientSessions();
clientSessions.put(clientSession.getClient().getId(), entity);
}
@Override private final String clientUuid;
public CacheOperation getOperation(UserSessionEntity session) { private final UUID clientSessionId;
return CacheOperation.REPLACE;
}
@Override public RegisterClientSessionTask(String clientUuid, UUID clientSessionId) {
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) { this.clientUuid = clientUuid;
return CrossDCMessageStatus.SYNC; this.clientSessionId = clientSessionId;
} }
}; @Override
updateTx.addTask(importedUserSession.getId(), importTask); public void runUpdate(UserSessionEntity session) {
AuthenticatedClientSessionStore clientSessions = session.getAuthenticatedClientSessions();
clientSessions.put(clientUuid, clientSessionId);
}
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, updateTx); @Override
public CacheOperation getOperation(UserSessionEntity session) {
return CacheOperation.REPLACE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<UserSessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
} }
} }

View file

@ -37,6 +37,7 @@ import org.keycloak.models.sessions.infinispan.initializer.CacheInitializer;
import org.keycloak.models.sessions.infinispan.initializer.DBLockBasedCacheInitializer; import org.keycloak.models.sessions.infinispan.initializer.DBLockBasedCacheInitializer;
import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker; import org.keycloak.models.sessions.infinispan.remotestore.RemoteCacheInvoker;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity; import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey; import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
@ -58,6 +59,7 @@ import org.keycloak.provider.ProviderEventListener;
import java.io.Serializable; import java.io.Serializable;
import java.util.Set; import java.util.Set;
import java.util.UUID;
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory { public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
@ -82,11 +84,14 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
@Override @Override
public InfinispanUserSessionProvider create(KeycloakSession session) { public InfinispanUserSessionProvider create(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class); InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = connections.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = connections.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = connections.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionsCache = connections.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME); Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailures = connections.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore, cache, offlineSessionsCache, loginFailures); return new InfinispanUserSessionProvider(session, remoteCacheInvoker, lastSessionRefreshStore, offlineLastSessionRefreshStore,
cache, offlineSessionsCache, clientSessionCache, offlineClientSessionsCache, loginFailures);
} }
@Override @Override
@ -205,7 +210,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class); InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class);
Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionsCache = ispn.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> sessionsCache = ispn.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
boolean sessionsRemoteCache = checkRemoteCache(session, sessionsCache, (RealmModel realm) -> { boolean sessionsRemoteCache = checkRemoteCache(session, sessionsCache, (RealmModel realm) -> {
return realm.getSsoSessionIdleTimeout() * 1000; return realm.getSsoSessionIdleTimeout() * 1000;
}); });
@ -214,8 +219,12 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
lastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, sessionsCache, false); lastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, sessionsCache, false);
} }
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionsCache = ispn.getCache(InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME);
checkRemoteCache(session, clientSessionsCache, (RealmModel realm) -> {
return realm.getSsoSessionIdleTimeout() * 1000;
});
Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = ispn.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> offlineSessionsCache = ispn.getCache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME);
boolean offlineSessionsRemoteCache = checkRemoteCache(session, offlineSessionsCache, (RealmModel realm) -> { boolean offlineSessionsRemoteCache = checkRemoteCache(session, offlineSessionsCache, (RealmModel realm) -> {
return realm.getOfflineSessionIdleTimeout() * 1000; return realm.getOfflineSessionIdleTimeout() * 1000;
}); });
@ -224,8 +233,13 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true); offlineLastSessionRefreshStore = new LastSessionRefreshStoreFactory().createAndInit(session, offlineSessionsCache, true);
} }
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> offlineClientSessionsCache = ispn.getCache(InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME);
checkRemoteCache(session, offlineClientSessionsCache, (RealmModel realm) -> {
return realm.getOfflineSessionIdleTimeout() * 1000;
});
Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME); Cache<LoginFailureKey, SessionEntityWrapper<LoginFailureEntity>> loginFailuresCache = ispn.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME);
boolean loginFailuresRemoteCache = checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> { checkRemoteCache(session, loginFailuresCache, (RealmModel realm) -> {
return realm.getMaxDeltaTimeSeconds() * 1000; return realm.getMaxDeltaTimeSeconds() * 1000;
}); });
} }

View file

@ -26,15 +26,22 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction; import org.keycloak.models.sessions.infinispan.changes.InfinispanChangelogBasedTransaction;
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshChecker; import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshChecker;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.changes.Tasks;
import org.keycloak.models.sessions.infinispan.changes.UserSessionUpdateTask; import org.keycloak.models.sessions.infinispan.changes.UserSessionUpdateTask;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionStore;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -45,7 +52,9 @@ public class UserSessionAdapter implements UserSessionModel {
private final InfinispanUserSessionProvider provider; private final InfinispanUserSessionProvider provider;
private final InfinispanChangelogBasedTransaction updateTx; private final InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx;
private final InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx;
private final RealmModel realm; private final RealmModel realm;
@ -53,11 +62,14 @@ public class UserSessionAdapter implements UserSessionModel {
private final boolean offline; private final boolean offline;
public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, InfinispanChangelogBasedTransaction updateTx, RealmModel realm, public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider,
UserSessionEntity entity, boolean offline) { InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx,
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx,
RealmModel realm, UserSessionEntity entity, boolean offline) {
this.session = session; this.session = session;
this.provider = provider; this.provider = provider;
this.updateTx = updateTx; this.userSessionUpdateTx = userSessionUpdateTx;
this.clientSessionUpdateTx = clientSessionUpdateTx;
this.realm = realm; this.realm = realm;
this.entity = entity; this.entity = entity;
this.offline = offline; this.offline = offline;
@ -65,17 +77,20 @@ public class UserSessionAdapter implements UserSessionModel {
@Override @Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() { public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
Map<String, AuthenticatedClientSessionEntity> clientSessionEntities = entity.getAuthenticatedClientSessions(); AuthenticatedClientSessionStore clientSessionEntities = entity.getAuthenticatedClientSessions();
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>(); Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
List<String> removedClientUUIDS = new LinkedList<>(); List<String> removedClientUUIDS = new LinkedList<>();
if (clientSessionEntities != null) { if (clientSessionEntities != null) {
clientSessionEntities.forEach((String key, AuthenticatedClientSessionEntity value) -> { clientSessionEntities.forEach((String key, UUID value) -> {
// Check if client still exists // Check if client still exists
ClientModel client = realm.getClientById(key); ClientModel client = realm.getClientById(key);
if (client != null) { if (client != null) {
result.put(key, new AuthenticatedClientSessionAdapter(value, client, this, provider, updateTx)); final AuthenticatedClientSessionAdapter clientSession = provider.getClientSession(this, client, value, offline);
if (clientSession != null) {
result.put(key, clientSession);
}
} else { } else {
removedClientUUIDS.add(key); removedClientUUIDS.add(key);
} }
@ -88,20 +103,53 @@ public class UserSessionAdapter implements UserSessionModel {
} }
@Override @Override
public void removeAuthenticatedClientSessions(Iterable<String> removedClientUUIDS) { public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) {
if (removedClientUUIDS == null || ! removedClientUUIDS.iterator().hasNext()) { AuthenticatedClientSessionStore clientSessionEntities = entity.getAuthenticatedClientSessions();
final UUID clientSessionId = clientSessionEntities.get(clientUUID);
if (clientSessionId == null) {
return null;
}
ClientModel client = realm.getClientById(clientUUID);
if (client != null) {
return provider.getClientSession(this, client, clientSessionId, offline);
}
removeAuthenticatedClientSessions(Collections.singleton(clientUUID));
return null;
}
private static final int MINIMUM_INACTIVE_CLIENT_SESSIONS_TO_CLEANUP = 5;
@Override
public void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS) {
if (removedClientUUIDS == null || ! removedClientUUIDS.isEmpty()) {
return; return;
} }
// Update user session // Performance: do not remove the clientUUIDs from the user session until there is enough of them;
UserSessionUpdateTask task = new UserSessionUpdateTask() { // an invalid session is handled as nonexistent in UserSessionAdapter.getAuthenticatedClientSessions()
@Override if (removedClientUUIDS.size() >= MINIMUM_INACTIVE_CLIENT_SESSIONS_TO_CLEANUP) {
public void runUpdate(UserSessionEntity entity) { // Update user session
removedClientUUIDS.forEach(entity.getAuthenticatedClientSessions()::remove); UserSessionUpdateTask task = new UserSessionUpdateTask() {
} @Override
}; public void runUpdate(UserSessionEntity entity) {
removedClientUUIDS.forEach(entity.getAuthenticatedClientSessions()::remove);
}
};
update(task);
}
update(task); // do not iterate the removedClientUUIDS and remove the clientSession directly as the addTask can manipulate
// the collection being iterated, and that can lead to unpredictable behaviour (e.g. NPE)
List<UUID> clientSessionUuids = removedClientUUIDS.stream()
.map(entity.getAuthenticatedClientSessions()::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
clientSessionUuids.forEach(clientSessionId -> this.clientSessionUpdateTx.addTask(clientSessionId, Tasks.removeSync()));
} }
public String getId() { public String getId() {
@ -276,7 +324,7 @@ public class UserSessionAdapter implements UserSessionModel {
} }
void update(UserSessionUpdateTask task) { void update(UserSessionUpdateTask task) {
updateTx.addTask(getId(), task); userSessionUpdateTx.addTask(getId(), task);
} }
} }

View file

@ -0,0 +1,37 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.changes;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class ClientSessionUpdateTask implements SessionUpdateTask<AuthenticatedClientSessionEntity> {
@Override
public CacheOperation getOperation(AuthenticatedClientSessionEntity session) {
return CacheOperation.REPLACE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<AuthenticatedClientSessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
}

View file

@ -45,9 +45,9 @@ public class InfinispanChangelogBasedTransaction<K, V extends SessionEntity> ext
private final Map<K, SessionUpdatesList<V>> updates = new HashMap<>(); private final Map<K, SessionUpdatesList<V>> updates = new HashMap<>();
public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, String cacheName, Cache<K, SessionEntityWrapper<V>> cache, RemoteCacheInvoker remoteCacheInvoker) { public InfinispanChangelogBasedTransaction(KeycloakSession kcSession, Cache<K, SessionEntityWrapper<V>> cache, RemoteCacheInvoker remoteCacheInvoker) {
this.kcSession = kcSession; this.kcSession = kcSession;
this.cacheName = cacheName; this.cacheName = cache.getName();
this.cache = cache; this.cache = cache;
this.remoteCacheInvoker = remoteCacheInvoker; this.remoteCacheInvoker = remoteCacheInvoker;
} }

View file

@ -0,0 +1,80 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.changes;
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.SessionEntity;
/**
*
* @author hmlnarik
*/
public class Tasks {
private static final SessionUpdateTask<? extends SessionEntity> ADD_IF_ABSENT_SYNC = new SessionUpdateTask<SessionEntity>() {
@Override
public void runUpdate(SessionEntity entity) {
}
@Override
public CacheOperation getOperation(SessionEntity entity) {
return CacheOperation.ADD_IF_ABSENT;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<SessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
private static final SessionUpdateTask<? extends SessionEntity> REMOVE_SYNC = new SessionUpdateTask<SessionEntity>() {
@Override
public void runUpdate(SessionEntity entity) {
}
@Override
public CacheOperation getOperation(SessionEntity entity) {
return CacheOperation.REMOVE;
}
@Override
public CrossDCMessageStatus getCrossDCMessageStatus(SessionEntityWrapper<SessionEntity> sessionWrapper) {
return CrossDCMessageStatus.SYNC;
}
};
/**
* Returns a typed task of type {@link CacheOperation#ADD_IF_ABSENT} that does no other update. This operation has DC message
* status {@link CrossDCMessageStatus#SYNC}.
* @param <S>
* @return
*/
public static <S extends SessionEntity> SessionUpdateTask<S> addIfAbsentSync() {
return (SessionUpdateTask<S>) ADD_IF_ABSENT_SYNC;
}
/**
* Returns a typed task of type {@link CacheOperation#REMOVE} that does no other update. This operation has DC message
* status {@link CrossDCMessageStatus#SYNC}.
* @param <S>
* @return
*/
public static <S extends SessionEntity> SessionUpdateTask<S> removeSync() {
return (SessionUpdateTask<S>) REMOVE_SYNC;
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.changes;
import org.jboss.logging.Logger;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
/**
* Task for create or update AuthenticatedClientSessionEntity within userSession
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class UserSessionClientSessionUpdateTask extends UserSessionUpdateTask {
public static final Logger logger = Logger.getLogger(UserSessionClientSessionUpdateTask.class);
private final String clientUUID;
public UserSessionClientSessionUpdateTask(String clientUUID) {
this.clientUUID = clientUUID;
}
@Override
public void runUpdate(UserSessionEntity userSession) {
AuthenticatedClientSessionEntity clientSession = userSession.getAuthenticatedClientSessions().get(clientUUID);
if (clientSession == null) {
logger.warnf("Not found authenticated client session entity for client %s in userSession %s", clientUUID, userSession.getId());
return;
}
runClientSessionUpdate(clientSession);
}
protected abstract void runClientSessionUpdate(AuthenticatedClientSessionEntity entity);
}

View file

@ -20,7 +20,6 @@ package org.keycloak.models.sessions.infinispan.entities;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
import java.io.ObjectOutput; import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -29,13 +28,14 @@ import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.MarshallUtil; import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.marshall.SerializeWith; import org.infinispan.commons.marshall.SerializeWith;
import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil; import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil;
import java.util.UUID;
/** /**
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@SerializeWith(AuthenticatedClientSessionEntity.ExternalizerImpl.class) @SerializeWith(AuthenticatedClientSessionEntity.ExternalizerImpl.class)
public class AuthenticatedClientSessionEntity implements Serializable { public class AuthenticatedClientSessionEntity extends SessionEntity {
private String authMethod; private String authMethod;
private String redirectUri; private String redirectUri;
@ -49,6 +49,16 @@ public class AuthenticatedClientSessionEntity implements Serializable {
private String currentRefreshToken; private String currentRefreshToken;
private int currentRefreshTokenUseCount; private int currentRefreshTokenUseCount;
private final UUID id;
private AuthenticatedClientSessionEntity(UUID id) {
this.id = id;
}
public AuthenticatedClientSessionEntity() {
this.id = UUID.randomUUID();
}
public String getAuthMethod() { public String getAuthMethod() {
return authMethod; return authMethod;
} }
@ -121,10 +131,16 @@ public class AuthenticatedClientSessionEntity implements Serializable {
this.currentRefreshTokenUseCount = currentRefreshTokenUseCount; this.currentRefreshTokenUseCount = currentRefreshTokenUseCount;
} }
public UUID getId() {
return id;
}
public static class ExternalizerImpl implements Externalizer<AuthenticatedClientSessionEntity> { public static class ExternalizerImpl implements Externalizer<AuthenticatedClientSessionEntity> {
@Override @Override
public void writeObject(ObjectOutput output, AuthenticatedClientSessionEntity session) throws IOException { public void writeObject(ObjectOutput output, AuthenticatedClientSessionEntity session) throws IOException {
MarshallUtil.marshallUUID(session.id, output, false);
MarshallUtil.marshallString(session.getRealmId(), output);
MarshallUtil.marshallString(session.getAuthMethod(), output); MarshallUtil.marshallString(session.getAuthMethod(), output);
MarshallUtil.marshallString(session.getRedirectUri(), output); MarshallUtil.marshallString(session.getRedirectUri(), output);
MarshallUtil.marshallInt(output, session.getTimestamp()); MarshallUtil.marshallInt(output, session.getTimestamp());
@ -143,7 +159,9 @@ public class AuthenticatedClientSessionEntity implements Serializable {
@Override @Override
public AuthenticatedClientSessionEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException { public AuthenticatedClientSessionEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
AuthenticatedClientSessionEntity sessionEntity = new AuthenticatedClientSessionEntity(); AuthenticatedClientSessionEntity sessionEntity = new AuthenticatedClientSessionEntity(MarshallUtil.unmarshallUUID(input, false));
sessionEntity.setRealmId(MarshallUtil.unmarshallString(input));
sessionEntity.setAuthMethod(MarshallUtil.unmarshallString(input)); sessionEntity.setAuthMethod(MarshallUtil.unmarshallString(input));
sessionEntity.setRedirectUri(MarshallUtil.unmarshallString(input)); sessionEntity.setRedirectUri(MarshallUtil.unmarshallString(input));

View file

@ -0,0 +1,115 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
/**
*
* @author hmlnarik
*/
@SerializeWith(AuthenticatedClientSessionStore.ExternalizerImpl.class)
public class AuthenticatedClientSessionStore {
/**
* Maps client UUID to client session ID.
*/
private final ConcurrentHashMap<String, UUID> authenticatedClientSessionIds;
public AuthenticatedClientSessionStore() {
authenticatedClientSessionIds = new ConcurrentHashMap<>();
}
private AuthenticatedClientSessionStore(ConcurrentHashMap<String, UUID> authenticatedClientSessionIds) {
this.authenticatedClientSessionIds = authenticatedClientSessionIds;
}
public void clear() {
authenticatedClientSessionIds.clear();
}
public boolean containsKey(String key) {
return authenticatedClientSessionIds.containsKey(key);
}
public void forEach(BiConsumer<? super String, ? super UUID> action) {
authenticatedClientSessionIds.forEach(action);
}
public UUID get(String key) {
return authenticatedClientSessionIds.get(key);
}
public Set<String> keySet() {
return authenticatedClientSessionIds.keySet();
}
public UUID put(String key, UUID value) {
return authenticatedClientSessionIds.put(key, value);
}
public UUID remove(String clientUUID) {
return authenticatedClientSessionIds.remove(clientUUID);
}
public int size() {
return authenticatedClientSessionIds.size();
}
@Override
public String toString() {
return this.authenticatedClientSessionIds.toString();
}
public static class ExternalizerImpl implements Externalizer<AuthenticatedClientSessionStore> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, AuthenticatedClientSessionStore obj) throws IOException {
output.writeByte(VERSION_1);
KeycloakMarshallUtil.writeMap(obj.authenticatedClientSessionIds, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.UUID_EXT, output);
}
@Override
public AuthenticatedClientSessionStore readObject(ObjectInput input) throws IOException, ClassNotFoundException {
switch (input.readByte()) {
case VERSION_1:
return readObjectVersion1(input);
default:
throw new IOException("Unknown version");
}
}
public AuthenticatedClientSessionStore readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
AuthenticatedClientSessionStore res = new AuthenticatedClientSessionStore(
KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.UUID_EXT, ConcurrentHashMap::new)
);
return res;
}
}
}

View file

@ -78,7 +78,7 @@ public class UserSessionEntity extends SessionEntity {
private Map<String, String> notes = new ConcurrentHashMap<>(); private Map<String, String> notes = new ConcurrentHashMap<>();
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions = new ConcurrentHashMap<>(); private AuthenticatedClientSessionStore authenticatedClientSessions = new AuthenticatedClientSessionStore();
public String getUser() { public String getUser() {
return user; return user;
@ -144,11 +144,11 @@ public class UserSessionEntity extends SessionEntity {
this.notes = notes; this.notes = notes;
} }
public Map<String, AuthenticatedClientSessionEntity> getAuthenticatedClientSessions() { public AuthenticatedClientSessionStore getAuthenticatedClientSessions() {
return authenticatedClientSessions; return authenticatedClientSessions;
} }
public void setAuthenticatedClientSessions(Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions) { public void setAuthenticatedClientSessions(AuthenticatedClientSessionStore authenticatedClientSessions) {
this.authenticatedClientSessions = authenticatedClientSessions; this.authenticatedClientSessions = authenticatedClientSessions;
} }
@ -264,8 +264,7 @@ public class UserSessionEntity extends SessionEntity {
Map<String, String> notes = session.getNotes(); Map<String, String> notes = session.getNotes();
KeycloakMarshallUtil.writeMap(notes, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, output); KeycloakMarshallUtil.writeMap(notes, KeycloakMarshallUtil.STRING_EXT, KeycloakMarshallUtil.STRING_EXT, output);
Map<String, AuthenticatedClientSessionEntity> authSessions = session.getAuthenticatedClientSessions(); output.writeObject(session.getAuthenticatedClientSessions());
KeycloakMarshallUtil.writeMap(authSessions, KeycloakMarshallUtil.STRING_EXT, new AuthenticatedClientSessionEntity.ExternalizerImpl(), output);
} }
@ -285,7 +284,8 @@ public class UserSessionEntity extends SessionEntity {
sessionEntity.setAuthMethod(MarshallUtil.unmarshallString(input)); sessionEntity.setAuthMethod(MarshallUtil.unmarshallString(input));
sessionEntity.setBrokerSessionId(MarshallUtil.unmarshallString(input)); sessionEntity.setBrokerSessionId(MarshallUtil.unmarshallString(input));
sessionEntity.setBrokerUserId(MarshallUtil.unmarshallString(input)); sessionEntity.setBrokerUserId(MarshallUtil.unmarshallString(input));
sessionEntity.setId(MarshallUtil.unmarshallString(input)); final String userSessionId = MarshallUtil.unmarshallString(input);
sessionEntity.setId(userSessionId);
sessionEntity.setIpAddress(MarshallUtil.unmarshallString(input)); sessionEntity.setIpAddress(MarshallUtil.unmarshallString(input));
sessionEntity.setLoginUsername(MarshallUtil.unmarshallString(input)); sessionEntity.setLoginUsername(MarshallUtil.unmarshallString(input));
sessionEntity.setRealmId(MarshallUtil.unmarshallString(input)); sessionEntity.setRealmId(MarshallUtil.unmarshallString(input));
@ -301,8 +301,7 @@ public class UserSessionEntity extends SessionEntity {
new KeycloakMarshallUtil.ConcurrentHashMapBuilder<>()); new KeycloakMarshallUtil.ConcurrentHashMapBuilder<>());
sessionEntity.setNotes(notes); sessionEntity.setNotes(notes);
Map<String, AuthenticatedClientSessionEntity> authSessions = KeycloakMarshallUtil.readMap(input, KeycloakMarshallUtil.STRING_EXT, new AuthenticatedClientSessionEntity.ExternalizerImpl(), AuthenticatedClientSessionStore authSessions = (AuthenticatedClientSessionStore) input.readObject();
new KeycloakMarshallUtil.ConcurrentHashMapBuilder<>());
sessionEntity.setAuthenticatedClientSessions(authSessions); sessionEntity.setAuthenticatedClientSessions(authSessions);
return sessionEntity; return sessionEntity;

View file

@ -142,7 +142,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE) .getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE)
.get(OfflinePersistentUserSessionLoader.PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC); .get(OfflinePersistentUserSessionLoader.PERSISTENT_SESSIONS_LOADED_IN_CURRENT_DC);
if (cacheName.equals(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) && sessionsLoaded != null && sessionsLoaded) { if (cacheName.equals(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) && sessionsLoaded != null && sessionsLoaded) {
log.debugf("Sessions already loaded in current DC. Skip sessions loading from remote cache '%s'", cacheName); log.debugf("Sessions already loaded in current DC. Skip sessions loading from remote cache '%s'", cacheName);
return true; return true;
} else { } else {

View file

@ -17,8 +17,8 @@
package org.keycloak.models.sessions.infinispan.stream; package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.AuthenticatedClientSessionAdapter;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil; import org.keycloak.models.sessions.infinispan.util.KeycloakMarshallUtil;
@ -54,6 +54,11 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
this.realm = realm; this.realm = realm;
} }
/**
* Creates a user session predicate. If using the {@link #client(java.lang.String)} method, see its warning.
* @param realm
* @return
*/
public static UserSessionPredicate create(String realm) { public static UserSessionPredicate create(String realm) {
return new UserSessionPredicate(realm); return new UserSessionPredicate(realm);
} }
@ -63,6 +68,15 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return this; return this;
} }
/**
* Adds a test for client. Note that this test can return stale sessions because on detaching client session
* from user session, only client session is deleted and user session is not updated for performance reason.
*
* @see AuthenticatedClientSessionAdapter#detachFromUserSession()
* @param clientSessionCache
* @param clientUUID
* @return
*/
public UserSessionPredicate client(String clientUUID) { public UserSessionPredicate client(String clientUUID) {
this.client = clientUUID; this.client = clientUUID;
return this; return this;
@ -86,9 +100,7 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
@Override @Override
public boolean test(Map.Entry<String, SessionEntityWrapper<UserSessionEntity>> entry) { public boolean test(Map.Entry<String, SessionEntityWrapper<UserSessionEntity>> entry) {
SessionEntity e = entry.getValue().getEntity(); UserSessionEntity entity = entry.getValue().getEntity();
UserSessionEntity entity = (UserSessionEntity) e;
if (!realm.equals(entity.getRealmId())) { if (!realm.equals(entity.getRealmId())) {
return false; return false;

View file

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.infinispan.commons.marshall.Externalizer; import org.infinispan.commons.marshall.Externalizer;
@ -41,7 +42,19 @@ public class KeycloakMarshallUtil {
private static final Logger log = Logger.getLogger(KeycloakMarshallUtil.class); private static final Logger log = Logger.getLogger(KeycloakMarshallUtil.class);
public static final StringExternalizer STRING_EXT = new StringExternalizer(); public static final Externalizer<String> STRING_EXT = new StringExternalizer();
public static final Externalizer<UUID> UUID_EXT = new Externalizer<UUID>() {
@Override
public void writeObject(ObjectOutput output, UUID uuid) throws IOException {
MarshallUtil.marshallUUID(uuid, output, true);
}
@Override
public UUID readObject(ObjectInput input) throws IOException, ClassNotFoundException {
return MarshallUtil.unmarshallUUID(input, true);
}
};
// MAP // MAP

View file

@ -77,8 +77,8 @@ public class ConcurrencyJDGRemoteCacheTest {
} }
private static Worker createWorker(int threadId) { private static Worker createWorker(int threadId) {
EmbeddedCacheManager manager = new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class); EmbeddedCacheManager manager = new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class);
Cache<String, Integer> cache = manager.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, Integer> cache = manager.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
System.out.println("Retrieved cache: " + threadId); System.out.println("Retrieved cache: " + threadId);

View file

@ -41,6 +41,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil; import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
import java.util.UUID;
/** /**
* Check that removing of session from remoteCache is session immediately removed on remoteCache in other DC. This is true. * Check that removing of session from remoteCache is session immediately removed on remoteCache in other DC. This is true.
@ -68,9 +69,11 @@ public class ConcurrencyJDGRemoveSessionTest {
//private static Map<String, EntryInfo> state = new HashMap<>(); //private static Map<String, EntryInfo> state = new HashMap<>();
private static final UUID CLIENT_1_UUID = UUID.randomUUID();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache1 = createManager(1).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache1 = createManager(1).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache2 = createManager(2).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache2 = createManager(2).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
// Create caches, listeners and finally worker threads // Create caches, listeners and finally worker threads
Thread worker1 = createWorker(cache1, 1); Thread worker1 = createWorker(cache1, 1);
@ -177,7 +180,7 @@ public class ConcurrencyJDGRemoveSessionTest {
clientSession.setTimestamp(1234); clientSession.setTimestamp(1234);
clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2"))); clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2")));
clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2"))); clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2")));
session.getAuthenticatedClientSessions().put("client1", clientSession); session.getAuthenticatedClientSessions().put(CLIENT_1_UUID.toString(), clientSession.getId());
SessionEntityWrapper<UserSessionEntity> wrappedSession = new SessionEntityWrapper<>(session); SessionEntityWrapper<UserSessionEntity> wrappedSession = new SessionEntityWrapper<>(session);
return wrappedSession; return wrappedSession;
@ -205,7 +208,7 @@ public class ConcurrencyJDGRemoveSessionTest {
private static EmbeddedCacheManager createManager(int threadId) { private static EmbeddedCacheManager createManager(int threadId) {
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class); return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class);
} }

View file

@ -36,7 +36,6 @@ import org.infinispan.client.hotrod.event.ClientCacheEntryModifiedEvent;
import org.infinispan.context.Flag; import org.infinispan.context.Flag;
import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.junit.Assert;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
@ -44,6 +43,7 @@ import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessi
import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.util.InfinispanUtil; import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
import java.util.UUID;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder; import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
/** /**
@ -74,9 +74,11 @@ public class ConcurrencyJDGSessionsCacheTest {
//private static Map<String, EntryInfo> state = new HashMap<>(); //private static Map<String, EntryInfo> state = new HashMap<>();
private static final UUID CLIENT_1_UUID = UUID.randomUUID();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache1 = createManager(1).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache1 = createManager(1).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache2 = createManager(2).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache2 = createManager(2).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
// Create initial item // Create initial item
UserSessionEntity session = new UserSessionEntity(); UserSessionEntity session = new UserSessionEntity();
@ -96,7 +98,7 @@ public class ConcurrencyJDGSessionsCacheTest {
clientSession.setTimestamp(1234); clientSession.setTimestamp(1234);
clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2"))); clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2")));
clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2"))); clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2")));
session.getAuthenticatedClientSessions().put("client1", clientSession); session.getAuthenticatedClientSessions().put(CLIENT_1_UUID.toString(), clientSession.getId());
SessionEntityWrapper<UserSessionEntity> wrappedSession = new SessionEntityWrapper<>(session); SessionEntityWrapper<UserSessionEntity> wrappedSession = new SessionEntityWrapper<>(session);
@ -219,7 +221,7 @@ public class ConcurrencyJDGSessionsCacheTest {
private static EmbeddedCacheManager createManager(int threadId) { private static EmbeddedCacheManager createManager(int threadId) {
return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class); return new TestCacheManagerFactory().createManager(threadId, InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, RemoteStoreConfigurationBuilder.class);
} }

View file

@ -38,6 +38,7 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.UUID;
/** /**
* Test concurrent writes to distributed cache with usage of atomic replace * Test concurrent writes to distributed cache with usage of atomic replace
@ -52,6 +53,8 @@ public class DistributedCacheConcurrentWritesTest {
private static final AtomicInteger failedReplaceCounter = new AtomicInteger(0); private static final AtomicInteger failedReplaceCounter = new AtomicInteger(0);
private static final AtomicInteger failedReplaceCounter2 = new AtomicInteger(0); private static final AtomicInteger failedReplaceCounter2 = new AtomicInteger(0);
private static final UUID CLIENT_1_UUID = UUID.randomUUID();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
CacheWrapper<String, UserSessionEntity> cache1 = createCache("node1"); CacheWrapper<String, UserSessionEntity> cache1 = createCache("node1");
CacheWrapper<String, UserSessionEntity> cache2 = createCache("node2"); CacheWrapper<String, UserSessionEntity> cache2 = createCache("node2");
@ -74,7 +77,7 @@ public class DistributedCacheConcurrentWritesTest {
clientSession.setTimestamp(1234); clientSession.setTimestamp(1234);
clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2"))); clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2")));
clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2"))); clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2")));
session.getAuthenticatedClientSessions().put("client1", clientSession); session.getAuthenticatedClientSessions().put(CLIENT_1_UUID.toString(), clientSession.getId());
cache1.put("123", session); cache1.put("123", session);
@ -211,7 +214,7 @@ public class DistributedCacheConcurrentWritesTest {
public static CacheWrapper<String, UserSessionEntity> createCache(String nodeName) { public static CacheWrapper<String, UserSessionEntity> createCache(String nodeName) {
EmbeddedCacheManager mgr = createManager(nodeName); EmbeddedCacheManager mgr = createManager(nodeName);
Cache<String, SessionEntityWrapper<UserSessionEntity>> wrapped = mgr.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> wrapped = mgr.getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
return new CacheWrapper<>(wrapped); return new CacheWrapper<>(wrapped);
} }
@ -245,7 +248,7 @@ public class DistributedCacheConcurrentWritesTest {
} }
Configuration distConfig = distConfigBuilder.build(); Configuration distConfig = distConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, distConfig); cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, distConfig);
return cacheManager; return cacheManager;
} }

View file

@ -35,27 +35,29 @@ import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup; import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel; import org.infinispan.util.concurrent.IsolationLevel;
import org.jgroups.JChannel; import org.jgroups.JChannel;
import org.junit.Ignore;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.UUID;
/** /**
* Test concurrent writes to distributed cache with usage of write skew * Test concurrent writes to distributed cache with usage of write skew
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@Ignore //@Ignore
public class DistributedCacheWriteSkewTest { public class DistributedCacheWriteSkewTest {
private static final int ITERATION_PER_WORKER = 1000; private static final int ITERATION_PER_WORKER = 1000;
private static final AtomicInteger failedReplaceCounter = new AtomicInteger(0); private static final AtomicInteger failedReplaceCounter = new AtomicInteger(0);
private static final UUID CLIENT_1_UUID = UUID.randomUUID();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Cache<String, UserSessionEntity> cache1 = createManager("node1").getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, UserSessionEntity> cache1 = createManager("node1").getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
Cache<String, UserSessionEntity> cache2 = createManager("node2").getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, UserSessionEntity> cache2 = createManager("node2").getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
// Create initial item // Create initial item
UserSessionEntity session = new UserSessionEntity(); UserSessionEntity session = new UserSessionEntity();
@ -75,7 +77,7 @@ public class DistributedCacheWriteSkewTest {
clientSession.setTimestamp(1234); clientSession.setTimestamp(1234);
clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2"))); clientSession.setProtocolMappers(new HashSet<>(Arrays.asList("mapper1", "mapper2")));
clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2"))); clientSession.setRoles(new HashSet<>(Arrays.asList("role1", "role2")));
session.getAuthenticatedClientSessions().put("client1", clientSession); session.getAuthenticatedClientSessions().put(CLIENT_1_UUID.toString(), clientSession.getId());
cache1.put("123", session); cache1.put("123", session);
@ -149,6 +151,7 @@ public class DistributedCacheWriteSkewTest {
replaced = true; replaced = true;
} catch (Exception e) { } catch (Exception e) {
System.out.println(e); System.out.println(e);
e.printStackTrace();
failedReplaceCounter.incrementAndGet(); failedReplaceCounter.incrementAndGet();
} }
@ -208,7 +211,7 @@ public class DistributedCacheWriteSkewTest {
} }
Configuration distConfig = distConfigBuilder.build(); Configuration distConfig = distConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, distConfig); cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, distConfig);
return cacheManager; return cacheManager;
} }

View file

@ -116,6 +116,10 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
} }
@Override @Override
public void detachFromUserSession() {
setUserSession(null);
}
public void setUserSession(UserSessionModel userSession) { public void setUserSession(UserSessionModel userSession) {
this.userSession = userSession; this.userSession = userSession;
} }

View file

@ -26,6 +26,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -161,7 +162,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
} }
@Override @Override
public void removeAuthenticatedClientSessions(Iterable<String> removedClientUUIDS) { public void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS) {
if (removedClientUUIDS == null || ! removedClientUUIDS.iterator().hasNext()) { if (removedClientUUIDS == null || ! removedClientUUIDS.iterator().hasNext()) {
return; return;
} }

View file

@ -27,7 +27,10 @@ import org.keycloak.sessions.CommonClientSessionModel;
*/ */
public interface AuthenticatedClientSessionModel extends CommonClientSessionModel { public interface AuthenticatedClientSessionModel extends CommonClientSessionModel {
void setUserSession(UserSessionModel userSession); /**
* Detaches the client session from its user session.
*/
void detachFromUserSession();
UserSessionModel getUserSession(); UserSessionModel getUserSession();
String getCurrentRefreshToken(); String getCurrentRefreshToken();

View file

@ -17,6 +17,7 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Collection;
import java.util.Map; import java.util.Map;
/** /**
@ -53,15 +54,22 @@ public interface UserSessionModel {
void setLastSessionRefresh(int seconds); void setLastSessionRefresh(int seconds);
/** /**
* Returns map where key is ID of the client (its UUID) and value is the respective {@link AuthenticatedClientSessionModel} object. * Returns map where key is ID of the client (its UUID) and value is ID respective {@link AuthenticatedClientSessionModel} object.
* @return * @return
*/ */
Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions(); Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions();
/**
* Returns a client session for the given client UUID.
* @return
*/
default AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) {
return getAuthenticatedClientSessions().get(clientUUID);
};
/** /**
* Removes authenticated client sessions for all clients whose UUID is present in {@code removedClientUUIDS} parameter. * Removes authenticated client sessions for all clients whose UUID is present in {@code removedClientUUIDS} parameter.
* @param removedClientUUIDS * @param removedClientUUIDS
*/ */
void removeAuthenticatedClientSessions(Iterable<String> removedClientUUIDS); void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS);
public String getNote(String name); public String getNote(String name);

View file

@ -20,6 +20,7 @@ package org.keycloak.models;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.function.Predicate; import java.util.function.Predicate;
/** /**
@ -29,6 +30,7 @@ import java.util.function.Predicate;
public interface UserSessionProvider extends Provider { public interface UserSessionProvider extends Provider {
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession); AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline);
UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId); UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
UserSessionModel getUserSession(RealmModel realm, String id); UserSessionModel getUserSession(RealmModel realm, String id);
@ -39,7 +41,7 @@ public interface UserSessionProvider extends Provider {
UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId); UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
/** /**
* Return userSession of specified ID as long as the predicate passes. Otherwise returs null. * Return userSession of specified ID as long as the predicate passes. Otherwise returns {@code null}.
* If predicate doesn't pass, implementation can do some best-effort actions to try have predicate passing (eg. download userSession from other DC) * If predicate doesn't pass, implementation can do some best-effort actions to try have predicate passing (eg. download userSession from other DC)
*/ */
UserSessionModel getUserSessionWithPredicate(RealmModel realm, String id, boolean offline, Predicate<UserSessionModel> predicate); UserSessionModel getUserSessionWithPredicate(RealmModel realm, String id, boolean offline, Predicate<UserSessionModel> predicate);

View file

@ -159,13 +159,13 @@ public class TokenManager {
} }
ClientModel client = session.getContext().getClient(); ClientModel client = session.getContext().getClient();
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
// Can theoretically happen in cross-dc environment. Try to see if userSession with our client is available in remoteCache // Can theoretically happen in cross-dc environment. Try to see if userSession with our client is available in remoteCache
if (clientSession == null) { if (clientSession == null) {
userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSession.getId(), offline, client.getId()); userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSession.getId(), offline, client.getId());
if (userSession != null) { if (userSession != null) {
clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
} else { } else {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session doesn't have required client", "Session doesn't have required client"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session doesn't have required client", "Session doesn't have required client");
} }
@ -400,7 +400,7 @@ public class TokenManager {
public static AuthenticatedClientSessionModel attachAuthenticationSession(KeycloakSession session, UserSessionModel userSession, AuthenticationSessionModel authSession) { public static AuthenticatedClientSessionModel attachAuthenticationSession(KeycloakSession session, UserSessionModel userSession, AuthenticationSessionModel authSession) {
ClientModel client = authSession.getClient(); ClientModel client = authSession.getClient();
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
if (clientSession == null) { if (clientSession == null) {
clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession); clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession);
} }
@ -436,8 +436,9 @@ public class TokenManager {
return; return;
} }
clientSession.setUserSession(null); clientSession.detachFromUserSession();
// TODO: Might need optimization to prevent loading client sessions from cache in getAuthenticatedClientSessions()
if (userSession.getAuthenticatedClientSessions().isEmpty()) { if (userSession.getAuthenticatedClientSessions().isEmpty()) {
sessions.removeUserSession(realm, userSession); sessions.removeUserSession(realm, userSession);
} }

View file

@ -254,7 +254,7 @@ public class TokenEndpoint {
// Attempt to use same code twice should invalidate existing clientSession // Attempt to use same code twice should invalidate existing clientSession
if (clientSession != null) { if (clientSession != null) {
clientSession.setUserSession(null); clientSession.detachFromUserSession();
} }
event.error(Errors.INVALID_CODE); event.error(Errors.INVALID_CODE);
@ -400,7 +400,7 @@ public class TokenEndpoint {
if (!result.isOfflineToken()) { if (!result.isOfflineToken()) {
UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState()); UserSessionModel userSession = session.sessions().getUserSession(realm, res.getSessionState());
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
updateClientSession(clientSession); updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession); updateUserSessionFromClientAuth(userSession);
} }

View file

@ -166,7 +166,7 @@ public class UserInfoEndpoint {
// Existence of authenticatedClientSession for our client already handled before // Existence of authenticatedClientSession for our client already handled before
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientModel.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientModel.getId());
AccessToken userInfo = new AccessToken(); AccessToken userInfo = new AccessToken();
tokenManager.transformUserInfoAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession); tokenManager.transformUserInfoAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession);

View file

@ -400,7 +400,7 @@ public class SamlService extends AuthorizationEndpointBase {
userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod()); userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL); userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
// remove client from logout requests // remove client from logout requests
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
if (clientSession != null) { if (clientSession != null) {
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name()); clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
} }

View file

@ -62,7 +62,7 @@ public class SamlSessionUtils {
return null; return null;
} }
return userSession.getAuthenticatedClientSessions().get(parts[1]); return userSession.getAuthenticatedClientSessionByClient(clientUUID);
} }
} }

View file

@ -395,7 +395,7 @@ public class AuthenticationManager {
public static void backchannelLogoutUserFromClient(KeycloakSession session, RealmModel realm, UserModel user, ClientModel client, UriInfo uriInfo, HttpHeaders headers) { public static void backchannelLogoutUserFromClient(KeycloakSession session, RealmModel realm, UserModel user, ClientModel client, UriInfo uriInfo, HttpHeaders headers) {
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user); List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
for (UserSessionModel userSession : userSessions) { for (UserSessionModel userSession : userSessions) {
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
if (clientSession != null) { if (clientSession != null) {
AuthenticationManager.backchannelLogoutClientSession(session, realm, clientSession, null, uriInfo, headers); AuthenticationManager.backchannelLogoutClientSession(session, realm, clientSession, null, uriInfo, headers);
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name()); clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());

View file

@ -172,7 +172,7 @@ class CodeGenerateUtil {
} }
} }
return userSession.getAuthenticatedClientSessions().get(codeJWT.getIssuedFor()); return userSession.getAuthenticatedClientSessionByClient(codeJWT.getIssuedFor());
} }

View file

@ -42,7 +42,7 @@ public class UserSessionCrossDCManager {
// get userSession if it has "authenticatedClientSession" of specified client attached to it. Otherwise download it from remoteCache // get userSession if it has "authenticatedClientSession" of specified client attached to it. Otherwise download it from remoteCache
public UserSessionModel getUserSessionWithClient(RealmModel realm, String id, boolean offline, String clientUUID) { public UserSessionModel getUserSessionWithClient(RealmModel realm, String id, boolean offline, String clientUUID) {
return kcSession.sessions().getUserSessionWithPredicate(realm, id, offline, userSession -> userSession.getAuthenticatedClientSessions().containsKey(clientUUID)); return kcSession.sessions().getUserSessionWithPredicate(realm, id, offline, userSession -> userSession.getAuthenticatedClientSessionByClient(clientUUID) != null);
} }
@ -52,8 +52,8 @@ public class UserSessionCrossDCManager {
return kcSession.sessions().getUserSessionWithPredicate(realm, id, false, (UserSessionModel userSession) -> { return kcSession.sessions().getUserSessionWithPredicate(realm, id, false, (UserSessionModel userSession) -> {
Map<String, AuthenticatedClientSessionModel> authSessions = userSession.getAuthenticatedClientSessions(); AuthenticatedClientSessionModel authSessions = userSession.getAuthenticatedClientSessionByClient(clientUUID);
return authSessions.containsKey(clientUUID); return authSessions != null;
}); });
} }

View file

@ -63,7 +63,7 @@ public class UserSessionManager {
} }
// Create and persist clientSession // Create and persist clientSession
AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().get(clientSession.getClient().getId()); AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessionByClient(clientSession.getClient().getId());
if (offlineClientSession == null) { if (offlineClientSession == null) {
createOfflineClientSession(user, clientSession, offlineUserSession); createOfflineClientSession(user, clientSession, offlineUserSession);
} }
@ -97,14 +97,14 @@ public class UserSessionManager {
List<UserSessionModel> userSessions = kcSession.sessions().getOfflineUserSessions(realm, user); List<UserSessionModel> userSessions = kcSession.sessions().getOfflineUserSessions(realm, user);
boolean anyRemoved = false; boolean anyRemoved = false;
for (UserSessionModel userSession : userSessions) { for (UserSessionModel userSession : userSessions) {
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
if (clientSession != null) { if (clientSession != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef("Removing existing offline token for user '%s' and client '%s' .", logger.tracef("Removing existing offline token for user '%s' and client '%s' .",
user.getUsername(), client.getClientId()); user.getUsername(), client.getClientId());
} }
clientSession.setUserSession(null); clientSession.detachFromUserSession();
persister.removeClientSession(userSession.getId(), client.getId(), true); persister.removeClientSession(userSession.getId(), client.getId(), true);
checkOfflineUserSessionHasClientSessions(realm, user, userSession); checkOfflineUserSessionHasClientSessions(realm, user, userSession);
anyRemoved = true; anyRemoved = true;
@ -154,7 +154,8 @@ public class UserSessionManager {
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not // Check if userSession has any offline clientSessions attached to it. Remove userSession if not
private void checkOfflineUserSessionHasClientSessions(RealmModel realm, UserModel user, UserSessionModel userSession) { private void checkOfflineUserSessionHasClientSessions(RealmModel realm, UserModel user, UserSessionModel userSession) {
if (userSession.getAuthenticatedClientSessions().size() > 0) { // TODO: Might need optimization to prevent loading client sessions from cache
if (! userSession.getAuthenticatedClientSessions().isEmpty()) {
return; return;
} }

View file

@ -142,7 +142,7 @@ public class AccountFormService extends AbstractSecuredLocalService {
if (authResult != null) { if (authResult != null) {
UserSessionModel userSession = authResult.getSession(); UserSessionModel userSession = authResult.getSession();
if (userSession != null) { if (userSession != null) {
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId()); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
if (clientSession == null) { if (clientSession == null) {
clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession); clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession);
} }

View file

@ -339,7 +339,7 @@ public class UserResource {
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session); UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
// Update lastSessionRefresh with the timestamp from clientSession // Update lastSessionRefresh with the timestamp from clientSession
AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessions().get(clientId); AuthenticatedClientSessionModel clientSession = session.getAuthenticatedClientSessionByClient(clientId);
// Skip if userSession is not for this client // Skip if userSession is not for this client
if (clientSession == null) { if (clientSession == null) {

View file

@ -60,6 +60,38 @@ echo ** Update distributed-cache offlineSessions element **
) )
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true) /subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache clientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/store=remote:add( \
passivation=false, \
fetch-state=false, \
purge=false, \
preload=false, \
shared=true, \
remote-servers=["remote-cache"], \
cache=clientSessions, \
properties={ \
rawValues=true, \
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
} \
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache offlineClientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/store=remote:add( \
passivation=false, \
fetch-state=false, \
purge=false, \
preload=false, \
shared=true, \
remote-servers=["remote-cache"], \
cache=offlineClientSessions, \
properties={ \
rawValues=true, \
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory \
} \
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Update distributed-cache loginFailures element ** echo ** Update distributed-cache loginFailures element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add( \ /subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add( \
passivation=false, \ passivation=false, \

View file

@ -185,6 +185,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
throw new NotFoundException("Session not found"); throw new NotFoundException("Session not found");
} }
// TODO: Might need optimization to prevent loading client sessions from cache
return sessionModel.getAuthenticatedClientSessions().size(); return sessionModel.getAuthenticatedClientSessions().size();
} }

View file

@ -42,9 +42,10 @@
</backups> </backups>
</replicated-cache-configuration> </replicated-cache-configuration>
<replicated-cache name="sessions" configuration="sessions-cfg" /> <replicated-cache name="sessions" configuration="sessions-cfg" />
<replicated-cache name="offlineSessions" configuration="sessions-cfg" /> <replicated-cache name="offlineSessions" configuration="sessions-cfg" />
<replicated-cache name="clientSessions" configuration="sessions-cfg" />
<replicated-cache name="offlineClientSessions" configuration="sessions-cfg" />
<replicated-cache name="loginFailures" configuration="sessions-cfg" /> <replicated-cache name="loginFailures" configuration="sessions-cfg" />
<replicated-cache name="actionTokens" configuration="sessions-cfg" /> <replicated-cache name="actionTokens" configuration="sessions-cfg" />
<replicated-cache name="work" configuration="sessions-cfg" /> <replicated-cache name="work" configuration="sessions-cfg" />

View file

@ -76,7 +76,7 @@ public class ConcurrentLoginClusterTest extends ConcurrentLoginTest {
@Override @Override
public void concurrentLoginSingleUser() throws Throwable { public void concurrentLoginSingleUser() throws Throwable {
super.concurrentLoginSingleUser(); super.concurrentLoginSingleUser();
JGroupsStats stats = testingClient.testing().cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).getJgroupsStats(); JGroupsStats stats = testingClient.testing().cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getJgroupsStats();
log.info("JGroups statistics: " + stats.statsAsString()); log.info("JGroups statistics: " + stats.statsAsString());
} }

View file

@ -388,10 +388,8 @@ public abstract class AbstractCrossDCTest extends AbstractTestRealmKeycloakTest
private void setTimeOffsetOnAllStartedContainers(int offset) { private void setTimeOffsetOnAllStartedContainers(int offset) {
backendTestingClients.entrySet().stream() backendTestingClients.entrySet().stream()
.filter(testingClientEntry -> testingClientEntry.getKey().isStarted()) .filter(testingClientEntry -> testingClientEntry.getKey().isStarted())
.forEach(testingClientEntry -> { .map(testingClientEntry -> testingClientEntry.getValue())
KeycloakTestingClient testingClient = testingClientEntry.getValue(); .forEach(testingClient -> testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset))));
testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(offset)));
});
} }
/** /**

View file

@ -26,7 +26,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.Retry; import org.keycloak.testsuite.Retry;
import org.keycloak.testsuite.arquillian.ContainerInfo; import org.keycloak.testsuite.arquillian.ContainerInfo;
import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.rest.representation.RemoteCacheStats; import org.keycloak.testsuite.rest.representation.RemoteCacheStats;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
@ -57,7 +56,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
// Get statistics // Get statistics
int lsr00 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId); int lsr00 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId);
int lsr10 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId); int lsr10 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId);
int lsrr0 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId); int lsrr0 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId);
log.infof("lsr00: %d, lsr10: %d, lsrr0: %d", lsr00, lsr10, lsrr0); log.infof("lsr00: %d, lsr10: %d, lsrr0: %d", lsr00, lsr10, lsrr0);
Assert.assertEquals(lsr00, lsr10); Assert.assertEquals(lsr00, lsr10);
@ -76,7 +75,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
Retry.execute(() -> { Retry.execute(() -> {
int lsr01 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId); int lsr01 = getTestingClientForStartedNodeInDc(0).testing("test").getLastSessionRefresh("test", sessionId);
int lsr11 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId); int lsr11 = getTestingClientForStartedNodeInDc(1).testing("test").getLastSessionRefresh("test", sessionId);
int lsrr1 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId); int lsrr1 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId);
log.infof("lsr01: %d, lsr11: %d, lsrr1: %d", lsr01, lsr11, lsrr1); log.infof("lsr01: %d, lsr11: %d, lsrr1: %d", lsr01, lsr11, lsrr1);
Assert.assertEquals(lsr01, lsr11); Assert.assertEquals(lsr01, lsr11);
@ -88,7 +87,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
disableDcOnLoadBalancer(DC.FIRST); disableDcOnLoadBalancer(DC.FIRST);
enableDcOnLoadBalancer(DC.SECOND); enableDcOnLoadBalancer(DC.SECOND);
tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password"); tokenResponse = oauth.doRefreshTokenRequest(refreshToken1, "password");
Assert.assertNull(tokenResponse.getAccessToken()); Assert.assertNull("Expecting no access token present", tokenResponse.getAccessToken());
Assert.assertNotNull(tokenResponse.getError()); Assert.assertNotNull(tokenResponse.getError());
// try refresh with new token on DC1. It should pass. // try refresh with new token on DC1. It should pass.
@ -164,7 +163,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
Assert.assertEquals(lsr10, lsr11.get()); Assert.assertEquals(lsr10, lsr11.get());
// assert that lastSessionRefresh still the same on remoteCache // assert that lastSessionRefresh still the same on remoteCache
int lsrr1 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId); int lsrr1 = getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId);
Assert.assertEquals(lsr00, lsrr1); Assert.assertEquals(lsr00, lsrr1);
log.infof("lsrr1: %d", lsrr1); log.infof("lsrr1: %d", lsrr1);
@ -187,7 +186,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
Assert.assertTrue(lsr02.get() > lsr01.get()); Assert.assertTrue(lsr02.get() > lsr01.get());
Assert.assertTrue(lsr12.get() > lsr11.get()); Assert.assertTrue(lsr12.get() > lsr11.get());
lsrr2.set(getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId)); lsrr2.set(getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId));
log.infof("lsrr2: %d", lsrr2.get()); log.infof("lsrr2: %d", lsrr2.get());
Assert.assertEquals(lsrr1, lsrr2.get()); Assert.assertEquals(lsrr1, lsrr2.get());
@ -218,7 +217,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
Assert.assertTrue(lsr03.get() > lsr02.get()); Assert.assertTrue(lsr03.get() > lsr02.get());
Assert.assertTrue(lsr13.get() > lsr12.get()); Assert.assertTrue(lsr13.get() > lsr12.get());
lsrr3.set(getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId)); lsrr3.set(getTestingClientForStartedNodeInDc(0).testing("test").cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).getRemoteCacheLastSessionRefresh(sessionId));
log.infof("lsrr3: %d", lsrr3.get()); log.infof("lsrr3: %d", lsrr3.get());
Assert.assertTrue(lsrr3.get() > lsrr2.get()); Assert.assertTrue(lsrr3.get() > lsrr2.get());
@ -232,7 +231,7 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
private RemoteCacheStats getRemoteCacheStats(int dcIndex) { private RemoteCacheStats getRemoteCacheStats(int dcIndex) {
return getTestingClientForStartedNodeInDc(dcIndex).testing("test") return getTestingClientForStartedNodeInDc(dcIndex).testing("test")
.cache(InfinispanConnectionProvider.SESSION_CACHE_NAME) .cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME)
.getRemoteCacheStats(); .getRemoteCacheStats();
} }

View file

@ -102,10 +102,12 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
@Test @Test
public void testRealmRemoveSessions( public void testRealmRemoveSessions(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientCacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME) InfinispanStatistics clientCacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true); createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
// log.infof("Sleeping!"); // log.infof("Sleeping!");
// Thread.sleep(10000000); // Thread.sleep(10000000);
@ -116,7 +118,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
getAdminClient().realm(REALM_NAME).remove(); getAdminClient().realm(REALM_NAME).remove();
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 100l); sessions01, sessions02, remoteSessions01, remoteSessions02, 100l);
} }
@ -194,11 +196,11 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
@Test @Test
public void testRealmRemoveOfflineSessions( public void testRealmRemoveOfflineSessions(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true); createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true);
channelStatisticsCrossDc.reset(); channelStatisticsCrossDc.reset();
@ -206,18 +208,18 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
getAdminClient().realm(REALM_NAME).remove(); getAdminClient().realm(REALM_NAME).remove();
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After realm remove", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 200l); // Might be bigger messages as online sessions removed too. sessions01, sessions02, remoteSessions01, remoteSessions02, 200l); // Might be bigger messages as online sessions removed too.
} }
@Test @Test
public void testLogoutAllInRealm( public void testLogoutAllInRealm(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true); createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
channelStatisticsCrossDc.reset(); channelStatisticsCrossDc.reset();
@ -225,18 +227,18 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
getAdminClient().realm(REALM_NAME).logoutAll(); getAdminClient().realm(REALM_NAME).logoutAll();
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After realm logout", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After realm logout", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 100l); sessions01, sessions02, remoteSessions01, remoteSessions02, 100l);
} }
@Test @Test
public void testPeriodicExpiration( public void testPeriodicExpiration(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true).get(SESSIONS_COUNT - 1); OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true).get(SESSIONS_COUNT - 1);
// Assert I am able to refresh // Assert I am able to refresh
OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password"); OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password");
@ -249,7 +251,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME); getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME);
// Nothing yet expired. Limit 5 for sent_messages is just if "lastSessionRefresh" periodic thread happened // Nothing yet expired. Limit 5 for sent_messages is just if "lastSessionRefresh" periodic thread happened
assertStatisticsExpected("After remove expired - 1", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After remove expired - 1", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01 + SESSIONS_COUNT, sessions02 + SESSIONS_COUNT, remoteSessions01 + SESSIONS_COUNT, remoteSessions02 + SESSIONS_COUNT, 5l); sessions01 + SESSIONS_COUNT, sessions02 + SESSIONS_COUNT, remoteSessions01 + SESSIONS_COUNT, remoteSessions02 + SESSIONS_COUNT, 5l);
@ -268,7 +270,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME); getTestingClientForStartedNodeInDc(0).testing().removeExpired(REALM_NAME);
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After remove expired - 2", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After remove expired - 2", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 100l); sessions01, sessions02, remoteSessions01, remoteSessions02, 100l);
} }
@ -277,10 +279,10 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
@Test @Test
public void testUserRemoveSessions( public void testUserRemoveSessions(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true); createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
// log.infof("Sleeping!"); // log.infof("Sleeping!");
// Thread.sleep(10000000); // Thread.sleep(10000000);
@ -292,17 +294,17 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After user remove", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After user remove", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 100l); sessions01, sessions02, remoteSessions01, remoteSessions02, 100l);
} }
@Test @Test
public void testUserRemoveOfflineSessions( public void testUserRemoveOfflineSessions(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true); createInitialSessions(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true);
// log.infof("Sleeping!"); // log.infof("Sleeping!");
// Thread.sleep(10000000); // Thread.sleep(10000000);
@ -314,18 +316,18 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After user remove", InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After user remove", InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 100l); sessions01, sessions02, remoteSessions01, remoteSessions02, 100l);
} }
@Test @Test
public void testLogoutUser( public void testLogoutUser(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true); createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
channelStatisticsCrossDc.reset(); channelStatisticsCrossDc.reset();
@ -335,29 +337,29 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
getAdminClient().realm(REALM_NAME).deleteSession(userSession.getId()); getAdminClient().realm(REALM_NAME).deleteSession(userSession.getId());
// Just one session expired. Limit 5 for sent_messages is just if "lastSessionRefresh" periodic thread happened // Just one session expired. Limit 5 for sent_messages is just if "lastSessionRefresh" periodic thread happened
assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01 + SESSIONS_COUNT - 1, sessions02 + SESSIONS_COUNT - 1, remoteSessions01 + SESSIONS_COUNT - 1, remoteSessions02 + SESSIONS_COUNT - 1, 5l); sessions01 + SESSIONS_COUNT - 1, sessions02 + SESSIONS_COUNT - 1, remoteSessions01 + SESSIONS_COUNT - 1, remoteSessions02 + SESSIONS_COUNT - 1, 5l);
// Logout all sessions for user now // Logout all sessions for user now
user.logout(); user.logout();
// Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big. // Assert sessions removed on node1 and node2 and on remote caches. Assert that count of messages sent between DCs is not too big.
assertStatisticsExpected("After user logout", InfinispanConnectionProvider.SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc, assertStatisticsExpected("After user logout", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, cacheDc1Statistics, cacheDc2Statistics, channelStatisticsCrossDc,
sessions01, sessions02, remoteSessions01, remoteSessions02, 100l); sessions01, sessions02, remoteSessions01, remoteSessions02, 100l);
} }
@Test @Test
public void testLogoutUserWithFailover( public void testLogoutUserWithFailover(
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
// Start node2 on first DC // Start node2 on first DC
startBackendNode(DC.FIRST, 1); startBackendNode(DC.FIRST, 1);
// Don't include remote stats. Size is smaller because of distributed cache // Don't include remote stats. Size is smaller because of distributed cache
List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, false); List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, false);
// Kill node2 now. Around 10 sessions (half of SESSIONS_COUNT) will be lost on Keycloak side. But not on infinispan side // Kill node2 now. Around 10 sessions (half of SESSIONS_COUNT) will be lost on Keycloak side. But not on infinispan side
stopBackendNode(DC.FIRST, 1); stopBackendNode(DC.FIRST, 1);

View file

@ -112,7 +112,7 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
@Test @Test
public void sessionsPreloadTest() throws Exception { public void sessionsPreloadTest() throws Exception {
int sessionsBefore = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).size(); int sessionsBefore = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).size();
log.infof("sessionsBefore: %d", sessionsBefore); log.infof("sessionsBefore: %d", sessionsBefore);
// Create initial sessions // Create initial sessions
@ -124,8 +124,8 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
enableLoadBalancerNode(DC.SECOND, 0); enableLoadBalancerNode(DC.SECOND, 0);
// Ensure sessions are loaded in both 1st DC and 2nd DC // Ensure sessions are loaded in both 1st DC and 2nd DC
int sessions01 = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).size(); int sessions01 = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).size();
int sessions02 = getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.SESSION_CACHE_NAME).size(); int sessions02 = getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME).size();
log.infof("sessions01: %d, sessions02: %d", sessions01, sessions02); log.infof("sessions01: %d, sessions02: %d", sessions01, sessions02);
Assert.assertEquals(sessions01, sessionsBefore + SESSIONS_COUNT); Assert.assertEquals(sessions01, sessionsBefore + SESSIONS_COUNT);
Assert.assertEquals(sessions02, sessionsBefore + SESSIONS_COUNT); Assert.assertEquals(sessions02, sessionsBefore + SESSIONS_COUNT);
@ -144,13 +144,13 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
@Test @Test
public void offlineSessionsPreloadTest() throws Exception { public void offlineSessionsPreloadTest() throws Exception {
int offlineSessionsBefore = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME).size(); int offlineSessionsBefore = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME).size();
log.infof("offlineSessionsBefore: %d", offlineSessionsBefore); log.infof("offlineSessionsBefore: %d", offlineSessionsBefore);
// Create initial sessions // Create initial sessions
List<OAuthClient.AccessTokenResponse> tokenResponses = createInitialSessions(true); List<OAuthClient.AccessTokenResponse> tokenResponses = createInitialSessions(true);
int offlineSessions01 = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME).size(); int offlineSessions01 = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME).size();
Assert.assertEquals(offlineSessions01, offlineSessionsBefore + SESSIONS_COUNT); Assert.assertEquals(offlineSessions01, offlineSessionsBefore + SESSIONS_COUNT);
log.infof("offlineSessions01: %d", offlineSessions01); log.infof("offlineSessions01: %d", offlineSessions01);
@ -168,8 +168,8 @@ public class SessionsPreloadCrossDCTest extends AbstractAdminCrossDCTest {
enableLoadBalancerNode(DC.SECOND, 0); enableLoadBalancerNode(DC.SECOND, 0);
// Ensure sessions are loaded in both 1st DC and 2nd DC // Ensure sessions are loaded in both 1st DC and 2nd DC
int offlineSessions11 = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME).size(); int offlineSessions11 = getTestingClientForStartedNodeInDc(0).testing().cache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME).size();
int offlineSessions12 = getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME).size(); int offlineSessions12 = getTestingClientForStartedNodeInDc(1).testing().cache(InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME).size();
log.infof("offlineSessions11: %d, offlineSessions12: %d", offlineSessions11, offlineSessions12); log.infof("offlineSessions11: %d, offlineSessions12: %d", offlineSessions11, offlineSessions12);
Assert.assertEquals(offlineSessions11, offlineSessionsBefore + SESSIONS_COUNT); Assert.assertEquals(offlineSessions11, offlineSessionsBefore + SESSIONS_COUNT);
Assert.assertEquals(offlineSessions12, offlineSessionsBefore + SESSIONS_COUNT); Assert.assertEquals(offlineSessions12, offlineSessionsBefore + SESSIONS_COUNT);

View file

@ -562,7 +562,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
RealmModel realmModel = session.getContext().getRealm(); RealmModel realmModel = session.getContext().getRealm();
String clientUuid = realmModel.getClientByClientId(clientId).getId(); String clientUuid = realmModel.getClientByClientId(clientId).getId();
UserSessionModel userSession = session.sessions().getUserSession(realmModel, sessionId); UserSessionModel userSession = session.sessions().getUserSession(realmModel, sessionId);
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUuid); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientUuid);
String claimsInSession = clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM); String claimsInSession = clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM);
assertEquals(claimsJson, claimsInSession); assertEquals(claimsJson, claimsInSession);

View file

@ -169,7 +169,7 @@ public class LastSessionRefreshUnitTest extends AbstractKeycloakTest {
}; };
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME); Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME);
return factory.createAndInit(session, cache, timerIntervalMs, maxIntervalBetweenMessagesSeconds, 10, false); return factory.createAndInit(session, cache, timerIntervalMs, maxIntervalBetweenMessagesSeconds, 10, false);
} }

View file

@ -132,7 +132,6 @@ public class UserSessionInitializerTest {
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) { private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession); AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect); clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state); if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles); if (roles != null) clientSession.setRoles(roles);

View file

@ -362,7 +362,6 @@ public class UserSessionPersisterProviderTest {
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) { private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession); AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect); clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state); if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles); if (roles != null) clientSession.setRoles(roles);

View file

@ -396,7 +396,6 @@ public class UserSessionProviderOfflineTest {
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) { private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client, userSession); AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect); clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state); if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles); if (roles != null) clientSession.setRoles(roles);

View file

@ -33,7 +33,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.models.UserManager; import org.keycloak.models.UserManager;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import java.util.Arrays; import java.util.Arrays;
@ -181,6 +180,30 @@ public class UserSessionProviderTest {
assertEquals(time + 10, updated.getTimestamp()); assertEquals(time + 10, updated.getTimestamp());
} }
@Test
public void testUpdateClientSessionWithGetByClientId() {
UserSessionModel[] sessions = createSessions();
String userSessionId = sessions[0].getId();
String clientUUID = realm.getClientByClientId("test-app").getId();
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientUUID);
int time = clientSession.getTimestamp();
assertEquals(null, clientSession.getAction());
clientSession.setAction(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name());
clientSession.setTimestamp(time + 10);
kc.stopSession(session, true);
session = kc.startSession();
AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessionByClient(clientUUID);
assertEquals(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name(), updated.getAction());
assertEquals(time + 10, updated.getTimestamp());
}
@Test @Test
public void testUpdateClientSessionInSameTransaction() { public void testUpdateClientSessionInSameTransaction() {
UserSessionModel[] sessions = createSessions(); UserSessionModel[] sessions = createSessions();
@ -189,12 +212,12 @@ public class UserSessionProviderTest {
String clientUUID = realm.getClientByClientId("test-app").getId(); String clientUUID = realm.getClientByClientId("test-app").getId();
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId); UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUUID); AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(clientUUID);
clientSession.setAction(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name()); clientSession.setAction(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name());
clientSession.setNote("foo", "bar"); clientSession.setNote("foo", "bar");
AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessions().get(clientUUID); AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessionByClient(clientUUID);
assertEquals(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name(), updated.getAction()); assertEquals(AuthenticatedClientSessionModel.Action.LOGGED_OUT.name(), updated.getAction());
assertEquals("bar", updated.getNote("foo")); assertEquals("bar", updated.getNote("foo"));
} }
@ -361,7 +384,6 @@ public class UserSessionProviderTest {
Time.setOffset(i); Time.setOffset(i);
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null); UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null);
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession); AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession);
clientSession.setUserSession(userSession);
clientSession.setRedirectUri("http://redirect"); clientSession.setRedirectUri("http://redirect");
clientSession.setRoles(new HashSet<String>()); clientSession.setRoles(new HashSet<String>());
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state"); clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
@ -451,7 +473,7 @@ public class UserSessionProviderTest {
// remove session // remove session
clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId()); clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId());
clientSession1.setUserSession(null); clientSession1.detachFromUserSession();
// Commit and ensure removed // Commit and ensure removed
resetSession(); resetSession();

View file

@ -31,19 +31,28 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity; import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public abstract class AbstractSessionCacheCommand extends AbstractCommand { public abstract class AbstractSessionCacheCommand extends AbstractCommand {
private static final Set<String> SUPPORTED_CACHE_NAMES = new TreeSet<>(Arrays.asList(
InfinispanConnectionProvider.USER_SESSION_CACHE_NAME,
InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME,
InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME
));
@Override @Override
protected void doRunCommand(KeycloakSession session) { protected void doRunCommand(KeycloakSession session) {
InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
String cacheName = getArg(0); String cacheName = getArg(0);
if (!cacheName.equals(InfinispanConnectionProvider.SESSION_CACHE_NAME) && !cacheName.equals(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME)) { if (! SUPPORTED_CACHE_NAMES.contains(cacheName)) {
log.errorf("Invalid cache name: '%s', Only cache names '%s' or '%s' are supported", cacheName, InfinispanConnectionProvider.SESSION_CACHE_NAME, log.errorf("Invalid cache name: '%s', Only cache names '%s' are supported", cacheName, SUPPORTED_CACHE_NAMES);
InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
throw new HandledException(); throw new HandledException();
} }

View file

@ -41,7 +41,7 @@ import java.util.List;
public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcessor { public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcessor {
private static final String[] CACHES = new String[] { private static final String[] CACHES = new String[] {
"realms", "users","sessions","authenticationSessions","offlineSessions","loginFailures","work","authorization","keys","actionTokens" "realms", "users","sessions","authenticationSessions","offlineSessions","clientSessions","offlineClientSessions","loginFailures","work","authorization","keys","actionTokens"
}; };
// This param name is defined again in Keycloak Services class // This param name is defined again in Keycloak Services class

View file

@ -34,6 +34,8 @@
<local-cache name="sessions"/> <local-cache name="sessions"/>
<local-cache name="authenticationSessions"/> <local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/> <local-cache name="offlineSessions"/>
<local-cache name="clientSessions"/>
<local-cache name="offlineClientSessions"/>
<local-cache name="loginFailures"/> <local-cache name="loginFailures"/>
<local-cache name="work"/> <local-cache name="work"/>
<local-cache name="authorization"> <local-cache name="authorization">
@ -94,6 +96,8 @@
<distributed-cache name="sessions" mode="SYNC" owners="1"/> <distributed-cache name="sessions" mode="SYNC" owners="1"/>
<distributed-cache name="authenticationSessions" mode="SYNC" owners="1"/> <distributed-cache name="authenticationSessions" mode="SYNC" owners="1"/>
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"/> <distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
<distributed-cache name="clientSessions" mode="SYNC" owners="1"/>
<distributed-cache name="offlineClientSessions" mode="SYNC" owners="1"/>
<distributed-cache name="loginFailures" mode="SYNC" owners="1"/> <distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
<local-cache name="authorization"> <local-cache name="authorization">
<eviction max-entries="10000" strategy="LRU"/> <eviction max-entries="10000" strategy="LRU"/>