keycloak-memoizeit
Changes
adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java 6(+3 -3)
adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java 14(+13 -1)
adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java 99(+78 -21)
adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java 24(+1 -23)
testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SessionServlet.java 4(+4 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SessionPortalDistributable.java 42(+42 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterClusteredTest.java 155(+155 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractSAMLAdapterClusteredTest.java 153(+7 -146)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/OIDCAdapterClusterTest.java 144(+144 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/SAMLAdapterClusterTest.java 8(+0 -8)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json 10(+10 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/META-INF/context.xml 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/jetty-web.xml 46(+46 -0)
Details
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java
index d11f1a9..4e374f5 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java
@@ -24,9 +24,8 @@ import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.wildfly.security.auth.server.SecurityIdentity;
-import javax.security.auth.callback.CallbackHandler;
+import java.io.Serializable;
import java.security.Principal;
import java.util.HashSet;
import java.util.Set;
@@ -34,8 +33,9 @@ import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public class ElytronAccount implements OidcKeycloakAccount {
+public class ElytronAccount implements Serializable, OidcKeycloakAccount {
+ private static final long serialVersionUID = -6775274346765339292L;
protected static Logger log = Logger.getLogger(ElytronAccount.class);
private final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java
index f5ecd88..86b6539 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java
@@ -26,15 +26,17 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.spi.UserSessionManagement;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.Scope;
import javax.security.auth.callback.CallbackHandler;
+import java.util.List;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public class ElytronCookieTokenStore implements ElytronTokeStore {
+public class ElytronCookieTokenStore implements ElytronTokeStore, UserSessionManagement {
protected static Logger log = Logger.getLogger(ElytronCookieTokenStore.class);
@@ -158,4 +160,14 @@ public class ElytronCookieTokenStore implements ElytronTokeStore {
}
}
}
+
+ @Override
+ public void logoutAll() {
+ //no-op
+ }
+
+ @Override
+ public void logoutHttpSessions(List<String> ids) {
+ //no-op
+ }
}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
index 1a81637..e1ec274 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
@@ -18,19 +18,27 @@
package org.keycloak.adapters.elytron;
-import java.util.function.Consumer;
+import static org.keycloak.adapters.elytron.ElytronHttpFacade.UNDERTOW_EXCHANGE;
import javax.security.auth.callback.CallbackHandler;
-
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.server.session.SessionConfig;
+import io.undertow.server.session.SessionManager;
+import io.undertow.servlet.handlers.ServletRequestContext;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.spi.UserSessionManagement;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpScopeNotification;
import org.wildfly.security.http.Scope;
@@ -38,7 +46,7 @@ import org.wildfly.security.http.Scope;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
-public class ElytronSessionTokenStore implements ElytronTokeStore {
+public class ElytronSessionTokenStore implements ElytronTokeStore, UserSessionManagement {
private static Logger log = Logger.getLogger(ElytronSessionTokenStore.class);
@@ -131,17 +139,21 @@ public class ElytronSessionTokenStore implements ElytronTokeStore {
if (!session.exists()) {
session.create();
+ session.registerForNotification(httpScopeNotification -> {
+ if (!httpScopeNotification.isOfType(HttpScopeNotification.SessionNotificationType.UNDEPLOY)) {
+ HttpScope invalidated = httpScopeNotification.getScope(Scope.SESSION);
+
+ if (invalidated != null) {
+ invalidated.setAttachment(ElytronAccount.class.getName(), null);
+ invalidated.setAttachment(KeycloakSecurityContext.class.getName(), null);
+ }
+ }
+ });
}
session.setAttachment(ElytronAccount.class.getName(), account);
session.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
- session.registerForNotification(httpScopeNotification -> {
- if (!httpScopeNotification.isOfType(HttpScopeNotification.SessionNotificationType.UNDEPLOY)) {
- logout();
- }
- });
-
HttpScope scope = this.httpFacade.getScope(Scope.EXCHANGE);
scope.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
@@ -176,27 +188,72 @@ public class ElytronSessionTokenStore implements ElytronTokeStore {
return;
}
- try {
- if (glo) {
- KeycloakSecurityContext ksc = (KeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
-
- if (ksc == null) {
- return;
- }
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
+ try {
+ if (glo && ksc != null) {
KeycloakDeployment deployment = httpFacade.getDeployment();
+ session.invalidate();
+
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
}
+ } else {
+ session.setAttachment(ElytronAccount.class.getName(), null);
+ session.setAttachment(KeycloakSecurityContext.class.getName(), null);
}
-
- session.setAttachment(KeycloakSecurityContext.class.getName(), null);
- session.setAttachment(ElytronAccount.class.getName(), null);
- session.invalidate();
} catch (IllegalStateException ise) {
// Session may be already logged-out in case that app has adminUrl
log.debugf("Session %s logged-out already", session.getID());
}
}
+
+ @Override
+ public void logoutAll() {
+ Collection<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
+ logoutHttpSessions(new ArrayList<>(sessions));
+ }
+
+ @Override
+ public void logoutHttpSessions(List<String> ids) {
+ HttpServerExchange exchange = ProtectedHttpServerExchange.class.cast(httpFacade.getScope(Scope.EXCHANGE).getAttachment(UNDERTOW_EXCHANGE)).getExchange();
+ ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ SessionManager sessionManager = servletRequestContext.getDeployment().getSessionManager();
+
+ for (String id : ids) {
+ // TODO: Workaround for WFLY-3345. Remove this once we fix KEYCLOAK-733. Same applies to legacy wildfly adapter.
+ Session session = sessionManager.getSession(null, new SessionConfig() {
+
+ @Override
+ public void setSessionId(HttpServerExchange exchange, String sessionId) {
+ }
+
+ @Override
+ public void clearSession(HttpServerExchange exchange, String sessionId) {
+ }
+
+ @Override
+ public String findSessionId(HttpServerExchange exchange) {
+ return id;
+ }
+
+ @Override
+ public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) {
+ return null;
+ }
+
+ @Override
+ public String rewriteUrl(String originalUrl, String sessionId) {
+ return null;
+ }
+
+ });
+
+ if (session != null) {
+ session.invalidate(exchange);
+ }
+ }
+
+ }
}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java
index 6be7607..9250f33 100644
--- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java
@@ -18,9 +18,6 @@
package org.keycloak.adapters.elytron;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
@@ -36,7 +33,6 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.wildfly.security.http.HttpAuthenticationException;
-import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.Scope;
@@ -137,25 +133,7 @@ class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticat
nodesRegistrationManagement.tryRegister(httpFacade.getDeployment());
- PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
- @Override
- public void logoutAll() {
- Collection<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
- logoutHttpSessions(new ArrayList<>(sessions));
- }
-
- @Override
- public void logoutHttpSessions(List<String> ids) {
- for (String id : ids) {
- HttpScope session = httpFacade.getScope(Scope.SESSION, id);
-
- if (session != null) {
- session.invalidate();
- }
- }
-
- }
- }, deploymentContext, httpFacade);
+ PreAuthActionsHandler preActions = new PreAuthActionsHandler(UserSessionManagement.class.cast(httpFacade.getTokenStore()), deploymentContext, httpFacade);
return preActions.handleRequest();
}
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SessionServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SessionServlet.java
index 7886872..7fa5d49 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SessionServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SessionServlet.java
@@ -34,6 +34,10 @@ public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ if (req.getRequestURI().endsWith("/logout")) {
+ req.logout();
+ return;
+ }
String counter = increaseAndGetCounter(req);
resp.setContentType("text/html");
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SessionPortalDistributable.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SessionPortalDistributable.java
new file mode 100644
index 0000000..8662530
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SessionPortalDistributable.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 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.testsuite.adapter.page;
+
+import java.net.URL;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class SessionPortalDistributable extends AbstractPageWithInjectedUrl {
+
+ public static final String DEPLOYMENT_NAME = "session-portal-distributable";
+
+ @ArquillianResource
+ @OperateOnDeployment(DEPLOYMENT_NAME)
+ private URL url;
+
+ @Override
+ public URL getInjectedUrl() {
+ return url;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterClusteredTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterClusteredTest.java
new file mode 100644
index 0000000..61827f4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterClusteredTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 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.testsuite.adapter;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.APP_SERVER_CURRENT;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import io.undertow.Undertow;
+import io.undertow.server.handlers.ResponseCodeHandler;
+import io.undertow.server.handlers.proxy.LoadBalancingProxyClient;
+import io.undertow.server.handlers.proxy.ProxyHandler;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jboss.arquillian.container.test.api.ContainerController;
+import org.jboss.arquillian.container.test.api.Deployer;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import org.keycloak.testsuite.auth.page.login.LoginActions;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public abstract class AbstractAdapterClusteredTest extends AbstractServletsAdapterTest {
+
+ protected static final String NODE_1_NAME = "ha-node-1";
+ protected static final String NODE_2_NAME = "ha-node-2";
+
+ // target containers will be replaced in runtime in DeploymentTargetModifier by real app-server
+ public static final String TARGET_CONTAINER_NODE_1 = APP_SERVER_CURRENT + NODE_1_NAME;
+ public static final String TARGET_CONTAINER_NODE_2 = APP_SERVER_CURRENT + NODE_2_NAME;
+
+ protected static final int PORT_OFFSET_NODE_REVPROXY = NumberUtils.toInt(System.getProperty("app.server.reverse-proxy.port.offset"), -1);
+ protected static final int HTTP_PORT_NODE_REVPROXY = 8080 + PORT_OFFSET_NODE_REVPROXY;
+ protected static final int PORT_OFFSET_NODE_1 = NumberUtils.toInt(System.getProperty("app.server.1.port.offset"), -1);
+ protected static final int HTTP_PORT_NODE_1 = 8080 + PORT_OFFSET_NODE_1;
+ protected static final int PORT_OFFSET_NODE_2 = NumberUtils.toInt(System.getProperty("app.server.2.port.offset"), -1);
+ protected static final int HTTP_PORT_NODE_2 = 8080 + PORT_OFFSET_NODE_2;
+ protected static final URI NODE_1_URI = URI.create("http://localhost:" + HTTP_PORT_NODE_1);
+ protected static final URI NODE_2_URI = URI.create("http://localhost:" + HTTP_PORT_NODE_2);
+
+ protected LoadBalancingProxyClient loadBalancerToNodes;
+ protected Undertow reverseProxyToNodes;
+
+ @ArquillianResource
+ protected ContainerController controller;
+
+ @ArquillianResource
+ protected Deployer deployer;
+
+ @Page
+ LoginActions loginActionsPage;
+
+ @BeforeClass
+ public static void checkPropertiesSet() {
+ Assume.assumeThat(PORT_OFFSET_NODE_1, not(is(-1)));
+ Assume.assumeThat(PORT_OFFSET_NODE_2, not(is(-1)));
+ Assume.assumeThat(PORT_OFFSET_NODE_REVPROXY, not(is(-1)));
+ }
+
+ @Before
+ public void prepareReverseProxy() throws Exception {
+ loadBalancerToNodes = new LoadBalancingProxyClient().addHost(NODE_1_URI, NODE_1_NAME).setConnectionsPerThread(10);
+ int maxTime = 3600000; // 1 hour for proxy request timeout, so we can debug the backend keycloak servers
+ reverseProxyToNodes = Undertow.builder()
+ .addHttpListener(HTTP_PORT_NODE_REVPROXY, "localhost")
+ .setIoThreads(2)
+ .setHandler(new ProxyHandler(loadBalancerToNodes, maxTime, ResponseCodeHandler.HANDLE_404)).build();
+ reverseProxyToNodes.start();
+ }
+
+ @Before
+ public void startServers() throws Exception {
+ prepareServerDirectories();
+
+ for (ContainerInfo containerInfo : testContext.getAppServerBackendsInfo()) {
+ controller.start(containerInfo.getQualifier());
+ }
+ deploy();
+ }
+
+ protected abstract void deploy();
+
+ protected void prepareServerDirectories() throws Exception {
+ prepareServerDirectory("standalone-cluster", "standalone-" + NODE_1_NAME);
+ prepareServerDirectory("standalone-cluster", "standalone-" + NODE_2_NAME);
+ }
+
+ protected void prepareServerDirectory(String baseDir, String targetSubdirectory) throws IOException {
+ Path path = Paths.get(System.getProperty("app.server.home"), targetSubdirectory);
+ File targetSubdirFile = path.toFile();
+ FileUtils.deleteDirectory(targetSubdirFile);
+ FileUtils.forceMkdir(targetSubdirFile);
+ //workaround for WFARQ-44
+ FileUtils.copyDirectory(Paths.get(System.getProperty("app.server.home"), baseDir, "deployments").toFile(), new File(targetSubdirFile, "deployments"));
+ FileUtils.copyDirectory(Paths.get(System.getProperty("app.server.home"), baseDir, "configuration").toFile(), new File(targetSubdirFile, "configuration"));
+ }
+
+ @After
+ public void stopReverseProxy() {
+ reverseProxyToNodes.stop();
+ }
+
+ @After
+ public void stopServers() {
+ undeploy();
+ for (ContainerInfo containerInfo : testContext.getAppServerBackendsInfo()) {
+ controller.stop(containerInfo.getQualifier());
+ }
+ }
+
+ protected abstract void undeploy();
+
+ protected void updateProxy(String hostToPointToName, URI hostToPointToUri, URI hostToRemove) {
+ loadBalancerToNodes.removeHost(hostToRemove);
+ loadBalancerToNodes.addHost(hostToPointToUri, hostToPointToName);
+ log.infov("Reverse proxy will direct requests to {0}", hostToPointToUri);
+ }
+
+ protected String getProxiedUrl(URL url) {
+ try {
+ return new URL(url.getProtocol(), url.getHost(), HTTP_PORT_NODE_REVPROXY, url.getFile()).toString();
+ } catch (MalformedURLException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractSAMLAdapterClusteredTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractSAMLAdapterClusteredTest.java
index 94e9919..bde6152 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractSAMLAdapterClusteredTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractSAMLAdapterClusteredTest.java
@@ -17,89 +17,34 @@
package org.keycloak.testsuite.adapter;
import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
-import static org.keycloak.testsuite.arquillian.DeploymentTargetModifier.APP_SERVER_CURRENT;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
import static org.keycloak.testsuite.utils.io.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
-import io.undertow.Undertow;
-import io.undertow.server.handlers.ResponseCodeHandler;
-import io.undertow.server.handlers.proxy.LoadBalancingProxyClient;
-import io.undertow.server.handlers.proxy.ProxyHandler;
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URI;
import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.List;
import java.util.function.BiConsumer;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.client.methods.HttpGet;
import org.jboss.arquillian.container.test.api.*;
-import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
-import org.jboss.logging.Logger;
import org.junit.*;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.*;
import org.keycloak.common.util.Retry;
import org.keycloak.testsuite.adapter.page.EmployeeServletDistributable;
-import org.keycloak.testsuite.adapter.page.SAMLServlet;
import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.arquillian.AppServerTestEnricher;
-import org.keycloak.testsuite.arquillian.ContainerInfo;
-import org.keycloak.testsuite.auth.page.AuthRealm;
-import org.keycloak.testsuite.auth.page.login.*;
-import org.keycloak.testsuite.page.AbstractPage;
import org.keycloak.testsuite.util.Matchers;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClient.Binding;
import org.keycloak.testsuite.util.SamlClientBuilder;
-import org.keycloak.testsuite.util.WaitUtils;
-import org.openqa.selenium.TimeoutException;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.support.ui.WebDriverWait;
/**
*
* @author hmlnarik
*/
-public abstract class AbstractSAMLAdapterClusteredTest extends AbstractServletsAdapterTest {
-
- protected static final String NODE_1_NAME = "ha-node-1";
- protected static final String NODE_2_NAME = "ha-node-2";
-
- // target containers will be replaced in runtime in DeploymentTargetModifier by real app-server
- public static final String TARGET_CONTAINER_NODE_1 = APP_SERVER_CURRENT + NODE_1_NAME;
- public static final String TARGET_CONTAINER_NODE_2 = APP_SERVER_CURRENT + NODE_2_NAME;
-
- protected static final int PORT_OFFSET_NODE_REVPROXY = NumberUtils.toInt(System.getProperty("app.server.reverse-proxy.port.offset"), -1);
- protected static final int HTTP_PORT_NODE_REVPROXY = 8080 + PORT_OFFSET_NODE_REVPROXY;
- protected static final int PORT_OFFSET_NODE_1 = NumberUtils.toInt(System.getProperty("app.server.1.port.offset"), -1);
- protected static final int HTTP_PORT_NODE_1 = 8080 + PORT_OFFSET_NODE_1;
- protected static final int PORT_OFFSET_NODE_2 = NumberUtils.toInt(System.getProperty("app.server.2.port.offset"), -1);
- protected static final int HTTP_PORT_NODE_2 = 8080 + PORT_OFFSET_NODE_2;
- protected static final URI NODE_1_URI = URI.create("http://localhost:" + HTTP_PORT_NODE_1);
- protected static final URI NODE_2_URI = URI.create("http://localhost:" + HTTP_PORT_NODE_2);
-
- protected LoadBalancingProxyClient loadBalancerToNodes;
- protected Undertow reverseProxyToNodes;
-
- @ArquillianResource
- protected ContainerController controller;
-
- @ArquillianResource
- protected Deployer deployer;
-
- @Page
- LoginActions loginActionsPage;
+public abstract class AbstractSAMLAdapterClusteredTest extends AbstractAdapterClusteredTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
@@ -109,79 +54,27 @@ public abstract class AbstractSAMLAdapterClusteredTest extends AbstractServletsA
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
-
testRealmSAMLPostLoginPage.setAuthRealm(DEMO);
loginPage.setAuthRealm(DEMO);
loginActionsPage.setAuthRealm(DEMO);
}
- @BeforeClass
- public static void checkPropertiesSet() {
- Assume.assumeThat(PORT_OFFSET_NODE_1, not(is(-1)));
- Assume.assumeThat(PORT_OFFSET_NODE_2, not(is(-1)));
- Assume.assumeThat(PORT_OFFSET_NODE_REVPROXY, not(is(-1)));
- }
-
- @Before
- public void prepareReverseProxy() throws Exception {
- loadBalancerToNodes = new LoadBalancingProxyClient().addHost(NODE_1_URI, NODE_1_NAME).setConnectionsPerThread(10);
- int maxTime = 3600000; // 1 hour for proxy request timeout, so we can debug the backend keycloak servers
- reverseProxyToNodes = Undertow.builder()
- .addHttpListener(HTTP_PORT_NODE_REVPROXY, "localhost")
- .setIoThreads(2)
- .setHandler(new ProxyHandler(loadBalancerToNodes, maxTime, ResponseCodeHandler.HANDLE_404)).build();
- reverseProxyToNodes.start();
- }
-
- @Before
- public void startServers() throws Exception {
- prepareServerDirectories();
-
- for (ContainerInfo containerInfo : testContext.getAppServerBackendsInfo()) {
- controller.start(containerInfo.getQualifier());
- }
+ @Override
+ protected void deploy() {
deployer.deploy(EmployeeServletDistributable.DEPLOYMENT_NAME);
deployer.deploy(EmployeeServletDistributable.DEPLOYMENT_NAME + "_2");
}
- protected abstract void prepareServerDirectories() throws Exception;
-
- protected void prepareServerDirectory(String baseDir, String targetSubdirectory) throws IOException {
- Path path = Paths.get(System.getProperty("app.server.home"), targetSubdirectory);
- File targetSubdirFile = path.toFile();
- FileUtils.deleteDirectory(targetSubdirFile);
- FileUtils.forceMkdir(targetSubdirFile);
- //workaround for WFARQ-44
- FileUtils.copyDirectory(Paths.get(System.getProperty("app.server.home"), baseDir, "deployments").toFile(), new File(targetSubdirFile, "deployments"));
- FileUtils.copyDirectory(Paths.get(System.getProperty("app.server.home"), baseDir, "configuration").toFile(), new File(targetSubdirFile, "configuration"));
- }
-
- @After
- public void stopReverseProxy() {
- reverseProxyToNodes.stop();
- }
-
- @After
- public void stopServers() {
+ @Override
+ protected void undeploy() {
deployer.undeploy(EmployeeServletDistributable.DEPLOYMENT_NAME);
deployer.undeploy(EmployeeServletDistributable.DEPLOYMENT_NAME + "_2");
-
- for (ContainerInfo containerInfo : testContext.getAppServerBackendsInfo()) {
- controller.stop(containerInfo.getQualifier());
- }
}
private void testLogoutViaSessionIndex(URL employeeUrl, boolean forceRefreshAtOtherNode, BiConsumer<SamlClientBuilder, String> logoutFunction) {
setPasswordFor(bburkeUser, CredentialRepresentation.PASSWORD);
- final String employeeUrlString;
- try {
- URL employeeUrlAtRevProxy = new URL(employeeUrl.getProtocol(), employeeUrl.getHost(), HTTP_PORT_NODE_REVPROXY, employeeUrl.getFile());
- employeeUrlString = employeeUrlAtRevProxy.toString();
- } catch (MalformedURLException ex) {
- throw new RuntimeException(ex);
- }
-
+ String employeeUrlString = getProxiedUrl(employeeUrl);
SamlClientBuilder builder = new SamlClientBuilder()
// Go to employee URL at reverse proxy which is set to forward to first node
.navigateTo(employeeUrlString)
@@ -270,36 +163,4 @@ public abstract class AbstractSAMLAdapterClusteredTest extends AbstractServletsA
;
});
}
-
- protected void updateProxy(String hostToPointToName, URI hostToPointToUri, URI hostToRemove) {
- loadBalancerToNodes.removeHost(hostToRemove);
- loadBalancerToNodes.addHost(hostToPointToUri, hostToPointToName);
- log.infov("Reverse proxy will direct requests to {0}", hostToPointToUri);
- }
-
- protected void assertSuccessfulLogin(SAMLServlet page, UserRepresentation user, Login loginPage, String expectedString) {
- page.navigateTo();
- assertCurrentUrlStartsWith(loginPage);
- loginPage.form().login(user);
- WebDriverWait wait = new WebDriverWait(driver, WaitUtils.PAGELOAD_TIMEOUT_MILLIS / 1000);
- wait.until((WebDriver d) -> d.getPageSource().contains(expectedString));
- }
-
- protected void delayedCheckLoggedOut(AbstractPage page, AuthRealm loginPage) {
- Retry.execute(() -> {
- try {
- checkLoggedOut(page, loginPage);
- } catch (AssertionError | TimeoutException ex) {
- driver.navigate().refresh();
- log.debug("[Retriable] Timed out waiting for login page");
- throw new RuntimeException(ex);
- }
- }, 10, 100);
- }
-
- protected void checkLoggedOut(AbstractPage page, AuthRealm loginPage) {
- page.navigateTo();
- WaitUtils.waitForPageToLoad();
- assertCurrentUrlStartsWith(loginPage);
- }
-}
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/OIDCAdapterClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/OIDCAdapterClusterTest.java
new file mode 100644
index 0000000..1a4e66a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/OIDCAdapterClusterTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2018 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.testsuite.adapter.servlet.cluster;
+
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.container.test.api.TargetsContainer;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.adapter.AbstractAdapterClusteredTest;
+import org.keycloak.testsuite.adapter.page.SessionPortalDistributable;
+import org.keycloak.testsuite.adapter.servlet.SessionServlet;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.arquillian.containers.ContainerConstants;
+import org.keycloak.testsuite.auth.page.AuthRealm;
+import org.keycloak.testsuite.auth.page.login.OIDCLogin;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_CLUSTER)
+@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED_CLUSTER)
+@AppServerContainer(ContainerConstants.APP_SERVER_EAP_CLUSTER)
+@AppServerContainer(ContainerConstants.APP_SERVER_EAP6_CLUSTER)
+public class OIDCAdapterClusterTest extends AbstractAdapterClusteredTest {
+
+ @TargetsContainer(value = TARGET_CONTAINER_NODE_1)
+ @Deployment(name = SessionPortalDistributable.DEPLOYMENT_NAME, managed = false)
+ protected static WebArchive sessionPortalNode1() {
+ return servletDeployment(SessionPortalDistributable.DEPLOYMENT_NAME, "keycloak.json", SessionServlet.class);
+ }
+
+ @TargetsContainer(value = TARGET_CONTAINER_NODE_2)
+ @Deployment(name = SessionPortalDistributable.DEPLOYMENT_NAME + "_2", managed = false)
+ protected static WebArchive sessionPortalNode2() {
+ return servletDeployment(SessionPortalDistributable.DEPLOYMENT_NAME, "keycloak.json", SessionServlet.class);
+ }
+
+ @Page
+ protected OIDCLogin loginPage;
+
+ @Page
+ protected SessionPortalDistributable sessionPortalPage;
+
+ @Override
+ public void setDefaultPageUriParameters() {
+ super.setDefaultPageUriParameters();
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ addAdapterTestRealms(testRealms);
+ }
+
+ @Override
+ protected void deploy() {
+ deployer.deploy(SessionPortalDistributable.DEPLOYMENT_NAME);
+ deployer.deploy(SessionPortalDistributable.DEPLOYMENT_NAME + "_2");
+ }
+
+ @Override
+ protected void undeploy() {
+ deployer.undeploy(SessionPortalDistributable.DEPLOYMENT_NAME);
+ deployer.undeploy(SessionPortalDistributable.DEPLOYMENT_NAME + "_2");
+ }
+
+ @Before
+ public void onBefore() {
+ loginPage.setAuthRealm(AuthRealm.DEMO);
+ }
+
+ @Test
+ public void testSuccessfulLoginAndBackchannelLogout(@ArquillianResource
+ @OperateOnDeployment(value = SessionPortalDistributable.DEPLOYMENT_NAME) URL appUrl) {
+ String proxiedUrl = getProxiedUrl(appUrl);
+ driver.navigate().to(proxiedUrl);
+ assertCurrentUrlStartsWith(loginPage);
+ loginPage.form().login("bburke@redhat.com", "password");
+ assertCurrentUrlEquals(proxiedUrl);
+ assertSessionCounter(NODE_2_NAME, NODE_2_URI, NODE_1_URI, proxiedUrl, 2);
+ assertSessionCounter(NODE_1_NAME, NODE_1_URI, NODE_2_URI, proxiedUrl, 3);
+ assertSessionCounter(NODE_2_NAME, NODE_2_URI, NODE_1_URI, proxiedUrl, 4);
+
+ String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
+ .queryParam(OAuth2Constants.REDIRECT_URI, proxiedUrl).build(AuthRealm.DEMO).toString();
+ driver.navigate().to(logoutUri);
+ driver.navigate().to(proxiedUrl);
+ assertCurrentUrlStartsWith(loginPage);
+ }
+
+ @Test
+ public void testSuccessfulLoginAndProgrammaticLogout(@ArquillianResource
+ @OperateOnDeployment(value = SessionPortalDistributable.DEPLOYMENT_NAME) URL appUrl) {
+ String proxiedUrl = getProxiedUrl(appUrl);
+ driver.navigate().to(proxiedUrl);
+ assertCurrentUrlStartsWith(loginPage);
+ loginPage.form().login("bburke@redhat.com", "password");
+ assertCurrentUrlEquals(proxiedUrl);
+ assertSessionCounter(NODE_2_NAME, NODE_2_URI, NODE_1_URI, proxiedUrl, 2);
+ assertSessionCounter(NODE_1_NAME, NODE_1_URI, NODE_2_URI, proxiedUrl, 3);
+ assertSessionCounter(NODE_2_NAME, NODE_2_URI, NODE_1_URI, proxiedUrl, 4);
+
+ String logoutUri = proxiedUrl + "/logout";
+ driver.navigate().to(logoutUri);
+ driver.navigate().to(proxiedUrl);
+ assertCurrentUrlStartsWith(loginPage);
+ }
+
+ private void assertSessionCounter(String hostToPointToName, URI hostToPointToUri, URI hostToRemove, String appUrl, int expectedCount) {
+ updateProxy(hostToPointToName, hostToPointToUri, hostToRemove);
+ driver.navigate().to(appUrl);
+ assertThat(driver.getPageSource(), containsString("Counter=" + expectedCount));
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/SAMLAdapterClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/SAMLAdapterClusterTest.java
index 84e80aa..7664984 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/SAMLAdapterClusterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/cluster/SAMLAdapterClusterTest.java
@@ -26,8 +26,6 @@ import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.arquillian.containers.ContainerConstants;
-import static org.keycloak.testsuite.adapter.AbstractServletsAdapterTest.samlServletDeployment;
-
/**
*
* @author hmlnarik
@@ -52,10 +50,4 @@ public class SAMLAdapterClusterTest extends AbstractSAMLAdapterClusteredTest {
protected static WebArchive employee2() {
return employee();
}
-
- @Override
- protected void prepareServerDirectories() throws Exception {
- prepareServerDirectory("standalone-cluster", "standalone-" + NODE_1_NAME);
- prepareServerDirectory("standalone-cluster", "standalone-" + NODE_2_NAME);
- }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index 5afd248..2d4f615 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -271,6 +271,16 @@
"secret": "password"
},
{
+ "clientId": "session-portal-distributable",
+ "enabled": true,
+ "adminUrl": "http://localhost:8580/session-portal-distributable",
+ "baseUrl": "http://localhost:8580/session-portal-distributable",
+ "redirectUris": [
+ "http://localhost:8580/session-portal-distributable/*"
+ ],
+ "secret": "password"
+ },
+ {
"clientId": "input-portal",
"enabled": true,
"adminUrl": "/input-portal",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/META-INF/context.xml
new file mode 100644
index 0000000..067e33d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/META-INF/context.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ * Copyright 2018 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.
+ -->
+
+<Context path="/session-portal-distributable">
+ <Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
+</Context>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..8c59313
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/jetty-web.xml
@@ -0,0 +1,46 @@
+<?xml version="1.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.
+ -->
+
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+ <Get name="securityHandler">
+ <Set name="authenticator">
+ <New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
+ <!--
+ <Set name="adapterConfig">
+ <New class="org.keycloak.representations.adapters.config.AdapterConfig">
+ <Set name="realm">tomcat</Set>
+ <Set name="resource">customer-portal</Set>
+ <Set name="authServerUrl">http://localhost:8180/auth</Set>
+ <Set name="sslRequired">external</Set>
+ <Set name="credentials">
+ <Map>
+ <Entry>
+ <Item>secret</Item>
+ <Item>password</Item>
+ </Entry>
+ </Map>
+ </Set>
+ <Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
+ </New>
+ </Set>
+ -->
+ </New>
+ </Set>
+ </Get>
+</Configure>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/keycloak.json
new file mode 100644
index 0000000..e46549e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/keycloak.json
@@ -0,0 +1,10 @@
+{
+ "realm" : "demo",
+ "resource" : "session-portal-distributable",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url" : "http://localhost:8180/auth",
+ "ssl-required" : "external",
+ "credentials" : {
+ "secret": "password"
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/web.xml
new file mode 100644
index 0000000..8fbba24
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/session-portal-distributable/WEB-INF/web.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ * Copyright 2018 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.
+ -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>session-portal-distributable</module-name>
+
+ <distributable/>
+
+ <absolute-ordering/>
+
+ <servlet>
+ <servlet-name>Servlet</servlet-name>
+ <servlet-class>org.keycloak.testsuite.adapter.servlet.SessionServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>Servlet</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Users</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ <realm-name>demo</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+</web-app>