KEYCLOAK-4189 - Cross DC testing
This commit is contained in:
1 parent
fd8a3dccaf
commit
a0f3a6469f
45 files changed
+1752
-158
No files matched your search
+29
-12
@@ -154,7 +154,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||
|
||||
if (clustered) {
|
||||
String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
|
||||
configureTransport(gcb, nodeName);
|
||||
String jgroupsUdpMcastAddr = config.get("jgroupsUdpMcastAddr", System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR));
|
||||
configureTransport(gcb, nodeName, jgroupsUdpMcastAddr);
|
||||
}
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
@@ -317,24 +318,40 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||
return cb.build();
|
||||
}
|
||||
|
||||
protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
|
||||
private static final Object CHANNEL_INIT_SYNCHRONIZER = new Object();
|
||||
|
||||
protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName, String jgroupsUdpMcastAddr) {
|
||||
if (nodeName == null) {
|
||||
gcb.transport().defaultTransport();
|
||||
} else {
|
||||
FileLookup fileLookup = FileLookupFactory.newInstance();
|
||||
|
||||
try {
|
||||
// Compatibility with Wildfly
|
||||
JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
|
||||
channel.setName(nodeName);
|
||||
JGroupsTransport transport = new JGroupsTransport(channel);
|
||||
synchronized (CHANNEL_INIT_SYNCHRONIZER) {
|
||||
String originalMcastAddr = System.getProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
|
||||
if (jgroupsUdpMcastAddr == null) {
|
||||
System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
|
||||
} else {
|
||||
System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, jgroupsUdpMcastAddr);
|
||||
}
|
||||
try {
|
||||
// Compatibility with Wildfly
|
||||
JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
|
||||
channel.setName(nodeName);
|
||||
JGroupsTransport transport = new JGroupsTransport(channel);
|
||||
|
||||
gcb.transport().nodeName(nodeName);
|
||||
gcb.transport().transport(transport);
|
||||
gcb.transport().nodeName(nodeName);
|
||||
gcb.transport().transport(transport);
|
||||
|
||||
logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (originalMcastAddr == null) {
|
||||
System.getProperties().remove(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR);
|
||||
} else {
|
||||
System.setProperty(InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR, originalMcastAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
||||
|
||||
// System property used on Wildfly to identify distributedCache address and sticky session route
|
||||
String JBOSS_NODE_NAME = "jboss.node.name";
|
||||
String JGROUPS_UDP_MCAST_ADDR = "jgroups.udp.mcast_addr";
|
||||
|
||||
|
||||
<K, V> Cache<K, V> getCache(String name);
|
||||
|
||||
+1
-1
@@ -220,7 +220,7 @@ public abstract class CacheManager {
|
||||
|
||||
addInvalidationsFromEvent(event, invalidations);
|
||||
|
||||
getLogger().debugf("Invalidating %d cache items after received event %s", invalidations.size(), event);
|
||||
getLogger().debugf("[%s] Invalidating %d cache items after received event %s", cache.getCacheManager().getAddress(), invalidations.size(), event);
|
||||
|
||||
for (String invalidation : invalidations) {
|
||||
invalidateObject(invalidation);
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.cache.infinispan;
|
||||
package org.keycloak.models.cache.infinispan.events;
|
||||
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.cache.infinispan;
|
||||
package org.keycloak.models.cache.infinispan.events;
|
||||
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
|
||||
+9
-4
@@ -19,8 +19,8 @@ package org.keycloak.models.sessions.infinispan;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.*;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
|
||||
import java.util.*;
|
||||
@@ -58,7 +58,12 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
|
||||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
|
||||
AddInvalidatedActionTokenEvent event = new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue);
|
||||
this.tx.notify(cluster, generateActionTokenEventId(), event, false);
|
||||
}
|
||||
|
||||
private static String generateActionTokenEventId() {
|
||||
return InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS + "/" + UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,6 +98,6 @@ public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvi
|
||||
}
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
|
||||
this.tx.notify(cluster, generateActionTokenEventId(), new RemoveActionTokensSpecificEvent(userId, actionId), false);
|
||||
}
|
||||
}
|
||||
+58
-30
@@ -23,14 +23,16 @@ import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.models.*;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.AddInvalidatedActionTokenEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RemoveActionTokensSpecificEvent;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.context.Flag;
|
||||
import org.infinispan.remoting.transport.Address;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -38,6 +40,10 @@ import org.infinispan.context.Flag;
|
||||
*/
|
||||
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProviderFactory.class);
|
||||
|
||||
private volatile Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache;
|
||||
|
||||
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
|
||||
|
||||
/**
|
||||
@@ -49,34 +55,7 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
|
||||
|
||||
@Override
|
||||
public ActionTokenStoreProvider create(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
|
||||
cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
|
||||
if (event instanceof RemoveActionTokensSpecificEvent) {
|
||||
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
|
||||
|
||||
actionTokenCache
|
||||
.getAdvancedCache()
|
||||
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
|
||||
.forEach(actionTokenCache::remove);
|
||||
} else if (event instanceof AddInvalidatedActionTokenEvent) {
|
||||
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
|
||||
|
||||
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
|
||||
actionTokenCache.put(e.getKey(), e.getTokenValue());
|
||||
} else {
|
||||
actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
|
||||
return new InfinispanActionTokenStoreProvider(session, this.actionTokenCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,8 +63,57 @@ public class InfinispanActionTokenStoreProviderFactory implements ActionTokenSto
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static Cache<ActionTokenReducedKey, ActionTokenValueEntity> initActionTokenCache(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
|
||||
final Address cacheAddress = cache.getCacheManager().getAddress();
|
||||
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
|
||||
cluster.registerListener(ClusterProvider.ALL, event -> {
|
||||
if (event instanceof RemoveActionTokensSpecificEvent) {
|
||||
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
|
||||
|
||||
LOG.debugf("[%s] Removing token invalidation for user+action: userId=%s, actionId=%s", cacheAddress, e.getUserId(), e.getActionId());
|
||||
|
||||
cache
|
||||
.getAdvancedCache()
|
||||
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
|
||||
.forEach(cache::remove);
|
||||
} else if (event instanceof AddInvalidatedActionTokenEvent) {
|
||||
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
|
||||
|
||||
LOG.debugf("[%s] Invalidating token %s", cacheAddress, e.getKey());
|
||||
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
|
||||
cache.put(e.getKey(), e.getTokenValue());
|
||||
} else {
|
||||
cache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LOG.debugf("[%s] Registered cluster listeners", cacheAddress);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = this.actionTokenCache;
|
||||
|
||||
// It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
|
||||
// means also listeners will start only after first cache initialization - that would be too late
|
||||
if (cache == null) {
|
||||
synchronized (this) {
|
||||
cache = this.actionTokenCache;
|
||||
if (cache == null) {
|
||||
this.actionTokenCache = initActionTokenCache(factory.create());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
|
||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
||||
cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
|
||||
|
||||
log.debug("Registered cluster listeners");
|
||||
log.debugf("[%s] Registered cluster listeners", authSessionsCache.getCacheManager().getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -81,6 +81,11 @@ public class ActionTokenReducedKey implements Serializable {
|
||||
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "userId=" + userId + ", actionId=" + actionId + ", actionVerificationNonce=" + actionVerificationNonce;
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
|
||||
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
|
||||
output.writeByte(VERSION_1);
|
||||
|
||||
output.writeBoolean(! t.notes.isEmpty());
|
||||
output.writeBoolean(t.notes.isEmpty());
|
||||
if (! t.notes.isEmpty()) {
|
||||
output.writeObject(t.notes);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user