keycloak-aplcache

Changes

testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/examples-realm.json 1795(+0 -1795)

testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/examples-realm-bak.json 1797(+0 -1797)

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
index 52d47df..edfd392 100644
--- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
@@ -19,6 +19,7 @@ package org.keycloak.keys.infinispan;
 
 import java.security.PublicKey;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -32,6 +33,7 @@ import org.keycloak.common.util.Time;
 import org.keycloak.keys.PublicKeyLoader;
 import org.keycloak.keys.PublicKeyStorageProvider;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.cache.infinispan.ClearCacheEvent;
 import org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory;
 
@@ -51,13 +53,17 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
 
     private final int minTimeBetweenRequests ;
 
+    private Set<String> invalidations = new HashSet<>();
+
     public InfinispanPublicKeyStorageProvider(KeycloakSession session, Cache<String, PublicKeysEntry> keys, Map<String, FutureTask<PublicKeysEntry>> tasksInProgress, int minTimeBetweenRequests) {
         this.session = session;
         this.keys = keys;
         this.tasksInProgress = tasksInProgress;
         this.minTimeBetweenRequests = minTimeBetweenRequests;
+        session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
     }
 
+
     @Override
     public void clearCache() {
         keys.clear();
@@ -65,6 +71,56 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
         cluster.notify(InfinispanPublicKeyStorageProviderFactory.KEYS_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
     }
 
+
+    void addInvalidation(String cacheKey) {
+        this.invalidations.add(cacheKey);
+    }
+
+
+    protected KeycloakTransaction getAfterTransaction() {
+        return new KeycloakTransaction() {
+
+            @Override
+            public void begin() {
+            }
+
+            @Override
+            public void commit() {
+                runInvalidations();
+            }
+
+            @Override
+            public void rollback() {
+                runInvalidations();
+            }
+
+            @Override
+            public void setRollbackOnly() {
+            }
+
+            @Override
+            public boolean getRollbackOnly() {
+                return false;
+            }
+
+            @Override
+            public boolean isActive() {
+                return true;
+            }
+        };
+    }
+
+
+    protected void runInvalidations() {
+        ClusterProvider cluster = session.getProvider(ClusterProvider.class);
+
+        for (String cacheKey : invalidations) {
+            keys.remove(cacheKey);
+            cluster.notify(cacheKey, PublicKeyStorageInvalidationEvent.create(cacheKey), true);
+        }
+    }
+
+
     @Override
     public PublicKey getPublicKey(String modelKey, String kid, PublicKeyLoader loader) {
         // Check if key is in cache
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
index 8f2e321..42a73fc 100644
--- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
@@ -30,9 +30,14 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.keys.PublicKeyStorageProvider;
 import org.keycloak.keys.PublicKeyStorageSpi;
 import org.keycloak.keys.PublicKeyStorageProviderFactory;
+import org.keycloak.keys.PublicKeyStorageUtils;
+import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -45,7 +50,7 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
 
     public static final String KEYS_CLEAR_CACHE_EVENTS = "KEYS_CLEAR_CACHE_EVENTS";
 
-    private Cache<String, PublicKeysEntry> keysCache;
+    private volatile Cache<String, PublicKeysEntry> keysCache;
 
     private final Map<String, FutureTask<PublicKeysEntry>> tasksInProgress = new ConcurrentHashMap<>();
 
@@ -64,6 +69,15 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
                     this.keysCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
 
                     ClusterProvider cluster = session.getProvider(ClusterProvider.class);
+                    cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
+
+                        if (event instanceof PublicKeyStorageInvalidationEvent) {
+                            PublicKeyStorageInvalidationEvent invalidationEvent = (PublicKeyStorageInvalidationEvent) event;
+                            keysCache.remove(invalidationEvent.getCacheKey());
+                        }
+
+                    });
+
                     cluster.registerListener(KEYS_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
 
                         keysCache.clear();
@@ -82,6 +96,55 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
 
     @Override
     public void postInit(KeycloakSessionFactory factory) {
+        factory.register(new ProviderEventListener() {
+
+            @Override
+            public void onEvent(ProviderEvent event) {
+                if (keysCache == null) {
+                    return;
+                }
+
+                SessionAndKeyHolder cacheKey = getCacheKeyToInvalidate(event);
+                if (cacheKey != null) {
+                    log.debugf("Invalidating %s from keysCache", cacheKey);
+                    InfinispanPublicKeyStorageProvider provider = (InfinispanPublicKeyStorageProvider) cacheKey.session.getProvider(PublicKeyStorageProvider.class, getId());
+                    provider.addInvalidation(cacheKey.cacheKey);
+                }
+            }
+
+        });
+    }
+
+    private SessionAndKeyHolder getCacheKeyToInvalidate(ProviderEvent event) {
+        if (event instanceof RealmModel.ClientUpdatedEvent) {
+            RealmModel.ClientUpdatedEvent eventt = (RealmModel.ClientUpdatedEvent) event;
+            String cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getUpdatedClient().getRealm().getId(), eventt.getUpdatedClient().getId());
+            return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+        } else if (event instanceof RealmModel.ClientRemovedEvent) {
+            RealmModel.ClientRemovedEvent eventt = (RealmModel.ClientRemovedEvent) event;
+            String cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getClient().getRealm().getId(), eventt.getClient().getId());
+            return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+        } else if (event instanceof RealmModel.IdentityProviderUpdatedEvent) {
+            RealmModel.IdentityProviderUpdatedEvent eventt = (RealmModel.IdentityProviderUpdatedEvent) event;
+            String cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getUpdatedIdentityProvider().getInternalId());
+            return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+        } else if (event instanceof RealmModel.IdentityProviderRemovedEvent) {
+            RealmModel.IdentityProviderRemovedEvent eventt = (RealmModel.IdentityProviderRemovedEvent) event;
+            String cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getRemovedIdentityProvider().getInternalId());
+            return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+        } else {
+            return null;
+        }
+    }
+
+    private class SessionAndKeyHolder {
+        private final KeycloakSession session;
+        private final String cacheKey;
+
+        public SessionAndKeyHolder(KeycloakSession session, String cacheKey) {
+            this.session = session;
+            this.cacheKey = cacheKey;
+        }
 
     }
 
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java
new file mode 100644
index 0000000..1afcf42
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.keys.infinispan;
+
+import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PublicKeyStorageInvalidationEvent extends InvalidationEvent {
+
+    private String cacheKey;
+
+    public static PublicKeyStorageInvalidationEvent create(String cacheKey) {
+        PublicKeyStorageInvalidationEvent event = new PublicKeyStorageInvalidationEvent();
+        event.cacheKey = cacheKey;
+        return event;
+    }
+
+    @Override
+    public String getId() {
+        return cacheKey;
+    }
+
+    public String getCacheKey() {
+        return cacheKey;
+    }
+
+    @Override
+    public String toString() {
+        return "PublicKeyStorageInvalidationEvent [ " + cacheKey + " ]";
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index b3f58b4..830da5f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -960,23 +960,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
         List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
 
         for (IdentityProviderEntity entity: entities) {
-            IdentityProviderModel identityProviderModel = new IdentityProviderModel();
-            identityProviderModel.setProviderId(entity.getProviderId());
-            identityProviderModel.setAlias(entity.getAlias());
-            identityProviderModel.setDisplayName(entity.getDisplayName());
-
-            identityProviderModel.setInternalId(entity.getInternalId());
-            Map<String, String> config = entity.getConfig();
-            Map<String, String> copy = new HashMap<>();
-            copy.putAll(config);
-            identityProviderModel.setConfig(copy);
-            identityProviderModel.setEnabled(entity.isEnabled());
-            identityProviderModel.setTrustEmail(entity.isTrustEmail());
-            identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
-            identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
-            identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
-            identityProviderModel.setStoreToken(entity.isStoreToken());
-            identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+            IdentityProviderModel identityProviderModel = entityToModel(entity);
 
             identityProviders.add(identityProviderModel);
         }
@@ -984,6 +968,27 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
         return Collections.unmodifiableList(identityProviders);
     }
 
+    private IdentityProviderModel entityToModel(IdentityProviderEntity entity) {
+        IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+        identityProviderModel.setProviderId(entity.getProviderId());
+        identityProviderModel.setAlias(entity.getAlias());
+        identityProviderModel.setDisplayName(entity.getDisplayName());
+
+        identityProviderModel.setInternalId(entity.getInternalId());
+        Map<String, String> config = entity.getConfig();
+        Map<String, String> copy = new HashMap<>();
+        copy.putAll(config);
+        identityProviderModel.setConfig(copy);
+        identityProviderModel.setEnabled(entity.isEnabled());
+        identityProviderModel.setTrustEmail(entity.isTrustEmail());
+        identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+        identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
+        identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
+        identityProviderModel.setStoreToken(entity.isStoreToken());
+        identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+        return identityProviderModel;
+    }
+
     @Override
     public IdentityProviderModel getIdentityProviderByAlias(String alias) {
         for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
@@ -1024,8 +1029,28 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
     public void removeIdentityProviderByAlias(String alias) {
         for (IdentityProviderEntity entity : realm.getIdentityProviders()) {
             if (entity.getAlias().equals(alias)) {
+
                 em.remove(entity);
                 em.flush();
+
+                session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderRemovedEvent() {
+
+                    @Override
+                    public RealmModel getRealm() {
+                        return RealmAdapter.this;
+                    }
+
+                    @Override
+                    public IdentityProviderModel getRemovedIdentityProvider() {
+                        return entityToModel(entity);
+                    }
+
+                    @Override
+                    public KeycloakSession getKeycloakSession() {
+                        return session;
+                    }
+                });
+
             }
         }
     }
@@ -1048,6 +1073,24 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
         }
 
         em.flush();
+
+        session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderUpdatedEvent() {
+
+            @Override
+            public RealmModel getRealm() {
+                return RealmAdapter.this;
+            }
+
+            @Override
+            public IdentityProviderModel getUpdatedIdentityProvider() {
+                return identityProvider;
+            }
+
+            @Override
+            public KeycloakSession getKeycloakSession() {
+                return session;
+            }
+        });
     }
 
     @Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d581710..429d389 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -775,23 +775,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
 
         for (IdentityProviderEntity entity: entities) {
-            IdentityProviderModel identityProviderModel = new IdentityProviderModel();
-
-            identityProviderModel.setProviderId(entity.getProviderId());
-            identityProviderModel.setAlias(entity.getAlias());
-            identityProviderModel.setDisplayName(entity.getDisplayName());
-            identityProviderModel.setInternalId(entity.getInternalId());
-            Map<String, String> config = entity.getConfig();
-            Map<String, String> copy = new HashMap<>();
-            copy.putAll(config);
-            identityProviderModel.setConfig(copy);
-            identityProviderModel.setEnabled(entity.isEnabled());
-            identityProviderModel.setTrustEmail(entity.isTrustEmail());
-            identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
-            identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
-            identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
-            identityProviderModel.setStoreToken(entity.isStoreToken());
-            identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+            IdentityProviderModel identityProviderModel = entityToModel(entity);
 
             identityProviders.add(identityProviderModel);
         }
@@ -799,6 +783,27 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         return Collections.unmodifiableList(identityProviders);
     }
 
+    private IdentityProviderModel entityToModel(IdentityProviderEntity entity) {
+        IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+        identityProviderModel.setProviderId(entity.getProviderId());
+        identityProviderModel.setAlias(entity.getAlias());
+        identityProviderModel.setDisplayName(entity.getDisplayName());
+        identityProviderModel.setInternalId(entity.getInternalId());
+        Map<String, String> config = entity.getConfig();
+        Map<String, String> copy = new HashMap<>();
+        copy.putAll(config);
+        identityProviderModel.setConfig(copy);
+        identityProviderModel.setEnabled(entity.isEnabled());
+        identityProviderModel.setTrustEmail(entity.isTrustEmail());
+        identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+        identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
+        identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
+        identityProviderModel.setStoreToken(entity.isStoreToken());
+        identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+        return identityProviderModel;
+    }
+
     @Override
     public IdentityProviderModel getIdentityProviderByAlias(String alias) {
         for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
@@ -837,6 +842,25 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             if (entity.getAlias().equals(alias)) {
                 realm.getIdentityProviders().remove(entity);
                 updateRealm();
+
+                session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderRemovedEvent() {
+
+                    @Override
+                    public RealmModel getRealm() {
+                        return RealmAdapter.this;
+                    }
+
+                    @Override
+                    public IdentityProviderModel getRemovedIdentityProvider() {
+                        return entityToModel(entity);
+                    }
+
+                    @Override
+                    public KeycloakSession getKeycloakSession() {
+                        return session;
+                    }
+                });
+
                 break;
             }
         }
@@ -860,6 +884,24 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         }
 
         updateRealm();
+
+        session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderUpdatedEvent() {
+
+            @Override
+            public RealmModel getRealm() {
+                return RealmAdapter.this;
+            }
+
+            @Override
+            public IdentityProviderModel getUpdatedIdentityProvider() {
+                return identityProvider;
+            }
+
+            @Override
+            public KeycloakSession getKeycloakSession() {
+                return session;
+            }
+        });
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 4409b9d..3149f50 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -67,6 +67,18 @@ public interface RealmModel extends RoleContainerModel {
         KeycloakSession getKeycloakSession();
     }
 
+    interface IdentityProviderUpdatedEvent extends ProviderEvent {
+        RealmModel getRealm();
+        IdentityProviderModel getUpdatedIdentityProvider();
+        KeycloakSession getKeycloakSession();
+    }
+
+    interface IdentityProviderRemovedEvent extends ProviderEvent {
+        RealmModel getRealm();
+        IdentityProviderModel getRemovedIdentityProvider();
+        KeycloakSession getKeycloakSession();
+    }
+
     String getId();
 
     String getName();
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
new file mode 100644
index 0000000..52eb7db
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.keys;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PublicKeyStorageUtils {
+
+    public static String getClientModelCacheKey(String realmId, String clientUuid) {
+        return realmId + "::client::" + clientUuid;
+    }
+
+    public static String getIdpModelCacheKey(String realmId, String idpInternalId) {
+        return realmId + "::idp::" + idpInternalId;
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 0ef5276..951d03a 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -66,6 +66,7 @@ import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -92,6 +93,7 @@ public class SAMLEndpoint {
     public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
     public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
     public static final String SAML_ASSERTION = "SAML_ASSERTION";
+    public static final String SAML_IDP_INITIATED_CLIENT_ID = "SAML_IDP_INITIATED_CLIENT_ID";
     public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
     protected RealmModel realm;
     protected EventBuilder event;
@@ -130,7 +132,7 @@ public class SAMLEndpoint {
     public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
                                     @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
                                     @QueryParam(GeneralConstants.RELAY_STATE) String relayState)  {
-        return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
+        return new RedirectBinding().execute(samlRequest, samlResponse, relayState, null);
     }
 
 
@@ -141,7 +143,29 @@ public class SAMLEndpoint {
     public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
                                 @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
                                 @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
-        return new PostBinding().execute(samlRequest, samlResponse, relayState);
+        return new PostBinding().execute(samlRequest, samlResponse, relayState, null);
+    }
+
+    @Path("clients/{client_id}")
+    @GET
+    public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+                                    @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+                                    @QueryParam(GeneralConstants.RELAY_STATE) String relayState,
+                                    @PathParam("client_id") String clientId)  {
+        return new RedirectBinding().execute(samlRequest, samlResponse, relayState, clientId);
+    }
+
+
+    /**
+     */
+    @Path("clients/{client_id}")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+                                @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+                                @FormParam(GeneralConstants.RELAY_STATE) String relayState,
+                                @PathParam("client_id") String clientId) {
+        return new PostBinding().execute(samlRequest, samlResponse, relayState, clientId);
     }
 
     protected abstract class Binding {
@@ -194,12 +218,12 @@ public class SAMLEndpoint {
             return new HardcodedKeyLocator(keys);
         }
 
-        public Response execute(String samlRequest, String samlResponse, String relayState) {
+        public Response execute(String samlRequest, String samlResponse, String relayState, String clientId) {
             event = new EventBuilder(realm, session, clientConnection);
             Response response = basicChecks(samlRequest, samlResponse);
             if (response != null) return response;
             if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
-            else return handleSamlResponse(samlResponse, relayState);
+            else return handleSamlResponse(samlResponse, relayState, clientId);
         }
 
         protected Response handleSamlRequest(String samlRequest, String relayState) {
@@ -304,7 +328,7 @@ public class SAMLEndpoint {
         private String getEntityId(UriInfo uriInfo, RealmModel realm) {
             return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
         }
-        protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
+        protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {
 
             try {
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
@@ -316,6 +340,9 @@ public class SAMLEndpoint {
                 BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
                 identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
                 identity.getContextData().put(SAML_ASSERTION, assertion);
+                if (clientId != null && ! clientId.trim().isEmpty()) {
+                    identity.getContextData().put(SAML_IDP_INITIATED_CLIENT_ID, clientId);
+                }
 
                 identity.setUsername(subjectNameID.getValue());
 
@@ -369,7 +396,7 @@ public class SAMLEndpoint {
 
 
 
-        public Response handleSamlResponse(String samlResponse, String relayState) {
+        public Response handleSamlResponse(String samlResponse, String relayState, String clientId) {
             SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
             StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
             // validate destination
@@ -390,7 +417,7 @@ public class SAMLEndpoint {
                 }
             }
             if (statusResponse instanceof ResponseType) {
-                return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
+                return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState, clientId);
 
             } else {
                 // todo need to check that it is actually a LogoutResponse
diff --git a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
index 811dacf..d4507e9 100644
--- a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
+++ b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
@@ -22,6 +22,7 @@ import java.security.PublicKey;
 import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.keys.PublicKeyStorageProvider;
+import org.keycloak.keys.PublicKeyStorageUtils;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -36,27 +37,20 @@ public class PublicKeyStorageManager {
 
         PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
 
-        String modelKey = getModelKey(client);
+        String modelKey = PublicKeyStorageUtils.getClientModelCacheKey(client.getRealm().getId(), client.getId());
         ClientPublicKeyLoader loader = new ClientPublicKeyLoader(session, client);
         return keyStorage.getPublicKey(modelKey, kid, loader);
     }
 
-    private static String getModelKey(ClientModel client) {
-        return client.getRealm().getId() + "::client::" + client.getId();
-    }
-
 
     public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) {
         String kid = input.getHeader().getKeyId();
 
         PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
 
-        String modelKey = getModelKey(realm, idpConfig);
+        String modelKey = PublicKeyStorageUtils.getIdpModelCacheKey(realm.getId(), idpConfig.getInternalId());
         OIDCIdentityProviderPublicKeyLoader loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig);
         return keyStorage.getPublicKey(modelKey, kid, loader);
     }
 
-    private static String getModelKey(RealmModel realm, OIDCIdentityProviderConfig idpConfig) {
-        return realm.getId() + "::idp::" + idpConfig.getInternalId();
-    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 14c5503..c404ef8 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -611,12 +611,29 @@ public class SamlService extends AuthorizationEndpointBase {
             return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
         }
 
+        ClientSessionModel clientSession = createClientSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
+
+        return newBrowserAuthentication(clientSession, false, false);
+    }
+
+    /**
+     * Creates a client session object for SAML IdP-initiated SSO session.
+     * The session takes the parameters from from client definition,
+     * namely binding type and redirect URL.
+     *
+     * @param session KC session
+     * @param realm Realm to create client session in
+     * @param client Client to create client session for
+     * @param relayState Optional relay state - free field as per SAML specification
+     * @return
+     */
+    public static ClientSessionModel createClientSessionForIdpInitiatedSso(KeycloakSession session, RealmModel realm, ClientModel client, String relayState) {
         String bindingType = SamlProtocol.SAML_POST_BINDING;
         if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
             bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
         }
 
-        String redirect = null;
+        String redirect;
         if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
             redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
         } else {
@@ -640,8 +657,7 @@ public class SamlService extends AuthorizationEndpointBase {
             clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
         }
 
-        return newBrowserAuthentication(clientSession, false, false);
-
+        return clientSession;
     }
 
     @POST
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index a8c4cc4..b1f4587 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -19,6 +19,7 @@ package org.keycloak.services.resources;
 import org.jboss.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
+
 import org.keycloak.OAuth2Constants;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
@@ -30,6 +31,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
 import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.broker.saml.SAMLEndpoint;
 import org.keycloak.broker.social.SocialIdentityProvider;
 import org.keycloak.common.ClientConnection;
 import org.keycloak.common.util.ObjectUtil;
@@ -54,8 +56,11 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.FormMessage;
 import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlService;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.saml.common.constants.GeneralConstants;
 import org.keycloak.services.ErrorPage;
 import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.ServicesLogger;
@@ -87,6 +92,8 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
@@ -255,7 +262,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     public Response authenticated(BrokeredIdentityContext context) {
         IdentityProviderModel identityProviderConfig = context.getIdpConfig();
 
-        ParsedCodeContext parsedCode = parseClientSessionCode(context.getCode());
+        final ParsedCodeContext parsedCode;
+        if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
+            parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
+        } else {
+            parsedCode = parseClientSessionCode(context.getCode());
+        }
         if (parsedCode.response != null) {
             return parsedCode.response;
         }
@@ -696,6 +708,53 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         return ParsedCodeContext.response(staleCodeError);
     }
 
+    /**
+     * If there is a client whose SAML IDP-initiated SSO URL name is set to the
+     * given {@code clientUrlName}, creates a fresh client session for that
+     * client and returns a {@link ParsedCodeContext} object with that session.
+     * Otherwise returns "client not found" response.
+     *
+     * @param clientUrlName
+     * @return see description
+     */
+    private ParsedCodeContext samlIdpInitiatedSSO(final String clientUrlName) {
+        event.event(EventType.LOGIN);
+        CacheControlUtil.noBackButtonCacheControlHeader();
+        Optional<ClientModel> oClient = this.realmModel.getClients().stream()
+          .filter(c -> Objects.equals(c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME), clientUrlName))
+          .findFirst();
+
+        if (! oClient.isPresent()) {
+            event.error(Errors.CLIENT_NOT_FOUND);
+            return ParsedCodeContext.response(redirectToErrorPage(Messages.CLIENT_NOT_FOUND));
+        }
+
+        ClientSessionModel clientSession = SamlService.createClientSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
+
+        return ParsedCodeContext.clientSessionCode(new ClientSessionCode(session, this.realmModel, clientSession));
+    }
+
+    /**
+     * Returns {@code true} if the client session is defined for the given code
+     * in the current session and for the current realm.
+     * Does <b>not</b> check the session validity. To obtain client session if
+     * and only if it exists and is valid, use {@link ClientSessionCode#parse}.
+     *
+     * @param code
+     * @return
+     */
+    protected boolean isClientSessionRegistered(String code) {
+        if (code == null) {
+            return false;
+        }
+
+        try {
+            return ClientSessionCode.getClientSession(code, this.session, this.realmModel) != null;
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
     private Response checkAccountManagementFailedLinking(ClientSessionModel clientSession, String error, Object... parameters) {
         if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
 
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
index cf0f55e..fd35caf 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
@@ -35,6 +35,8 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+
+import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -63,13 +65,20 @@ public class TestingOIDCEndpointsApplicationResource {
     @NoCache
     public Map<String, String> generateKeys() {
         try {
-            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
-            generator.initialize(2048);
-            clientData.setSigningKeyPair(generator.generateKeyPair());
-        } catch (NoSuchAlgorithmException e) {
+            KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+            clientData.setSigningKeyPair(keyPair);
+        } catch (Exception e) {
             throw new BadRequestException("Error generating signing keypair", e);
         }
 
+        return getKeysAsPem();
+    }
+
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/get-keys-as-pem")
+    public Map<String, String> getKeysAsPem() {
         String privateKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPrivate());
         String publicKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPublic());
 
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
new file mode 100644
index 0000000..d9d031a
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
@@ -0,0 +1,56 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.keycloak.testsuite</groupId>
+        <artifactId>integration-arquillian-test-apps</artifactId>
+        <version>2.4.1.Final-SNAPSHOT</version>
+    </parent>
+    
+    <artifactId>keycloak-test-app-profile-jee</artifactId>
+
+    <name>Keycloak Test App Profile JEE</name>
+    <description/>
+
+    <packaging>war</packaging>
+
+    <properties>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>app-profile-jee</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.wildfly.plugins</groupId>
+                <artifactId>wildfly-maven-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/README.md b/testsuite/integration-arquillian/test-apps/app-profile-jee/README.md
new file mode 100644
index 0000000..71bb42f
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/README.md
@@ -0,0 +1,10 @@
+You need to create a client in Keycloak. The configuration options when creating the client should be:
+
+* Client ID: You choose
+* Access Type: confidential
+* Root URL: Root URL for where you're hosting the application (for example http://localhost:8080)
+* Valie Redirect URIs: /app-profile-jee/*
+* Base URL: /app-profile-jee/
+* Admin URL: /app-profile-jee/
+
+Then, build the WAR with Maven and install as per the Adapter configuration for your server as described in the Keycloak documentation.
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java
new file mode 100644
index 0000000..2f863b9
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.quickstart.profilejee;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.constants.ServiceUrlConstants;
+import org.keycloak.representations.IDToken;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * Controller simplifies access to the server environment from the JSP.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
+ */
+public class Controller {
+
+    public void handleLogout(HttpServletRequest req) throws ServletException {
+        if (req.getParameter("logout") != null) {
+            req.logout();
+        }
+    }
+
+    public boolean isLoggedIn(HttpServletRequest req) {
+        return getSession(req) != null;
+    }
+
+    public boolean showToken(HttpServletRequest req) {
+        return req.getParameter("showToken") != null;
+    }
+
+    public IDToken getIDToken(HttpServletRequest req) {
+        return getSession(req).getIdToken();
+    }
+
+    public String getAccountUri(HttpServletRequest req) {
+        KeycloakSecurityContext session = getSession(req);
+        String baseUrl = getAuthServerBaseUrl(req);
+        String realm = session.getRealm();
+        return KeycloakUriBuilder.fromUri(baseUrl).path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH)
+                .queryParam("referrer", "app-profile-jee").build(realm).toString();
+
+    }
+
+    private String getAuthServerBaseUrl(HttpServletRequest req) {
+        AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) req.getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
+        KeycloakDeployment deployment = deploymentContext.resolveDeployment(null);
+        return deployment.getAuthServerBaseUrl();
+    }
+
+    public String getTokenString(HttpServletRequest req) throws IOException {
+        return JsonSerialization.writeValueAsPrettyString(getIDToken(req));
+    }
+
+    private KeycloakSecurityContext getSession(HttpServletRequest req) {
+        return (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
+    }
+}
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/index.jsp b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/index.jsp
new file mode 100644
index 0000000..beab972
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/index.jsp
@@ -0,0 +1,47 @@
+<%-- 
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.
+--%>
+
+<%@page contentType="text/html" pageEncoding="ISO-8859-1"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+        <title>Keycloak Example App</title>
+
+        <link rel="stylesheet" type="text/css" href="styles.css"/>
+    </head>
+    <body>
+        <jsp:useBean id="controller" class="org.keycloak.quickstart.profilejee.Controller" scope="request"/>
+        <% controller.handleLogout(request); %>
+        
+        <c:set var="isLoggedIn" value="<%=controller.isLoggedIn(request)%>"/>
+        <c:if test="${isLoggedIn}">
+            <c:redirect url="profile.jsp"/>
+        </c:if>
+
+        <div class="wrapper" id="welcome">
+            <div class="menu">
+                <button onclick="location.href = 'profile.jsp'" type="button">Login</button>
+            </div>
+
+            <div class="content">
+                <div class="message">Please login</div>
+            </div>
+        </div>
+    </body>
+</html>
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/profile.jsp b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/profile.jsp
new file mode 100644
index 0000000..0b74a94
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/profile.jsp
@@ -0,0 +1,79 @@
+<%-- 
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.
+--%>
+
+<%@page contentType="text/html" pageEncoding="ISO-8859-1"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+        <title>Keycloak Example App</title>
+        <link rel="stylesheet" type="text/css" href="styles.css"/>
+    </head>
+    <body>
+        <jsp:useBean id="controller" class="org.keycloak.quickstart.profilejee.Controller" scope="request"/>
+        <c:set var="idToken" value="<%=controller.getIDToken(request)%>"/>
+        <c:set var="tokenString" value="<%=controller.getTokenString(request)%>"/>
+        <c:set var="accountUri" value="<%=controller.getAccountUri(request)%>"/>
+        <c:set var="showToken" value="<%=controller.showToken(request)%>"/>
+
+        <div class="wrapper" id="profile">
+            <div class="menu">
+                <c:if test="${!showToken}">
+                    <button onclick="location.href = 'profile.jsp?showToken=true'">Token</button>
+                </c:if>
+                <c:if test="${showToken}">
+                    <button onclick="location.href = 'profile.jsp'">Profile</button>
+                </c:if>
+                <button onclick="location.href = 'index.jsp?logout=true'" type="button">Logout</button>
+                <button onclick="location.href = '${accountUri}'" type="button">Account</button>
+            </div>
+
+            <c:if test="${showToken}">
+                <div class="content">
+                    <div id="token-content" class="message">${tokenString}</div>
+                   <!-- <script>document.write(JSON.stringify(JSON.parse('${tokenString}'), null, '  '));</script>-->
+                </div>
+            </c:if>
+
+            <c:if test="${!showToken}">
+                <div class="content">
+                    <div id="profile-content" class="message">
+                        <table cellpadding="0" cellspacing="0">
+                            <tr>
+                                <td class="label">First name</td>
+                                <td><span id="firstName">${idToken.givenName}</span></td>
+                            </tr>
+                            <tr class="even">
+                                <td class="label">Last name</td>
+                                <td><span id="lastName">${idToken.familyName}</span></td>
+                            </tr>
+                            <tr>
+                                <td class="label">Username</td>
+                                <td><span id="username">${idToken.preferredUsername}</span></td>
+                            </tr>
+                            <tr class="even">
+                                <td class="label">Email</td>
+                                <td><span id="email">${idToken.email}</span></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </c:if>
+        </div>
+    </body>
+</html>
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/styles.css b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/styles.css
new file mode 100644
index 0000000..815abe4
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/styles.css
@@ -0,0 +1,101 @@
+
+body {
+    background-color: #333;
+    font-family: sans-serif;
+    font-size: 30px;
+}
+
+button {
+    font-family: sans-serif;
+    font-size: 30px;
+    width: 200px;
+
+    background-color: #0085cf;
+    background-image: linear-gradient(to bottom, #00a8e1 0%, #0085cf 100%);
+    background-repeat: repeat-x;
+
+    border: 2px solid #ccc;
+    color: #fff;
+    -webkit-border-radius: 30px;
+
+    text-transform: uppercase;
+
+    -webkit-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+    -moz-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+    box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+}
+
+button:hover {
+    background-color: #006ba6;
+    background-image: none;
+    -webkit-box-shadow: none;
+    -moz-box-shadow: none;
+    box-shadow: none;
+}
+
+hr {
+    border: none;
+    background-color: #eee;
+    height: 10px;
+}
+
+.menu {
+    padding: 10px;
+    margin-bottom: 10px;
+}
+
+.content {
+    background-color: #eee;
+    border: 1px solid #ccc;
+    padding: 10px;
+    -webkit-border-radius: 10px;
+
+    -webkit-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+    -moz-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+    box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+}
+
+.content .message {
+    padding: 10px;
+    background-color: #fff;
+    border: 1px solid #ccc;
+    font-size: 40px;
+    -webkit-border-radius: 10px;
+}
+
+#token .content .message {
+    font-size: 20px;
+    overflow: scroll;
+    padding: 5px;
+    white-space: pre;
+    text-transform: none;
+}
+
+.wrapper {
+    position: absolute;
+    left: 10px;
+    top: 10px;
+    bottom: 10px;
+    right: 10px;
+}
+
+.error {
+    color: #a21e22;
+}
+
+table {
+   width: 100%; 
+}
+
+tr.even {
+    background-color: #eee;
+}
+
+td {
+    padding: 5px;
+}
+
+td.label {
+    font-weight: bold;
+    width: 250px;
+}
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000..311f66d
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,10 @@
+{
+    "realm": "Test",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "/auth",
+    "ssl-required": "external",
+    "resource": "app-profile-jee",
+    "credentials": {
+        "secret": "4f36f31a-be9d-4f92-b982-425301bac5df"
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c044282
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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>app-profile-jee</module-name>
+
+    <security-constraint>
+        <web-resource-collection>
+            <url-pattern>/profile.jsp</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>user</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <login-config>
+        <auth-method>KEYCLOAK</auth-method>
+    </login-config>
+
+    <security-role>
+        <role-name>user</role-name>
+    </security-role>
+
+</web-app>
diff --git a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html b/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
index 1c2b61d..65540f1 100755
--- a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
+++ b/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
@@ -43,6 +43,7 @@
     <button onclick="output(keycloak.createRegisterUrl())">Show Register URL</button>
     <button onclick="createBearerRequest()">Create Bearer Request</button>
     <button onclick="output(showTime())">Show current time</button>
+    <button onclick="cert()">Cert request</button>
     <input id="timeSkewInput"/>
     <button onclick="addToTimeSkew()">timeSkew offset</button>
     <button onclick="refreshTimeSkew()">refresh timeSkew</button>
@@ -215,6 +216,30 @@ TimeSkew: <div id="timeSkew"></div>
         req.send();
     }
 
+    function cert() {
+        var url = 'http://localhost:8180/auth/realms/example/protocol/openid-connect/certs';
+        if (window.location.href.indexOf("8543") > -1) {
+            url = url.replace("8180","8543");
+            url = url.replace("http","https");
+        }
+        var req = new XMLHttpRequest();
+        req.open('GET', url, true);
+        req.setRequestHeader('Accept', 'application/json');
+        req.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
+        req.onreadystatechange = function () {
+            if (req.readyState == 4) {
+                if (req.status == 200) {
+                    output('Success');
+                } else if (req.status == 403) {
+                    output('Forbidden');
+                } else if (req.status == 401) {
+                    output('Unauthorized');
+                }
+            }
+        };
+        req.send();
+    }
+
     var keycloak;
 
     function keycloakInit() {
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 2acbdb0..4d74389 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -22,6 +22,7 @@
         <module>hello-world-authz-service</module>
         <module>servlet-authz</module>
         <module>servlets</module>
+        <module>app-profile-jee</module>
     </modules>
 
     <build>
@@ -35,4 +36,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
\ No newline at end of file
+</project>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
index 4822c4b..069481f 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
@@ -77,6 +77,8 @@ public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
     private WebElement createBearerRequest;
     @FindBy(xpath = "//button[text() = 'Bearer to keycloak']")
     private WebElement createBearerRequestToKeycloakButton;
+    @FindBy(xpath = "//button[text() = 'Cert request']")
+    private WebElement certRequestButton;
     @FindBy(xpath = "//button[text() = 'refresh timeSkew']")
     private WebElement refreshTimeSkewButton;
 
@@ -178,4 +180,8 @@ public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
     public void refreshTimeSkew() {
         refreshTimeSkewButton.click();
     }
+
+    public void sendCertRequest() {
+        certRequestButton.click();
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java
new file mode 100644
index 0000000..f46c736
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+
+import java.net.URL;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class ProductPortalSubsystem extends AbstractPageWithInjectedUrl {
+
+    public static final String DEPLOYMENT_NAME = "product-portal-subsystem";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
index 9c4f324..f8d8e98 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
@@ -37,6 +37,11 @@ public interface TestOIDCEndpointsApplicationResource {
     @Path("/generate-keys")
     Map<String, String> generateKeys();
 
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/get-keys-as-pem")
+    Map<String, String> getKeysAsPem();
+
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
index ea4725a..216e2ca 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
@@ -74,19 +74,21 @@ public final class WaitUtils {
     }
 
     public static void pause(long millis) {
-        log.info("Wait: " + millis + "ms");
-        try {
-            Thread.sleep(millis);
-        } catch (InterruptedException ex) {
-            Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
-            Thread.currentThread().interrupt();
+        if (millis > 0) {
+            log.info("Wait: " + millis + "ms");
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException ex) {
+                Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
+                Thread.currentThread().interrupt();
+            }
         }
     }
 
     /**
      * Waits for page to finish any pending redirects, REST API requests etc.
-     * Because Keycloak's Admin Console is a single-page application, we need to take extra steps to ensure
-     * the page is fully loaded
+     * Because Keycloak's Admin Console is a single-page application, we need to
+     * take extra steps to ensure the page is fully loaded
      *
      * @param driver
      */
@@ -99,12 +101,11 @@ public final class WaitUtils {
             // Checks if the document is ready and asks AngularJS, if present, whether there are any REST API requests
             // in progress
             wait.until(javaScriptThrowsNoExceptions(
-                "if (document.readyState !== 'complete' " +
-                    "|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {" +
-                        "throw \"Not ready\";" +
-                "}"));
-        }
-        catch (TimeoutException e) {
+                    "if (document.readyState !== 'complete' "
+                    + "|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {"
+                    + "throw \"Not ready\";"
+                    + "}"));
+        } catch (TimeoutException e) {
             // Sometimes, for no obvious reason, the browser/JS doesn't set document.readyState to 'complete' correctly
             // but that's no reason to let the test fail; after the timeout the page is surely fully loaded
             log.warn("waitForPageToLoad time exceeded!");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
index e36bc36..179bb0f 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
@@ -190,6 +190,15 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
     }
 
     @Test
+    public void testCertEndpoint() {
+        logInAndInit("standard");
+        waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
+
+        jsConsoleTestAppPage.sendCertRequest();
+        waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
+    }
+
+    @Test
     public void grantBrowserBasedApp() {
         testRealmPage.setAuthRealm(EXAMPLE);
         testRealmLoginPage.setAuthRealm(EXAMPLE);
@@ -323,6 +332,16 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
     }
 
     @Test
+    public void implicitFlowCertEndpoint() {
+        setImplicitFlowForClient();
+        logInAndInit("implicit");
+        waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
+
+        jsConsoleTestAppPage.sendCertRequest();
+        waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
+    }
+
+    @Test
     public void testBearerRequest() {
         jsConsoleTestAppPage.navigateTo();
         jsConsoleTestAppPage.init();
@@ -406,6 +425,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
         jsConsoleTestAppPage.setFlow(flow);
         jsConsoleTestAppPage.init();
         jsConsoleTestAppPage.logIn();
+        waitUntilElement(By.xpath("//body")).is().present();
         testRealmLoginPage.form().login(user, "password");
         jsConsoleTestAppPage.setFlow(flow);
         jsConsoleTestAppPage.init();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java
index fed5ab7..0ad81d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java
@@ -16,13 +16,6 @@ public abstract class AbstractDemoFilterServletAdapterTest extends AbstractDemoS
     @Test
     @Override
     @Ignore
-    public void testCustomerPortalWithSubsystemSettings() {
-
-    }
-
-    @Test
-    @Override
-    @Ignore
     public void testAuthenticated() {
 
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index ed2191d..a7e5992 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.keycloak.testsuite.adapter.servlet;
 
 import org.apache.commons.io.FileUtils;
@@ -32,7 +31,6 @@ import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.Time;
 import org.keycloak.constants.AdapterConstants;
 import org.keycloak.keys.KeyProvider;
-import org.keycloak.models.Constants;
 import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@@ -48,7 +46,6 @@ import org.keycloak.testsuite.adapter.page.BasicAuth;
 import org.keycloak.testsuite.adapter.page.CustomerDb;
 import org.keycloak.testsuite.adapter.page.CustomerDbErrorPage;
 import org.keycloak.testsuite.adapter.page.CustomerPortal;
-import org.keycloak.testsuite.adapter.page.CustomerPortalSubsystem;
 import org.keycloak.testsuite.adapter.page.InputPortal;
 import org.keycloak.testsuite.adapter.page.ProductPortal;
 import org.keycloak.testsuite.adapter.page.SecurePortal;
@@ -58,7 +55,6 @@ import org.keycloak.testsuite.auth.page.account.Applications;
 import org.keycloak.testsuite.auth.page.login.OAuthGrant;
 import org.keycloak.testsuite.console.page.events.Config;
 import org.keycloak.testsuite.console.page.events.LoginEvents;
-import org.keycloak.testsuite.util.URLAssert;
 import org.keycloak.testsuite.util.URLUtils;
 import org.keycloak.util.BasicAuthHelper;
 import org.openqa.selenium.By;
@@ -86,7 +82,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import org.keycloak.testsuite.adapter.page.CustomerPortalNoConf;
 import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
@@ -107,8 +102,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     @Page
     private CustomerPortalNoConf customerPortalNoConf;
     @Page
-    private CustomerPortalSubsystem customerPortalSubsystem;
-    @Page
     private SecurePortal securePortal;
     @Page
     private CustomerDb customerDb;
@@ -135,17 +128,12 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     protected static WebArchive customerPortal() {
         return servletDeployment(CustomerPortal.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
     }
-    
+
     @Deployment(name = CustomerPortalNoConf.DEPLOYMENT_NAME)
     protected static WebArchive customerPortalNoConf() {
         return servletDeployment(CustomerPortalNoConf.DEPLOYMENT_NAME, CustomerServletNoConf.class, ErrorServlet.class);
     }
 
-    @Deployment(name = CustomerPortalSubsystem.DEPLOYMENT_NAME)
-    protected static WebArchive customerPortalSubsystem() {
-        return servletDeployment(CustomerPortalSubsystem.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
-    }
-
     @Deployment(name = SecurePortal.DEPLOYMENT_NAME)
     protected static WebArchive securePortal() {
         return servletDeployment(SecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
@@ -198,14 +186,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     }
 
     @Test
-    public void testCustomerPortalWithSubsystemSettings() {
-        customerPortalSubsystem.navigateTo();
-        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
-        testRealmLoginPage.form().login("bburke@redhat.com", "password");
-        assertTrue(driver.getPageSource().contains("Bill Burke") && driver.getPageSource().contains("Stian Thorgersen"));
-    }
-
-    @Test
     public void testSavedPostRequest() throws InterruptedException {
         // test login to customer-portal which does a bearer request to customer-db
         inputPortal.navigateTo();
@@ -843,7 +823,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
             log.info("Checking app server log on app-server: \"" + System.getProperty("app.server") + "\" is not supported.");
         }
     }
-    
+
     @Test
     public void testWithoutKeycloakConf() {
         customerPortalNoConf.navigateTo();
@@ -851,5 +831,4 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         assertTrue(pageSource.contains("Forbidden") || pageSource.contains("HTTP Status 401"));
     }
 
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java
new file mode 100644
index 0000000..ad94cd1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java
@@ -0,0 +1,48 @@
+package org.keycloak.testsuite.adapter.servlet;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.keycloak.testsuite.adapter.page.CustomerPortalSubsystem;
+import org.keycloak.testsuite.adapter.page.ProductPortalSubsystem;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
+
+/**
+ * OIDC adapter test specific for JBoss-based containers.
+ * @author tkyjovsk
+ */
+public abstract class AbstractJBossOIDCServletsAdapterTest extends AbstractDemoServletsAdapterTest {
+
+    @Page
+    private CustomerPortalSubsystem customerPortalSubsystem;
+
+    @Page
+    private ProductPortalSubsystem productPortalSubsystem;
+
+    @Deployment(name = CustomerPortalSubsystem.DEPLOYMENT_NAME)
+    protected static WebArchive customerPortalSubsystem() {
+        return servletDeployment(CustomerPortalSubsystem.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
+    }
+
+    @Deployment(name = ProductPortalSubsystem.DEPLOYMENT_NAME)
+    protected static WebArchive productPortalSubsystem() {
+        return servletDeployment(ProductPortalSubsystem.DEPLOYMENT_NAME, ProductServlet.class);
+    }
+
+    @Test
+    public void testSecureDeployments() {
+        customerPortalSubsystem.navigateTo();
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
+        assertTrue(driver.getPageSource().contains("Bill Burke") && driver.getPageSource().contains("Stian Thorgersen"));
+
+        productPortalSubsystem.navigateTo();
+        assertCurrentUrlEquals(productPortalSubsystem);
+        String pageSource = driver.getPageSource();
+        assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
index 5adfb94..f2ba5ae 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
@@ -29,6 +29,9 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
         salesPostSigPersistentServletPage.checkRoles(true);
         salesPostSigTransientServletPage.checkRoles(true);
         salesPostAssertionAndResponseSigPage.checkRoles(true);
+        employeeSigPostNoIdpKeyServletPage.checkRoles(true);
+        employeeSigRedirNoIdpKeyServletPage.checkRoles(true);
+        employeeSigRedirOptNoIdpKeyServletPage.checkRoles(true);
 
         //using endpoint instead of query param because we are not able to put query param to IDP initiated login
         employee2ServletPage.navigateTo();
@@ -54,6 +57,9 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
         salesPostSigEmailServletPage.checkRoles(false);
         salesPostSigPersistentServletPage.checkRoles(false);
         salesPostSigTransientServletPage.checkRoles(false);
+        employeeSigPostNoIdpKeyServletPage.checkRoles(false);
+        employeeSigRedirNoIdpKeyServletPage.checkRoles(false);
+        employeeSigRedirOptNoIdpKeyServletPage.checkRoles(false);
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java
index 5e6b30d..9bdc40e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java
@@ -28,6 +28,8 @@ import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.PublicKeyStorageUtils;
+import org.keycloak.keys.loader.PublicKeyStorageManager;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.ComponentRepresentation;
@@ -107,15 +109,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
     @Test
     public void testSignatureVerificationJwksUrl() throws Exception {
         // Configure OIDC identity provider with JWKS URL
-        IdentityProviderRepresentation idpRep = getIdentityProvider();
-        OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
-        cfg.setValidateSignature(true);
-        cfg.setUseJwksUrl(true);
-
-        UriBuilder b = OIDCLoginProtocolService.certsUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
-        String jwksUrl = b.build(bc.providerRealmName()).toString();
-        cfg.setJwksUrl(jwksUrl);
-        updateIdentityProvider(idpRep);
+        updateIdentityProviderWithJwksUrl();
 
         // Check that user is able to login
         logInAsUserInIDPForFirstTime();
@@ -139,6 +133,19 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
         assertLoggedInAccountManagement();
     }
 
+    // Configure OIDC identity provider with JWKS URL and validateSignature=true
+    private void updateIdentityProviderWithJwksUrl() {
+        IdentityProviderRepresentation idpRep = getIdentityProvider();
+        OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
+        cfg.setValidateSignature(true);
+        cfg.setUseJwksUrl(true);
+
+        UriBuilder b = OIDCLoginProtocolService.certsUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
+        String jwksUrl = b.build(bc.providerRealmName()).toString();
+        cfg.setJwksUrl(jwksUrl);
+        updateIdentityProvider(idpRep);
+    }
+
 
     @Test
     public void testSignatureVerificationHardcodedPublicKey() throws Exception {
@@ -178,23 +185,17 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
     @Test
     public void testClearKeysCache() throws Exception {
         // Configure OIDC identity provider with JWKS URL
-        IdentityProviderRepresentation idpRep = getIdentityProvider();
-        OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
-        cfg.setValidateSignature(true);
-        cfg.setUseJwksUrl(true);
-
-        UriBuilder b = OIDCLoginProtocolService.certsUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
-        String jwksUrl = b.build(bc.providerRealmName()).toString();
-        cfg.setJwksUrl(jwksUrl);
-        updateIdentityProvider(idpRep);
+        updateIdentityProviderWithJwksUrl();
 
         // Check that user is able to login
         logInAsUserInIDPForFirstTime();
         assertLoggedInAccountManagement();
 
+        logoutFromRealm(bc.consumerRealmName());
 
         // Check that key is cached
-        String expectedCacheKey = consumerRealm().toRepresentation().getId() + "::idp::" + idpRep.getInternalId();
+        IdentityProviderRepresentation idpRep = getIdentityProvider();
+        String expectedCacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(consumerRealm().toRepresentation().getId(), idpRep.getInternalId());
         TestingCacheResource cache = testingClient.testing(bc.consumerRealmName()).cache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
         Assert.assertTrue(cache.contains(expectedCacheKey));
 
@@ -205,6 +206,40 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
     }
 
 
+    // Test that when I update identityProvier, then the record in publicKey cache is cleared and it's not possible to authenticate with it anymore
+    @Test
+    public void testPublicKeyCacheInvalidatedWhenProviderUpdated() throws Exception {
+        // Configure OIDC identity provider with JWKS URL
+        updateIdentityProviderWithJwksUrl();
+
+        // Check that user is able to login
+        logInAsUserInIDPForFirstTime();
+        assertLoggedInAccountManagement();
+
+        logoutFromRealm(bc.consumerRealmName());
+
+        // Check that key is cached
+        IdentityProviderRepresentation idpRep = getIdentityProvider();
+        String expectedCacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(consumerRealm().toRepresentation().getId(), idpRep.getInternalId());
+        TestingCacheResource cache = testingClient.testing(bc.consumerRealmName()).cache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
+        Assert.assertTrue(cache.contains(expectedCacheKey));
+
+        // Update identityProvider to some bad JWKS_URL
+        OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
+        cfg.setJwksUrl("http://localhost:43214/non-existent");
+        updateIdentityProvider(idpRep);
+
+        // Check that key is not cached anymore
+        Assert.assertFalse(cache.contains(expectedCacheKey));
+
+        // Check that user is not able to login with IDP
+        setTimeOffset(20);
+        logInAsUserInIDP();
+        assertErrorPage("Unexpected error when authenticating with identity provider");
+    }
+
+
+
     private void rotateKeys() {
         String activeKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java
new file mode 100644
index 0000000..4bb367f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java
@@ -0,0 +1,132 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.keycloak.testsuite.broker;
+
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
+import org.keycloak.testsuite.adapter.page.SalesPostServlet;
+import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
+import org.keycloak.testsuite.util.IOUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
+import static org.hamcrest.Matchers.*;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
+
+    private static final String PROVIDER_REALM_USER_NAME = "test";
+    private static final String PROVIDER_REALM_USER_PASSWORD = "test";
+
+    @Page
+    protected LoginPage accountLoginPage;
+
+    @Page
+    protected UpdateAccountInformationPage updateAccountInformationPage;
+
+    protected String getAuthRoot() {
+        return suiteContext.getAuthServerInfo().getContextRoot().toString();
+    }
+
+    private RealmRepresentation loadFromClasspath(String fileName, Properties properties) {
+        InputStream is = KcSamlIdPInitiatedSsoTest.class.getResourceAsStream(fileName);
+        try {
+            String template = StreamUtil.readString(is);
+            String realmString = StringPropertyReplacer.replaceProperties(template, properties);
+            return IOUtil.loadRealm(new ByteArrayInputStream(realmString.getBytes("UTF-8")));
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        Properties p = new Properties();
+        p.put("name.realm.provider", REALM_PROV_NAME);
+        p.put("name.realm.consumer", REALM_CONS_NAME);
+        p.put("url.realm.provider", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME);
+        p.put("url.realm.consumer", getAuthRoot() + "/auth/realms/" + REALM_CONS_NAME);
+        
+        testRealms.add(loadFromClasspath("kc3731-provider-realm.json", p));
+        testRealms.add(loadFromClasspath("kc3731-broker-realm.json", p));
+    }
+
+    @Test
+    public void testProviderIdpInitiatedLogin() {
+        driver.navigate().to(getSamlIdpInitiatedUrl(REALM_PROV_NAME, "samlbroker"));
+
+        waitForPage("log in to");
+
+        Assert.assertThat("Driver should be on the provider realm page right now",
+                driver.getCurrentUrl(), containsString("/auth/realms/" + REALM_PROV_NAME + "/"));
+
+        log.debug("Logging in");
+        accountLoginPage.login(PROVIDER_REALM_USER_NAME, PROVIDER_REALM_USER_PASSWORD);
+
+        waitForPage("update account information");
+
+        Assert.assertTrue(updateAccountInformationPage.isCurrent());
+        Assert.assertThat("We must be on consumer realm right now",
+                driver.getCurrentUrl(), containsString("/auth/realms/" + REALM_CONS_NAME + "/"));
+
+        log.debug("Updating info on updateAccount page");
+        updateAccountInformationPage.updateAccountInformation("mytest", "test@localhost", "Firstname", "Lastname");
+
+        UsersResource consumerUsers = adminClient.realm(REALM_CONS_NAME).users();
+
+        int userCount = consumerUsers.count();
+        Assert.assertTrue("There must be at least one user", userCount > 0);
+
+        List<UserRepresentation> users = consumerUsers.search("", 0, userCount);
+
+        boolean isUserFound = users.stream().anyMatch(user -> user.getUsername().equals("mytest") && user.getEmail().equals("test@localhost"));
+        Assert.assertTrue("There must be user " + "mytest" + " in realm " + REALM_CONS_NAME, isUserFound);
+
+        Assert.assertThat(driver.findElement(org.openqa.selenium.By.tagName("form")).getAttribute("action"), containsString("http://localhost:18080/sales-post-enc/"));
+    }
+
+    private String getSamlIdpInitiatedUrl(String realmName, String samlIdpInitiatedSsoUrlName) {
+        return getAuthRoot() + "/auth/realms/" + realmName + "/protocol/saml/clients/" + samlIdpInitiatedSsoUrlName;
+    }
+
+    private void waitForPage(final String title) {
+        WebDriverWait wait = new WebDriverWait(driver, 5);
+
+        ExpectedCondition<Boolean> condition = (WebDriver input) -> input.getTitle().toLowerCase().contains(title);
+
+        wait.until(condition);
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
index c72bdba..f2f8c6d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -57,6 +57,7 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
     public void addTestRealms(List<RealmRepresentation> testRealms) {
         RealmRepresentation rep = new RealmRepresentation();
         rep.setEnabled(true);
+        rep.setId(REALM_NAME);
         rep.setRealm(REALM_NAME);
         rep.setUsers(new LinkedList<UserRepresentation>());
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java
index 5a86d59..74432a8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java
@@ -18,6 +18,7 @@
 package org.keycloak.testsuite.client;
 
 import java.security.KeyPair;
+import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.Collections;
@@ -40,9 +41,14 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
 import org.keycloak.client.registration.Auth;
 import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.constants.ServiceUrlConstants;
 import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKBuilder;
 import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.keys.PublicKeyStorageUtils;
+import org.keycloak.keys.loader.PublicKeyStorageManager;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@@ -139,6 +145,16 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
     // The "kid" is set manually to some custom value
     @Test
     public void createClientWithJWKS_customKid() throws Exception {
+        OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
+
+        Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
+
+        // Tries to authenticate client with privateKey JWT
+        assertAuthenticateClientSuccess(generatedKeys, response, "a1");
+    }
+
+
+    private OIDCClientRepresentation createClientWithManuallySetKid(String kid) throws Exception {
         OIDCClientRepresentation clientRep = createRep();
 
         clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
@@ -146,20 +162,84 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
 
         // Generate keys for client
         TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
-        Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+        oidcClientEndpointsResource.generateKeys();
 
         JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
 
         // Override kid with custom value
-        keySet.getKeys()[0].setKeyId("a1");
+        keySet.getKeys()[0].setKeyId(kid);
         clientRep.setJwks(keySet);
 
-        OIDCClientRepresentation response = reg.oidc().create(clientRep);
+        return reg.oidc().create(clientRep);
+    }
+
+
+    @Test
+    public void testTwoClientsWithSameKid() throws Exception {
+        // Create client with manually set "kid"
+        OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
+
+
+        // Create client2
+        OIDCClientRepresentation clientRep2 = createRep();
+
+        clientRep2.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
+        clientRep2.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
+
+        // Generate some random keys for client2
+        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+        generator.initialize(2048);
+        PublicKey client2PublicKey = generator.generateKeyPair().getPublic();
+
+        // Set client2 with manually set "kid" to be same like kid of client1 (but keys for both clients are different)
+        JSONWebKeySet keySet = new JSONWebKeySet();
+        keySet.setKeys(new JWK[]{JWKBuilder.create().kid("a1").rs256(client2PublicKey)});
+
+        clientRep2.setJwks(keySet);
+        clientRep2 = reg.oidc().create(clientRep2);
+
+
+        // Authenticate client1
+        Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
+        assertAuthenticateClientSuccess(generatedKeys, response, "a1");
+
+        // Assert item in publicKey cache for client1
+        String expectedCacheKey = PublicKeyStorageUtils.getClientModelCacheKey(REALM_NAME, response.getClientId());
+        Assert.assertTrue(testingClient.testing().cache(InfinispanConnectionProvider.KEYS_CACHE_NAME).contains(expectedCacheKey));
+
+        // Assert it's not possible to authenticate as client2 with the same "kid" like client1
+        assertAuthenticateClientError(generatedKeys, clientRep2, "a1");
+    }
+
+
+    @Test
+    public void testPublicKeyCacheInvalidatedWhenUpdatingClient() throws Exception {
+        OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
+
+        Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
 
         // Tries to authenticate client with privateKey JWT
         assertAuthenticateClientSuccess(generatedKeys, response, "a1");
+
+        // Assert item in publicKey cache for client1
+        String expectedCacheKey = PublicKeyStorageUtils.getClientModelCacheKey(REALM_NAME, response.getClientId());
+        Assert.assertTrue(testingClient.testing().cache(InfinispanConnectionProvider.KEYS_CACHE_NAME).contains(expectedCacheKey));
+
+
+
+        // Update client with some bad JWKS_URI
+        response.setJwksUri("http://localhost:4321/non-existent");
+        reg.auth(Auth.token(response.getRegistrationAccessToken()))
+                .oidc().update(response);
+
+        // Assert item not any longer for client1
+        Assert.assertFalse(testingClient.testing().cache(InfinispanConnectionProvider.KEYS_CACHE_NAME).contains(expectedCacheKey));
+
+        // Assert it's not possible to authenticate as client1
+        assertAuthenticateClientError(generatedKeys, response, "a1");
     }
 
+
     @Test
     public void createClientWithJWKSURI() throws Exception {
         OIDCClientRepresentation clientRep = createRep();
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 0272584..d65fa94 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
@@ -176,6 +176,16 @@
             "secret": "password"
         },
         {
+            "clientId": "product-portal-subsystem",
+            "enabled": true,
+            "adminUrl": "/product-portal-subsystem",
+            "baseUrl": "/product-portal-subsystem",
+            "redirectUris": [
+                "/product-portal-subsystem/*"
+            ],
+            "secret": "password"
+        },
+        {
             "clientId": "secure-portal",
             "enabled": true,
             "adminUrl": "/secure-portal",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml
new file mode 100644
index 0000000..b4ddcce
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<Context path="/customer-portal">
+    <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/product-portal-subsystem/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/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/product-portal-subsystem/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/product-portal-subsystem/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/web.xml
new file mode 100644
index 0000000..0af7958
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/web.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<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>product-portal-subsystem</module-name>
+
+    <servlet>
+        <servlet-name>Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.adapter.servlet.ProductServlet</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>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json
new file mode 100644
index 0000000..6e5c7e0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json
@@ -0,0 +1,64 @@
+{
+  "id" : "${name.realm.consumer}",
+  "realm" : "${name.realm.consumer}",
+  "enabled" : true,
+  "sslRequired" : "external",
+  "roles" : {
+    "client" : {
+      "http://localhost:18080/sales-post-enc/" : [ {
+        "name" : "manager"
+      } ]
+    }
+  },
+  "clients" : [ {
+    "clientId": "http://localhost:18080/sales-post-enc/",
+    "enabled": true,
+    "protocol": "saml",
+    "fullScopeAllowed": true,
+    "redirectUris": [
+      "http://localhost:18080/sales-post-enc/*"
+    ],
+    "attributes": {
+      "saml.authnstatement": "true",
+      "saml.client.signature": "true",
+      "saml.encrypt": "false",
+      "saml.server.signature": "true",
+      "saml.signature.algorithm": "RSA_SHA512",
+      "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==",
+      "saml.signing.private.key": "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t",
+      "saml_idp_initiated_sso_url_name" : "sales"
+    },
+    "baseUrl": "http://localhost:18080/sales-post-enc/",
+    "adminUrl": "http://localhost:18080/sales-post-enc/saml"
+  } ],
+  "identityProviders" : [ {
+    "alias" : "saml-leaf",
+    "providerId" : "saml",
+    "enabled" : true,
+    "updateProfileFirstLoginMode" : "on",
+    "trustEmail" : false,
+    "storeToken" : false,
+    "addReadTokenRoleOnCreate" : false,
+    "authenticateByDefault" : false,
+    "firstBrokerLoginFlowAlias" : "first broker login",
+    "config" : {
+      "nameIDPolicyFormat" : "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
+      "postBindingAuthnRequest" : "true",
+      "postBindingResponse" : "true",
+      "singleLogoutServiceUrl" : "${url.realm.provider}/protocol/saml",
+      "singleSignOnServiceUrl" : "${url.realm.provider}/protocol/saml",
+      "validateSignature" : "false",
+      "wantAuthnRequestsSigned" : "false"
+    }
+  } ],
+  "identityProviderMappers" : [ {
+    "name" : "manager-role",
+    "identityProviderAlias" : "saml-leaf",
+    "identityProviderMapper" : "saml-role-idp-mapper",
+    "config" : {
+      "attribute.value" : "manager",
+      "role" : "http://localhost:18080/sales-post-enc/.manager",
+      "attribute.name" : "Role"
+    }
+  } ]
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json
new file mode 100644
index 0000000..8804a36
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json
@@ -0,0 +1,49 @@
+{
+  "id" : "${name.realm.provider}",
+  "realm" : "${name.realm.provider}",
+  "enabled" : true,
+  "sslRequired" : "external",
+  "roles" : {
+    "client" : {
+      "${url.realm.consumer}" : [ {
+        "name" : "manager"
+      } ]
+    }
+  },
+  "clients" : [ {
+    "clientId": "${url.realm.consumer}",
+    "enabled": true,
+    "protocol": "saml",
+    "fullScopeAllowed": true,
+    "redirectUris": [
+      "${url.realm.consumer}/broker/saml-leaf/endpoint"
+    ],
+    "attributes" : {
+      "saml.assertion.signature" : "false",
+      "saml.authnstatement" : "true",
+      "saml.client.signature" : "false",
+      "saml.encrypt" : "false",
+      "saml.force.post.binding" : "true",
+      "saml.server.signature" : "false",
+      "saml_assertion_consumer_url_post" : "${url.realm.consumer}/broker/saml-leaf/endpoint/clients/sales",
+      "saml_force_name_id_format" : "false",
+      "saml_idp_initiated_sso_url_name" : "samlbroker",
+      "saml_name_id_format" : "persistent",
+      "saml_single_logout_service_url_post" : "${url.realm.consumer}/broker/saml-leaf/endpoint"
+    }
+  } ],
+  "users" : [ {
+    "username" : "test",
+    "enabled" : true,
+    "email" : "a@localhost",
+    "firstName": "b",
+    "lastName": "c",
+    "credentials" : [ {
+      "type" : "password",
+      "value" : "test"
+    } ],
+    "clientRoles" : {
+      "${url.realm.consumer}" : [ "manager" ]
+    }
+  } ]
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java
index bd9843e..281de0b 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java
@@ -1,6 +1,6 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-as7")
-public class AS7OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class AS7OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
index 0027550..114d875 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
@@ -21,6 +21,16 @@
                     <resource>customer-portal-subsystem</resource>
                     <credential name="secret">password</credential>
                 </secure-deployment>
+                
+                <secure-deployment name="product-portal-subsystem.war">
+                    <realm>demo</realm>
+                    <realm-public-key>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</realm-public-key>
+                    <auth-server-url><xsl:value-of select="$auth-server-host"/>/auth</auth-server-url>
+                    <ssl-required>EXTERNAL</ssl-required>
+                    <resource>product-portal-subsystem</resource>
+                    <credential name="secret">password</credential>
+                </secure-deployment>
+                
             </xsl:copy>
     </xsl:template>
 
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl
new file mode 100644
index 0000000..3306012
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl
@@ -0,0 +1,45 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                version="2.0"
+                exclude-result-prefixes="xalan">
+
+    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:variable name="keycloakSubsystem" select="'urn:jboss:domain:keycloak:1.1'"/>
+    <xsl:param name="auth-server-host"/>
+
+    <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $keycloakSubsystem)]">
+        <xsl:copy>
+            <xsl:apply-templates select="@* | node()" />
+
+            <realm name="demo">
+                <realm-public-key>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</realm-public-key>
+                <auth-server-url>
+                    <xsl:value-of select="$auth-server-host"/>/auth
+                </auth-server-url>
+                <ssl-required>EXTERNAL</ssl-required>
+            </realm>
+                
+            <secure-deployment name="customer-portal-subsystem.war">
+                <realm>demo</realm>
+                <resource>customer-portal-subsystem</resource>
+                <credential name="secret">password</credential>
+            </secure-deployment>
+            
+            <secure-deployment name="product-portal-subsystem.war">
+                <realm>demo</realm>
+                <resource>product-portal-subsystem</resource>
+                <credential name="secret">password</credential>
+            </secure-deployment>
+            
+        </xsl:copy>
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+        <xsl:copy>
+            <xsl:apply-templates select="@*|node()" />
+        </xsl:copy>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java
index 169cc6e..b5f3ee9 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java
@@ -1,6 +1,6 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-eap")
-public class EAPOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class EAPOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java
index 4afd228..1367d95 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java
@@ -1,6 +1,6 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-eap6")
-public class EAP6OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class EAP6OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml
index 72e4c20..d59e6b0 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>integration-arquillian-tests-adapters-eap6-fuse</artifactId>
 
-    <name>Adapter Tests - JBoss - EAP 6</name>
+    <name>Adapter Tests - JBoss - EAP 6 Fuse</name>
     
     <properties>
         <app.server>eap6-fuse</app.server>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
index defe63a..17a362f 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
@@ -36,6 +36,8 @@
     <properties>
         <common.resources>${project.parent.basedir}/common</common.resources>
         <app.server.type>managed</app.server.type>
+        <auth.server.actual.http.port>${auth.server.http.port}</auth.server.actual.http.port>
+        <keycloak.subsystem.xsl>keycloak-subsystem.xsl</keycloak.subsystem.xsl>
     </properties>
 
     <build>
@@ -57,12 +59,12 @@
                                     <includes>
                                         <include>standalone.xml</include>
                                     </includes>
-                                    <stylesheet>${common.resources}/xslt/keycloak-subsystem.xsl</stylesheet>
+                                    <stylesheet>${common.resources}/xslt/${keycloak.subsystem.xsl}</stylesheet>
                                     <outputDir>${app.server.home}/standalone/configuration</outputDir>
                                     <parameters>
                                         <parameter>
                                             <name>auth-server-host</name>
-                                            <value>http://localhost:${auth.server.http.port}</value>
+                                            <value>http://localhost:${auth.server.actual.http.port}</value>
                                         </parameter>
                                     </parameters>
                                 </transformationSet>
@@ -83,41 +85,17 @@
                     <value>true</value>
                 </property>
             </activation>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>xml-maven-plugin</artifactId>
-                        <executions>
-                            <execution>
-                                <id>configure-keycloak-subsystem</id>
-                                <phase>process-test-resources</phase>
-                                <goals>
-                                    <goal>transform</goal>
-                                </goals>
-                                <configuration>
-                                    <transformationSets>
-                                        <transformationSet>
-                                            <dir>${app.server.home}/standalone/configuration</dir>
-                                            <includes>
-                                                <include>standalone.xml</include>
-                                            </includes>
-                                            <stylesheet>${common.resources}/xslt/keycloak-subsystem.xsl</stylesheet>
-                                            <outputDir>${app.server.home}/standalone/configuration</outputDir>
-                                            <parameters>
-                                                <parameter>
-                                                    <name>auth-server-host</name>
-                                                    <value>https://localhost:${auth.server.https.port}</value>
-                                                </parameter>
-                                            </parameters>
-                                        </transformationSet>
-                                    </transformationSets>
-                                </configuration>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
+            <properties>
+                <!-- one realm definition for each secure-deployment -->
+                <auth.server.actual.http.port>${auth.server.https.port}</auth.server.actual.http.port>
+            </properties>
+        </profile>
+        <profile>
+            <id>keycloak-subsystem-separate-realm</id>
+            <properties>
+                <!-- single realm definition, multiple secure-deployments -->
+                <keycloak.subsystem.xsl>keycloak-subsystem_separate-realm-def.xsl</keycloak.subsystem.xsl>
+            </properties>
         </profile>
         <profile>
             <id>adapter-test-jboss-submodules</id>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java
index 8d0cedc..fe87f9f 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java
@@ -1,11 +1,11 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 
 /**
  *
  * @author tkyjovsk
  */
-public class RelativeEAPOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class RelativeEAPOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java
index 09ed2bc..0e14d93 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java
@@ -1,11 +1,11 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 
 /**
  *
  * @author tkyjovsk
  */
-public class RelativeWildflyOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class RelativeWildflyOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
index b07ae38..989dd2b 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.keycloak.testsuite</groupId>
         <artifactId>integration-arquillian-tests-adapters-jboss</artifactId>
-        <version>2.1.0-SNAPSHOT</version>
+        <version>2.4.1.Final-SNAPSHOT</version>
     </parent>
 
     <artifactId>integration-arquillian-tests-adapters-remote</artifactId>
@@ -70,9 +70,9 @@
                         <configuration>
                             <artifactItems>
                                 <artifactItem>
-                                    <groupId>org.keycloak.quickstart</groupId>
-                                    <artifactId>keycloak-quickstart-app-profile-jee</artifactId>        
-                                    <version>0.5-SNAPSHOT</version>
+                                    <groupId>org.keycloak.testsuite</groupId>
+                                    <artifactId>keycloak-test-app-profile-jee</artifactId>        
+                                    <version>${project.version}</version>
                                     <type>war</type>
                                 </artifactItem>
                             </artifactItems>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java
index a853e2b..c665f79 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java
@@ -53,7 +53,7 @@ public class HttpClientLoginLogoutPerfTest extends HttpClientPerformanceTest {
 
     private static final Logger LOG = Logger.getLogger(HttpClientLoginLogoutPerfTest.class);
 
-    private static final String EXAMPLES = "Examples";
+    private static final String TEST_REALM = "Test";
 
     private String securedUrl;
     private String logoutUrl;
@@ -72,18 +72,18 @@ public class HttpClientLoginLogoutPerfTest extends HttpClientPerformanceTest {
 
     @Deployment(name = AppProfileJEE.DEPLOYMENT_NAME)
     private static WebArchive appProfileJEE() throws IOException {
-        return warDeployment("keycloak-quickstart-app-profile-jee-0.5-SNAPSHOT");
+        return exampleDeployment("keycloak-test-app-profile-jee");
     }
 
     @Override
     public void setDefaultPageUriParameters() {
         super.setDefaultPageUriParameters();
-        testRealmPage.setAuthRealm(EXAMPLES);
+        testRealmPage.setAuthRealm(TEST_REALM);
     }
 
     @Override
     public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
-        RealmRepresentation examplesRealm = loadRealm("/examples-realm.json");
+        RealmRepresentation examplesRealm = loadRealm("/test-realm.json");
         examplesRealm.setPasswordPolicy("hashIterations(" + PASSWORD_HASH_ITERATIONS + ")");
         testRealms.add(examplesRealm);
     }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json
new file mode 100644
index 0000000..2ae0417
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json
@@ -0,0 +1,52 @@
+{
+    "id": "Test",
+    "realm": "Test",
+    "enabled": true,
+    "accessTokenLifespan": 600,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": ["password"],
+    "users": [{
+            "username": "admin-user",
+            "enabled": true,
+            "firstName": "Admin",
+            "lastName": "User",
+            "email": "admin@user.com",
+            "credentials": [{
+                    "type": "password",
+                    "value": "password"
+                }],
+            "realmRoles": ["offline_access", "user", "admin"],
+            "clientRoles": {
+                "account": ["view-profile", "manage-account"]
+            }
+        }, {
+            "username": "secure-user",
+            "enabled": true,
+            "firstName": "Secure",
+            "lastName": "User",
+            "email": "secure@user.com",
+            "credentials": [{
+                    "type": "password",
+                    "value": "password"
+                }],
+            "realmRoles": ["offline_access", "user"],
+            "clientRoles": {
+                "account": ["view-profile", "manage-account"]
+            }
+        }],
+    "clients": [
+        {
+            "clientId": "app-profile-jee",
+            "enabled": true,
+            "adminUrl": "/app-profile-jee/",
+            "baseUrl": "/app-profile-jee/",
+            "redirectUris": ["/app-profile-jee/*"],
+            "secret": "4f36f31a-be9d-4f92-b982-425301bac5df"
+        }
+    ]
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java
index 51ba75e..abea3db 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java
@@ -1,6 +1,6 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-wildfly")
-public class WildflyOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class WildflyOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java
index f853f0d..2ffdfb2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java
@@ -1,6 +1,6 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-wildfly8")
-public class Wildfly8OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class Wildfly8OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java
index 8c92df2..0ffc804 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java
@@ -1,6 +1,6 @@
 package org.keycloak.testsuite.adapter;
 
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
 import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
 
 /**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
  * @author tkyjovsk
  */
 @AppServerContainer("app-server-wildfly9")
-public class Wildfly9OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class Wildfly9OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
 
 }
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java
index 501583f..fc26648 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java
@@ -6,6 +6,7 @@ import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 import org.openqa.selenium.support.ui.Select;
 
+import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
 import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
 
 /**
@@ -33,8 +34,9 @@ public class PasswordPolicy extends Authentication {
     public void addPolicy(Type policy, String value) {
         waitUntilElement(addPolicySelectElement).is().present();
         addPolicySelect.selectByVisibleText(policy.getName());
-        setPolicyValue(policy, value);
+        if (value != null) {setPolicyValue(policy, value);}
         primaryButton.click();
+        waitForPageToLoad(driver);
     }
 
 
@@ -43,15 +45,13 @@ public class PasswordPolicy extends Authentication {
     }
 
     public void addPolicy(Type policy) {
-        addPolicySelect.selectByVisibleText(policy.getName());
-        primaryButton.click();
+        addPolicy(policy, null);
     }
 
     public void removePolicy(Type policy) {
         getPolicyRow(policy).findElement(By.cssSelector("td.kc-action-cell")).click();
-        if (!primaryButton.isDisplayed()) {
-            primaryButton.click();
-        }
+        primaryButton.click();
+        waitForPageToLoad(driver);
     }
 
     public void editPolicy(Type policy, int value) {
@@ -61,6 +61,7 @@ public class PasswordPolicy extends Authentication {
     public void editPolicy(Type policy, String value) {
         setPolicyValue(policy, value);
         primaryButton.click();
+        waitForPageToLoad(driver);
     }
 
     private void setPolicyValue(Type policy, String value) {
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java
index f20348a..97a4b21 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java
@@ -49,6 +49,7 @@ public class PasswordPolicyTest extends AbstractConsoleTest {
     public void testAddAndRemovePolicy() {
         passwordPolicyPage.navigateTo();
         passwordPolicyPage.addPolicy(DIGITS, 5);
+        assertAlertSuccess();
         passwordPolicyPage.removePolicy(DIGITS);
         assertAlertSuccess();
     }
diff --git a/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
index 48ba4b4..56fe43a 100644
--- a/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
+++ b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
@@ -106,7 +106,6 @@ public class SSSDTest extends AbstractKeycloakTest {
         driver.navigate().to(getAccountUrl());
         Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
         accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD);
-
         Assert.assertEquals("Unexpected error when handling authentication request to identity provider.", accountLoginPage.getInstruction());
     }
 
@@ -117,8 +116,7 @@ public class SSSDTest extends AbstractKeycloakTest {
         driver.navigate().to(getAccountUrl());
         Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
         accountLoginPage.login(USERNAME, PASSWORD);
-        Assert.assertEquals("Browser should be on account page now, logged in", "Keycloak Account Management", driver.getTitle());
-
+        Assert.assertTrue(profilePage.isCurrent());
         testUserGroups();
     }
 
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 9668db4..bb9e838 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -209,7 +209,7 @@
             </properties>
             <dependencies>
                 <dependency>
-                    <groupId>org.wildfly</groupId>
+                    <groupId>org.wildfly.arquillian</groupId>
                     <artifactId>wildfly-arquillian-container-remote</artifactId>
                 </dependency>
             </dependencies>
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index ed747ae..81af97b 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -637,6 +637,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
     console.log('UserFederationCtrl ++++****');
     $scope.realm = realm;
     $scope.providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
+    $scope.instancesLoaded = false;
 
     if (!$scope.providers) $scope.providers = [];
     
@@ -716,7 +717,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
                 data[i].isUserFederationProvider = true;
                 $scope.instances.push(data[i]);
             }
-            
+            $scope.instancesLoaded = true;
         });
     });
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
index e4d2e4f..8298a99 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
@@ -3,7 +3,7 @@
         <span>{{:: 'user-federation' | translate}}</span>
     </h1>
 
-    <div class="blank-slate-pf" data-ng-hide="instances && instances.length > 0">
+    <div class="blank-slate-pf" data-ng-hide="!instancesLoaded || (instances && instances.length > 0)">
         <div class="blank-slate-pf-icon">
             <span class="fa fa-database"></span>
         </div>
diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java
index 3240625..538ed81 100755
--- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java
+++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java
@@ -85,7 +85,7 @@ public final class KeycloakAdapterConfigService {
     // be where the Keycloak server's Config interface expects it to be.
     
     private void massageScheduledTaskInterval(ModelNode copy) {
-        if (!copy.hasDefined("scheduled-task-intervale")) return;
+        if (!copy.hasDefined("scheduled-task-interval")) return;
         ModelNode taskInterval = copy.remove("scheduled-task-interval");
         copy.get("scheduled", "interval").set(taskInterval);
     }