KEYCLOAK-4995 Support for distributed SAML logout in cross DC
This commit is contained in:
1 parent
3f8083e34c
commit
794c508b10
28 files changed
+1324
-189
No files matched your search
@@ -78,6 +78,18 @@
|
||||
<version>7.1.2.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>5.2.20.Final</version> <!-- override version to match EAP's -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-cachestore-remote</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>5.2.20.Final</version> <!-- override version to match EAP's -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
|
||||
|
||||
+42
-32
@@ -16,9 +16,11 @@
|
||||
*/
|
||||
package org.keycloak.adapters.saml.jbossweb.infinispan;
|
||||
|
||||
import org.keycloak.adapters.saml.AdapterConstants;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
|
||||
|
||||
import java.util.List;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.servlet.ServletContext;
|
||||
@@ -26,6 +28,8 @@ import org.apache.catalina.Context;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.loaders.CacheLoaderManager;
|
||||
import org.infinispan.loaders.remote.RemoteCacheStore;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
@@ -37,24 +41,12 @@ public class InfinispanSessionCacheIdMapperUpdater {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(InfinispanSessionCacheIdMapperUpdater.class);
|
||||
|
||||
public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container/web";
|
||||
|
||||
private static final String DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheContainerJndi";
|
||||
private static final String DEPLOYMENT_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.deploymentCacheName";
|
||||
private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
|
||||
public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container";
|
||||
|
||||
public static SessionIdMapperUpdater addTokenStoreUpdaters(Context context, SessionIdMapper mapper, SessionIdMapperUpdater previousIdMapperUpdater) {
|
||||
boolean distributable = context.getDistributable();
|
||||
|
||||
if (! distributable) {
|
||||
LOG.warnv("Deployment {0} does not use supported distributed session cache mechanism", context.getName());
|
||||
return previousIdMapperUpdater;
|
||||
}
|
||||
|
||||
ServletContext servletContext = context.getServletContext();
|
||||
String cacheContainerLookup = (servletContext != null && servletContext.getInitParameter(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME) != null)
|
||||
? servletContext.getInitParameter(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME)
|
||||
: DEFAULT_CACHE_CONTAINER_JNDI_NAME;
|
||||
String containerName = servletContext == null ? null : servletContext.getInitParameter(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
|
||||
String cacheName = servletContext == null ? null : servletContext.getInitParameter(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
|
||||
|
||||
// the following is based on https://github.com/jbossas/jboss-as/blob/7.2.0.Final/clustering/web-infinispan/src/main/java/org/jboss/as/clustering/web/infinispan/DistributedCacheManagerFactory.java#L116-L122
|
||||
String host = context.getParent() == null ? "" : context.getParent().getName();
|
||||
@@ -62,43 +54,48 @@ public class InfinispanSessionCacheIdMapperUpdater {
|
||||
if ("/".equals(contextPath)) {
|
||||
contextPath = "/ROOT";
|
||||
}
|
||||
String deploymentSessionCacheName = host + contextPath;
|
||||
|
||||
boolean deploymentSessionCacheNamePreset = servletContext != null && servletContext.getInitParameter(DEPLOYMENT_CACHE_NAME_PARAM_NAME) != null;
|
||||
String deploymentSessionCacheName = deploymentSessionCacheNamePreset
|
||||
? servletContext.getInitParameter(DEPLOYMENT_CACHE_NAME_PARAM_NAME)
|
||||
: host + contextPath;
|
||||
boolean ssoCacheNamePreset = servletContext != null && servletContext.getInitParameter(SSO_CACHE_NAME_PARAM_NAME) != null;
|
||||
String ssoCacheName = ssoCacheNamePreset
|
||||
? servletContext.getInitParameter(SSO_CACHE_NAME_PARAM_NAME)
|
||||
: deploymentSessionCacheName + ".ssoCache";
|
||||
if (containerName == null || cacheName == null || deploymentSessionCacheName == null) {
|
||||
LOG.warnv("Cannot determine parameters of SSO cache for deployment {0}.", host + contextPath);
|
||||
|
||||
return previousIdMapperUpdater;
|
||||
}
|
||||
|
||||
String cacheContainerLookup = DEFAULT_CACHE_CONTAINER_JNDI_NAME + "/" + containerName;
|
||||
|
||||
try {
|
||||
EmbeddedCacheManager cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
|
||||
Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(ssoCacheName);
|
||||
Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(cacheName);
|
||||
if (ssoCacheConfiguration == null) {
|
||||
Configuration cacheConfiguration = cacheManager.getCacheConfiguration(deploymentSessionCacheName);
|
||||
if (cacheConfiguration == null) {
|
||||
LOG.debugv("Using default cache container configuration for SSO cache. lookup={0}, looked up configuration of cache={1}", cacheContainerLookup, deploymentSessionCacheName);
|
||||
LOG.debugv("Using default configuration for SSO cache {0}.{1}.", containerName, cacheName);
|
||||
ssoCacheConfiguration = cacheManager.getDefaultCacheConfiguration();
|
||||
} else {
|
||||
LOG.debugv("Using distributed HTTP session cache configuration for SSO cache. lookup={0}, configuration taken from cache={1}", cacheContainerLookup, deploymentSessionCacheName);
|
||||
LOG.debugv("Using distributed HTTP session cache configuration for SSO cache {0}.{1}, configuration taken from cache {2}",
|
||||
containerName, cacheName, deploymentSessionCacheName);
|
||||
ssoCacheConfiguration = cacheConfiguration;
|
||||
cacheManager.defineConfiguration(ssoCacheName, ssoCacheConfiguration);
|
||||
cacheManager.defineConfiguration(cacheName, ssoCacheConfiguration);
|
||||
}
|
||||
} else {
|
||||
LOG.debugv("Using custom configuration for SSO cache. lookup={0}, cache name={1}", cacheContainerLookup, ssoCacheName);
|
||||
LOG.debugv("Using custom configuration of SSO cache {0}.{1}.", containerName, cacheName);
|
||||
}
|
||||
|
||||
CacheMode ssoCacheMode = ssoCacheConfiguration.clustering().cacheMode();
|
||||
if (ssoCacheMode != CacheMode.REPL_ASYNC && ssoCacheMode != CacheMode.REPL_SYNC) {
|
||||
LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead", ssoCacheConfiguration.clustering().cacheModeString());
|
||||
LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead.", ssoCacheConfiguration.clustering().cacheModeString());
|
||||
}
|
||||
|
||||
Cache<String, String[]> ssoCache = cacheManager.getCache(ssoCacheName, true);
|
||||
ssoCache.addListener(new SsoSessionCacheListener(mapper));
|
||||
Cache<String, String[]> ssoCache = cacheManager.getCache(cacheName, true);
|
||||
final SsoSessionCacheListener listener = new SsoSessionCacheListener(ssoCache, mapper);
|
||||
ssoCache.addListener(listener);
|
||||
|
||||
LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, deploymentSessionCacheName);
|
||||
// Not possible to add listener for cross-DC support because of too old Infinispan in AS 7
|
||||
warnIfRemoteStoreIsUsed(ssoCache);
|
||||
|
||||
LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, cacheName);
|
||||
|
||||
SsoCacheSessionIdMapperUpdater updater = new SsoCacheSessionIdMapperUpdater(ssoCache, previousIdMapperUpdater);
|
||||
|
||||
@@ -108,4 +105,17 @@ public class InfinispanSessionCacheIdMapperUpdater {
|
||||
return previousIdMapperUpdater;
|
||||
}
|
||||
}
|
||||
|
||||
private static void warnIfRemoteStoreIsUsed(Cache<String, String[]> ssoCache) {
|
||||
final List<RemoteCacheStore> stores = getRemoteStores(ssoCache);
|
||||
if (stores == null || stores.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.warnv("Unable to listen for events on remote stores configured for cache {0} (unsupported in this Infinispan limitations), logouts will not be propagated.", ssoCache.getName());
|
||||
}
|
||||
|
||||
public static List<RemoteCacheStore> getRemoteStores(Cache ssoCache) {
|
||||
return ssoCache.getAdvancedCache().getComponentRegistry().getComponent(CacheLoaderManager.class).getCacheLoaders(RemoteCacheStore.class);
|
||||
}
|
||||
}
|
||||
+46
-27
@@ -20,6 +20,7 @@ import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.*;
|
||||
import org.infinispan.notifications.cachelistener.event.*;
|
||||
@@ -43,9 +44,12 @@ public class SsoSessionCacheListener {
|
||||
|
||||
private final SessionIdMapper idMapper;
|
||||
|
||||
private final Cache<String, String[]> ssoCache;
|
||||
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
public SsoSessionCacheListener(SessionIdMapper idMapper) {
|
||||
public SsoSessionCacheListener(Cache<String, String[]> ssoCache, SessionIdMapper idMapper) {
|
||||
this.ssoCache = ssoCache;
|
||||
this.idMapper = idMapper;
|
||||
}
|
||||
|
||||
@@ -68,8 +72,10 @@ public class SsoSessionCacheListener {
|
||||
@CacheEntryRemoved
|
||||
@CacheEntryModified
|
||||
public void addEvent(TransactionalEvent event) {
|
||||
if (event.isPre() == false) {
|
||||
if (event.getGlobalTransaction() != null) {
|
||||
map.get(event.getGlobalTransaction()).add(event);
|
||||
} else {
|
||||
processEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,40 +93,53 @@ public class SsoSessionCacheListener {
|
||||
}
|
||||
|
||||
for (final Event e : events) {
|
||||
switch (e.getType()) {
|
||||
case CACHE_ENTRY_CREATED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryCreated((CacheEntryCreatedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
processEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
case CACHE_ENTRY_MODIFIED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryModified((CacheEntryModifiedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
private void processEvent(final Event e) {
|
||||
switch (e.getType()) {
|
||||
case CACHE_ENTRY_CREATED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryCreated((CacheEntryCreatedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case CACHE_ENTRY_MODIFIED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryModified((CacheEntryModifiedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case CACHE_ENTRY_REMOVED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryRemoved((CacheEntryRemovedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case CACHE_ENTRY_REMOVED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryRemoved((CacheEntryRemovedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheEntryCreated(CacheEntryCreatedEvent event) {
|
||||
if (! (event.getKey() instanceof String) || ! (event.getValue() instanceof String[])) {
|
||||
if (! (event.getKey() instanceof String)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String httpSessionId = (String) event.getKey();
|
||||
String[] value = (String[]) event.getValue();
|
||||
|
||||
if (idMapper.hasSession(httpSessionId)) {
|
||||
// Ignore local events generated by remote store
|
||||
LOG.tracev("IGNORING cacheEntryCreated {0}", httpSessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] value = ssoCache.get((String) httpSessionId);
|
||||
|
||||
String ssoId = value[0];
|
||||
String principal = value[1];
|
||||
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.subsystem.saml.as7;
|
||||
|
||||
import org.keycloak.adapters.saml.AdapterConstants;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.jboss.as.server.deployment.DeploymentPhaseContext;
|
||||
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
|
||||
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
|
||||
import org.jboss.as.web.deployment.WarMetaData;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
|
||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||
import org.jboss.metadata.web.spec.LoginConfigMetaData;
|
||||
import org.jboss.msc.service.ServiceName;
|
||||
import org.jboss.msc.service.ServiceTarget;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class KeycloakClusteredSsoDeploymentProcessor implements DeploymentUnitProcessor {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KeycloakClusteredSsoDeploymentProcessor.class);
|
||||
|
||||
private static final String DEFAULT_CACHE_CONTAINER = "web";
|
||||
private static final String SSO_CACHE_CONTAINER_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.containerName";
|
||||
private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
|
||||
|
||||
@Override
|
||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||
|
||||
if (isKeycloakSamlAuthMethod(deploymentUnit) && isDistributable(deploymentUnit)) {
|
||||
addSamlReplicationConfiguration(deploymentUnit, phaseContext);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDistributable(final DeploymentUnit deploymentUnit) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return webMetaData.getDistributable() != null || webMetaData.getReplicationConfig() != null;
|
||||
}
|
||||
|
||||
public static boolean isKeycloakSamlAuthMethod(final DeploymentUnit deploymentUnit) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Configuration.INSTANCE.isSecureDeployment(deploymentUnit)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||
|
||||
return loginConfig != null && Objects.equals(loginConfig.getAuthMethod(), "KEYCLOAK-SAML");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undeploy(DeploymentUnit du) {
|
||||
|
||||
}
|
||||
|
||||
private void addSamlReplicationConfiguration(DeploymentUnit deploymentUnit, DeploymentPhaseContext context) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
webMetaData = new JBossWebMetaData();
|
||||
warMetaData.setMergedJBossWebMetaData(webMetaData);
|
||||
}
|
||||
|
||||
// Find out default names of cache container and cache
|
||||
String cacheContainer = DEFAULT_CACHE_CONTAINER;
|
||||
String deploymentSessionCacheName =
|
||||
(deploymentUnit.getParent() == null
|
||||
? ""
|
||||
: deploymentUnit.getParent().getName() + ".")
|
||||
+ deploymentUnit.getName();
|
||||
|
||||
// Update names from jboss-web.xml's <replicationConfig>
|
||||
if (webMetaData.getReplicationConfig() != null && webMetaData.getReplicationConfig().getCacheName() != null) {
|
||||
ServiceName sn = ServiceName.parse(webMetaData.getReplicationConfig().getCacheName());
|
||||
cacheContainer = sn.getParent().getSimpleName();
|
||||
deploymentSessionCacheName = sn.getSimpleName();
|
||||
}
|
||||
String ssoCacheName = deploymentSessionCacheName + ".ssoCache";
|
||||
|
||||
// Override if they were set in the context parameters
|
||||
List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
|
||||
if (contextParams == null) {
|
||||
contextParams = new ArrayList<>();
|
||||
}
|
||||
for (ParamValueMetaData contextParam : contextParams) {
|
||||
if (Objects.equals(contextParam.getParamName(), SSO_CACHE_CONTAINER_NAME_PARAM_NAME)) {
|
||||
cacheContainer = contextParam.getParamValue();
|
||||
} else if (Objects.equals(contextParam.getParamName(), SSO_CACHE_NAME_PARAM_NAME)) {
|
||||
ssoCacheName = contextParam.getParamValue();
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debugv("Determined SSO cache container configuration: container: {0}, cache: {1}", cacheContainer, ssoCacheName);
|
||||
// addCacheDependency(context, deploymentUnit, cacheContainer, cacheName);
|
||||
|
||||
// Set context parameters for SSO cache container/name
|
||||
ParamValueMetaData paramContainer = new ParamValueMetaData();
|
||||
paramContainer.setParamName(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
|
||||
paramContainer.setParamValue(cacheContainer);
|
||||
contextParams.add(paramContainer);
|
||||
|
||||
ParamValueMetaData paramSsoCache = new ParamValueMetaData();
|
||||
paramSsoCache.setParamName(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
|
||||
paramSsoCache.setParamValue(ssoCacheName);
|
||||
contextParams.add(paramSsoCache);
|
||||
|
||||
webMetaData.setContextParams(contextParams);
|
||||
}
|
||||
|
||||
private void addCacheDependency(DeploymentPhaseContext context, DeploymentUnit deploymentUnit, String cacheContainer, String cacheName) {
|
||||
ServiceName jbossAsCacheContainerService = ServiceName.of("jboss", "infinispan", cacheContainer);
|
||||
ServiceTarget st = context.getServiceTarget();
|
||||
st.addDependency(jbossAsCacheContainerService.append(cacheName));
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -48,6 +48,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
|
||||
Phase.POST_MODULE, // PHASE
|
||||
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
|
||||
chooseConfigDeploymentProcessor());
|
||||
processorTarget.addDeploymentProcessor(KeycloakSamlExtension.SUBSYSTEM_NAME,
|
||||
Phase.POST_MODULE, // PHASE
|
||||
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
|
||||
chooseClusteredSsoDeploymentProcessor());
|
||||
}
|
||||
}, OperationContext.Stage.RUNTIME);
|
||||
}
|
||||
@@ -60,6 +64,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
|
||||
return new KeycloakAdapterConfigDeploymentProcessor();
|
||||
}
|
||||
|
||||
private DeploymentUnitProcessor chooseClusteredSsoDeploymentProcessor() {
|
||||
return new KeycloakClusteredSsoDeploymentProcessor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@ package org.keycloak.adapters.saml;
|
||||
*/
|
||||
public class AdapterConstants {
|
||||
public static final String AUTH_DATA_PARAM_NAME="org.keycloak.saml.xml.adapterConfig";
|
||||
public static final String REPLICATION_CONFIG_CONTAINER_PARAM_NAME = "org.keycloak.saml.replication.container";
|
||||
public static final String REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME = "org.keycloak.saml.replication.cache.sso";
|
||||
}
|
||||
+9
-2
@@ -152,12 +152,19 @@ public class ServletSamlSessionStore implements SamlSessionStore {
|
||||
public boolean isLoggedIn() {
|
||||
HttpSession session = getSession(false);
|
||||
if (session == null) {
|
||||
log.debug("session was null, returning null");
|
||||
log.debug("Session was not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! idMapper.hasSession(session.getId())) {
|
||||
log.debugf("Session %s has expired on some other node", session.getId());
|
||||
session.removeAttribute(SamlSession.class.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
||||
if (samlSession == null) {
|
||||
log.debug("SamlSession was not in session, returning null");
|
||||
log.debug("SamlSession was not found in the session");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-cachestore-remote</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketbox</groupId>
|
||||
<artifactId>picketbox</artifactId>
|
||||
|
||||
+47
-32
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.keycloak.adapters.saml.wildfly.infinispan;
|
||||
|
||||
import org.keycloak.adapters.saml.AdapterConstants;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
|
||||
|
||||
@@ -27,6 +28,8 @@ import org.infinispan.Cache;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.persistence.manager.PersistenceManager;
|
||||
import org.infinispan.persistence.remote.RemoteStore;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
@@ -37,64 +40,55 @@ public class InfinispanSessionCacheIdMapperUpdater {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(InfinispanSessionCacheIdMapperUpdater.class);
|
||||
|
||||
public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container/web";
|
||||
|
||||
private static final String DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheContainerJndi";
|
||||
private static final String DEPLOYMENT_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.deploymentCacheName";
|
||||
private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
|
||||
public static final String DEFAULT_CACHE_CONTAINER_JNDI_NAME = "java:jboss/infinispan/container";
|
||||
|
||||
public static SessionIdMapperUpdater addTokenStoreUpdaters(DeploymentInfo deploymentInfo, SessionIdMapper mapper, SessionIdMapperUpdater previousIdMapperUpdater) {
|
||||
boolean distributable = Objects.equals(
|
||||
deploymentInfo.getSessionManagerFactory().getClass().getName(),
|
||||
"org.wildfly.clustering.web.undertow.session.DistributableSessionManagerFactory"
|
||||
);
|
||||
Map<String, String> initParameters = deploymentInfo.getInitParameters();
|
||||
String containerName = initParameters == null ? null : initParameters.get(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
|
||||
String cacheName = initParameters == null ? null : initParameters.get(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
|
||||
|
||||
if (containerName == null || cacheName == null) {
|
||||
LOG.warnv("Cannot determine parameters of SSO cache for deployment {0}.", deploymentInfo.getDeploymentName());
|
||||
|
||||
if (! distributable) {
|
||||
LOG.warnv("Deployment {0} does not use supported distributed session cache mechanism", deploymentInfo.getDeploymentName());
|
||||
return previousIdMapperUpdater;
|
||||
}
|
||||
|
||||
Map<String, String> initParameters = deploymentInfo.getInitParameters();
|
||||
String cacheContainerLookup = (initParameters != null && initParameters.get(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME) != null)
|
||||
? initParameters.get(DEPLOYMENT_CACHE_CONTAINER_JNDI_NAME_PARAM_NAME)
|
||||
: DEFAULT_CACHE_CONTAINER_JNDI_NAME;
|
||||
boolean deploymentSessionCacheNamePreset = initParameters != null && initParameters.get(DEPLOYMENT_CACHE_NAME_PARAM_NAME) != null;
|
||||
String deploymentSessionCacheName = deploymentSessionCacheNamePreset
|
||||
? initParameters.get(DEPLOYMENT_CACHE_NAME_PARAM_NAME)
|
||||
: deploymentInfo.getDeploymentName();
|
||||
boolean ssoCacheNamePreset = initParameters != null && initParameters.get(SSO_CACHE_NAME_PARAM_NAME) != null;
|
||||
String ssoCacheName = ssoCacheNamePreset
|
||||
? initParameters.get(SSO_CACHE_NAME_PARAM_NAME)
|
||||
: deploymentSessionCacheName + ".ssoCache";
|
||||
String cacheContainerLookup = DEFAULT_CACHE_CONTAINER_JNDI_NAME + "/" + containerName;
|
||||
String deploymentSessionCacheName = deploymentInfo.getDeploymentName();
|
||||
|
||||
try {
|
||||
EmbeddedCacheManager cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
|
||||
Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(ssoCacheName);
|
||||
Configuration ssoCacheConfiguration = cacheManager.getCacheConfiguration(cacheName);
|
||||
if (ssoCacheConfiguration == null) {
|
||||
Configuration cacheConfiguration = cacheManager.getCacheConfiguration(deploymentSessionCacheName);
|
||||
if (cacheConfiguration == null) {
|
||||
LOG.debugv("Using default cache container configuration for SSO cache. lookup={0}, looked up configuration of cache={1}", cacheContainerLookup, deploymentSessionCacheName);
|
||||
LOG.debugv("Using default configuration for SSO cache {0}.{1}.", containerName, cacheName);
|
||||
ssoCacheConfiguration = cacheManager.getDefaultCacheConfiguration();
|
||||
} else {
|
||||
LOG.debugv("Using distributed HTTP session cache configuration for SSO cache. lookup={0}, configuration taken from cache={1}", cacheContainerLookup, deploymentSessionCacheName);
|
||||
LOG.debugv("Using distributed HTTP session cache configuration for SSO cache {0}.{1}, configuration taken from cache {2}",
|
||||
containerName, cacheName, deploymentSessionCacheName);
|
||||
ssoCacheConfiguration = cacheConfiguration;
|
||||
cacheManager.defineConfiguration(ssoCacheName, ssoCacheConfiguration);
|
||||
cacheManager.defineConfiguration(cacheName, ssoCacheConfiguration);
|
||||
}
|
||||
} else {
|
||||
LOG.debugv("Using custom configuration of SSO cache. lookup={0}, cache name={1}", cacheContainerLookup, ssoCacheName);
|
||||
LOG.debugv("Using custom configuration of SSO cache {0}.{1}.", containerName, cacheName);
|
||||
}
|
||||
|
||||
CacheMode ssoCacheMode = ssoCacheConfiguration.clustering().cacheMode();
|
||||
if (ssoCacheMode != CacheMode.REPL_ASYNC && ssoCacheMode != CacheMode.REPL_SYNC) {
|
||||
LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead", ssoCacheConfiguration.clustering().cacheModeString());
|
||||
LOG.warnv("SSO cache mode is {0}, it is recommended to use replicated mode instead.", ssoCacheConfiguration.clustering().cacheModeString());
|
||||
}
|
||||
|
||||
Cache<String, String[]> ssoCache = cacheManager.getCache(ssoCacheName, true);
|
||||
ssoCache.addListener(new SsoSessionCacheListener(mapper));
|
||||
Cache<String, String[]> ssoCache = cacheManager.getCache(cacheName, true);
|
||||
final SsoSessionCacheListener listener = new SsoSessionCacheListener(ssoCache, mapper);
|
||||
ssoCache.addListener(listener);
|
||||
|
||||
LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, deploymentSessionCacheName);
|
||||
addSsoCacheCrossDcListener(ssoCache, listener);
|
||||
|
||||
LOG.debugv("Added distributed SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, cacheName);
|
||||
|
||||
LOG.debugv("Adding session listener for SSO session cache, lookup={0}, cache name={1}", cacheContainerLookup, cacheName);
|
||||
SsoCacheSessionIdMapperUpdater updater = new SsoCacheSessionIdMapperUpdater(ssoCache, previousIdMapperUpdater);
|
||||
deploymentInfo.addSessionListener(updater);
|
||||
|
||||
@@ -104,4 +98,25 @@ public class InfinispanSessionCacheIdMapperUpdater {
|
||||
return previousIdMapperUpdater;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSsoCacheCrossDcListener(Cache<String, String[]> ssoCache, SsoSessionCacheListener listener) {
|
||||
if (ssoCache.getCacheConfiguration().persistence() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Set<RemoteStore> stores = getRemoteStores(ssoCache);
|
||||
if (stores == null || stores.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.infov("Listening for events on remote stores configured for cache {0}", ssoCache.getName());
|
||||
|
||||
for (RemoteStore store : stores) {
|
||||
store.getRemoteCache().addClientListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<RemoteStore> getRemoteStores(Cache ispnCache) {
|
||||
return ispnCache.getAdvancedCache().getComponentRegistry().getComponent(PersistenceManager.class).getStores(RemoteStore.class);
|
||||
}
|
||||
}
|
||||
+87
-23
@@ -20,6 +20,12 @@ import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated;
|
||||
import org.infinispan.client.hotrod.annotation.ClientCacheEntryRemoved;
|
||||
import org.infinispan.client.hotrod.annotation.ClientListener;
|
||||
import org.infinispan.client.hotrod.event.ClientCacheEntryCreatedEvent;
|
||||
import org.infinispan.client.hotrod.event.ClientCacheEntryRemovedEvent;
|
||||
import org.infinispan.notifications.Listener;
|
||||
import org.infinispan.notifications.cachelistener.annotation.*;
|
||||
import org.infinispan.notifications.cachelistener.event.*;
|
||||
@@ -34,6 +40,7 @@ import org.jboss.logging.Logger;
|
||||
* @author hmlnarik
|
||||
*/
|
||||
@Listener
|
||||
@ClientListener
|
||||
public class SsoSessionCacheListener {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SsoSessionCacheListener.class);
|
||||
@@ -42,14 +49,21 @@ public class SsoSessionCacheListener {
|
||||
|
||||
private final SessionIdMapper idMapper;
|
||||
|
||||
private final Cache<String, String[]> ssoCache;
|
||||
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
public SsoSessionCacheListener(SessionIdMapper idMapper) {
|
||||
public SsoSessionCacheListener(Cache<String, String[]> ssoCache, SessionIdMapper idMapper) {
|
||||
this.ssoCache = ssoCache;
|
||||
this.idMapper = idMapper;
|
||||
}
|
||||
|
||||
@TransactionRegistered
|
||||
public void startTransaction(TransactionRegisteredEvent event) {
|
||||
if (event.getGlobalTransaction() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
map.put(event.getGlobalTransaction().globalId(), new ConcurrentLinkedQueue<Event>());
|
||||
}
|
||||
|
||||
@@ -66,42 +80,56 @@ public class SsoSessionCacheListener {
|
||||
@CacheEntryCreated
|
||||
@CacheEntryRemoved
|
||||
public void addEvent(TransactionalEvent event) {
|
||||
if (event.isPre() == false) {
|
||||
if (event.isOriginLocal()) {
|
||||
// Local events are processed by local HTTP session listener
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isPre()) { // only handle post events
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getGlobalTransaction() != null) {
|
||||
map.get(event.getGlobalTransaction().globalId()).add(event);
|
||||
} else {
|
||||
processEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@TransactionCompleted
|
||||
public void endTransaction(TransactionCompletedEvent event) {
|
||||
if (event.getGlobalTransaction() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Queue<Event> events = map.remove(event.getGlobalTransaction().globalId());
|
||||
|
||||
if (events == null || ! event.isTransactionSuccessful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isOriginLocal()) {
|
||||
// Local events are processed by local HTTP session listener
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Event e : events) {
|
||||
switch (e.getType()) {
|
||||
case CACHE_ENTRY_CREATED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryCreated((CacheEntryCreatedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
processEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
case CACHE_ENTRY_REMOVED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryRemoved((CacheEntryRemovedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
private void processEvent(final Event e) {
|
||||
switch (e.getType()) {
|
||||
case CACHE_ENTRY_CREATED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryCreated((CacheEntryCreatedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case CACHE_ENTRY_REMOVED:
|
||||
this.executor.submit(new Runnable() {
|
||||
@Override public void run() {
|
||||
cacheEntryRemoved((CacheEntryRemovedEvent) e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +156,40 @@ public class SsoSessionCacheListener {
|
||||
|
||||
this.idMapper.removeSession((String) event.getKey());
|
||||
}
|
||||
|
||||
@ClientCacheEntryCreated
|
||||
public void remoteCacheEntryCreated(ClientCacheEntryCreatedEvent event) {
|
||||
if (! (event.getKey() instanceof String)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String httpSessionId = (String) event.getKey();
|
||||
|
||||
if (idMapper.hasSession(httpSessionId)) {
|
||||
// Ignore local events generated by remote store
|
||||
LOG.tracev("IGNORING remoteCacheEntryCreated {0}", httpSessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] value = ssoCache.get((String) httpSessionId);
|
||||
|
||||
if (value != null) {
|
||||
String ssoId = value[0];
|
||||
String principal = value[1];
|
||||
|
||||
LOG.tracev("remoteCacheEntryCreated {0}:{1}", httpSessionId, ssoId);
|
||||
|
||||
this.idMapper.map(ssoId, principal, httpSessionId);
|
||||
} else {
|
||||
LOG.tracev("remoteCacheEntryCreated {0}", event.getKey());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ClientCacheEntryRemoved
|
||||
public void remoteCacheEntryRemoved(ClientCacheEntryRemovedEvent event) {
|
||||
LOG.tracev("remoteCacheEntryRemoved {0}", event.getKey());
|
||||
|
||||
this.idMapper.removeSession((String) event.getKey());
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.subsystem.adapter.saml.extension;
|
||||
|
||||
import org.keycloak.adapters.saml.AdapterConstants;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.jboss.as.controller.capability.CapabilityServiceSupport;
|
||||
import org.jboss.as.server.deployment.Attachments;
|
||||
import org.jboss.as.server.deployment.DeploymentPhaseContext;
|
||||
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
|
||||
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
|
||||
import org.jboss.as.web.common.WarMetaData;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
|
||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||
import org.jboss.metadata.web.spec.LoginConfigMetaData;
|
||||
import org.jboss.msc.service.ServiceController;
|
||||
import org.jboss.msc.service.ServiceName;
|
||||
import org.jboss.msc.service.ServiceTarget;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class KeycloakClusteredSsoDeploymentProcessor implements DeploymentUnitProcessor {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KeycloakClusteredSsoDeploymentProcessor.class);
|
||||
|
||||
private static final String DEFAULT_CACHE_CONTAINER = "web";
|
||||
private static final String SSO_CACHE_CONTAINER_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.containerName";
|
||||
private static final String SSO_CACHE_NAME_PARAM_NAME = "keycloak.sessionIdMapperUpdater.infinispan.cacheName";
|
||||
|
||||
@Override
|
||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||
|
||||
if (isKeycloakSamlAuthMethod(deploymentUnit) && isDistributable(deploymentUnit)) {
|
||||
addSamlReplicationConfiguration(deploymentUnit, phaseContext);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDistributable(final DeploymentUnit deploymentUnit) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return webMetaData.getDistributable() != null || webMetaData.getReplicationConfig() != null;
|
||||
}
|
||||
|
||||
public static boolean isKeycloakSamlAuthMethod(final DeploymentUnit deploymentUnit) {
|
||||
if (Configuration.INSTANCE.getSecureDeployment(deploymentUnit) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||
|
||||
return loginConfig != null && Objects.equals(loginConfig.getAuthMethod(), "KEYCLOAK-SAML");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undeploy(DeploymentUnit du) {
|
||||
|
||||
}
|
||||
|
||||
private void addSamlReplicationConfiguration(DeploymentUnit deploymentUnit, DeploymentPhaseContext context) {
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
webMetaData = new JBossWebMetaData();
|
||||
warMetaData.setMergedJBossWebMetaData(webMetaData);
|
||||
}
|
||||
|
||||
// Find out default names of cache container and cache
|
||||
String cacheContainer = DEFAULT_CACHE_CONTAINER;
|
||||
String deploymentSessionCacheName =
|
||||
(deploymentUnit.getParent() == null
|
||||
? ""
|
||||
: deploymentUnit.getParent().getName() + ".")
|
||||
+ deploymentUnit.getName();
|
||||
|
||||
// Update names from jboss-web.xml's <replicationConfig>
|
||||
if (webMetaData.getReplicationConfig() != null && webMetaData.getReplicationConfig().getCacheName() != null) {
|
||||
ServiceName sn = ServiceName.parse(webMetaData.getReplicationConfig().getCacheName());
|
||||
cacheContainer = sn.getParent().getSimpleName();
|
||||
deploymentSessionCacheName = sn.getSimpleName();
|
||||
}
|
||||
String ssoCacheName = deploymentSessionCacheName + ".ssoCache";
|
||||
|
||||
// Override if they were set in the context parameters
|
||||
List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
|
||||
if (contextParams == null) {
|
||||
contextParams = new ArrayList<>();
|
||||
}
|
||||
for (ParamValueMetaData contextParam : contextParams) {
|
||||
if (Objects.equals(contextParam.getParamName(), SSO_CACHE_CONTAINER_NAME_PARAM_NAME)) {
|
||||
cacheContainer = contextParam.getParamValue();
|
||||
} else if (Objects.equals(contextParam.getParamName(), SSO_CACHE_NAME_PARAM_NAME)) {
|
||||
ssoCacheName = contextParam.getParamValue();
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debugv("Determined SSO cache container configuration: container: {0}, cache: {1}", cacheContainer, ssoCacheName);
|
||||
addCacheDependency(context, deploymentUnit, cacheContainer, ssoCacheName);
|
||||
|
||||
// Set context parameters for SSO cache container/name
|
||||
ParamValueMetaData paramContainer = new ParamValueMetaData();
|
||||
paramContainer.setParamName(AdapterConstants.REPLICATION_CONFIG_CONTAINER_PARAM_NAME);
|
||||
paramContainer.setParamValue(cacheContainer);
|
||||
contextParams.add(paramContainer);
|
||||
|
||||
ParamValueMetaData paramSsoCache = new ParamValueMetaData();
|
||||
paramSsoCache.setParamName(AdapterConstants.REPLICATION_CONFIG_SSO_CACHE_PARAM_NAME);
|
||||
paramSsoCache.setParamValue(ssoCacheName);
|
||||
contextParams.add(paramSsoCache);
|
||||
|
||||
webMetaData.setContextParams(contextParams);
|
||||
}
|
||||
|
||||
private void addCacheDependency(DeploymentPhaseContext context, DeploymentUnit deploymentUnit, String cacheContainer, String cacheName) {
|
||||
ServiceName wf10CacheContainerServiceName = ServiceName.of("jboss", "infinispan", cacheContainer);
|
||||
final ServiceController<?> wf10CacheContainerService = context.getServiceRegistry().getService(wf10CacheContainerServiceName);
|
||||
|
||||
boolean legacy = wf10CacheContainerService != null;
|
||||
ServiceTarget st = context.getServiceTarget();
|
||||
|
||||
if (legacy) {
|
||||
ServiceName cacheServiceName = wf10CacheContainerServiceName.append(cacheName);
|
||||
ServiceController<?> cacheService = context.getServiceRegistry().getService(cacheServiceName);
|
||||
if (cacheService != null) {
|
||||
st.addDependency(cacheServiceName);
|
||||
}
|
||||
} else {
|
||||
CapabilityServiceSupport support = deploymentUnit.getAttachment(Attachments.CAPABILITY_SERVICE_SUPPORT);
|
||||
|
||||
ServiceName cacheServiceName = support.getCapabilityServiceName("org.wildfly.clustering.infinispan.cache." + cacheContainer + "." + cacheName);
|
||||
ServiceController<?> cacheService = context.getServiceRegistry().getService(cacheServiceName);
|
||||
if (cacheService != null) {
|
||||
st.addDependency(cacheServiceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -43,6 +43,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
|
||||
Phase.POST_MODULE, // PHASE
|
||||
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
|
||||
chooseConfigDeploymentProcessor());
|
||||
processorTarget.addDeploymentProcessor(KeycloakSamlExtension.SUBSYSTEM_NAME,
|
||||
Phase.POST_MODULE, // PHASE
|
||||
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
|
||||
chooseClusteredSsoDeploymentProcessor());
|
||||
}
|
||||
}, OperationContext.Stage.RUNTIME);
|
||||
}
|
||||
@@ -54,4 +58,8 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
|
||||
private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
|
||||
return new KeycloakAdapterConfigDeploymentProcessor();
|
||||
}
|
||||
|
||||
private DeploymentUnitProcessor chooseClusteredSsoDeploymentProcessor() {
|
||||
return new KeycloakClusteredSsoDeploymentProcessor();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user