keycloak-aplcache

merge

11/14/2016 6:09:41 PM

Changes

.travis.yml 8(+7 -1)

adapters/saml/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java 133(+0 -133)

pom.xml 10(+8 -2)

saml-core/pom.xml 12(+12 -0)

server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java 48(+0 -48)

server-spi/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java 27(+0 -27)

server-spi/src/main/java/org/keycloak/models/dblock/DBLockManager.java 68(+0 -68)

server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java 60(+0 -60)

server-spi/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java 31(+0 -31)

server-spi/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java 126(+0 -126)

server-spi/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java 79(+0 -79)

server-spi/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java 54(+0 -54)

server-spi/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java 26(+0 -26)

server-spi/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java 48(+0 -48)

server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java 93(+0 -93)

server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java 44(+0 -44)

server-spi/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java 62(+0 -62)

server-spi/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java 83(+0 -83)

server-spi/src/main/java/org/keycloak/models/utils/FormMessage.java 87(+0 -87)

server-spi/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java 28(+0 -28)

server-spi/src/main/java/org/keycloak/models/utils/RealmImporter.java 31(+0 -31)

server-spi/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java 38(+0 -38)

server-spi/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java 40(+0 -40)

server-spi/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java 25(+0 -25)

server-spi/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java 57(+0 -57)

server-spi/src/main/java/org/keycloak/models/utils/reflection/Properties.java 59(+0 -59)

server-spi/src/main/java/org/keycloak/models/utils/reflection/Property.java 122(+0 -122)

server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java 43(+0 -43)

server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java 42(+0 -42)

server-spi/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java 179(+0 -179)

server-spi/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java 88(+0 -88)

server-spi/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java 77(+0 -77)

server-spi/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java 123(+0 -123)

server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java 245(+0 -245)

server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java 80(+0 -80)

server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java 51(+0 -51)

server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java 63(+0 -63)

server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java 91(+0 -91)

server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java 92(+0 -92)

server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java 92(+0 -92)

server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java 83(+0 -83)

server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java 74(+0 -74)

server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java 57(+0 -57)

server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java 63(+0 -63)

server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java 59(+0 -59)

server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java 32(+0 -32)

server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java 27(+0 -27)

server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java 32(+0 -32)

server-spi/src/main/java/org/keycloak/policy/PasswordPolicySpi.java 48(+0 -48)

server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java 67(+0 -67)

server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java 63(+0 -63)

server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java 63(+0 -63)

server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java 73(+0 -73)

server-spi/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java 56(+0 -56)

server-spi/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java 44(+0 -44)

server-spi/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/protocol/LoginProtocol.java 84(+0 -84)

server-spi/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java 66(+0 -66)

server-spi/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java 38(+0 -38)

server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java 28(+0 -28)

server-spi/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java 69(+0 -69)

server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java 121(+0 -121)

server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java 33(+0 -33)

server-spi/src/main/java/org/keycloak/provider/ProviderLoader.java 42(+0 -42)

server-spi/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java 29(+0 -29)

server-spi/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java 37(+0 -37)

server-spi/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java 118(+0 -118)

server-spi/src/main/java/org/keycloak/scripting/Script.java 115(+0 -115)

server-spi/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java 41(+0 -41)

server-spi/src/main/java/org/keycloak/scripting/ScriptExecutionException.java 33(+0 -33)

server-spi/src/main/java/org/keycloak/scripting/ScriptingProvider.java 51(+0 -51)

server-spi/src/main/java/org/keycloak/scripting/ScriptingSpi.java 31(+0 -31)

server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtector.java 34(+0 -34)

server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java 27(+0 -27)

server-spi/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/services/managers/ClientSessionCode.java 279(+0 -279)

server-spi/src/main/java/org/keycloak/services/managers/UserManager.java 59(+0 -59)

server-spi/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java 38(+0 -38)

server-spi/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java 30(+0 -30)

server-spi/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java 54(+0 -54)

server-spi/src/main/java/org/keycloak/theme/Theme.java 72(+0 -72)

server-spi/src/main/java/org/keycloak/theme/ThemeProvider.java 38(+0 -38)

server-spi/src/main/java/org/keycloak/theme/ThemeProviderFactory.java 26(+0 -26)

server-spi/src/main/java/org/keycloak/theme/ThemeSpi.java 48(+0 -48)

server-spi/src/main/java/org/keycloak/timer/ScheduledTask.java 29(+0 -29)

server-spi/src/main/java/org/keycloak/timer/TimerProvider.java 33(+0 -33)

server-spi/src/main/java/org/keycloak/timer/TimerProviderFactory.java 26(+0 -26)

server-spi/src/main/java/org/keycloak/timer/TimerSpi.java 48(+0 -48)

server-spi/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java 43(+0 -43)

server-spi/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java 49(+0 -49)

server-spi/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java 36(+0 -36)

server-spi/src/main/java/org/keycloak/truststore/TruststoreProvider.java 32(+0 -32)

server-spi/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java 26(+0 -26)

server-spi/src/main/java/org/keycloak/truststore/TruststoreSpi.java 48(+0 -48)

server-spi/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory 18(+0 -18)

server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory 18(+0 -18)

server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory 28(+0 -28)

server-spi/src/test/java/org/keycloak/models/HmacTest.java 40(+0 -40)

services/pom.xml 5(+5 -0)

travis-run-tests.sh 30(+30 -0)

Details

.travis.yml 8(+7 -1)

diff --git a/.travis.yml b/.travis.yml
index 35c4061..ef531a2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,12 @@ env:
   global:
     - MAVEN_SKIP_RC=true
     - MAVEN_OPTS="-Xms512m -Xmx2048m"
+  matrix:
+    - TESTS=old
+    - TESTS=group1
+    - TESTS=group2
+    - TESTS=group3
+    - TESTS=adapter
 
 jdk:
   - oraclejdk8
@@ -22,6 +28,6 @@ install:
   - travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
 
 script:
-  - mvn test -B
+  - ./travis-run-tests.sh $TESTS
 
 sudo: false
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
index 63cdab8..e6d6588 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
 import org.apache.http.params.HttpConnectionParams;
 import org.keycloak.common.util.EnvUtil;
 import org.keycloak.common.util.KeystoreUtil;
-import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
@@ -333,7 +333,7 @@ public class HttpClientBuilder {
         }
     }
 
-    public HttpClient build(AdapterConfig adapterConfig) {
+    public HttpClient build(AdapterHttpClientConfig adapterConfig) {
         disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
 
         String truststorePath = adapterConfig.getTruststore();
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
     /**
      * Configures a the proxy to use for auth-server requests if provided.
      * <p>
-     * If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
+     * If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
      * given URL as a proxy server, otherwise the proxy configuration is ignored.
      * </p>
      *
      * @param adapterConfig
      */
-    private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
+    private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
 
         if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
             return;
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
index 32bcf39..e73f88c 100755
--- a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java
@@ -127,7 +127,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
             InputStream configInputStream = getConfigInputStream(context);
             KeycloakDeployment kd;
             if (configInputStream == null) {
-                log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+                log.warning("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
                 kd = new KeycloakDeployment();
             } else {
                 kd = KeycloakDeploymentBuilder.build(configInputStream);
@@ -196,6 +196,8 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
         CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
         KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
         if (deployment == null || !deployment.isConfigured()) {
+            //needed for the EAP6/AS7 adapter relying on the tomcat core adapter
+            facade.getResponse().sendError(401);
             return false;
         }
         AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
index e65d922..2398c95 100755
--- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
@@ -92,7 +92,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
                 UndertowHttpFacade facade = createFacade(exchange);
                 KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
                 KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
-                if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
+                if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
                     ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
                 }
                 AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
diff --git a/adapters/saml/core/nbproject/project.properties b/adapters/saml/core/nbproject/project.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/adapters/saml/core/nbproject/project.properties
diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml
index 16dce33..b01061b 100755
--- a/adapters/saml/core/pom.xml
+++ b/adapters/saml/core/pom.xml
@@ -34,6 +34,7 @@
         <timestamp>${maven.build.timestamp}</timestamp>
         <maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
     </properties>
+
     <dependencies>
         <dependency>
             <groupId>org.keycloak</groupId>
@@ -70,6 +71,11 @@
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
new file mode 100644
index 0000000..5c94fdb
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
@@ -0,0 +1,75 @@
+/*
+ * 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.adapters.cloned;
+
+/**
+ * Configuration options relevant for configuring http client that can be used by adapter.
+ *
+ * NOTE: keep in sync with core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java until unified.
+ *
+ * @author hmlnarik
+ */
+public interface AdapterHttpClientConfig {
+
+    /**
+     * Returns truststore filename.
+     */
+    public String getTruststore();
+
+    /**
+     * Returns truststore password.
+     */
+    public String getTruststorePassword();
+
+    /**
+     * Returns keystore with client keys.
+     */
+    public String getClientKeystore();
+
+    /**
+     * Returns keystore password.
+     */
+    public String getClientKeystorePassword();
+
+    /**
+     * Returns boolean flag whether any hostname verification is done on the server's
+     * certificate, {@code true} means that verification is not done.
+     * @return
+     */
+    public boolean isAllowAnyHostname();
+
+    /**
+     * Returns boolean flag whether any trust management and hostname verification is done.
+     * <p>
+     * <i>NOTE</i> Disabling trust manager is a security hole, so only set this option
+     * if you cannot or do not want to verify the identity of the
+     * host you are communicating with.
+     */
+    public boolean isDisableTrustManager();
+
+    /**
+     * Returns size of connection pool.
+     */
+    public int getConnectionPoolSize();
+
+    /**
+     * Returns URL of HTTP proxy.
+     */
+    public String getProxyUrl();
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
new file mode 100644
index 0000000..0fa330e
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
@@ -0,0 +1,78 @@
+/*
+ * 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.adapters.cloned;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.util.EntityUtils;
+import org.keycloak.adapters.saml.descriptor.parsers.SamlDescriptorIDPKeysExtractor;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ * @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
+ */
+public class HttpAdapterUtils {
+
+    public static MultivaluedHashMap<String, KeyInfo> downloadKeysFromSamlDescriptor(HttpClient client, String descriptorUrl) throws HttpClientAdapterException {
+        try {
+            HttpGet httpRequest = new HttpGet(descriptorUrl);
+            HttpResponse response = client.execute(httpRequest);
+            int status = response.getStatusLine().getStatusCode();
+            if (status != HttpStatus.SC_OK) {
+                EntityUtils.consumeQuietly(response.getEntity());
+                throw new HttpClientAdapterException("Unexpected status = " + status);
+            }
+
+            HttpEntity entity = response.getEntity();
+            if (entity == null) {
+                throw new HttpClientAdapterException("There was no entity.");
+            }
+
+            MultivaluedHashMap<String, KeyInfo> res;
+            try (InputStream is = entity.getContent()) {
+                res = extractKeysFromSamlDescriptor(is);
+            }
+
+            EntityUtils.consumeQuietly(entity);
+
+            return res;
+        } catch (IOException | ParsingException e) {
+            throw new HttpClientAdapterException("IO error", e);
+        }
+    }
+
+    /**
+     * Parses SAML descriptor and extracts keys from it.
+     * @param xmlStream
+     * @return List of KeyInfo objects containing keys from the descriptor.
+     * @throws IOException
+     */
+    public static MultivaluedHashMap<String, KeyInfo> extractKeysFromSamlDescriptor(InputStream xmlStream) throws ParsingException {
+        Object res = new SamlDescriptorIDPKeysExtractor().parse(xmlStream);
+        return (MultivaluedHashMap<String, KeyInfo>) res;
+    }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
new file mode 100644
index 0000000..e0371ad
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.adapters.cloned;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HttpClientAdapterException extends Exception {
+
+    public HttpClientAdapterException(String message) {
+        super(message);
+    }
+
+    public HttpClientAdapterException(String message, Throwable t) {
+        super(message, t);
+    }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
new file mode 100644
index 0000000..7e26c01
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
@@ -0,0 +1,396 @@
+/*
+ * 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.adapters.cloned;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.keycloak.common.util.EnvUtil;
+import org.keycloak.common.util.KeystoreUtil;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Abstraction for creating HttpClients. Allows SSL configuration.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HttpClientBuilder {
+    public static enum HostnameVerificationPolicy {
+        /**
+         * Hostname verification is not done on the server's certificate
+         */
+        ANY,
+        /**
+         * Allows wildcards in subdomain names i.e. *.foo.com
+         */
+        WILDCARD,
+        /**
+         * CN must match hostname connecting to
+         */
+        STRICT
+    }
+
+
+    /**
+     * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+     * @version $Revision: 1 $
+     */
+    private static class PassthroughTrustManager implements X509TrustManager {
+        public void checkClientTrusted(X509Certificate[] chain,
+                                       String authType) throws CertificateException {
+        }
+
+        public void checkServerTrusted(X509Certificate[] chain,
+                                       String authType) throws CertificateException {
+        }
+
+        public X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+    }
+
+    protected KeyStore truststore;
+    protected KeyStore clientKeyStore;
+    protected String clientPrivateKeyPassword;
+    protected boolean disableTrustManager;
+    protected boolean disableCookieCache = true;
+    protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
+    protected SSLContext sslContext;
+    protected int connectionPoolSize = 100;
+    protected int maxPooledPerRoute = 0;
+    protected long connectionTTL = -1;
+    protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
+    protected HostnameVerifier verifier = null;
+    protected long socketTimeout = -1;
+    protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
+    protected long establishConnectionTimeout = -1;
+    protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
+    protected HttpHost proxyHost;
+
+
+    /**
+     * Socket inactivity timeout
+     *
+     * @param timeout
+     * @param unit
+     * @return
+     */
+    public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
+        this.socketTimeout = timeout;
+        this.socketTimeoutUnits = unit;
+        return this;
+    }
+
+    /**
+     * When trying to make an initial socket connection, what is the timeout?
+     *
+     * @param timeout
+     * @param unit
+     * @return
+     */
+    public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
+        this.establishConnectionTimeout = timeout;
+        this.establishConnectionTimeoutUnits = unit;
+        return this;
+    }
+
+    public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
+        this.connectionTTL = ttl;
+        this.connectionTTLUnit = unit;
+        return this;
+    }
+
+    public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
+        this.maxPooledPerRoute = maxPooledPerRoute;
+        return this;
+    }
+
+    public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
+        this.connectionPoolSize = connectionPoolSize;
+        return this;
+    }
+
+    /**
+     * Disable trust management and hostname verification. <i>NOTE</i> this is a security
+     * hole, so only set this option if you cannot or do not want to verify the identity of the
+     * host you are communicating with.
+     */
+    public HttpClientBuilder disableTrustManager() {
+        this.disableTrustManager = true;
+        return this;
+    }
+
+    public HttpClientBuilder disableCookieCache() {
+        this.disableCookieCache = true;
+        return this;
+    }
+
+    /**
+     * SSL policy used to verify hostnames
+     *
+     * @param policy
+     * @return
+     */
+    public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
+        this.policy = policy;
+        return this;
+    }
+
+
+    public HttpClientBuilder sslContext(SSLContext sslContext) {
+        this.sslContext = sslContext;
+        return this;
+    }
+
+    public HttpClientBuilder trustStore(KeyStore truststore) {
+        this.truststore = truststore;
+        return this;
+    }
+
+    public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
+        this.clientKeyStore = keyStore;
+        this.clientPrivateKeyPassword = password;
+        return this;
+    }
+
+    public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
+        this.clientKeyStore = keyStore;
+        this.clientPrivateKeyPassword = new String(password);
+        return this;
+    }
+
+
+    static class VerifierWrapper implements X509HostnameVerifier {
+        protected HostnameVerifier verifier;
+
+        VerifierWrapper(HostnameVerifier verifier) {
+            this.verifier = verifier;
+        }
+
+        @Override
+        public void verify(String host, SSLSocket ssl) throws IOException {
+            if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
+        }
+
+        @Override
+        public void verify(String host, X509Certificate cert) throws SSLException {
+            throw new SSLException("This verification path not implemented");
+        }
+
+        @Override
+        public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
+            throw new SSLException("This verification path not implemented");
+        }
+
+        @Override
+        public boolean verify(String s, SSLSession sslSession) {
+            return verifier.verify(s, sslSession);
+        }
+    }
+
+    public HttpClient build() {
+        X509HostnameVerifier verifier = null;
+        if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
+        else {
+            switch (policy) {
+                case ANY:
+                    verifier = new AllowAllHostnameVerifier();
+                    break;
+                case WILDCARD:
+                    verifier = new BrowserCompatHostnameVerifier();
+                    break;
+                case STRICT:
+                    verifier = new StrictHostnameVerifier();
+                    break;
+            }
+        }
+        try {
+            SSLSocketFactory sslsf = null;
+            SSLContext theContext = sslContext;
+            if (disableTrustManager) {
+                theContext = SSLContext.getInstance("SSL");
+                theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
+                        new SecureRandom());
+                verifier = new AllowAllHostnameVerifier();
+                sslsf = new SniSSLSocketFactory(theContext, verifier);
+            } else if (theContext != null) {
+                sslsf = new SniSSLSocketFactory(theContext, verifier);
+            } else if (clientKeyStore != null || truststore != null) {
+                sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
+            } else {
+                final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
+                tlsContext.init(null, null, null);
+                sslsf = new SniSSLSocketFactory(tlsContext, verifier);
+            }
+            SchemeRegistry registry = new SchemeRegistry();
+            registry.register(
+                    new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+            Scheme httpsScheme = new Scheme("https", 443, sslsf);
+            registry.register(httpsScheme);
+            ClientConnectionManager cm = null;
+            if (connectionPoolSize > 0) {
+                ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
+                tcm.setMaxTotal(connectionPoolSize);
+                if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
+                tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
+                cm = tcm;
+
+            } else {
+                cm = new SingleClientConnManager(registry);
+            }
+            BasicHttpParams params = new BasicHttpParams();
+
+            if (proxyHost != null) {
+                params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
+            }
+
+            if (socketTimeout > -1) {
+                HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
+
+            }
+            if (establishConnectionTimeout > -1) {
+                HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
+            }
+            DefaultHttpClient client = new DefaultHttpClient(cm, params);
+
+            if (disableCookieCache) {
+                client.setCookieStore(new CookieStore() {
+                    @Override
+                    public void addCookie(Cookie cookie) {
+                        //To change body of implemented methods use File | Settings | File Templates.
+                    }
+
+                    @Override
+                    public List<Cookie> getCookies() {
+                        return Collections.emptyList();
+                    }
+
+                    @Override
+                    public boolean clearExpired(Date date) {
+                        return false;  //To change body of implemented methods use File | Settings | File Templates.
+                    }
+
+                    @Override
+                    public void clear() {
+                        //To change body of implemented methods use File | Settings | File Templates.
+                    }
+                });
+
+            }
+            return client;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public HttpClient build(AdapterHttpClientConfig adapterConfig) {
+        disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
+
+        String truststorePath = adapterConfig.getTruststore();
+        if (truststorePath != null) {
+            truststorePath = EnvUtil.replace(truststorePath);
+            String truststorePassword = adapterConfig.getTruststorePassword();
+            try {
+                this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to load truststore", e);
+            }
+        }
+        String clientKeystore = adapterConfig.getClientKeystore();
+        if (clientKeystore != null) {
+            clientKeystore = EnvUtil.replace(clientKeystore);
+            String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
+            try {
+                KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
+                keyStore(clientCertKeystore, clientKeystorePassword);
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to load keystore", e);
+            }
+        }
+        int size = 10;
+        if (adapterConfig.getConnectionPoolSize() > 0)
+            size = adapterConfig.getConnectionPoolSize();
+        HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
+        if (adapterConfig.isAllowAnyHostname())
+            policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
+        connectionPoolSize(size);
+        hostnameVerification(policy);
+        if (adapterConfig.isDisableTrustManager()) {
+            disableTrustManager();
+        } else {
+            trustStore(truststore);
+        }
+
+        configureProxyForAuthServerIfProvided(adapterConfig);
+
+        return build();
+    }
+
+    /**
+     * Configures a the proxy to use for auth-server requests if provided.
+     * <p>
+     * If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
+     * given URL as a proxy server, otherwise the proxy configuration is ignored.
+     * </p>
+     *
+     * @param adapterConfig
+     */
+    private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
+
+        if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
+            return;
+        }
+
+        URI uri = URI.create(adapterConfig.getProxyUrl());
+        this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+    }
+}
\ No newline at end of file
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
new file mode 100644
index 0000000..8c064b0
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
@@ -0,0 +1,143 @@
+/*
+ * 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.adapters.cloned;
+
+import org.apache.http.HttpHost;
+import org.apache.http.conn.scheme.HostNameResolver;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.protocol.HttpContext;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.security.AccessController;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * SSLSocketFactory that uses Server Name Indication (SNI) TLS extension.
+ *
+ * <p>
+ * Originally copied from <b>keycloak-adapter-core</b> project.
+ *
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ * @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
+ */
+public class SniSSLSocketFactory extends SSLSocketFactory {
+
+    private static final Logger LOG = Logger.getLogger(SniSSLSocketFactory.class.getName());
+
+    public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, HostNameResolver nameResolver) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(algorithm, keystore, keyPassword, truststore, random, nameResolver);
+    }
+
+    public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(algorithm, keystore, keyPassword, truststore, random, trustStrategy, hostnameVerifier);
+    }
+
+    public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(algorithm, keystore, keyPassword, truststore, random, hostnameVerifier);
+    }
+
+    public SniSSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(keystore, keystorePassword, truststore);
+    }
+
+    public SniSSLSocketFactory(KeyStore keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(keystore, keystorePassword);
+    }
+
+    public SniSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(truststore);
+    }
+
+    public SniSSLSocketFactory(TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(trustStrategy, hostnameVerifier);
+    }
+
+    public SniSSLSocketFactory(TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(trustStrategy);
+    }
+
+    public SniSSLSocketFactory(SSLContext sslContext) {
+        super(sslContext);
+    }
+
+    public SniSSLSocketFactory(SSLContext sslContext, HostNameResolver nameResolver) {
+        super(sslContext, nameResolver);
+    }
+
+    public SniSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
+        super(sslContext, hostnameVerifier);
+    }
+
+    public SniSSLSocketFactory(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
+        super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
+    }
+
+    public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) {
+        super(socketfactory, hostnameVerifier);
+    }
+
+    public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
+        super(socketfactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
+    }
+
+    @Override
+    public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
+        return super.connectSocket(connectTimeout, applySNI(socket, host.getHostName()), host, remoteAddress, localAddress, context);
+    }
+
+    @Override
+    public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
+        return super.createLayeredSocket(applySNI(socket, target), target, port, context);
+    }
+
+    private Socket applySNI(final Socket socket, String hostname) {
+        if (socket instanceof SSLSocket) {
+            try {
+                Method setHostMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+                    @Override
+                    public Method run() throws NoSuchMethodException {
+                        return socket.getClass().getMethod("setHost", String.class);
+                    }
+                });
+
+                setHostMethod.invoke(socket, hostname);
+                LOG.log(Level.FINEST, "Applied SNI to socket for host {0}", hostname);
+            } catch (PrivilegedActionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+                LOG.log(Level.WARNING, "Failed to apply SNI to SSLSocket", e);
+            }
+        }
+        return socket;
+    }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
index 305ffeb..6ddf52c 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
@@ -79,7 +79,9 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
                 binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
             }
 
-            binding.signWith(keypair);
+            binding.signWith(null, keypair);
+            // TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
+            //   <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
             binding.signDocument();
         }
         return binding;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
index 3960b46..de95d87 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
@@ -19,6 +19,7 @@ package org.keycloak.adapters.saml.config;
 
 import java.io.Serializable;
 import java.util.List;
+import org.keycloak.adapters.cloned.AdapterHttpClientConfig;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -157,12 +158,97 @@ public class IDP implements Serializable {
         }
     }
 
+    public static class HttpClientConfig implements AdapterHttpClientConfig {
+
+        private String truststore;
+        private String truststorePassword;
+        private String clientKeystore;
+        private String clientKeystorePassword;
+        private boolean allowAnyHostname;
+        private boolean disableTrustManager;
+        private int connectionPoolSize;
+        private String proxyUrl;
+
+        @Override
+        public String getTruststore() {
+            return truststore;
+        }
+
+        public void setTruststore(String truststore) {
+            this.truststore = truststore;
+        }
+
+        @Override
+        public String getTruststorePassword() {
+            return truststorePassword;
+        }
+
+        public void setTruststorePassword(String truststorePassword) {
+            this.truststorePassword = truststorePassword;
+        }
+
+        @Override
+        public String getClientKeystore() {
+            return clientKeystore;
+        }
+
+        public void setClientKeystore(String clientKeystore) {
+            this.clientKeystore = clientKeystore;
+        }
+
+        @Override
+        public String getClientKeystorePassword() {
+            return clientKeystorePassword;
+        }
+
+        public void setClientKeystorePassword(String clientKeystorePassword) {
+            this.clientKeystorePassword = clientKeystorePassword;
+        }
+
+        @Override
+        public boolean isAllowAnyHostname() {
+            return allowAnyHostname;
+        }
+
+        public void setAllowAnyHostname(boolean allowAnyHostname) {
+            this.allowAnyHostname = allowAnyHostname;
+        }
+
+        @Override
+        public boolean isDisableTrustManager() {
+            return disableTrustManager;
+        }
+
+        public void setDisableTrustManager(boolean disableTrustManager) {
+            this.disableTrustManager = disableTrustManager;
+        }
+
+        @Override
+        public int getConnectionPoolSize() {
+            return connectionPoolSize;
+        }
+
+        public void setConnectionPoolSize(int connectionPoolSize) {
+            this.connectionPoolSize = connectionPoolSize;
+        }
+
+        @Override
+        public String getProxyUrl() {
+            return proxyUrl;
+        }
+
+        public void setProxyUrl(String proxyUrl) {
+            this.proxyUrl = proxyUrl;
+        }
+    }
+
     private String entityID;
     private String signatureAlgorithm;
     private String signatureCanonicalizationMethod;
     private SingleSignOnService singleSignOnService;
     private SingleLogoutService singleLogoutService;
     private List<Key> keys;
+    private AdapterHttpClientConfig httpClientConfig = new HttpClientConfig();
 
     public String getEntityID() {
         return entityID;
@@ -212,4 +298,12 @@ public class IDP implements Serializable {
         this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
     }
 
+    public AdapterHttpClientConfig getHttpClientConfig() {
+        return httpClientConfig;
+    }
+
+    public void setHttpClientConfig(AdapterHttpClientConfig httpClientConfig) {
+        this.httpClientConfig = httpClientConfig;
+    }
+
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 0085a6a..1a3dd04 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -72,4 +72,15 @@ public class ConfigXmlConstants {
     public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
     public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
     public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
+
+    public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
+    public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";
+    public static final String CLIENT_KEYSTORE_ATTR = "clientKeystore";
+    public static final String CLIENT_KEYSTORE_PASSWORD_ATTR = "clientKeystorePassword";
+    public static final String CONNECTION_POOL_SIZE_ATTR = "connectionPoolSize";
+    public static final String DISABLE_TRUST_MANAGER_ATTR = "disableTrustManager";
+    public static final String PROXY_URL_ATTR = "proxyUrl";
+    public static final String TRUSTSTORE_ATTR = "truststore";
+    public static final String TRUSTSTORE_PASSWORD_ATTR = "truststorePassword";
+
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index d6e4bce..7af71ba 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -40,6 +40,7 @@ import java.security.PublicKey;
 import java.security.cert.Certificate;
 import java.util.HashSet;
 import java.util.Set;
+import org.keycloak.adapters.cloned.HttpClientBuilder;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -178,35 +179,39 @@ public class DeploymentBuilder {
         if (sp.getIdp().getKeys() != null) {
             for (Key key : sp.getIdp().getKeys()) {
                 if (key.isSigning()) {
-                    if (key.getKeystore() != null) {
-                        KeyStore keyStore = loadKeystore(resourceLoader, key);
-                        Certificate cert = null;
-                        try {
-                            cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
-                        } catch (KeyStoreException e) {
-                            throw new RuntimeException(e);
-                        }
-                        idp.setSignatureValidationKey(cert.getPublicKey());
-                    } else {
-                        if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
-                            throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
-                        }
-                        try {
-                            PublicKey publicKey = getPublicKeyFromPem(key);
-                            idp.setSignatureValidationKey(publicKey);
-                        } catch (Exception e) {
-                            throw new RuntimeException(e);
-                        }
-                    }
+                    processSigningKey(idp, key, resourceLoader);
                 }
             }
         }
 
+        idp.setClient(new HttpClientBuilder().build(sp.getIdp().getHttpClientConfig()));
+        idp.refreshKeyLocatorConfiguration();
 
         return deployment;
     }
 
-    protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
+    private void processSigningKey(DefaultSamlDeployment.DefaultIDP idp, Key key, ResourceLoader resourceLoader) throws RuntimeException {
+        PublicKey publicKey;
+        if (key.getKeystore() != null) {
+            KeyStore keyStore = loadKeystore(resourceLoader, key);
+            Certificate cert = null;
+            try {
+                cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
+            } catch (KeyStoreException e) {
+                throw new RuntimeException(e);
+            }
+            publicKey = cert.getPublicKey();
+        } else {
+            if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
+                throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
+            }
+            publicKey = getPublicKeyFromPem(key);
+        }
+
+        idp.addSignatureValidationKey(publicKey);
+    }
+
+    protected static PublicKey getPublicKeyFromPem(Key key) {
         PublicKey publicKey;
         if (key.getPublicKeyPem() != null) {
             publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
index e649d1c..be54223 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
@@ -29,6 +29,10 @@ import javax.xml.stream.events.EndElement;
 import javax.xml.stream.events.StartElement;
 import javax.xml.stream.events.XMLEvent;
 import java.util.List;
+import org.keycloak.adapters.saml.config.IDP.HttpClientConfig;
+import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getAttributeValue;
+import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getBooleanAttributeValue;
+import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getIntegerAttributeValue;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,16 +45,16 @@ public class IDPXmlParser extends AbstractParser {
         StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
         StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
         IDP idp = new IDP();
-        String entityID = SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
+        String entityID = getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
         if (entityID == null) {
             throw new ParsingException("entityID must be set on IDP");
 
         }
         idp.setEntityID(entityID);
 
-        boolean signaturesRequired = SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
-        idp.setSignatureCanonicalizationMethod(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
-        idp.setSignatureAlgorithm(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
+        boolean signaturesRequired = getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
+        idp.setSignatureCanonicalizationMethod(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
+        idp.setSignatureAlgorithm(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
         while (xmlEventReader.hasNext()) {
             XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
             if (xmlEvent == null)
@@ -75,6 +79,10 @@ public class IDPXmlParser extends AbstractParser {
                 IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
                 idp.setSingleLogoutService(slo);
 
+            } else if (tag.equals(ConfigXmlConstants.HTTP_CLIENT_ELEMENT)) {
+                HttpClientConfig config = parseHttpClientElement(xmlEventReader);
+                idp.setHttpClientConfig(config);
+
             } else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
                 KeysXmlParser parser = new KeysXmlParser();
                 List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
@@ -90,29 +98,63 @@ public class IDPXmlParser extends AbstractParser {
     protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
         IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
         StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
-        slo.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
-        slo.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
-        slo.setValidateRequestSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
-        slo.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
-        slo.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
-        slo.setSignResponse(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
-        slo.setPostBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
-        slo.setRedirectBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
+        slo.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
+        slo.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
+        slo.setValidateRequestSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
+        slo.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+        slo.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+        slo.setSignResponse(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
+        slo.setPostBindingUrl(getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
+        slo.setRedirectBindingUrl(getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
         return slo;
     }
 
     protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
         IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
         StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
-        sso.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
-        sso.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
-        sso.setValidateAssertionSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
-        sso.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
-        sso.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
-        sso.setBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
+        sso.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
+        sso.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
+        sso.setValidateAssertionSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
+        sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+        sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+        sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
         return sso;
     }
 
+    private HttpClientConfig parseHttpClientElement(XMLEventReader xmlEventReader) throws ParsingException {
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, ConfigXmlConstants.HTTP_CLIENT_ELEMENT);
+        HttpClientConfig config = new HttpClientConfig();
+
+        config.setAllowAnyHostname(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
+        config.setClientKeystore(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_ATTR));
+        config.setClientKeystorePassword(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_PASSWORD_ATTR));
+        config.setConnectionPoolSize(getIntegerAttributeValue(startElement, ConfigXmlConstants.CONNECTION_POOL_SIZE_ATTR, 0));
+        config.setDisableTrustManager(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
+        config.setProxyUrl(getAttributeValue(startElement, ConfigXmlConstants.PROXY_URL_ATTR));
+        config.setTruststore(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_ATTR));
+        config.setTruststorePassword(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_PASSWORD_ATTR));
+
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent == null)
+                break;
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+                String endElementName = StaxParserUtil.getEndElementName(endElement);
+                if (endElementName.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT))
+                    break;
+                else
+                    continue;
+            }
+
+            String tag = StaxParserUtil.getStartElementName(startElement);
+            StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+        }
+
+        return config;
+    }
+
     @Override
     public boolean supports(QName qname) {
         return false;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index 3eeb1f7..be6d682 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -48,6 +48,13 @@ public class SPXmlParser extends AbstractParser {
             return str;
     }
 
+    public static int getIntegerAttributeValue(StartElement startElement, String tag, int defaultValue) {
+        String result = getAttributeValue(startElement, tag);
+        if (result == null)
+            return defaultValue;
+        return Integer.valueOf(result);
+    }
+
     public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
         String result = getAttributeValue(startElement, tag);
         if (result == null)
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index ee753ad..a52cdc2 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -23,7 +23,14 @@ import org.keycloak.saml.SignatureAlgorithm;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
+import org.apache.http.client.HttpClient;
+import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
+import org.keycloak.rotation.CompositeKeyLocator;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -179,10 +186,15 @@ public class DefaultSamlDeployment implements SamlDeployment {
 
     public static class DefaultIDP implements IDP {
 
+        private static final int DEFAULT_CACHE_TTL = 24 * 60 * 60;
+
         private String entityID;
-        private PublicKey signatureValidationKey;
+        private final CompositeKeyLocator signatureValidationKeyLocator = new CompositeKeyLocator();
         private SingleSignOnService singleSignOnService;
         private SingleLogoutService singleLogoutService;
+        private final List<PublicKey> signatureValidationKeys = new LinkedList<>();
+        private int minTimeBetweenDescriptorRequests;
+        private HttpClient client;
 
         @Override
         public String getEntityID() {
@@ -200,16 +212,25 @@ public class DefaultSamlDeployment implements SamlDeployment {
         }
 
         @Override
-        public PublicKey getSignatureValidationKey() {
-            return signatureValidationKey;
+        public KeyLocator getSignatureValidationKeyLocator() {
+            return this.signatureValidationKeyLocator;
+        }
+
+        @Override
+        public int getMinTimeBetweenDescriptorRequests() {
+            return minTimeBetweenDescriptorRequests;
+        }
+
+        public void setMinTimeBetweenDescriptorRequests(int minTimeBetweenDescriptorRequests) {
+            this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests;
         }
 
         public void setEntityID(String entityID) {
             this.entityID = entityID;
         }
 
-        public void setSignatureValidationKey(PublicKey signatureValidationKey) {
-            this.signatureValidationKey = signatureValidationKey;
+        public void addSignatureValidationKey(PublicKey signatureValidationKey) {
+            this.signatureValidationKeys.add(signatureValidationKey);
         }
 
         public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
@@ -219,6 +240,31 @@ public class DefaultSamlDeployment implements SamlDeployment {
         public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
             this.singleLogoutService = singleLogoutService;
         }
+
+        public void refreshKeyLocatorConfiguration() {
+            this.signatureValidationKeyLocator.clear();
+
+            // When key is set, use that (and only that), otherwise configure dynamic key locator
+            if (! this.signatureValidationKeys.isEmpty()) {
+                this.signatureValidationKeyLocator.add(new HardcodedKeyLocator(this.signatureValidationKeys));
+            } else if (this.singleSignOnService != null) {
+                String samlDescriptorUrl = singleSignOnService.getRequestBindingUrl() + "/descriptor";
+                HttpClient httpClient = getClient();
+                SamlDescriptorPublicKeyLocator samlDescriptorPublicKeyLocator =
+                  new SamlDescriptorPublicKeyLocator(
+                    samlDescriptorUrl, this.minTimeBetweenDescriptorRequests, DEFAULT_CACHE_TTL, httpClient);
+                this.signatureValidationKeyLocator.add(samlDescriptorPublicKeyLocator);
+            }
+        }
+
+        @Override
+        public HttpClient getClient() {
+            return this.client;
+        }
+
+        public void setClient(HttpClient client) {
+            this.client = client;
+        }
     }
 
     private IDP idp;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java
new file mode 100644
index 0000000..0858675
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java
@@ -0,0 +1,101 @@
+/*
+ * 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.adapters.saml.descriptor.parsers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.processing.core.util.NamespaceContext;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Goes through the given XML file and extracts names, certificates and keys from the KeyInfo elements.
+ * @author hmlnarik
+ */
+public class SamlDescriptorIDPKeysExtractor {
+
+    private static final NamespaceContext NS_CONTEXT = new NamespaceContext();
+    static {
+        NS_CONTEXT.addNsUriPair("m", JBossSAMLURIConstants.METADATA_NSURI.get());
+        NS_CONTEXT.addNsUriPair("dsig", JBossSAMLURIConstants.XMLDSIG_NSURI.get());
+    }
+
+    private final KeyInfoFactory kif = KeyInfoFactory.getInstance();
+
+    private final XPathFactory xPathfactory = XPathFactory.newInstance();
+    private final XPath xpath = xPathfactory.newXPath();
+    {
+        xpath.setNamespaceContext(NS_CONTEXT);
+    }
+
+    public MultivaluedHashMap<String, KeyInfo> parse(InputStream stream) throws ParsingException {
+        MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>();
+
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            Document doc = builder.parse(stream);
+
+            XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");
+            NodeList keyDescriptors = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+            for (int i = 0; i < keyDescriptors.getLength(); i ++) {
+                Node keyDescriptor = keyDescriptors.item(i);
+                Element keyDescriptorEl = (Element) keyDescriptor;
+                KeyInfo ki = processKeyDescriptor(keyDescriptorEl);
+                if (ki != null) {
+                    String use = keyDescriptorEl.getAttribute(JBossSAMLConstants.USE.get());
+                    res.add(use, ki);
+                }
+            }
+        } catch (SAXException | IOException | ParserConfigurationException | MarshalException | XPathExpressionException e) {
+            throw new ParsingException("Error parsing SAML descriptor", e);
+        }
+
+        return res;
+    }
+
+    private KeyInfo processKeyDescriptor(Element keyDescriptor) throws MarshalException {
+        NodeList childNodes = keyDescriptor.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), JBossSAMLConstants.KEY_INFO.get());
+
+        if (childNodes.getLength() == 0) {
+            return null;
+        }
+        Node keyInfoNode = childNodes.item(0);
+        return (keyInfoNode == null) ? null : kif.unmarshalKeyInfo(new DOMStructure(keyInfoNode));
+    }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index e9247b3..429d610 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -64,11 +64,20 @@ import org.w3c.dom.Node;
 
 import java.io.IOException;
 import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyManagementException;
 import java.security.PublicKey;
 import java.security.Signature;
+import java.security.SignatureException;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.w3c.dom.Element;
 
 /**
  *
@@ -257,13 +266,44 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
     }
 
     private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
+        KeyLocator signatureValidationKey = deployment.getIDP().getSignatureValidationKeyLocator();
         if (postBinding) {
-            verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
+            verifyPostBindingSignature(holder.getSamlDocument(), signatureValidationKey);
         } else {
-            verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
+            String keyId = getMessageSigningKeyId(holder.getSamlObject());
+            verifyRedirectBindingSignature(paramKey, signatureValidationKey, keyId);
         }
     }
 
+    private String getMessageSigningKeyId(SAML2Object doc) {
+        final ExtensionsType extensions;
+        if (doc instanceof RequestAbstractType) {
+            extensions = ((RequestAbstractType) doc).getExtensions();
+        } else if (doc instanceof StatusResponseType) {
+            extensions = ((StatusResponseType) doc).getExtensions();
+        } else {
+            return null;
+        }
+
+        if (extensions == null) {
+            return null;
+        }
+
+        for (Object ext : extensions.getAny()) {
+            if (! (ext instanceof Element)) {
+                continue;
+            }
+
+            String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
+
+            if (res != null) {
+                return res;
+            }
+        }
+
+        return null;
+    }
+
     private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
         if(statusCode != null && statusCode.getValue()!=null){
             String v = statusCode.getValue().toString();
@@ -473,10 +513,10 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
         return false;
     }
 
-    public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
+    public void verifyPostBindingSignature(Document document, KeyLocator keyLocator) throws VerificationException {
         SAML2Signature saml2Signature = new SAML2Signature();
         try {
-            if (!saml2Signature.validate(document, publicKey)) {
+            if (!saml2Signature.validate(document, keyLocator)) {
                 throw new VerificationException("Invalid signature on document");
             }
         } catch (ProcessingException e) {
@@ -484,7 +524,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
         }
     }
 
-    public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
+    private void verifyRedirectBindingSignature(String paramKey, KeyLocator keyLocator, String keyId) throws VerificationException {
         String request = facade.getRequest().getQueryParamValue(paramKey);
         String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
         String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
@@ -511,16 +551,80 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
         try {
             //byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
             byte[] decodedSignature = Base64.decode(signature);
+            byte[] rawQueryBytes = rawQuery.getBytes("UTF-8");
 
             SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
-            Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
-            validator.initVerify(publicKey);
-            validator.update(rawQuery.getBytes("UTF-8"));
-            if (!validator.verify(decodedSignature)) {
+
+            if (! validateRedirectBindingSignature(signatureAlgorithm, rawQueryBytes, decodedSignature, keyLocator, keyId)) {
                 throw new VerificationException("Invalid query param signature");
             }
         } catch (Exception e) {
             throw new VerificationException(e);
         }
     }
+
+    private boolean validateRedirectBindingSignature(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, KeyLocator locator, String keyId)
+      throws KeyManagementException, VerificationException {
+        try {
+            Key key;
+            try {
+                key = locator.getKey(keyId);
+                boolean keyLocated = key != null;
+
+                if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
+                    return true;
+                }
+
+                if (keyLocated) {
+                    return false;
+                }
+            } catch (KeyManagementException ex) {
+            }
+        } catch (SignatureException ex) {
+            log.debug("Verification failed for key %s: %s", keyId, ex);
+            log.trace(ex);
+        }
+
+        if (locator instanceof Iterable) {
+            Iterable<Key> availableKeys = (Iterable<Key>) locator;
+
+            log.trace("Trying hard to validate XML signature using all available keys.");
+
+            for (Key key : availableKeys) {
+                try {
+                    if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
+                        return true;
+                    }
+                } catch (SignatureException ex) {
+                    log.debug("Verification failed: %s", ex);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private boolean validateRedirectBindingSignatureForKey(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, Key key)
+      throws SignatureException {
+        if (key == null) {
+            return false;
+        }
+
+        if (! (key instanceof PublicKey)) {
+            log.warnf("Unusable key for signature validation: %s", key);
+            return false;
+        }
+
+        Signature signature = sigAlg.createSignature(); // todo plugin signature alg
+        try {
+            signature.initVerify((PublicKey) key);
+        } catch (InvalidKeyException ex) {
+            log.warnf(ex, "Unusable key for signature validation: %s", key);
+            return false;
+        }
+
+        signature.update(rawQueryBytes);
+
+        return signature.verify(decodedSignature);
+    }
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
index 5c1454f..231c425 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
@@ -82,8 +82,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
             if (deployment.getSignatureCanonicalizationMethod() != null)
                 binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
             binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
-                    .signWith(deployment.getSigningKeyPair())
+                    .signWith(null, deployment.getSigningKeyPair())
                     .signDocument();
+            // TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
+            //   <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
         }
 
 
@@ -113,8 +115,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
             if (deployment.getSignatureCanonicalizationMethod() != null)
                 binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
             binding.signatureAlgorithm(deployment.getSignatureAlgorithm());
-            binding.signWith(deployment.getSigningKeyPair())
+            binding.signWith(null, deployment.getSigningKeyPair())
                     .signDocument();
+            // TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
+            //   <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
         }
 
         binding.relayState("logout");
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java
new file mode 100644
index 0000000..7a45fb7
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java
@@ -0,0 +1,175 @@
+/*
+ * 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.adapters.saml.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import org.apache.http.client.HttpClient;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.cloned.HttpAdapterUtils;
+import org.keycloak.adapters.cloned.HttpClientAdapterException;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.common.util.Time;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.api.util.KeyInfoTools;
+
+/**
+ * This class defines a {@link KeyLocator} that looks up public keys and certificates in IdP's
+ * SAML descriptor (i.e. http://{host}/auth/realms/{realm}/protocol/saml/descriptor).
+ *
+ * Based on {@code JWKPublicKeyLocator}.
+ *
+ * @author hmlnarik
+ */
+public class SamlDescriptorPublicKeyLocator implements KeyLocator, Iterable<PublicKey> {
+
+    private static final Logger LOG = Logger.getLogger(SamlDescriptorPublicKeyLocator.class);
+
+    /**
+     * Time between two subsequent requests (in seconds).
+     */
+    private final int minTimeBetweenDescriptorRequests;
+
+    /**
+     * Time to live for cache entries (in seconds).
+     */
+    private final int cacheEntryTtl;
+
+    /**
+     * Target descriptor URL.
+     */
+    private final String descriptorUrl;
+
+    private final Map<String, PublicKey> publicKeyCache = new ConcurrentHashMap<>();
+
+    private final HttpClient client;
+
+    private volatile int lastRequestTime = 0;
+
+    public SamlDescriptorPublicKeyLocator(String descriptorUrl, int minTimeBetweenDescriptorRequests, int cacheEntryTtl, HttpClient httpClient) {
+        this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests <= 0
+          ? 20
+          : minTimeBetweenDescriptorRequests;
+
+        this.descriptorUrl = descriptorUrl;
+        this.cacheEntryTtl = cacheEntryTtl;
+
+        this.client = httpClient;
+    }
+
+    @Override
+    public Key getKey(String kid) throws KeyManagementException {
+        if (kid == null) {
+            LOG.debugf("Invalid key id: %s", kid);
+            return null;
+        }
+
+        LOG.tracef("Requested key id: %s", kid);
+
+        int currentTime = Time.currentTime();
+
+        PublicKey res;
+        if (currentTime > this.lastRequestTime + this.cacheEntryTtl) {
+            LOG.debugf("Performing regular cache cleanup.");
+            res = refreshCertificateCacheAndGet(kid);
+        } else {
+            res = publicKeyCache.get(kid);
+
+            if (res == null) {
+                if (currentTime > this.lastRequestTime + this.minTimeBetweenDescriptorRequests) {
+                    res = refreshCertificateCacheAndGet(kid);
+                } else {
+                    LOG.debugf("Won't send request to realm SAML descriptor url, timeout not expired. Last request time was %d", lastRequestTime);
+                }
+            }
+        }
+
+        return res;
+    }
+
+    @Override
+    public synchronized void refreshKeyCache() {
+        LOG.info("Forcing key cache cleanup and refresh.");
+        this.publicKeyCache.clear();
+        refreshCertificateCacheAndGet(null);
+    }
+
+    private synchronized PublicKey refreshCertificateCacheAndGet(String kid) {
+        if (this.descriptorUrl == null) {
+            return null;
+        }
+
+        this.lastRequestTime = Time.currentTime();
+
+        LOG.debugf("Refreshing public key cache from %s", this.descriptorUrl);
+        List<KeyInfo> signingCerts;
+        try {
+            MultivaluedHashMap<String, KeyInfo> certs = HttpAdapterUtils.downloadKeysFromSamlDescriptor(client, this.descriptorUrl);
+            signingCerts = certs.get(KeyTypes.SIGNING.value());
+        } catch (HttpClientAdapterException ex) {
+            LOG.error("Could not refresh certificates from the server", ex);
+            return null;
+        }
+
+        if (signingCerts == null) {
+            return null;
+        }
+
+        LOG.debugf("Certificates retrieved from server, filling public key cache");
+
+        // Only clear cache after it is certain that the SAML descriptor has been read successfully
+        this.publicKeyCache.clear();
+
+        for (KeyInfo ki : signingCerts) {
+            KeyName keyName = KeyInfoTools.getKeyName(ki);
+            X509Certificate x509certificate = KeyInfoTools.getX509Certificate(ki);
+            if (x509certificate != null && keyName != null) {
+                LOG.tracef("Registering signing certificate %s", keyName.getName());
+                this.publicKeyCache.put(keyName.getName(), x509certificate.getPublicKey());
+            } else {
+                LOG.tracef("Ignoring certificate %s: %s", keyName, x509certificate);
+            }
+
+        }
+
+        return (kid == null ? null : this.publicKeyCache.get(kid));
+    }
+
+    @Override
+    public String toString() {
+        return "Keys retrieved from SAML descriptor at " + descriptorUrl;
+    }
+
+    @Override
+    public Iterator<PublicKey> iterator() {
+        if (this.publicKeyCache.isEmpty()) {
+            refreshCertificateCacheAndGet(null);
+        }
+
+        return this.publicKeyCache.values().iterator();
+    }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index 0b82ff2..4442177 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -22,14 +22,18 @@ import org.keycloak.saml.SignatureAlgorithm;
 
 import java.security.KeyPair;
 import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.util.Set;
+import org.apache.http.client.HttpClient;
+import org.keycloak.rotation.KeyLocator;
 
 /**
+ * Represents SAML deployment configuration.
+ * 
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public interface SamlDeployment {
+
     enum Binding {
         POST,
         REDIRECT;
@@ -41,20 +45,68 @@ public interface SamlDeployment {
     }
 
     public interface IDP {
+        /**
+         * Returns entity identifier of this IdP.
+         * @return see description.
+         */
         String getEntityID();
 
+        /**
+         * Returns Single sign on service configuration for this IdP.
+         * @return see description.
+         */
         SingleSignOnService getSingleSignOnService();
+
+        /**
+         * Returns Single logout service configuration for this IdP.
+         * @return see description.
+         */
         SingleLogoutService getSingleLogoutService();
-        PublicKey getSignatureValidationKey();
+
+        /**
+         * Returns {@link KeyLocator} looking up public keys used for validation of IdP signatures.
+         * @return see description.
+         */
+        KeyLocator getSignatureValidationKeyLocator();
+
+        /**
+         * Returns minimum time (in seconds) between issuing requests to IdP SAML descriptor.
+         * Used e.g. by {@link KeyLocator} looking up public keys for validation of IdP signatures
+         * to prevent too frequent requests.
+         *
+         * @return see description.
+         */
+        int getMinTimeBetweenDescriptorRequests();
+
+        /**
+         * Returns {@link HttpClient} instance that will be used for http communication with this IdP.
+         * @return see description
+         */
+        HttpClient getClient();
 
         public interface SingleSignOnService {
+            /**
+             * Returns {@code true} if the requests to IdP need to be signed by SP key.
+             * @return see dscription
+             */
             boolean signRequest();
+            /**
+             * Returns {@code true} if the complete response message from IdP should
+             * be checked for valid signature.
+             * @return see dscription
+             */
             boolean validateResponseSignature();
+            /**
+             * Returns {@code true} if individual assertions in response from IdP should
+             * be checked for valid signature.
+             * @return see dscription
+             */
             boolean validateAssertionSignature();
             Binding getRequestBinding();
             Binding getResponseBinding();
             String getRequestBindingUrl();
         }
+
         public interface SingleLogoutService {
             boolean validateRequestSignature();
             boolean validateResponseSignature();
@@ -67,10 +119,19 @@ public interface SamlDeployment {
         }
     }
 
+    /**
+     * Returns Identity Provider configuration for this SAML deployment.
+     * @return see description.
+     */
     public IDP getIDP();
 
     public boolean isConfigured();
     SslRequired getSslRequired();
+
+    /**
+     * Returns entity identifier of this SP.
+     * @return see description.
+     */
     String getEntityID();
     String getNameIDPolicyFormat();
     boolean isForceAuthentication();
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
new file mode 100644
index 0000000..fc9cb5e
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
@@ -0,0 +1,451 @@
+<?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.
+  -->
+
+<xs:schema version="1.0"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns="urn:keycloak:saml:adapter"
+           targetNamespace="urn:keycloak:saml:adapter"
+           elementFormDefault="qualified"
+           attributeFormDefault="unqualified">
+
+    <xs:element name="keycloak-saml-adapter" type="adapter-type"/>
+    <xs:complexType name="adapter-type">
+        <xs:annotation>
+            <xs:documentation>Keycloak SAML Adapter configuration file.</xs:documentation>
+        </xs:annotation>
+        <xs:all>
+            <xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type">
+                <xs:annotation>
+                    <xs:documentation>Describes SAML service provider configuration.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+    </xs:complexType>
+
+    <xs:complexType name="sp-type">
+        <xs:all>
+            <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>
+                        List of service provider encryption and validation keys.
+
+                        If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
+                    By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Describes configuration of SAML identity provider for this service provider.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+        <xs:attribute name="entityID" type="xs:string" use="required">
+                <xs:annotation>
+                    <xs:documentation>This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="sslPolicy" type="ssl-policy-type" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SSL policy the adapter will enforce.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="logoutPage" type="xs:string" use="optional">
+                <xs:annotation>
+                    <xs:documentation>URL of the logout page.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="forceAuthentication" type="xs:boolean" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="isPassive" type="xs:boolean" use="optional">
+                <xs:annotation>
+                    <xs:documentation>SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional">
+                <xs:annotation>
+                    <xs:documentation>The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.</xs:documentation>
+                </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="keys-type">
+        <xs:sequence>
+            <xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded">
+                <xs:annotation>
+                    <xs:documentation>Describes a single key used for signing or encryption.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType name="key-type">
+        <xs:all>
+            <xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type">
+                <xs:annotation>
+                    <xs:documentation>Java keystore to load keys and certificates from.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Private key (PEM format)</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Public key (PEM format)</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Certificate key (PEM format)</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+        <xs:attribute name="signing" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Flag defining whether the key should be used for signing.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="encryption" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Flag defining whether the key should be used for encryption</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="key-store-type">
+        <xs:all>
+            <xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type">
+                <xs:annotation>
+                    <xs:documentation>Private key declaration</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Certificate declaration</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:all>
+        <xs:attribute name="file" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>File path to the key store.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="resource" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="password" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>The password of the key store.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="private-key-type">
+        <xs:attribute name="alias" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="password" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="certificate-type">
+        <xs:attribute name="alias" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="principal-name-mapping-type">
+        <xs:attribute name="policy" type="principal-name-mapping-policy-type" use="required">
+            <xs:annotation>
+                <xs:documentation>Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="attribute" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>Name of the SAML assertion attribute to use within.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:simpleType name="principal-name-mapping-policy-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="FROM_NAME_ID">
+                <xs:annotation>
+                    <xs:documentation>This policy just uses whatever the SAML subject value is. This is the default setting</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="FROM_ATTRIBUTE">
+                <xs:annotation>
+                    <xs:documentation>This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="ssl-policy-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="ALL">
+                <xs:annotation>
+                    <xs:documentation>All requests must come in via HTTPS.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="EXTERNAL">
+                <xs:annotation>
+                    <xs:documentation>Only non-private IP addresses must come over the wire via HTTPS.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+            <xs:enumeration value="NONE">
+                <xs:annotation>
+                    <xs:documentation>no requests are required to come over via HTTPS.</xs:documentation>
+                </xs:annotation>
+            </xs:enumeration>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="signature-algorithm-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="RSA_SHA1"/>
+            <xs:enumeration value="RSA_SHA256"/>
+            <xs:enumeration value="RSA_SHA512"/>
+            <xs:enumeration value="DSA_SHA1"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="binding-type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="POST"/>
+            <xs:enumeration value="REDIRECT"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:complexType name="role-identifiers-type">
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type">
+                <xs:annotation>
+                    <xs:documentation>Specifies SAML attribute to be converted into roles.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:choice>
+    </xs:complexType>
+    <xs:complexType name="attribute-type">
+        <xs:attribute name="name" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>Specifies name of the SAML attribute to be converted into roles.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="idp-type">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type">
+                <xs:annotation>
+                    <xs:documentation>Configuration of the login SAML endpoint of the IDP.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Configuration of the logout SAML endpoint of the IDP</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1">
+                <xs:annotation>
+                    <xs:documentation>Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.</xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:sequence>
+        <xs:attribute name="entityID" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>issuer ID of the IDP.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signaturesRequired" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signatureAlgorithm" type="signature-algorithm-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the signature canonicalization method that the IDP expects signed documents to use. The default value is http://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="encryption" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation></xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+    <xs:complexType name="sign-on-type">
+        <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="requestBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="responseBinding" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>SAML allows the client to request what binding type it wants authn responses to use. The default is that the client will not request a specific binding type for responses.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="bindingUrl" type="xs:string" use="required">
+            <xs:annotation>
+                <xs:documentation>This is the URL for the IDP login service that the client will send requests to.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="logout-type">
+        <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="signResponse" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+            <xs:annotation>
+                <xs:documentation>Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="requestBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="responseBinding" type="binding-type" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="postBindingUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+    <xs:complexType name="http-client-type">
+        <xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional" default="false">
+            <xs:annotation>
+                <xs:documentation>If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
+                    is validated via the truststore, but host name validation is not done. This setting should only be used during
+                    development and never in production as it will partly disable verification of SSL certificates.
+                    This seting may be useful in test environments. The default value is false.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="clientKeystore" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>This is the file path to a keystore file. This keystore contains client certificate 
+                    for two-way SSL when the adapter makes HTTPS requests to the IDP server.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="clientKeystorePassword" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>Password for the client keystore and for the client's key.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="connectionPoolSize" type="xs:int" use="optional" default="10">
+            <xs:annotation>
+                <xs:documentation>Defines number of pooled connections.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="disableTrustManager" type="xs:boolean" use="optional" default="false">
+            <xs:annotation>
+                <xs:documentation>If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
+                    This setting should only be used during development and never in production as it will disable verification of SSL certificates.
+                    The default value is false.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="proxyUrl" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>URL to HTTP proxy to use for HTTP connections.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="truststore" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
+                    then the truststore will be obtained from the deployment's classpath instead. Used for outgoing 
+                    HTTPS communications to the IDP server. Client making HTTPS requests need
+                    a way to verify the host of the server they are talking to. This is what the trustore does.
+                    The keystore contains one or more trusted host certificates or certificate authorities.
+                    You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="truststorePassword" type="xs:string" use="optional">
+            <xs:annotation>
+                <xs:documentation>Password for the truststore keystore.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+</xs:schema>
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java
new file mode 100644
index 0000000..2c03ef8
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.adapters.cloned;
+
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+import static org.hamcrest.CoreMatchers.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.keycloak.adapters.saml.config.parsers.ConfigXmlConstants;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class HttpAdapterUtilsTest {
+
+    private <T> T getContent(List<Object> objects, Class<T> clazz) {
+        for (Object o : objects) {
+            if (clazz.isInstance(o)) {
+                return (T) o;
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void testExtractKeysFromSamlDescriptor() throws ParsingException {
+        InputStream xmlStream = HttpAdapterUtilsTest.class.getResourceAsStream("saml-descriptor-valid.xml");
+        MultivaluedHashMap<String, KeyInfo> res = HttpAdapterUtils.extractKeysFromSamlDescriptor(xmlStream);
+
+        assertThat(res, notNullValue());
+        assertThat(res.keySet(), hasItems(KeyTypes.SIGNING.value()));
+        assertThat(res.get(ConfigXmlConstants.SIGNING_ATTR), notNullValue());
+        assertThat(res.get(ConfigXmlConstants.SIGNING_ATTR).size(), equalTo(2));
+
+        KeyInfo ki;
+        KeyName keyName;
+        X509Data x509data;
+        X509Certificate x509certificate;
+
+        ki = res.get(ConfigXmlConstants.SIGNING_ATTR).get(0);
+        assertThat(ki.getContent().size(), equalTo(2));
+        assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(X509Data.class)));
+        assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(KeyName.class)));
+
+        keyName = getContent(ki.getContent(), KeyName.class);
+        assertThat(keyName.getName(), equalTo("rJkJlvowmv1Id74GznieaAC5jU5QQp_ILzuG-GsweTI"));
+
+        x509data = getContent(ki.getContent(), X509Data.class);
+        assertThat(x509data, notNullValue());
+        x509certificate = getContent(x509data.getContent(), X509Certificate.class);
+        assertThat(x509certificate, notNullValue());
+        assertThat(x509certificate.getSigAlgName(), equalTo("SHA256withRSA"));
+
+        ki = res.get(ConfigXmlConstants.SIGNING_ATTR).get(1);
+        assertThat(ki.getContent().size(), equalTo(2));
+        assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(X509Data.class)));
+        assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(KeyName.class)));
+
+        keyName = getContent(ki.getContent(), KeyName.class);
+        assertThat(keyName.getName(), equalTo("BzYc4GwL8HVrAhNyNdp-lTah2DvU9jU03kby9Ynohr4"));
+
+        x509data = getContent(ki.getContent(), X509Data.class);
+        assertThat(x509data, notNullValue());
+        x509certificate = getContent(x509data.getContent(), X509Certificate.class);
+        assertThat(x509certificate, notNullValue());
+        assertThat(x509certificate.getSigAlgName(), equalTo("SHA256withRSA"));
+
+    }
+
+}
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
new file mode 100755
index 0000000..b64f181
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.adapters.saml.config.parsers;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.junit.Test;
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import java.io.InputStream;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAdapterXMLParserTest {
+
+    private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private void testValidationValid(String fileName) throws Exception {
+        InputStream schema = getClass().getResourceAsStream(CURRENT_XSD_LOCATION);
+        InputStream is = getClass().getResourceAsStream(fileName);
+        assertNotNull(is);
+        assertNotNull(schema);
+        StaxParserUtil.validate(is, schema);
+    }
+
+    @Test
+    public void testValidationSimpleFile() throws Exception {
+        testValidationValid("keycloak-saml.xml");
+    }
+
+    @Test
+    public void testValidationMultipleKeys() throws Exception {
+        testValidationValid("keycloak-saml-multiple-signing-keys.xml");
+    }
+
+    @Test
+    public void testValidationWithHttpClient() throws Exception {
+        testValidationValid("keycloak-saml-wth-http-client-settings.xml");
+    }
+
+    @Test
+    public void testValidationKeyInvalid() throws Exception {
+        InputStream schemaIs = KeycloakSamlAdapterXMLParser.class.getResourceAsStream(CURRENT_XSD_LOCATION);
+        InputStream is = getClass().getResourceAsStream("keycloak-saml-invalid.xml");
+        assertNotNull(is);
+        assertNotNull(schemaIs);
+
+        expectedException.expect(ParsingException.class);
+        StaxParserUtil.validate(is, schemaIs);
+    }
+
+    @Test
+    public void testXmlParser() throws Exception {
+        InputStream is = getClass().getResourceAsStream("keycloak-saml.xml");
+        assertNotNull(is);
+        KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+
+        KeycloakSamlAdapter config = (KeycloakSamlAdapter)parser.parse(is);
+        assertNotNull(config);
+        assertEquals(1, config.getSps().size());
+        SP sp = config.getSps().get(0);
+        assertEquals("sp", sp.getEntityID());
+        assertEquals("EXTERNAL", sp.getSslPolicy());
+        assertEquals("format", sp.getNameIDPolicyFormat());
+        assertTrue(sp.isForceAuthentication());
+        assertTrue(sp.isIsPassive());
+        assertEquals(2, sp.getKeys().size());
+        Key signing = sp.getKeys().get(0);
+        assertTrue(signing.isSigning());
+        Key.KeyStoreConfig keystore = signing.getKeystore();
+        assertNotNull(keystore);
+        assertEquals("file", keystore.getFile());
+        assertEquals("cp", keystore.getResource());
+        assertEquals("pw", keystore.getPassword());
+        assertEquals("private alias", keystore.getPrivateKeyAlias());
+        assertEquals("private pw", keystore.getPrivateKeyPassword());
+        assertEquals("cert alias", keystore.getCertificateAlias());
+        Key encryption = sp.getKeys().get(1);
+        assertTrue(encryption.isEncryption());
+        assertEquals("private pem", encryption.getPrivateKeyPem());
+        assertEquals("public pem", encryption.getPublicKeyPem());
+        assertEquals("FROM_ATTRIBUTE", sp.getPrincipalNameMapping().getPolicy());
+        assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
+        assertTrue(sp.getRoleAttributes().size() == 1);
+        assertTrue(sp.getRoleAttributes().contains("member"));
+
+        IDP idp = sp.getIdp();
+        assertEquals("idp", idp.getEntityID());
+        assertEquals("RSA_SHA256", idp.getSignatureAlgorithm());
+        assertEquals("canon", idp.getSignatureCanonicalizationMethod());
+        assertTrue(idp.getSingleSignOnService().isSignRequest());
+        assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
+        assertEquals("POST", idp.getSingleSignOnService().getRequestBinding());
+        assertEquals("url", idp.getSingleSignOnService().getBindingUrl());
+
+        assertTrue(idp.getSingleLogoutService().isSignRequest());
+        assertTrue(idp.getSingleLogoutService().isSignResponse());
+        assertTrue(idp.getSingleLogoutService().isValidateRequestSignature());
+        assertTrue(idp.getSingleLogoutService().isValidateResponseSignature());
+        assertEquals("REDIRECT", idp.getSingleLogoutService().getRequestBinding());
+        assertEquals("POST", idp.getSingleLogoutService().getResponseBinding());
+        assertEquals("posturl", idp.getSingleLogoutService().getPostBindingUrl());
+        assertEquals("redirecturl", idp.getSingleLogoutService().getRedirectBindingUrl());
+
+        assertTrue(idp.getKeys().size() == 1);
+        assertTrue(idp.getKeys().get(0).isSigning());
+        assertEquals("cert pem", idp.getKeys().get(0).getCertificatePem());
+    }
+
+
+    @Test
+    public void testXmlParserMultipleSigningKeys() throws Exception {
+        InputStream is = getClass().getResourceAsStream("keycloak-saml-multiple-signing-keys.xml");
+        assertNotNull(is);
+        KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+
+        KeycloakSamlAdapter config = (KeycloakSamlAdapter) parser.parse(is);
+        assertNotNull(config);
+        assertEquals(1, config.getSps().size());
+        SP sp = config.getSps().get(0);
+        IDP idp = sp.getIdp();
+
+        assertTrue(idp.getKeys().size() == 4);
+        for (int i = 0; i < 4; i ++) {
+            Key key = idp.getKeys().get(i);
+            assertTrue(key.isSigning());
+            assertEquals("cert pem " + i, idp.getKeys().get(i).getCertificatePem());
+        }
+    }
+
+    @Test
+    public void testXmlParserHttpClientSettings() throws Exception {
+        InputStream is = getClass().getResourceAsStream("keycloak-saml-wth-http-client-settings.xml");
+        assertNotNull(is);
+        KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+
+        KeycloakSamlAdapter config = (KeycloakSamlAdapter) parser.parse(is);
+        assertNotNull(config);
+        assertEquals(1, config.getSps().size());
+        SP sp = config.getSps().get(0);
+        IDP idp = sp.getIdp();
+
+        assertThat(idp.getHttpClientConfig(), notNullValue());
+        assertThat(idp.getHttpClientConfig().getClientKeystore(), is("ks"));
+        assertThat(idp.getHttpClientConfig().getClientKeystorePassword(), is("ks-pwd"));
+        assertThat(idp.getHttpClientConfig().getProxyUrl(), is("pu"));
+        assertThat(idp.getHttpClientConfig().getTruststore(), is("ts"));
+        assertThat(idp.getHttpClientConfig().getTruststorePassword(), is("tsp"));
+        assertThat(idp.getHttpClientConfig().getConnectionPoolSize(), is(42));
+        assertThat(idp.getHttpClientConfig().isAllowAnyHostname(), is(true));
+        assertThat(idp.getHttpClientConfig().isDisableTrustManager(), is(true));
+    }
+}
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/cloned/saml-descriptor-valid.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/cloned/saml-descriptor-valid.xml
new file mode 100644
index 0000000..8dd3e41
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/cloned/saml-descriptor-valid.xml
@@ -0,0 +1,62 @@
+<?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.
+  
+-->
+<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak">
+    <EntityDescriptor entityID="http://localhost:8081/auth/realms/master">
+        <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+            <KeyDescriptor use="signing">
+                <dsig:KeyInfo>
+                    <dsig:KeyName>rJkJlvowmv1Id74GznieaAC5jU5QQp_ILzuG-GsweTI</dsig:KeyName>
+                    <dsig:X509Data>
+                        <dsig:X509Certificate>
+                            MIICmzCCAYMCBgFX/9ccIDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYxMDI2MDcxMjUwWhcNMjYxMDI2MDgxNDMwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjDrM890OoFWLIU5xNT+v8B8EkpOGY1y/9Yi/yQd95uG/p5LaywiPsw+lPy4tSn1pH/2SxNDST2zynKPDd1lYDev43m0sC2FfD2H73q3udQRqSOxW1e8FrTrGDIHxb82UNrCPlu+fH+xYSkigrkOvLvPigTwSIcu8vgs0lk9FqJ81ty3Wj2e9lS7JJGAJ3pC7rp39VLdJSKbfyj/v2RYBeG5Pscncl8cjUOHUq5u19hThjkU2jOBzgIK2JS0bNmzSfH1eBTZMoCQBI1UJ1IbA8tqjQwpOXc+JkPBRU8T/JUQoQlSR6DTcPFvDgH2oGZYFHFfUontZqtz8jrIt2pxBAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAK5VgQp1x1FKgabFI6W/iGuy9ZCRoAixOOEGGkDps6dOEFgTQKTy5D/FZts9KuNxhhiD+NvS0d5BKYa5ITPLVPnGucgYkZhz+/+GhxmbjeQr0eJPaY7ZgLfH3tPA6tfdIkA0iE1En1sKEwt6R6DZjh9jtP9laoUoddTvYaFLJpZ2u1Ik94q6ZqX0fS/RKchaBHjhg6MtqCcHt07CBKHh8XNmKPXVSJC/p0MjyXv+qLaNNqyaAvAw6P6DX1hNjzrdkuaaHGXhu6kkezZUVlDWAm9cd1ppqalSK6ggy7yMW1NWTd/NYOPsFU2TS8DDPzRo14s1Qvw4v+TY6yT0NURJPQA=
+                        </dsig:X509Certificate>
+                    </dsig:X509Data>
+                </dsig:KeyInfo>
+            </KeyDescriptor>
+            <KeyDescriptor use="signing">
+                <dsig:KeyInfo>
+                    <dsig:KeyName>BzYc4GwL8HVrAhNyNdp-lTah2DvU9jU03kby9Ynohr4</dsig:KeyName>
+                    <dsig:X509Data>
+                        <dsig:X509Certificate>
+                            MIICmzCCAYMCBgFX/9eK7TANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYxMDI2MDcxMzE4WhcNMjYxMDI2MDgxNDU4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCDLT+40/BWzWPSVmpaSaZRs5lBMQ9VP9TCoXkby4PHqxIWRecTPM8fcNkPNPE/tiR2tUIpMXPDzgXNFA/EMoB3V1OEVXPecjKtiZczdR6pi75CBx7PJ2fSXg6xpjhZmHu0k7x591GZdP8Iiu2E6b9QA2p5VXgNgfuP07XzgabnSvIrLG60Imus3u6C2qA/QEuY7EYQWrFooriYLW6B8s3xU8R1a92SLMT8JsfMWXi+1CzAhIbVvdwUwkhVDDhAU6pUek88QQgxodd3FAMksoijCGFN1yrCkovlFhKb3j9AC6Icd9eeJuwYddN/nMeMGEDOeCcAGBACiaUisjUvZDw1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHAHbBI0CRfdw5ZHxHAjgSQvSj41c/4cfwln4Q7X3I5lMBbW3tcgond6Ku9eU46FzG5VpgXIgvEf4u0O9jUnxLlO50+t2SHwQ1RwHdBWQngVSZCRzscq3KrSzx1hx88qLyqcPrr3QtR92fYipDjENxttT/qJtDMrXlwLZEITlHDoneX319USYB9C4zlrCIsQ5XxQTTyCx886Pz15DSVSRxVp61HGk6ROsX/DG5/xwInlzgMZ0r3JWnAjtAaXqUrcwH9FXxco+xkiqKW79bGhWGQI9sXXvQSSNAaENMIUhxtd9uOi1l5e0EkKHE2fHlYyfdUDnFJWwSMXd/NM+hVI4Lw=
+                        </dsig:X509Certificate>
+                    </dsig:X509Data>
+                </dsig:KeyInfo>
+            </KeyDescriptor>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
+            <NameIDFormat>
+                urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+            </NameIDFormat>
+            <NameIDFormat>
+                urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+            </NameIDFormat>
+            <NameIDFormat>
+                urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+            </NameIDFormat>
+            <NameIDFormat>
+                urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+            </NameIDFormat>
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
+        </IDPSSODescriptor>
+    </EntityDescriptor>
+</EntitiesDescriptor>
\ No newline at end of file
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml
new file mode 100644
index 0000000..37fc7b5
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml
@@ -0,0 +1,83 @@
+<!--
+  ~ 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.
+  -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+    <SP entityID="sp"
+        sslPolicy="ALL"
+        nameIDPolicyFormat="format"
+        forceAuthentication="true"
+        isPassive="true">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore file="file" resource="cp" password="pw">
+                    <PrivateKey alias="private alias" password="private pw"/>
+                    <Certificate alias="cert alias"/>
+                </KeyStore>
+            </Key>
+            <Key encryption="true">
+                <PrivateKeyPem>
+                    private pem
+                </PrivateKeyPem>
+                <PublicKeyPem>
+                    public pem
+                </PublicKeyPem>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_ATTRIBUTE" attribute="attribute"/>
+        <RoleIdentifiers>
+            <Attribute name="member"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp"
+             signatureAlgorithm="RSA_SHA512"
+             signatureCanonicalizationMethod="canon"
+             signaturesRequired="true"
+                >
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="bindingurl"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="POST"
+                    postBindingUrl="posturl"
+                    redirectBindingUrl="redirecturl"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <CertificatePem>cert pem 0</CertificatePem>
+                </Key>
+                <Key signing="true">
+                    <CertificatePem>cert pem 1</CertificatePem>
+                </Key>
+                <Key signing="true">
+                    <CertificatePem>cert pem 2</CertificatePem>
+                </Key>
+                <Key signing="true">
+                    <CertificatePem>cert pem 3</CertificatePem>
+                </Key>
+            </Keys>
+        </IDP>
+    </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml
new file mode 100644
index 0000000..0c4abb2
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml
@@ -0,0 +1,83 @@
+<!--
+  ~ 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.
+  -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+    <SP entityID="sp"
+        sslPolicy="ALL"
+        nameIDPolicyFormat="format"
+        forceAuthentication="true"
+        isPassive="true">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore file="file" resource="cp" password="pw">
+                    <PrivateKey alias="private alias" password="private pw"/>
+                    <Certificate alias="cert alias"/>
+                </KeyStore>
+            </Key>
+            <Key encryption="true">
+                <PrivateKeyPem>
+                    private pem
+                </PrivateKeyPem>
+                <PublicKeyPem>
+                    public pem
+                </PublicKeyPem>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_ATTRIBUTE" attribute="attribute"/>
+        <RoleIdentifiers>
+            <Attribute name="member"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp"
+             signatureAlgorithm="RSA_SHA512"
+             signatureCanonicalizationMethod="canon"
+             signaturesRequired="true"
+                >
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="url"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="POST"
+                    postBindingUrl="posturl"
+                    redirectBindingUrl="redirecturl"
+                    />
+            <Keys>
+                <Key signing="true">
+                    <CertificatePem>
+                        cert pem
+                    </CertificatePem>
+                </Key>
+            </Keys>
+            <HttpClient allowAnyHostname="true"
+                        clientKeystore="ks" clientKeystorePassword="ks-pwd"
+                        connectionPoolSize="42"
+                        disableTrustManager="true"
+                        proxyUrl="pu"
+                        truststore="ts" truststorePassword="tsp"
+                        />
+        </IDP>
+    </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/authz/policy/common/pom.xml b/authz/policy/common/pom.xml
index 6149e3a..31df304 100644
--- a/authz/policy/common/pom.xml
+++ b/authz/policy/common/pom.xml
@@ -46,6 +46,11 @@
             <artifactId>keycloak-server-spi</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/authz/policy/drools/pom.xml b/authz/policy/drools/pom.xml
index 7dc1b45..1594329 100644
--- a/authz/policy/drools/pom.xml
+++ b/authz/policy/drools/pom.xml
@@ -25,6 +25,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-services</artifactId>
             <scope>provided</scope>
         </dependency>
diff --git a/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java b/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
index 3e4839a..283eb3e 100755
--- a/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
+++ b/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
@@ -98,7 +98,7 @@ public final class StringPropertyReplacer
     public static String replaceProperties(final String string, final Properties props)
     {
         final char[] chars = string.toCharArray();
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         boolean properties = false;
         int state = NORMAL;
         int start = 0;
diff --git a/common/src/main/java/org/keycloak/common/util/Time.java b/common/src/main/java/org/keycloak/common/util/Time.java
index ef5d174..54809d8 100644
--- a/common/src/main/java/org/keycloak/common/util/Time.java
+++ b/common/src/main/java/org/keycloak/common/util/Time.java
@@ -26,26 +26,51 @@ public class Time {
 
     private static int offset;
 
+    /**
+     * Returns current time in seconds adjusted by adding {@link #offset) seconds.
+     * @return see description
+     */
     public static int currentTime() {
         return ((int) (System.currentTimeMillis() / 1000)) + offset;
     }
 
+    /**
+     * Returns current time in milliseconds adjusted by adding {@link #offset) seconds.
+     * @return see description
+     */
     public static long currentTimeMillis() {
         return System.currentTimeMillis() + (offset * 1000);
     }
 
+    /**
+     * Returns {@link Date} object, its value set to time
+     * @param time Time in milliseconds since the epoch
+     * @return see description
+     */
     public static Date toDate(int time) {
         return new Date(((long) time ) * 1000);
     }
 
+    /**
+     * Returns time in milliseconds for a time in seconds. No adjustment is made to the parameter.
+     * @param time Time in seconds since the epoch
+     * @return Time in milliseconds
+     */
     public static long toMillis(int time) {
         return ((long) time) * 1000;
     }
 
+    /**
+     * @return Time offset in seconds that will be added to {@link #currentTime()} and {@link #currentTimeMillis()}.
+     */
     public static int getOffset() {
         return offset;
     }
 
+    /**
+     * Sets time offset in seconds that will be added to {@link #currentTime()} and {@link #currentTimeMillis()}.
+     * @param offset Offset (in seconds)
+     */
     public static void setOffset(int offset) {
         Time.offset = offset;
     }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index c4818b4..0ba327d 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -39,7 +39,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
         "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
         "policy-enforcer"
 })
-public class AdapterConfig extends BaseAdapterConfig {
+public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClientConfig {
 
     @JsonProperty("allow-any-hostname")
     protected boolean allowAnyHostname;
@@ -82,6 +82,7 @@ public class AdapterConfig extends BaseAdapterConfig {
     @JsonProperty("proxy-url")
     protected String proxyUrl;
 
+    @Override
     public boolean isAllowAnyHostname() {
         return allowAnyHostname;
     }
@@ -90,6 +91,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.allowAnyHostname = allowAnyHostname;
     }
 
+    @Override
     public boolean isDisableTrustManager() {
         return disableTrustManager;
     }
@@ -98,6 +100,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.disableTrustManager = disableTrustManager;
     }
 
+    @Override
     public String getTruststore() {
         return truststore;
     }
@@ -106,6 +109,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.truststore = truststore;
     }
 
+    @Override
     public String getTruststorePassword() {
         return truststorePassword;
     }
@@ -114,6 +118,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.truststorePassword = truststorePassword;
     }
 
+    @Override
     public String getClientKeystore() {
         return clientKeystore;
     }
@@ -122,6 +127,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.clientKeystore = clientKeystore;
     }
 
+    @Override
     public String getClientKeystorePassword() {
         return clientKeystorePassword;
     }
@@ -138,6 +144,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.clientKeyPassword = clientKeyPassword;
     }
 
+    @Override
     public int getConnectionPoolSize() {
         return connectionPoolSize;
     }
@@ -202,6 +209,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.policyEnforcerConfig = policyEnforcerConfig;
     }
 
+    @Override
     public String getProxyUrl() {
         return proxyUrl;
     }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java
new file mode 100644
index 0000000..fa4c87e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java
@@ -0,0 +1,75 @@
+/*
+ * 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.representations.adapters.config;
+
+/**
+ * Configuration options relevant for configuring http client that can be used by adapter.
+ *
+ * NOTE: keep in sync with adapters/saml/core/src/main/java/org/keycloak/adapters/AdapterHttpClientConfig.java until unified.
+ *
+ * @author hmlnarik
+ */
+public interface AdapterHttpClientConfig {
+
+    /**
+     * Returns truststore filename.
+     */
+    public String getTruststore();
+
+    /**
+     * Returns truststore password.
+     */
+    public String getTruststorePassword();
+
+    /**
+     * Returns keystore with client keys.
+     */
+    public String getClientKeystore();
+
+    /**
+     * Returns keystore password.
+     */
+    public String getClientKeystorePassword();
+
+    /**
+     * Returns boolean flag whether any hostname verification is done on the server's
+     * certificate, {@code true} means that verification is not done.
+     * @return
+     */
+    public boolean isAllowAnyHostname();
+
+    /**
+     * Returns boolean flag whether any trust management and hostname verification is done.
+     * <p>
+     * <i>NOTE</i> Disabling trust manager is a security hole, so only set this option
+     * if you cannot or do not want to verify the identity of the
+     * host you are communicating with.
+     */
+    public boolean isDisableTrustManager();
+
+    /**
+     * Returns size of connection pool.
+     */
+    public int getConnectionPoolSize();
+
+    /**
+     * Returns URL of HTTP proxy.
+     */
+    public String getProxyUrl();
+
+}
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index 6beae84..1c17f64 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -60,6 +60,10 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-server-spi</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+        </dependency>
          <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-js-adapter</artifactId>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/drools/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/drools/main/module.xml
index 09d9479..4c5af2f 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/drools/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/drools/main/module.xml
@@ -31,6 +31,7 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="com.thoughtworks.xstream"/>
         <module name="org.antlr" slot="3.5"/>
         <module name="org.kie"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/eclipse/jdt/core/compiler/ecj/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/eclipse/jdt/core/compiler/ecj/main/module.xml
index 8440c1d..849fc35 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/eclipse/jdt/core/compiler/ecj/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/eclipse/jdt/core/compiler/ecj/main/module.xml
@@ -31,6 +31,7 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
     </dependencies>
 
 </module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml
index 422c683..a8ab099 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-common/main/module.xml
@@ -29,6 +29,7 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.keycloak.keycloak-services"/>
     </dependencies>
 
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-drools/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-drools/main/module.xml
index cf36e0a..60dc78a 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-drools/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-authz-policy-drools/main/module.xml
@@ -28,6 +28,7 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.keycloak.keycloak-services"/>
         <module name="org.kie"/>
     </dependencies>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
index 4a3d370..7b024b1 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
@@ -16,10 +16,6 @@
   ~ limitations under the License.
   -->
 <module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-common">
-    <properties>
-        <property name="jboss.api" value="private"/>
-    </properties>
-
     <resources>
         <artifact name="${org.keycloak:keycloak-common}"/>
     </resources>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml
index 1afc30d..659a055 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml
@@ -16,10 +16,6 @@
   ~ limitations under the License.
   -->
 <module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-core">
-    <properties>
-        <property name="jboss.api" value="private"/>
-    </properties>
-
     <resources>
         <artifact name="${org.keycloak:keycloak-core}"/>
     </resources>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-kerberos-federation/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-kerberos-federation/main/module.xml
index d0b9730..ebb263c 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-kerberos-federation/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-kerberos-federation/main/module.xml
@@ -28,6 +28,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
         <module name="org.jboss.logging"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml
index 43a1d64..dcca1bf 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml
@@ -28,6 +28,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.keycloak.keycloak-kerberos-federation"/>
         <module name="javax.ws.rs.api"/>
         <module name="org.jboss.resteasy.resteasy-jaxrs"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-infinispan/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-infinispan/main/module.xml
index 91f423a..a80a008 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-infinispan/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-infinispan/main/module.xml
@@ -28,6 +28,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.infinispan"/>
         <module name="org.jboss.logging"/>
         <module name="javax.api"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
index 94c80a2..88cae4f 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
@@ -29,6 +29,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="javax.persistence.api"/>
         <module name="org.jboss.logging"/>
         <module name="org.liquibase"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-mongo/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-mongo/main/module.xml
index 20cff7c..3bd99ae 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-mongo/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-mongo/main/module.xml
@@ -29,6 +29,7 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-services"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.mongodb.mongo-java-driver"/>
         <module name="org.jboss.logging"/>
         <module name="javax.api"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
index 8b5632e..daed0af 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
@@ -16,10 +16,6 @@
   ~ limitations under the License.
   -->
 <module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-server-spi">
-    <properties>
-        <property name="jboss.api" value="private"/>
-    </properties>
-
     <resources>
         <artifact name="${org.keycloak:keycloak-server-spi}"/>
     </resources>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml
new file mode 100755
index 0000000..0f136fd
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml
@@ -0,0 +1,40 @@
+<?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.
+  -->
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-server-spi-private">
+    <properties>
+        <property name="jboss.api" value="private"/>
+    </properties>
+
+    <resources>
+        <artifact name="${org.keycloak:keycloak-server-spi-private}"/>
+    </resources>
+
+    <dependencies>
+        <module name="org.jboss.logging"/>
+        <module name="org.keycloak.keycloak-common"/>
+        <module name="org.keycloak.keycloak-core"/>
+        <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.bouncycastle" />
+        <module name="javax.api"/>
+        <module name="javax.ws.rs.api"/>
+        <module name="org.apache.httpcomponents"/>
+        <module name="org.jboss.resteasy.resteasy-jaxrs"/>
+        <module name="javax.transaction.api"/>
+    </dependencies>
+</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
index 4f351fd..9db480c 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
@@ -32,6 +32,7 @@
         <module name="org.keycloak.keycloak-ldap-federation" services="import"/>
         <module name="org.keycloak.keycloak-sssd-federation" services="import"/>
         <module name="org.keycloak.keycloak-server-spi" services="import"/>
+        <module name="org.keycloak.keycloak-server-spi-private" services="import"/>
         <module name="org.keycloak.keycloak-model-jpa" services="import"/>
         <module name="org.keycloak.keycloak-model-mongo" services="import"/>
         <module name="org.keycloak.keycloak-model-infinispan" services="import"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml
index ad16f3c..58939eb 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml
@@ -28,5 +28,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.keycloak.keycloak-core" />
         <module name="org.keycloak.keycloak-server-spi" />
+        <module name="org.keycloak.keycloak-server-spi-private"/>
     </dependencies>
 </module>
\ No newline at end of file
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-adduser/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-adduser/main/module.xml
index 4ee5c50..178470b 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-adduser/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-adduser/main/module.xml
@@ -30,6 +30,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.jboss.aesh"/>
         <module name="org.jboss.as.domain-management"/>
         <module name="com.fasterxml.jackson.core.jackson-core"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml
index d785cb7..89aa901 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml
@@ -28,6 +28,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.keycloak.keycloak-services"/>
         <module name="org.jboss.modules"/>
     </dependencies>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/kie/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/kie/main/module.xml
index 242b508..9b41daa 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/kie/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/kie/main/module.xml
@@ -32,11 +32,13 @@
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.slf4j"/>
         <module name="org.apache.commons.logging"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="com.sun.xml.bind"/>
         <module name="com.thoughtworks.xstream"/>
         <module name="org.apache.ant"/>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
index 451497f..1438ed9 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
@@ -30,6 +30,7 @@
         <module name="org.keycloak.keycloak-saml-core-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
index 49e14c4..4973aa1 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
@@ -34,6 +34,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
index 1d728f5..b85e56f 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
@@ -41,6 +41,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-adapter-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml
index d720b02..9d1a63e 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml
@@ -39,5 +39,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.jboss.vfs"/>
         <module name="org.jboss.metadata"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
index aac9500..7851fd5 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
@@ -36,6 +36,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
index 550dac8..35977c7 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
@@ -32,6 +32,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
index f04205b..40186d7 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -29,6 +29,7 @@
         <module name="org.picketbox"/>
         <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
index 451497f..1438ed9 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
@@ -30,6 +30,7 @@
         <module name="org.keycloak.keycloak-saml-core-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
index 34e6895..e19e0f0 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
@@ -34,6 +34,7 @@
         <module name="org.keycloak.keycloak-saml-core-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
index aac9500..7851fd5 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
@@ -36,6 +36,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
index e5e572c..5637635 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
@@ -35,6 +35,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml
index 397901c..d4cefc5 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml
@@ -40,6 +40,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-adapter-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml
index 3027115..ee00fcc 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml
@@ -41,6 +41,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-adapter-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
index cda8970..857a8e3 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
@@ -39,5 +39,6 @@
         <module name="org.jboss.vfs"/>
         <module name="org.jboss.as.web-common"/>
         <module name="org.jboss.metadata"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 </module>
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
index 4e24bf3..0992966 100755
--- a/examples/providers/authenticator/pom.xml
+++ b/examples/providers/authenticator/pom.xml
@@ -42,6 +42,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.logging</groupId>
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
diff --git a/examples/providers/domain-extension/pom.xml b/examples/providers/domain-extension/pom.xml
index 43e5cf7..d31ccbf 100755
--- a/examples/providers/domain-extension/pom.xml
+++ b/examples/providers/domain-extension/pom.xml
@@ -48,6 +48,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-model-jpa</artifactId>
             <scope>provided</scope>
         </dependency>
diff --git a/examples/providers/domain-extension/README.md b/examples/providers/domain-extension/README.md
index 9ec5e63..465551b 100644
--- a/examples/providers/domain-extension/README.md
+++ b/examples/providers/domain-extension/README.md
@@ -3,7 +3,7 @@ Example Domain Extension
 
 To run, deploy as a module by running:
 
-    $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist,org.liquibase"
+    $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist,org.liquibase"
 
 
 Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
diff --git a/examples/providers/event-listener-sysout/pom.xml b/examples/providers/event-listener-sysout/pom.xml
index 83f348b..3d61dfd 100755
--- a/examples/providers/event-listener-sysout/pom.xml
+++ b/examples/providers/event-listener-sysout/pom.xml
@@ -40,6 +40,11 @@
             <artifactId>keycloak-server-spi</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/providers/event-listener-sysout/README.md b/examples/providers/event-listener-sysout/README.md
index 8e1a085..5540d9b 100644
--- a/examples/providers/event-listener-sysout/README.md
+++ b/examples/providers/event-listener-sysout/README.md
@@ -3,7 +3,7 @@ Example Event Listener that prints events to System.out
 
 To deploy copy target/event-listener-sysout-example.jar to providers directory. Alternatively you can deploy as a module by running:
 
-    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
 
 Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
 
diff --git a/examples/providers/event-store-mem/pom.xml b/examples/providers/event-store-mem/pom.xml
index 16db181..2e48c2d 100755
--- a/examples/providers/event-store-mem/pom.xml
+++ b/examples/providers/event-store-mem/pom.xml
@@ -40,6 +40,11 @@
             <artifactId>keycloak-server-spi</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/examples/providers/event-store-mem/README.md b/examples/providers/event-store-mem/README.md
index 682ff42..2c55109 100644
--- a/examples/providers/event-store-mem/README.md
+++ b/examples/providers/event-store-mem/README.md
@@ -3,7 +3,7 @@ Example Event Store that stores events in memory
 
 To deploy copy target/event-store-mem-example.jar to providers directory. Alternatively you can deploy as a module by running:
 
-    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
 
 Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
 
diff --git a/examples/providers/federation-provider/pom.xml b/examples/providers/federation-provider/pom.xml
index bd4e1c8..be02cc7 100755
--- a/examples/providers/federation-provider/pom.xml
+++ b/examples/providers/federation-provider/pom.xml
@@ -42,6 +42,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.logging</groupId>
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
diff --git a/examples/providers/federation-provider/README.md b/examples/providers/federation-provider/README.md
index c90e791..ae02fc8 100755
--- a/examples/providers/federation-provider/README.md
+++ b/examples/providers/federation-provider/README.md
@@ -4,7 +4,7 @@ Example User Federation Provider
 This is an example of user federation backed by a simple properties file.  This properties file only contains username/password
 key pairs.  To deploy, build this directory then take the jar and copy it to providers directory. Alternatively you can deploy as a module by running:
 
-    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
+    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private"
 
 Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
 
diff --git a/examples/providers/rest/pom.xml b/examples/providers/rest/pom.xml
index 416acfc..b94cf43 100755
--- a/examples/providers/rest/pom.xml
+++ b/examples/providers/rest/pom.xml
@@ -42,6 +42,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.spec.javax.ws.rs</groupId>
             <artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
         </dependency>
diff --git a/examples/providers/rest/README.md b/examples/providers/rest/README.md
index 5ee9327..d78e9bc 100644
--- a/examples/providers/rest/README.md
+++ b/examples/providers/rest/README.md
@@ -3,7 +3,7 @@ Example Realm REST Resource provider
 
 To deploy copy target/hello-rest-example.jar to providers directory. Alternatively you can deploy as a module by running:
 
-    $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=target/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,javax.ws.rs.api"
+    $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=target/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private,javax.ws.rs.api"
 
 Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
 
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
index 837f2b6..86b2a30 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
@@ -30,7 +30,6 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.cache.CachedUserModel;
 import org.keycloak.models.cache.OnUserCache;
-import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.user.UserLookupProvider;
@@ -49,6 +48,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -139,7 +139,7 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
     @Override
     public UserModel addUser(RealmModel realm, String username) {
         UserEntity entity = new UserEntity();
-        entity.setId(KeycloakModelUtils.generateId());
+        entity.setId(UUID.randomUUID().toString());
         entity.setUsername(username);
         em.persist(entity);
         logger.info("added user: " + username);
diff --git a/examples/saml/post-with-encryption/src/main/webapp/WEB-INF/keycloak-saml.xml b/examples/saml/post-with-encryption/src/main/webapp/WEB-INF/keycloak-saml.xml
index 6001080..b2e1b66 100755
--- a/examples/saml/post-with-encryption/src/main/webapp/WEB-INF/keycloak-saml.xml
+++ b/examples/saml/post-with-encryption/src/main/webapp/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8080/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/examples/saml/post-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml b/examples/saml/post-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml
index 815e426..2b50009 100755
--- a/examples/saml/post-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml
+++ b/examples/saml/post-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8080/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/examples/saml/redirect-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml b/examples/saml/redirect-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml
index 1e0ad02..b631653 100755
--- a/examples/saml/redirect-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml
+++ b/examples/saml/redirect-with-signature/src/main/webapp/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8080/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/examples/saml/servlet-filter/src/main/webapp/WEB-INF/keycloak-saml.xml b/examples/saml/servlet-filter/src/main/webapp/WEB-INF/keycloak-saml.xml
index fd9e2fa..9e229e2 100755
--- a/examples/saml/servlet-filter/src/main/webapp/WEB-INF/keycloak-saml.xml
+++ b/examples/saml/servlet-filter/src/main/webapp/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8080/saml-servlet-filter/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp">
diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml
index 75d3ab4..da453d6 100755
--- a/federation/kerberos/pom.xml
+++ b/federation/kerberos/pom.xml
@@ -41,6 +41,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.logging</groupId>
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
index d656a0d..b5f4709 100755
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
@@ -33,7 +33,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 
 import java.util.Collections;
 import java.util.HashMap;
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index 4187ef6..38fa19d 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -42,6 +42,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-kerberos-federation</artifactId>
             <scope>provided</scope>
         </dependency>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 880f0f7..67cfb45 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -47,7 +47,7 @@ import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 
 import javax.naming.AuthenticationException;
 import java.util.ArrayList;
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
index 460ab62..d135dfa 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
@@ -38,6 +38,7 @@ import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.models.utils.UserModelDelegate;
 
 import java.util.Collection;
@@ -563,7 +564,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
 
         @Override
         public boolean hasRole(RoleModel role) {
-            return super.hasRole(role) || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+            return super.hasRole(role) || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
         }
 
         @Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
index 9d6e2a1..b45f348 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
@@ -38,6 +38,7 @@ import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserFederationSyncResult;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.models.utils.UserModelDelegate;
 
 import java.util.Collection;
@@ -348,8 +349,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple
         @Override
         public boolean hasRole(RoleModel role) {
             Set<RoleModel> roles = getRoleMappings();
-            return KeycloakModelUtils.hasRole(roles, role)
-              || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+            return RoleUtils.hasRole(roles, role)
+              || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
         }
 
         @Override
diff --git a/federation/sssd/pom.xml b/federation/sssd/pom.xml
index 8430e7c..29113f7 100644
--- a/federation/sssd/pom.xml
+++ b/federation/sssd/pom.xml
@@ -61,6 +61,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.logging</groupId>
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
index 2820947..44662de 100755
--- a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
+++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
@@ -34,7 +34,7 @@ import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 
 import java.util.Collections;
 import java.util.HashSet;
diff --git a/model/infinispan/pom.xml b/model/infinispan/pom.xml
index 08aef53..f10fa60 100755
--- a/model/infinispan/pom.xml
+++ b/model/infinispan/pom.xml
@@ -38,6 +38,10 @@
         <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-server-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index bc49440..3094521 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -27,6 +27,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.cache.CachedUserModel;
 import org.keycloak.models.cache.infinispan.entities.CachedUser;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -306,7 +307,7 @@ public class UserAdapter implements CachedUserModel {
         for (RoleModel mapping: mappings) {
            if (mapping.hasRole(role)) return true;
         }
-        return KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+        return RoleUtils.hasRoleFromGroup(getGroups(), role, true);
     }
 
     @Override
@@ -373,7 +374,7 @@ public class UserAdapter implements CachedUserModel {
         if (updated != null) return updated.isMemberOf(group);
         if (cached.getGroups().contains(group.getId())) return true;
         Set<GroupModel> roles = getGroups();
-        return KeycloakModelUtils.isMember(roles, group);
+        return RoleUtils.isMember(roles, group);
     }
 
     @Override
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 13f3ae0..0b2ae4d 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -50,6 +50,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-services</artifactId>
         </dependency>
         <dependency>
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
index 6ea87c5..3dc7bcc 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
@@ -27,6 +27,7 @@ import org.keycloak.models.jpa.entities.GroupAttributeEntity;
 import org.keycloak.models.jpa.entities.GroupEntity;
 import org.keycloak.models.jpa.entities.GroupRoleMappingEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
@@ -228,7 +229,7 @@ public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        return KeycloakModelUtils.hasRole(roles, role);
+        return RoleUtils.hasRole(roles, role);
     }
 
     protected TypedQuery<GroupRoleMappingEntity> getGroupRoleMappingEntityTypedQuery(RoleModel role) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 5ee5490..a95548b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -31,6 +31,7 @@ import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
 import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
 import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 
 import javax.persistence.EntityManager;
 import javax.persistence.Query;
@@ -338,7 +339,7 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
     @Override
     public boolean isMemberOf(GroupModel group) {
         Set<GroupModel> roles = getGroups();
-        return KeycloakModelUtils.isMember(roles, group);
+        return RoleUtils.isMember(roles, group);
     }
 
     protected TypedQuery<UserGroupMembershipEntity> getUserGroupMappingQuery(GroupModel group) {
@@ -352,8 +353,8 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        return KeycloakModelUtils.hasRole(roles, role)
-          || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+        return RoleUtils.hasRole(roles, role)
+          || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
     }
 
     protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(RoleModel role) {
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index d31fa69..054d320 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -50,6 +50,10 @@
         <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-server-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java
index 724dd69..d4ad3af 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/GroupAdapter.java
@@ -28,6 +28,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -146,7 +147,7 @@ public class GroupAdapter extends AbstractMongoAdapter<MongoGroupEntity> impleme
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        return KeycloakModelUtils.hasRole(roles, role);
+        return RoleUtils.hasRole(roles, role);
     }
 
     @Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 972f440..9d5ad7c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -27,6 +27,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -262,14 +263,14 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     public boolean isMemberOf(GroupModel group) {
         if (user.getGroupIds().contains(group.getId())) return true;
         Set<GroupModel> groups = getGroups();
-        return KeycloakModelUtils.isMember(groups, group);
+        return RoleUtils.isMember(groups, group);
     }
 
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        return KeycloakModelUtils.hasRole(roles, role)
-          || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+        return RoleUtils.hasRole(roles, role)
+          || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
     }
 
     @Override

pom.xml 10(+8 -2)

diff --git a/pom.xml b/pom.xml
index 8a61e5b..7918c6d 100755
--- a/pom.xml
+++ b/pom.xml
@@ -120,7 +120,7 @@
         <osgi.bundle.plugin.version>2.3.7</osgi.bundle.plugin.version>
         <wildfly.plugin.version>1.0.1.Final</wildfly.plugin.version>
         <nexus.staging.plugin.version>1.6.5</nexus.staging.plugin.version>
-        
+
         <!-- Surefire Settings -->
         <surefire.memory.settings>-Xms512m -Xmx2048m -XX:MetaspaceSize=96m -XX:MaxMetaspaceSize=256m</surefire.memory.settings>
     </properties>
@@ -175,6 +175,7 @@
         <module>core</module>
         <module>dependencies</module>
         <module>server-spi</module>
+        <module>server-spi-private</module>
         <module>saml-core-api</module>
         <module>saml-core</module>
         <module>proxy</module>
@@ -940,6 +941,11 @@
             </dependency>
             <dependency>
                 <groupId>org.keycloak</groupId>
+                <artifactId>keycloak-server-spi-private</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-model-jpa</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -1483,7 +1489,7 @@
                 </plugins>
             </build>
         </profile>
-        
+
         <profile>
             <id>nexus-staging</id>
             <build>
diff --git a/saml-core/nbproject/project.properties b/saml-core/nbproject/project.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/saml-core/nbproject/project.properties

saml-core/pom.xml 12(+12 -0)

diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 8483240..8c08b69 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -53,6 +53,18 @@
             <groupId>org.apache.santuario</groupId>
             <artifactId>xmlsec</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java
new file mode 100644
index 0000000..4b3cb57
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java
@@ -0,0 +1,159 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * {@link KeyLocator} that represents a list of multiple {@link KeyLocator}s. Key is searched
+ * from the first to the last {@link KeyLocator} in the order given by the list. If there are
+ * multiple {@link KeyLocator}s providing key with the same key ID, the first matching key is
+ * returned.
+ *
+ * @author hmlnarik
+ */
+public class CompositeKeyLocator implements KeyLocator, Iterable<Key> {
+
+    private final List<KeyLocator> keyLocators = new LinkedList<>();
+
+    @Override
+    public Key getKey(String kid) throws KeyManagementException {
+        for (KeyLocator keyLocator : keyLocators) {
+            Key k = keyLocator.getKey(kid);
+            if (k != null) {
+                return k;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void refreshKeyCache() {
+        for (KeyLocator keyLocator : keyLocators) {
+            keyLocator.refreshKeyCache();
+        }
+    }
+
+    /**
+     * Registers a given {@link KeyLocator} as the first {@link KeyLocator}.
+     */
+    public void addFirst(KeyLocator keyLocator) {
+        this.keyLocators.add(0, keyLocator);
+    }
+
+    /**
+     * Registers a given {@link KeyLocator} as the last {@link KeyLocator}.
+     */
+    public void add(KeyLocator keyLocator) {
+        this.keyLocators.add(keyLocator);
+    }
+
+    /**
+     * Clears the list of registered {@link KeyLocator}s
+     */
+    public void clear() {
+        this.keyLocators.clear();
+    }
+
+    @Override
+    public String toString() {
+        if (this.keyLocators.size() == 1) {
+            return this.keyLocators.get(0).toString();
+        }
+
+        StringBuilder sb = new StringBuilder("Key locator chain: [");
+        for (Iterator<KeyLocator> it = keyLocators.iterator(); it.hasNext();) {
+            KeyLocator keyLocator = it.next();
+            sb.append(keyLocator.toString());
+            if (it.hasNext()) {
+                sb.append(", ");
+            }
+        }
+        return sb.append("]").toString();
+    }
+
+    @Override
+    public Iterator<Key> iterator() {
+        final Iterator<Iterable<Key>> iterablesIterator = getKeyLocatorIterators().iterator();
+
+        return new JointKeyIterator(iterablesIterator).iterator();
+    }
+
+    @SuppressWarnings("unchecked")
+    private Iterable<Iterable<Key>> getKeyLocatorIterators() {
+        List<Iterable<Key>> res = new LinkedList<>();
+        for (KeyLocator kl : this.keyLocators) {
+            if (kl instanceof Iterable) {
+                res.add(((Iterable<Key>) kl));
+            }
+        }
+        return Collections.unmodifiableCollection(res);
+    }
+
+    private class JointKeyIterator implements Iterable<Key> {
+
+        // based on http://stackoverflow.com/a/34126154/6930869
+        private final Iterator<Iterable<Key>> iterablesIterator;
+
+        public JointKeyIterator(Iterator<Iterable<Key>> iterablesIterator) {
+            this.iterablesIterator = iterablesIterator;
+        }
+
+        @Override
+        public Iterator<Key> iterator() {
+            if (! iterablesIterator.hasNext()) {
+                return Collections.<Key>emptyIterator();
+            }
+
+            return new Iterator<Key>() {
+                private Iterator<Key> currentIterator = nextIterator();
+
+                @Override
+                public boolean hasNext() {
+                    return currentIterator.hasNext();
+                }
+
+                @Override
+                public Key next() {
+                    final Key next = currentIterator.next();
+                    findNext();
+                    return next;
+                }
+
+                private Iterator<Key> nextIterator() {
+                    return iterablesIterator.next().iterator();
+                }
+
+                private Iterator<Key> findNext() {
+                    while (! currentIterator.hasNext()) {
+                        if (! iterablesIterator.hasNext()) {
+                            break;
+                        }
+                        currentIterator = nextIterator();
+                    }
+                    return this;
+                }
+            }.findNext();
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java
new file mode 100644
index 0000000..ae2615a
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Key locator that always returns a specified key.
+ *
+ * @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
+ */
+public class HardcodedKeyLocator implements KeyLocator, Iterable<Key> {
+
+    private final Collection<? extends Key> keys;
+
+    public HardcodedKeyLocator(Key key) {
+        this.keys = Collections.singleton(key);
+    }
+
+    public HardcodedKeyLocator(Collection<? extends Key> keys) {
+        if (keys == null) {
+            throw new NullPointerException("keys");
+        }
+        this.keys = new LinkedList<>(keys);
+    }
+
+    @Override
+    public Key getKey(String kid) {
+        if (this.keys.size() == 1) {
+            return this.keys.iterator().next();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void refreshKeyCache() {
+        // do nothing
+    }
+
+    @Override
+    public String toString() {
+        return "hardcoded keys, count: " + this.keys.size();
+    }
+
+    @Override
+    public Iterator<Key> iterator() {
+        return Collections.unmodifiableCollection(keys).iterator();
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java
new file mode 100644
index 0000000..7112eca
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+
+/**
+ * This interface defines a method for obtaining a security key by ID.
+ * <p>
+ * If the {@code KeyLocator} implementor wants to make all its keys available for iteration,
+ * it should implement {@link Iterable}&lt;{@code T extends }{@link Key}&gt; interface.
+ * The base {@code KeyLocator} does not extend this interface to enable {@code KeyLocators}
+ * that do not support listing their keys.
+ *
+ * @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
+ */
+public interface KeyLocator {
+
+    /**
+     * Returns a key with a particular ID.
+     * @param kid Key ID
+     * @param configuration Configuration
+     * @return key, which should be used for verify signature on given "input"
+     * @throws KeyManagementException
+     */
+    Key getKey(String kid) throws KeyManagementException;
+
+    /**
+     * If this key locator caches keys in any way, forces this cache cleanup
+     * and refreshing the keys.
+     */
+    void refreshKeyCache();
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
index 6d84c13..f820a5e 100755
--- a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
@@ -38,11 +38,14 @@ import javax.crypto.spec.SecretKeySpec;
 import javax.xml.crypto.dsig.CanonicalizationMethod;
 import javax.xml.namespace.QName;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.security.InvalidKeyException;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.Signature;
+import java.security.SignatureException;
 import java.security.cert.X509Certificate;
 
 import static org.keycloak.common.util.HtmlUtils.escapeAttribute;
@@ -55,6 +58,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
 public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
     protected static final Logger logger = Logger.getLogger(BaseSAML2BindingBuilder.class);
 
+    protected String signingKeyId;
     protected KeyPair signingKeyPair;
     protected X509Certificate signingCertificate;
     protected boolean sign;
@@ -82,23 +86,27 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
         return (T)this;
     }
 
-    public T signWith(KeyPair keyPair) {
+    public T signWith(String signingKeyId, KeyPair keyPair) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = keyPair;
         return (T)this;
     }
 
-    public T signWith(PrivateKey privateKey, PublicKey publicKey) {
+    public T signWith(String signingKeyId, PrivateKey privateKey, PublicKey publicKey) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = new KeyPair(publicKey, privateKey);
         return (T)this;
     }
 
-    public T signWith(KeyPair keyPair, X509Certificate cert) {
+    public T signWith(String signingKeyId, KeyPair keyPair, X509Certificate cert) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = keyPair;
         this.signingCertificate = cert;
         return (T)this;
     }
 
-    public T signWith(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
+    public T signWith(String signingKeyId, PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = new KeyPair(publicKey, privateKey);
         this.signingCertificate = cert;
         return (T)this;
@@ -263,7 +271,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
             samlSignature.setX509Certificate(signingCertificate);
         }
 
-        samlSignature.signSAMLDocument(samlDocument, signingKeyPair, canonicalizationMethodType);
+        samlSignature.signSAMLDocument(samlDocument, signingKeyId, signingKeyPair, canonicalizationMethodType);
     }
 
     public void signAssertion(Document samlDocument) throws ProcessingException {
@@ -333,7 +341,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
 
     public String base64Encoded(Document document) throws ConfigurationException, ProcessingException, IOException  {
         String documentAsString = DocumentUtil.getDocumentAsString(document);
-        logger.debugv("saml docment: {0}", documentAsString);
+        logger.debugv("saml document: {0}", documentAsString);
         byte[] responseBytes = documentAsString.getBytes("UTF-8");
 
         return RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
@@ -358,7 +366,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
                 signature.initSign(signingKeyPair.getPrivate());
                 signature.update(rawQuery.getBytes("UTF-8"));
                 sig = signature.sign();
-            } catch (Exception e) {
+            } catch (InvalidKeyException | UnsupportedEncodingException | SignatureException e) {
                 throw new ProcessingException(e);
             }
             String encodedSig = RedirectBindingUtil.base64URLEncode(sig);
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
index 76a72ae..2f58911 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
@@ -450,6 +450,11 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
         return new RuntimeException(ErrorCodes.EXPECTED_TAG + tag + ">.  Found <" + foundElementTag + ">");
     }
 
+    @Override
+    public RuntimeException parserExpectedNamespace(String ns, String foundElementNs) {
+        return new RuntimeException(ErrorCodes.EXPECTED_NAMESPACE + ns + ">.  Found <" + foundElementNs + ">");
+    }
+
     /*
      *(non-Javadoc)
      *
@@ -2378,4 +2383,10 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
         return new ProcessingException("Wrong audience [" + serviceURL + "].");
     }
 
+    @Override
+    public ProcessingException samlExtensionUnknownChild(Class<?> clazz) {
+        return new ProcessingException("Unknown child type specified for extension: " 
+          + (clazz == null ? "<null>" : clazz.getSimpleName())
+          + ".");
+    }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
index 09f4301..37a7755 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
@@ -48,6 +48,8 @@ public interface ErrorCodes {
 
     String EXPECTED_TAG = "PL00066: Parser : Expected start tag:";
 
+    String EXPECTED_NAMESPACE = "PL00107: Parser : Expected start element namespace:";
+
     String EXPECTED_TEXT_VALUE = "PL00071: Parser: Expected text value:";
 
     String EXPECTED_END_TAG = "PL00066: Parser : Expected end tag:";
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
index 7ac6a09..91f2f54 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
@@ -297,6 +297,14 @@ public interface PicketLinkLogger {
     RuntimeException parserExpectedTag(String tag, String foundElementTag);
 
     /**
+     * @param ns
+     * @param foundElementNs
+     *
+     * @return
+     */
+    RuntimeException parserExpectedNamespace(String ns, String foundElementNs);
+
+    /**
      * @param elementName
      *
      * @return
@@ -1219,4 +1227,6 @@ public interface PicketLinkLogger {
     RuntimeException parserFeatureNotSupported(String feature);
 
     ProcessingException samlAssertionWrongAudience(String serviceURL);
+
+    ProcessingException samlExtensionUnknownChild(Class<?> clazz);
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
index 5ac8ce1..49c8df8 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
@@ -35,8 +35,8 @@ import javax.xml.crypto.dsig.XMLSignatureException;
 import javax.xml.parsers.ParserConfigurationException;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
-import java.security.PublicKey;
 import java.security.cert.X509Certificate;
+import org.keycloak.rotation.KeyLocator;
 
 /**
  * Class that deals with SAML2 Signature
@@ -121,7 +121,7 @@ public class SAML2Signature {
      * @throws MarshalException
      * @throws GeneralSecurityException
      */
-    public Document sign(Document doc, String referenceID, KeyPair keyPair, String canonicalizationMethodType) throws ParserConfigurationException,
+    public Document sign(Document doc, String referenceID, String keyId, KeyPair keyPair, String canonicalizationMethodType) throws ParserConfigurationException,
             GeneralSecurityException, MarshalException, XMLSignatureException {
         String referenceURI = "#" + referenceID;
 
@@ -130,6 +130,7 @@ public class SAML2Signature {
         if (sibling != null) {
             SignatureUtilTransferObject dto = new SignatureUtilTransferObject();
             dto.setDocumentToBeSigned(doc);
+            dto.setKeyId(keyId);
             dto.setKeyPair(keyPair);
             dto.setDigestMethod(digestMethod);
             dto.setSignatureMethod(signatureMethod);
@@ -142,7 +143,7 @@ public class SAML2Signature {
 
             return XMLSignatureUtil.sign(dto, canonicalizationMethodType);
         }
-        return XMLSignatureUtil.sign(doc, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
+        return XMLSignatureUtil.sign(doc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
     }
 
     /**
@@ -153,12 +154,12 @@ public class SAML2Signature {
      *
      * @throws org.keycloak.saml.common.exceptions.ProcessingException
      */
-    public void signSAMLDocument(Document samlDocument, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
+    public void signSAMLDocument(Document samlDocument, String keyId, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
         // Get the ID from the root
         String id = samlDocument.getDocumentElement().getAttribute(ID_ATTRIBUTE_NAME);
         try {
-            sign(samlDocument, id, keypair, canonicalizationMethodType);
-        } catch (Exception e) {
+            sign(samlDocument, id, keyId, keypair, canonicalizationMethodType);
+        } catch (ParserConfigurationException | GeneralSecurityException | MarshalException | XMLSignatureException e) {
             throw new ProcessingException(logger.signatureError(e));
         }
     }
@@ -167,20 +168,18 @@ public class SAML2Signature {
      * Validate the SAML2 Document
      *
      * @param signedDocument
-     * @param publicKey
+     * @param keyLocator
      *
      * @return
      *
      * @throws ProcessingException
      */
-    public boolean validate(Document signedDocument, PublicKey publicKey) throws ProcessingException {
+    public boolean validate(Document signedDocument, KeyLocator keyLocator) throws ProcessingException {
         try {
             configureIdAttribute(signedDocument);
-            return XMLSignatureUtil.validate(signedDocument, publicKey);
-        } catch (MarshalException me) {
+            return XMLSignatureUtil.validate(signedDocument, keyLocator);
+        } catch (MarshalException | XMLSignatureException me) {
             throw new ProcessingException(logger.signatureError(me));
-        } catch (XMLSignatureException xse) {
-            throw new ProcessingException(logger.signatureError(xse));
         }
     }
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java
new file mode 100644
index 0000000..be9bf51
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java
@@ -0,0 +1,60 @@
+/*
+ * 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.saml.processing.api.util;
+
+import java.security.cert.X509Certificate;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+/**
+ * Tools for {@link KeyInfo} object manipulation.
+ * @author hmlnarik
+ */
+public class KeyInfoTools {
+
+    /**
+     * Returns the first object of the given class from the given Iterable.
+     * @param <T>
+     * @param objects
+     * @param clazz
+     * @return The object or {@code null} if not found.
+     */
+    public static <T> T getContent(Iterable<Object> objects, Class<T> clazz) {
+        for (Object o : objects) {
+            if (clazz.isInstance(o)) {
+                return (T) o;
+            }
+        }
+        return null;
+    }
+
+
+    public static KeyName getKeyName(KeyInfo keyInfo) {
+        return getContent(keyInfo.getContent(), KeyName.class);
+    }
+
+    public static X509Data getX509Data(KeyInfo keyInfo) {
+        return getContent(keyInfo.getContent(), X509Data.class);
+    }
+
+    public static X509Certificate getX509Certificate(KeyInfo keyInfo) {
+        X509Data d = getX509Data(keyInfo);
+        return d == null ? null : getContent(d.getContent(), X509Certificate.class);
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
index ca0316b..06d6042 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
@@ -58,6 +58,8 @@ public class SAMLArtifactResolveParser extends SAMLRequestAbstractParser impleme
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
index 5f98403..9d3686b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
@@ -68,6 +68,9 @@ public class SAMLArtifactResponseParser extends SAMLStatusResponseTypeParser imp
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
                 response.setSignature(sig);
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.AUTHN_REQUEST.get().equals(elementName)) {
                 SAMLAuthNRequestParser authnParser = new SAMLAuthNRequestParser();
                 AuthnRequestType authn = (AuthnRequestType) authnParser.parse(xmlEventReader);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
index 139e4e0..6102e9e 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
@@ -60,6 +60,8 @@ public class SAMLAttributeQueryParser extends SAMLRequestAbstractParser implemen
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
index 5a15b2c..f1d3349 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
@@ -76,6 +76,8 @@ public class SAMLAuthNRequestParser extends SAMLRequestAbstractParser implements
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
new file mode 100644
index 0000000..5f7ebbb
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
@@ -0,0 +1,82 @@
+/*
+ * 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.saml.processing.core.parsers.saml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.common.PicketLinkLogger;
+import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.ParserNamespaceSupport;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+/**
+ * Parses &lt;samlp:Extensions&gt; SAML2 element into series of DOM nodes.
+ *
+ * @author hmlnarik
+ */
+public class SAMLExtensionsParser implements ParserNamespaceSupport {
+
+    private static final String EXTENSIONS = JBossSAMLConstants.EXTENSIONS.get();
+
+    private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+    @Override
+    public ExtensionsType parse(XMLEventReader xmlEventReader) throws ParsingException {
+        // Get the startelement
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, EXTENSIONS);
+
+        ExtensionsType extensions = new ExtensionsType();
+
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) xmlEvent;
+                if (StaxParserUtil.matches(endElement, EXTENSIONS)) {
+                    endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
+                    break;
+                } else
+                    throw logger.parserUnknownEndElement(StaxParserUtil.getEndElementName(endElement));
+            }
+
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+
+            extensions.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        String nsURI = qname.getNamespaceURI();
+        String localPart = qname.getLocalPart();
+
+        return nsURI.equals(JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+                && localPart.equals(JBossSAMLConstants.EXTENSIONS.get());
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
index a2691c2..92eaf90 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
@@ -71,6 +71,9 @@ public class SAMLResponseParser extends SAMLStatusResponseTypeParser implements 
             } else if (JBossSAMLConstants.ASSERTION.get().equals(elementName)) {
                 SAMLAssertionParser assertionParser = new SAMLAssertionParser();
                 response.addAssertion(new RTChoiceType((AssertionType) assertionParser.parse(xmlEventReader)));
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
                 response.setStatus(parseStatus(xmlEventReader));
             } else if (JBossSAMLConstants.ENCRYPTED_ASSERTION.get().equals(elementName)) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
index f604cf5..22ed383 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
@@ -74,6 +74,8 @@ public class SAMLSloRequestParser extends SAMLRequestAbstractParser implements P
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw logger.parserUnknownTag(elementName, startElement.getLocation());
         }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
index 167a3c5..c0f473b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
@@ -60,6 +60,9 @@ public class SAMLSloResponseParser extends SAMLStatusResponseTypeParser implemen
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
                 StaxParserUtil.bypassElementBlock(xmlEventReader, JBossSAMLConstants.SIGNATURE.get());
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
                 response.setStatus(parseStatus(xmlEventReader));
             }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index 67fb78f..ed941a0 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -62,6 +62,7 @@ import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import org.keycloak.rotation.HardcodedKeyLocator;
 
 /**
  * Utility to deal with assertions
@@ -276,7 +277,7 @@ public class AssertionUtil {
             Node n = doc.importNode(assertionElement, true);
             doc.appendChild(n);
 
-            return new SAML2Signature().validate(doc, publicKey);
+            return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
         } catch (Exception e) {
             logger.signatureAssertionValidationError(e);
         }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
index 4c041d1..068c91a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
@@ -43,8 +43,12 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
 
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
+import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
+import org.w3c.dom.Node;
 
 /**
  * Base Class for the Stax writers for SAML
@@ -244,6 +248,28 @@ public class BaseWriter {
         StaxUtil.flush(writer);
     }
 
+    public void write(ExtensionsType extensions) throws ProcessingException {
+        if (extensions.getAny().isEmpty()) {
+            return;
+        }
+
+        StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.EXTENSIONS.get(), PROTOCOL_NSURI.get());
+
+        for (Object o : extensions.getAny()) {
+            if (o instanceof Node) {
+                StaxUtil.writeDOMNode(writer, (Node) o);
+            } else if (o instanceof SamlProtocolExtensionsAwareBuilder.NodeGenerator) {
+                SamlProtocolExtensionsAwareBuilder.NodeGenerator ng = (SamlProtocolExtensionsAwareBuilder.NodeGenerator) o;
+                ng.write(writer);
+            } else {
+                throw logger.samlExtensionUnknownChild(o == null ? null : o.getClass());
+            }
+        }
+
+        StaxUtil.writeEndElement(writer);
+        StaxUtil.flush(writer);
+    }
+
     private void write(SubjectConfirmationType subjectConfirmationType) throws ProcessingException {
         StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.SUBJECT_CONFIRMATION.get(),
                 ASSERTION_NSURI.get());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
index 9f99780..8c115f5 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
@@ -36,6 +36,7 @@ import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamWriter;
 import java.net.URI;
 import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
@@ -122,6 +123,11 @@ public class SAMLRequestWriter extends BaseWriter {
             StaxUtil.writeDOMElement(writer, sig);
         }
 
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         NameIDPolicyType nameIDPolicy = request.getNameIDPolicy();
         if (nameIDPolicy != null) {
             write(nameIDPolicy);
@@ -171,6 +177,11 @@ public class SAMLRequestWriter extends BaseWriter {
             StaxUtil.writeDOMElement(writer, signature);
         }
 
+        ExtensionsType extensions = logOutRequest.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         NameIDType nameID = logOutRequest.getNameID();
         if (nameID != null) {
             write(nameID, new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.NAMEID.get(), ASSERTION_PREFIX));
@@ -278,6 +289,11 @@ public class SAMLRequestWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         String artifact = request.getArtifact();
         if (StringUtil.isNotNull(artifact)) {
             StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.ARTIFACT.get(), PROTOCOL_NSURI.get());
@@ -315,6 +331,10 @@ public class SAMLRequestWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
         SubjectType subject = request.getSubject();
         if (subject != null) {
             write(subject);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
index 07fae2a..9327a73 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
@@ -37,6 +37,7 @@ import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamWriter;
 import java.net.URI;
 import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * Write a SAML Response to stream
@@ -78,6 +79,10 @@ public class SAMLResponseWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
 
         StatusType status = response.getStatus();
         write(status);
@@ -119,6 +124,10 @@ public class SAMLResponseWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
 
         StatusType status = response.getStatus();
         if (status != null) {
@@ -163,6 +172,15 @@ public class SAMLResponseWriter extends BaseWriter {
         NameIDType issuer = response.getIssuer();
         write(issuer, new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ISSUER.get(), ASSERTION_PREFIX));
 
+        Element sig = response.getSignature();
+        if (sig != null) {
+            StaxUtil.writeDOMElement(writer, sig);
+        }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         StatusType status = response.getStatus();
         write(status);
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java
new file mode 100644
index 0000000..1bb90ea
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.saml.processing.core.util;
+
+import java.util.Objects;
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.StaxUtil;
+import org.w3c.dom.Element;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KeycloakKeySamlExtensionGenerator implements SamlProtocolExtensionsAwareBuilder.NodeGenerator {
+
+    public static final String NS_URI = "urn:keycloak:ext:key:1.0";
+
+    public static final String NS_PREFIX = "kckey";
+
+    public static final String KC_KEY_INFO_ELEMENT_NAME = "KeyInfo";
+
+    public static final String KEY_ID_ATTRIBUTE_NAME = "MessageSigningKeyId";
+
+    private final String keyId;
+
+    public KeycloakKeySamlExtensionGenerator(String keyId) {
+        this.keyId = keyId;
+    }
+
+    @Override
+    public void write(XMLStreamWriter writer) throws ProcessingException {
+        StaxUtil.writeStartElement(writer, NS_PREFIX, KC_KEY_INFO_ELEMENT_NAME, NS_URI);
+        StaxUtil.writeNameSpace(writer, NS_PREFIX, NS_URI);
+        if (this.keyId != null) {
+            StaxUtil.writeAttribute(writer, KEY_ID_ATTRIBUTE_NAME, this.keyId);
+        }
+        StaxUtil.writeEndElement(writer);
+        StaxUtil.flush(writer);
+    }
+
+    /**
+     * Checks that the given element is indeed a Keycloak extension {@code KeyInfo} element and
+     * returns a content of {@code MessageSigningKeyId} attribute in the given element.
+     * @param element Element to obtain the key info from.
+     * @return {@code null} if the element is unknown or there is {@code MessageSigningKeyId} attribute unset,
+     *   value of the {@code MessageSigningKeyId} attribute otherwise.
+     */
+    public static String getMessageSigningKeyIdFromElement(Element element) {
+        if (Objects.equals(element.getNamespaceURI(), NS_URI) &&
+          Objects.equals(element.getLocalName(), KC_KEY_INFO_ELEMENT_NAME) &&
+          element.hasAttribute(KEY_ID_ATTRIBUTE_NAME)) {
+            return element.getAttribute(KEY_ID_ATTRIBUTE_NAME);
+        }
+
+        return null;
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
index 19924e9..f8181fe 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
@@ -32,6 +32,9 @@ public class SignatureUtilTransferObject {
     private X509Certificate x509Certificate;
 
     private Document documentToBeSigned;
+
+    private String keyId;
+
     private KeyPair keyPair;
 
     private Node nextSibling;
@@ -111,4 +114,12 @@ public class SignatureUtilTransferObject {
     public void setX509Certificate(X509Certificate x509Certificate) {
         this.x509Certificate = x509Certificate;
     }
+
+    public String getKeyId() {
+        return keyId;
+    }
+
+    public void setKeyId(String keyId) {
+        this.keyId = keyId;
+    }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java
index e767d29..245cff9 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java
@@ -20,12 +20,14 @@ import org.apache.xml.security.encryption.EncryptedData;
 import org.apache.xml.security.encryption.EncryptedKey;
 import org.apache.xml.security.encryption.XMLCipher;
 import org.apache.xml.security.encryption.XMLEncryptionException;
+
 import org.keycloak.saml.common.PicketLinkLogger;
 import org.keycloak.saml.common.PicketLinkLoggerFactory;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ProcessingException;
 import org.keycloak.saml.common.util.DocumentUtil;
 import org.keycloak.saml.common.util.StringUtil;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -37,6 +39,7 @@ import java.security.Key;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.HashMap;
+import java.util.Objects;
 
 /**
  * Utility for XML Encryption <b>Note: </b> This utility is currently using Apache XML Security library API. JSR-106 is
@@ -69,6 +72,10 @@ public class XMLEncryptionUtil {
 
     private static HashMap<String, EncryptionAlgorithm> algorithms = new HashMap<String, EncryptionAlgorithm>(4);
 
+    private static final String RSA_ENCRYPTION_SCHEME = Objects.equals(System.getProperty("keycloak.saml.key_trans.rsa_v1.5"), "true")
+      ? XMLCipher.RSA_v1dot5
+      : XMLCipher.RSA_OAEP;
+
     private static class EncryptionAlgorithm {
 
         EncryptionAlgorithm(String jceName, String xmlSecName, int size) {
@@ -514,7 +521,7 @@ public class XMLEncryptionUtil {
             }
         }
         if (publicKeyAlgo.contains("RSA"))
-            return XMLCipher.RSA_v1dot5;
+            return RSA_ENCRYPTION_SCHEME;
         if (publicKeyAlgo.contains("DES"))
             return XMLCipher.TRIPLEDES_KeyWrap;
         throw logger.unsupportedType("unsupported publicKey Algo:" + publicKeyAlgo);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
index 98635b7..193af19 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
@@ -54,8 +54,6 @@ import javax.xml.crypto.dsig.dom.DOMSignContext;
 import javax.xml.crypto.dsig.dom.DOMValidateContext;
 import javax.xml.crypto.dsig.keyinfo.KeyInfo;
 import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
-import javax.xml.crypto.dsig.keyinfo.KeyValue;
-import javax.xml.crypto.dsig.keyinfo.X509Data;
 import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
 import javax.xml.crypto.dsig.spec.TransformParameterSpec;
 import javax.xml.namespace.QName;
@@ -69,6 +67,7 @@ import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.Key;
 import java.security.KeyException;
+import java.security.KeyManagementException;
 import java.security.KeyPair;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
@@ -79,7 +78,16 @@ import java.security.interfaces.DSAPublicKey;
 import java.security.interfaces.RSAPublicKey;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import javax.xml.crypto.AlgorithmMethod;
+import javax.xml.crypto.KeySelector;
+import javax.xml.crypto.KeySelectorException;
+import javax.xml.crypto.KeySelectorResult;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.api.util.KeyInfoTools;
 
 /**
  * Utility for XML Signature <b>Note:</b> You can change the canonicalization method type by using the system property
@@ -105,15 +113,66 @@ public class XMLSignatureUtil {
 
     ;
 
-    private static String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
-
-    private static XMLSignatureFactory fac = getXMLSignatureFactory();
+    private static final XMLSignatureFactory fac = getXMLSignatureFactory();
 
     /**
      * By default, we include the keyinfo in the signature
      */
     private static boolean includeKeyInfoInSignature = true;
 
+    private static class KeySelectorUtilizingKeyNameHint extends KeySelector {
+
+        private final KeyLocator locator;
+
+        private boolean keyLocated = false;
+
+        private String keyName = null;
+
+        public KeySelectorUtilizingKeyNameHint(KeyLocator locator) {
+            this.locator = locator;
+        }
+
+        @Override
+        public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
+            try {
+                KeyName keyNameEl = KeyInfoTools.getKeyName(keyInfo);
+                this.keyName = keyNameEl == null ? null : keyNameEl.getName();
+                final Key key = locator.getKey(keyName);
+                this.keyLocated = key != null;
+                return new KeySelectorResult() {
+                    @Override public Key getKey() {
+                        return key;
+                    }
+                };
+            } catch (KeyManagementException ex) {
+                throw new KeySelectorException(ex);
+            }
+
+        }
+
+        private boolean wasKeyLocated() {
+            return this.keyLocated;
+        }
+    }
+
+    private static class KeySelectorPresetKey extends KeySelector {
+
+        private final Key key;
+
+        public KeySelectorPresetKey(Key key) {
+            this.key = key;
+        }
+
+        @Override
+        public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) {
+            return new KeySelectorResult() {
+                @Override public Key getKey() {
+                    return key;
+                }
+            };
+        }
+    }
+
     private static XMLSignatureFactory getXMLSignatureFactory() {
         XMLSignatureFactory xsf = null;
 
@@ -157,7 +216,7 @@ public class XMLSignatureUtil {
      * @throws MarshalException
      * @throws GeneralSecurityException
      */
-    public static Document sign(Document doc, Node nodeToBeSigned, KeyPair keyPair, String digestMethod,
+    public static Document sign(Document doc, Node nodeToBeSigned, String keyId, KeyPair keyPair, String digestMethod,
                                 String signatureMethod, String referenceURI, X509Certificate x509Certificate,
                                 String canonicalizationMethodType) throws ParserConfigurationException, GeneralSecurityException,
             MarshalException, XMLSignatureException {
@@ -179,7 +238,7 @@ public class XMLSignatureUtil {
         if (!referenceURI.isEmpty()) {
             propagateIDAttributeSetup(nodeToBeSigned, newDoc.getDocumentElement());
         }
-        newDoc = sign(newDoc, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
+        newDoc = sign(newDoc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
 
         // if the signed element is a SAMLv2.0 assertion we need to move the signature element to the position
         // specified in the schema (before the assertion subject element).
@@ -220,10 +279,10 @@ public class XMLSignatureUtil {
      * @throws MarshalException
      * @throws XMLSignatureException
      */
-    public static void sign(Element elementToSign, Node nextSibling, KeyPair keyPair, String digestMethod,
+    public static void sign(Element elementToSign, Node nextSibling, String keyId, KeyPair keyPair, String digestMethod,
                             String signatureMethod, String referenceURI, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
-        sign(elementToSign, nextSibling, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
+        sign(elementToSign, nextSibling, keyId, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
     }
 
     /**
@@ -242,7 +301,7 @@ public class XMLSignatureUtil {
      * @throws XMLSignatureException
      * @since 2.5.0
      */
-    public static void sign(Element elementToSign, Node nextSibling, KeyPair keyPair, String digestMethod,
+    public static void sign(Element elementToSign, Node nextSibling, String keyId, KeyPair keyPair, String digestMethod,
                             String signatureMethod, String referenceURI, X509Certificate x509Certificate, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
         PrivateKey signingKey = keyPair.getPrivate();
@@ -250,7 +309,7 @@ public class XMLSignatureUtil {
 
         DOMSignContext dsc = new DOMSignContext(signingKey, elementToSign, nextSibling);
 
-        signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, x509Certificate, canonicalizationMethodType);
+        signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, x509Certificate, canonicalizationMethodType);
     }
 
     /**
@@ -284,9 +343,9 @@ public class XMLSignatureUtil {
      * @throws XMLSignatureException
      * @throws MarshalException
      */
-    public static Document sign(Document doc, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, String canonicalizationMethodType)
+    public static Document sign(Document doc, String keyId, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
-        return sign(doc, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
+        return sign(doc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
     }
 
     /**
@@ -304,7 +363,7 @@ public class XMLSignatureUtil {
      * @throws MarshalException
      * @since 2.5.0
      */
-    public static Document sign(Document doc, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
+    public static Document sign(Document doc, String keyId, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
                                 X509Certificate x509Certificate, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
         logger.trace("Document to be signed=" + DocumentUtil.asString(doc));
@@ -313,7 +372,7 @@ public class XMLSignatureUtil {
 
         DOMSignContext dsc = new DOMSignContext(signingKey, doc.getDocumentElement());
 
-        signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, x509Certificate, canonicalizationMethodType);
+        signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, x509Certificate, canonicalizationMethodType);
 
         return doc;
     }
@@ -331,6 +390,7 @@ public class XMLSignatureUtil {
     public static Document sign(SignatureUtilTransferObject dto, String canonicalizationMethodType) throws GeneralSecurityException, MarshalException,
             XMLSignatureException {
         Document doc = dto.getDocumentToBeSigned();
+        String keyId = dto.getKeyId();
         KeyPair keyPair = dto.getKeyPair();
         Node nextSibling = dto.getNextSibling();
         String digestMethod = dto.getDigestMethod();
@@ -344,13 +404,14 @@ public class XMLSignatureUtil {
 
         DOMSignContext dsc = new DOMSignContext(signingKey, doc.getDocumentElement(), nextSibling);
 
-        signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
+        signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
 
         return doc;
     }
 
     /**
-     * Validate a signed document with the given public key
+     * Validate a signed document with the given public key. All elements that contain a Signature are checked,
+     * this way both assertions and the containing document are verified when signed.
      *
      * @param signedDoc
      * @param publicKey
@@ -361,7 +422,7 @@ public class XMLSignatureUtil {
      * @throws XMLSignatureException
      */
     @SuppressWarnings("unchecked")
-    public static boolean validate(Document signedDoc, Key publicKey) throws MarshalException, XMLSignatureException {
+    public static boolean validate(Document signedDoc, final KeyLocator locator) throws MarshalException, XMLSignatureException {
         if (signedDoc == null)
             throw logger.nullArgumentError("Signed Document");
 
@@ -374,7 +435,7 @@ public class XMLSignatureUtil {
             return false;
         }
 
-        if (publicKey == null)
+        if (locator == null)
             throw logger.nullValueError("Public Key");
 
         int signedAssertions = 0;
@@ -390,24 +451,7 @@ public class XMLSignatureUtil {
                 }
             }
 
-            DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(i));
-            XMLSignature signature = fac.unmarshalXMLSignature(valContext);
-
-            boolean coreValidity = signature.validate(valContext);
-
-            if (!coreValidity) {
-                if (logger.isTraceEnabled()) {
-                    boolean sv = signature.getSignatureValue().validate(valContext);
-                    logger.trace("Signature validation status: " + sv);
-
-                    List<Reference> references = signature.getSignedInfo().getReferences();
-                    for (Reference ref : references) {
-                        logger.trace("[Ref id=" + ref.getId() + ":uri=" + ref.getURI() + "]validity status:" + ref.validate(valContext));
-                    }
-                }
-
-                return false;
-            }
+            if (! validateSingleNode(signatureNode, locator)) return false;
         }
 
         NodeList assertions = signedDoc.getElementsByTagNameNS(assertionNameSpaceUri, JBossSAMLConstants.ASSERTION.get());
@@ -423,6 +467,62 @@ public class XMLSignatureUtil {
         return true;
     }
 
+    private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
+        KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
+        try {
+            if (validateUsingKeySelector(signatureNode, sel)) {
+                return true;
+            }
+            if (sel.wasKeyLocated()) {
+                return false;
+            }
+        } catch (XMLSignatureException ex) { // pass through MarshalException
+            logger.debug("Verification failed for key " + sel.keyName + ": " + ex);
+            logger.trace(ex);
+        }
+
+        logger.trace("Could not validate signature using ds:KeyInfo/ds:KeyName hint.");
+
+        if (locator instanceof Iterable) {
+            Iterable<Key> availableKeys = (Iterable<Key>) locator;
+
+            logger.trace("Trying hard to validate XML signature using all available keys.");
+
+            for (Key key : availableKeys) {
+                try {
+                    if (validateUsingKeySelector(signatureNode, new KeySelectorPresetKey(key))) {
+                        return true;
+                    }
+                } catch (XMLSignatureException ex) { // pass through MarshalException
+                    logger.debug("Verification failed: " + ex);
+                    logger.trace(ex);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean validateUsingKeySelector(Node signatureNode, KeySelector validationKeySelector) throws XMLSignatureException, MarshalException {
+        DOMValidateContext valContext = new DOMValidateContext(validationKeySelector, signatureNode);
+        XMLSignature signature = fac.unmarshalXMLSignature(valContext);
+        boolean coreValidity = signature.validate(valContext);
+        
+        if (! coreValidity) {
+            if (logger.isTraceEnabled()) {
+                boolean sv = signature.getSignatureValue().validate(valContext);
+                logger.trace("Signature validation status: " + sv);
+
+                List<Reference> references = signature.getSignedInfo().getReferences();
+                for (Reference ref : references) {
+                    logger.trace("[Ref id=" + ref.getId() + ":uri=" + ref.getURI() + "]validity status:" + ref.validate(valContext));
+                }
+            }
+        }
+
+        return coreValidity;
+    }
+
     /**
      * Marshall a SignatureType to output stream
      *
@@ -594,7 +694,7 @@ public class XMLSignatureUtil {
         throw logger.unsupportedType(key.toString());
     }
 
-    private static void signImpl(DOMSignContext dsc, String digestMethod, String signatureMethod, String referenceURI, PublicKey publicKey,
+    private static void signImpl(DOMSignContext dsc, String digestMethod, String signatureMethod, String referenceURI, String keyId, PublicKey publicKey,
                                  X509Certificate x509Certificate, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
         dsc.setDefaultNamespacePrefix("dsig");
@@ -603,7 +703,7 @@ public class XMLSignatureUtil {
         Transform transform1 = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
         Transform transform2 = fac.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (TransformParameterSpec) null);
 
-        List<Transform> transformList = new ArrayList<Transform>();
+        List<Transform> transformList = new ArrayList<>();
         transformList.add(transform1);
         transformList.add(transform2);
 
@@ -616,37 +716,34 @@ public class XMLSignatureUtil {
         SignatureMethod signatureMethodObj = fac.newSignatureMethod(signatureMethod, null);
         SignedInfo si = fac.newSignedInfo(canonicalizationMethod, signatureMethodObj, referenceList);
 
-        KeyInfo ki = null;
+        KeyInfo ki;
         if (includeKeyInfoInSignature) {
-            ki = createKeyInfo(publicKey, x509Certificate);
+            ki = createKeyInfo(keyId, publicKey, x509Certificate);
+        } else {
+            ki = createKeyInfo(keyId, null, null);
         }
         XMLSignature signature = fac.newXMLSignature(si, ki);
 
         signature.sign(dsc);
     }
 
-    private static KeyInfo createKeyInfo(PublicKey publicKey, X509Certificate x509Certificate) throws KeyException {
+    private static KeyInfo createKeyInfo(String keyId, PublicKey publicKey, X509Certificate x509Certificate) throws KeyException {
         KeyInfoFactory keyInfoFactory = fac.getKeyInfoFactory();
-        KeyInfo keyInfo = null;
-        KeyValue keyValue = null;
-        //Just with public key
-        if (publicKey != null) {
-            keyValue = keyInfoFactory.newKeyValue(publicKey);
-            keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(keyValue));
+
+        List<Object> items = new LinkedList<>();
+
+        if (keyId != null) {
+            items.add(keyInfoFactory.newKeyName(keyId));
         }
-        if (x509Certificate != null) {
-            List x509list = new ArrayList();
 
-            x509list.add(x509Certificate);
-            X509Data x509Data = keyInfoFactory.newX509Data(x509list);
-            List items = new ArrayList();
+        if (x509Certificate != null) {
+            items.add(keyInfoFactory.newX509Data(Collections.singletonList(x509Certificate)));
+        }
 
-            items.add(x509Data);
-            if (keyValue != null) {
-                items.add(keyValue);
-            }
-            keyInfo = keyInfoFactory.newKeyInfo(items);
+        if (publicKey != null) {
+            items.add(keyInfoFactory.newKeyValue(publicKey));
         }
-        return keyInfo;
+
+        return keyInfoFactory.newKeyInfo(items);
     }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
index 302237e..ec4fc28 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
@@ -25,15 +25,19 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author pedroigor
  */
-public class SAML2AuthnRequestBuilder {
+public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2AuthnRequestBuilder> {
 
     private final AuthnRequestType authnRequestType;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2AuthnRequestBuilder destination(String destination) {
         this.destination = destination;
@@ -45,6 +49,12 @@ public class SAML2AuthnRequestBuilder {
         return this;
     }
 
+    @Override
+    public SAML2AuthnRequestBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     public SAML2AuthnRequestBuilder() {
         try {
             this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
@@ -90,6 +100,14 @@ public class SAML2AuthnRequestBuilder {
 
             authnRequestType.setDestination(URI.create(this.destination));
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                authnRequestType.setExtensions(extensionsType);
+            }
+
             return new SAML2Request().convert(authnRequestType);
         } catch (Exception e) {
             throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index 99d1c1f..6da6799 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -17,7 +17,10 @@
 
 package org.keycloak.saml;
 
+import java.util.LinkedList;
+import java.util.List;
 import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ParsingException;
@@ -32,11 +35,12 @@ import org.w3c.dom.Document;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2ErrorResponseBuilder {
+public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2ErrorResponseBuilder> {
 
     protected String status;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2ErrorResponseBuilder status(String status) {
         this.status = status;
@@ -53,6 +57,11 @@ public class SAML2ErrorResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2ErrorResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
 
     public Document buildDocument() throws ProcessingException {
 
@@ -66,6 +75,14 @@ public class SAML2ErrorResponseBuilder {
             statusResponse.setIssuer(issuer);
             statusResponse.setDestination(destination);
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                statusResponse.setExtensions(extensionsType);
+            }
+
             SAML2Response saml2Response = new SAML2Response();
             return saml2Response.convert(statusResponse);
         } catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
index 2386edb..17dafc7 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
@@ -39,6 +39,9 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 import static org.keycloak.saml.common.util.StringUtil.isNotNull;
 
@@ -49,7 +52,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
  *
  * @author bburke@redhat.com
 */
-public class SAML2LoginResponseBuilder {
+public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LoginResponseBuilder> {
     protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
 
     protected String destination;
@@ -64,6 +67,7 @@ public class SAML2LoginResponseBuilder {
     protected String authMethod;
     protected String requestIssuer;
     protected String sessionIndex;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
 
     public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) {
@@ -136,6 +140,12 @@ public class SAML2LoginResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException {
         Document samlResponseDocument = null;
 
@@ -207,6 +217,14 @@ public class SAML2LoginResponseBuilder {
             assertion.addStatement(authnStatement);
         }
 
+        if (! this.extensions.isEmpty()) {
+            ExtensionsType extensionsType = new ExtensionsType();
+            for (NodeGenerator extension : this.extensions) {
+                extensionsType.addExtension(extension);
+            }
+            responseType.setExtensions(extensionsType);
+        }
+
         return responseType;
     }
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
index 99b1cf8..d0e81ba 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
@@ -27,18 +27,22 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2LogoutRequestBuilder {
+public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutRequestBuilder> {
     protected String userPrincipal;
     protected String userPrincipalFormat;
     protected String sessionIndex;
     protected long assertionExpiration;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2LogoutRequestBuilder destination(String destination) {
         this.destination = destination;
@@ -50,6 +54,12 @@ public class SAML2LogoutRequestBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LogoutRequestBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     /**
      * Length of time in seconds the assertion is valid for
      * See SAML core specification 2.5.1.2 NotOnOrAfter
@@ -99,6 +109,15 @@ public class SAML2LogoutRequestBuilder {
 
         if (assertionExpiration > 0) lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionExpiration * 1000));
         lort.setDestination(URI.create(destination));
+
+        if (! this.extensions.isEmpty()) {
+            ExtensionsType extensionsType = new ExtensionsType();
+            for (NodeGenerator extension : this.extensions) {
+                extensionsType.addExtension(extension);
+            }
+            lort.setExtensions(extensionsType);
+        }
+
         return lort;
     }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
index c00a4d4..8050e81 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
@@ -31,16 +31,20 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2LogoutResponseBuilder {
+public class SAML2LogoutResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutResponseBuilder> {
 
     protected String logoutRequestID;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
         this.logoutRequestID = logoutRequestID;
@@ -57,6 +61,11 @@ public class SAML2LogoutResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LogoutResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
 
     public Document buildDocument() throws ProcessingException {
         Document samlResponse = null;
@@ -77,6 +86,14 @@ public class SAML2LogoutResponseBuilder {
             statusResponse.setIssuer(issuer);
             statusResponse.setDestination(destination);
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                statusResponse.setExtensions(extensionsType);
+            }
+
             SAML2Response saml2Response = new SAML2Response();
             samlResponse = saml2Response.convert(statusResponse);
         } catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
new file mode 100644
index 0000000..2192df6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * 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.saml;
+
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
+/**
+ * Implementations of this interface are builders that can register &lt;samlp:Extensions&gt;
+ * content providers.
+ *
+ * @author hmlnarik
+ */
+public interface SamlProtocolExtensionsAwareBuilder<T> {
+
+    public interface NodeGenerator {
+        /**
+         * Generate contents of the &lt;samlp:Extensions&gt; tag. When this method is invoked,
+         * the writer has already emitted the &lt;samlp:Extensions&gt; start tag.
+         *
+         * @param writer Writer to use for producing XML output
+         * @throws ProcessingException If any exception fails
+         */
+        void write(XMLStreamWriter writer) throws ProcessingException;
+    }
+
+    /**
+     * Adds a given node subtree as a SAML protocol extension into the SAML protocol message.
+     *
+     * @param extension
+     * @return
+     */
+    T addExtension(NodeGenerator extension);
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
index e6c10af..9a28137 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
@@ -22,21 +22,14 @@ package org.keycloak.saml;
  * @version $Revision: 1 $
  */
 public class SPMetadataDescriptor {
-    public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String certificatePem) {
+
+    public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
         String descriptor =
                 "<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
                 "    <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
                 "            protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n";
-        if (wantAuthnRequestsSigned) {
-            descriptor +=
-                    "        <KeyDescriptor use=\"signing\">\n" +
-                            "            <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
-                            "                <dsig:X509Data>\n" +
-                            "                    <dsig:X509Certificate>\n" + certificatePem + "\n" +
-                            "                    </dsig:X509Certificate>\n" +
-                            "                </dsig:X509Data>\n" +
-                            "            </dsig:KeyInfo>\n" +
-                            "        </KeyDescriptor>\n";
+        if (wantAuthnRequestsSigned  && signingCerts != null) {
+            descriptor += signingCerts;
         }
         descriptor +=
                 "        <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
@@ -44,10 +37,34 @@ public class SPMetadataDescriptor {
                 "        </NameIDFormat>\n" +
                 "        <AssertionConsumerService\n" +
                 "                Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
-                "                index=\"1\" isDefault=\"true\" />\n";
-        descriptor +=
+                "                index=\"1\" isDefault=\"true\" />\n" +
                 "    </SPSSODescriptor>\n" +
                 "</EntityDescriptor>\n";
         return descriptor;
     }
+
+    public static String xmlKeyInfo(String indentation, String keyId, String pemEncodedCertificate, String purpose, boolean declareDSigNamespace) {
+        if (pemEncodedCertificate == null) {
+            return "";
+        }
+
+        StringBuilder target = new StringBuilder()
+          .append(indentation).append("<KeyDescriptor use=\"").append(purpose).append("\">\n")
+          .append(indentation).append("  <dsig:KeyInfo").append(declareDSigNamespace ? " xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" : ">\n");
+
+        if (keyId != null) {
+            target.append(indentation).append("    <dsig:KeyName>").append(keyId).append("</dsig:KeyName>\n");
+        }
+
+        target
+          .append(indentation).append("    <dsig:X509Data>\n")
+          .append(indentation).append("      <dsig:X509Certificate>").append(pemEncodedCertificate).append("</dsig:X509Certificate>\n")
+          .append(indentation).append("    </dsig:X509Data>\n")
+          .append(indentation).append("  </dsig:KeyInfo>\n")
+          .append(indentation).append("</KeyDescriptor>\n")
+        ;
+
+        return target.toString();
+    }
+
 }
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
new file mode 100644
index 0000000..ad150e9
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.saml.processing.core.parsers.saml;
+
+import java.io.InputStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.w3c.dom.Element;
+
+/**
+ * Test class for SAML parser.
+ *
+ * TODO: Add further tests.
+ *
+ * @author hmlnarik
+ */
+public class SAMLParserTest {
+
+    @Test
+    public void testSaml20EncryptedAssertionsSignedReceivedWithRedirectBinding() throws Exception {
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(ResponseType.class));
+        
+        ResponseType resp = (ResponseType) parsedObject;
+        assertThat(resp.getSignature(), nullValue());
+        assertThat(resp.getConsent(), nullValue());
+        assertThat(resp.getIssuer(), not(nullValue()));
+        assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+        assertThat(resp.getExtensions(), not(nullValue()));
+        assertThat(resp.getExtensions().getAny().size(), is(1));
+        assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+        Element el = (Element) resp.getExtensions().getAny().get(0);
+        assertThat(el.getLocalName(), is("KeyInfo"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+        assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+        assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+
+        assertThat(resp.getAssertions(), not(nullValue()));
+        assertThat(resp.getAssertions().size(), is(1));
+    }
+
+    @Test
+    public void testSaml20EncryptedAssertionsSignedTwoExtensionsReceivedWithRedirectBinding() throws Exception {
+        Element el;
+
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response-two-extensions.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(ResponseType.class));
+
+        ResponseType resp = (ResponseType) parsedObject;
+        assertThat(resp.getSignature(), nullValue());
+        assertThat(resp.getConsent(), nullValue());
+        assertThat(resp.getIssuer(), not(nullValue()));
+        assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+        assertThat(resp.getExtensions(), not(nullValue()));
+        assertThat(resp.getExtensions().getAny().size(), is(2));
+        assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+        el = (Element) resp.getExtensions().getAny().get(0);
+        assertThat(el.getLocalName(), is("KeyInfo"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+        assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+        assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+        assertThat(resp.getExtensions().getAny().get(1), instanceOf(Element.class));
+        el = (Element) resp.getExtensions().getAny().get(1);
+        assertThat(el.getLocalName(), is("ever"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:what:1.0"));
+        assertThat(el.hasAttribute("what"), is(true));
+        assertThat(el.getAttribute("what"), is("ever"));
+
+        assertThat(resp.getAssertions(), not(nullValue()));
+        assertThat(resp.getAssertions().size(), is(1));
+    }
+
+    @Test
+    public void testSaml20PostLogoutRequest() throws Exception {
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-signed-logout-request.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(LogoutRequestType.class));
+
+    }
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
new file mode 100644
index 0000000..d8d4c15
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
@@ -0,0 +1,29 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+    <samlp:Extensions>
+        <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+    </samlp:Extensions>
+    <samlp:Status>
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+    </samlp:Status>
+    <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+                <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+                    <xenc:CipherData>
+                        <xenc:CipherValue>
+                            OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+                        </xenc:CipherValue>
+                    </xenc:CipherData>
+                </xenc:EncryptedKey>
+            </ds:KeyInfo>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+                    RW2eu9nP2Ez9hfRlug9xC+kFfVF3HZpEb4kIFH33gmVbzrQjPk0l67uXkwRjC82FZZ482QnHCBIqNFlAryds/zTa6wdRvFmhQnIM6WxoAl8TM+e9h8MoKkalMc8J/Qfp+WQ7/XdmCg2pp9VvUZTK+g0+G4aGuL+S5+ssZq4rl9k7LrSYyp6vj+djgvISZiz5hPYJCN/WY/gWXfVuLHSpu4CmZt8D2APtT3ax1WmGcuzStAfTW8q3MFIDNV59hkpFmDb+gvyLNbZ95cDYxofiPXaC5cOTftnSBp68Ay1eienqdttEDo4fyakszdvq128KwXkH9azCg6sqLxli6B8l2xdq41MeuJO54VqmOhhLxwKy42NtnJvK/NkNwttH4yMwDPpPbC4vOKCXxT2r2F7jjvJQNB2VFv+oiUAWSSc3fGQcc2uNlx9YQVuzTmjqc7fXAWCGgYoogC8AeNWni204bnBoVpFrEo3gzuOe2fFsddJIclglmTH1hWf31FXUHDO2nl/lT4puQVTo+I+d6jpiV+qdp823NDntRxljRlUJO2AzSTXuIIGtF5q5KWyEi9Nj93BCWa1Llcddkn3ZEZMvDwR4MacwUj8G8hwoH73VvT3jAiakjSpNEIqYCzofeejdfN/gEuuAUfe8uNbTu+gBS+iP3QJe3Pc0Fs/lKJzd3frPNj7xb83wpOf865EQQoOozhnRIKKcMReSjakr/Px5NNooeiJcWEreDagQO2TbwTnHg1kCNG3BAXV/2lV3XBU4afZBoUfxAzYWFOl6xFCAPzhQCPL1SFJp1VRADY/1MU2Kaje5AZoJ4jjph8+yspxBvjic1vC1uYRGW8LWRind9w4eVhCm0LfPiFRCpP+jKPQOJzcNH580/nIMFXPHHnLKv/It7Qex1unDv/QjkuCFFHR6SWJm4WBrwDek+MyOIvgT6o878Cu0Ps472QpoYBQ+7l2WoylWdG1lHZV1UiHPj7PLHPNAL4rbbN3U88fS6N9OJHegQTfcX0i/1KPk4IN/5Z+/15dHI658BINjRvI/6O1QqaTVZkqM8ORcoGpn6BjAiz5rRhjWpOCwlmT+VzOAp3IqACURS1X+txjWE2mfVjlHLJsvyGRDLv1dUR3IeStDAEfsjR/ruRgn5XTFpYaccB/u//DJonJr5A+KFiLbYl+sbbSVAoQCAiAdxKdUpKPx7C473UJ2nYQGby5H5xwboa0Uj0SnJLYWdQ0jvVvzWpWFVWATc4UqnaxdoUDAmewrM6cSSIAmQBB34orCunFbriK9Z4efZ7gB9erQ1fpi3z/IjQBoTEpOUUIPW/qMAApIDPVM6UV9PumW7RL9zKEP5PuWJoGGnKbWGP/b9G4vMFiWMaSNHBYYMI6OLH4WJ3E+4QBGh2vjjfQ0gobhaLgIerIwCQFYEdl9KddAjaflUEFXal9fIQ8Bz9L3rDhQE5AGBZL6ULZmJe3GnkN6Cc+UWAGyD5zv2rsCG2lvR5ox4UE2mFi6nBJbC5Vj5m9Sz1l0QpRwUkH2kD2QQ5iV6nNmQOcU/mz7ulxluf8+FBJJimYVqK8UkJ6+W6j8Eft9Q8fTpEuEVLxqTWGgOAEUBf87RWDU+iF3A+AxFGsJLc5RC+5BKNTEDlV2qDCjHT7b5wqBKJ3FHulOih9EenlZiI51m6kg5yyxnMdbhasvSh6Az8Mp/4lFo/wSA/mXxNhBrEEmRhFiIE5yYUEYIj5F8fH+93tIuWQqyhXIwCntEOdSSmoei9EYFzj8deXcEzVf8y/N6HQErZcJjyg34caOsfRcJYoxEiCm4icA/btWhdjUNT02B20qnxGFndO4CRUQlyDqTbyVD8LRLK9/95L9+5v9zojLle8xQe30dsxKn7r9TTJH8QQai5iam9lU1ik50lwTKpZb18k4rNdO5cnnYoHzCXeCg38YZxyFt9G7um/MxlID5Qd5Ywq6thDzL7WxvanKeRhCuJ2MTVV0EoJxZKIj9Yv0Ars9mZHkoHoP0ikcW8d5ciDj1Onnbj+XDcYI3FZj0Y2vToZvYi/7eLWi8EnSjaIQrr/AHnrmZK1w3Uicd691U6r3Y0UdnzQEl4Ub/l1uhSaGAg2oEdDxkOdZ3Frvf/C4nTEBmunPlNvnJjVFssdeVVXKLBOZ5eRiJjasHUKnTeJVwolvd/dBI+ypfw1+5ae/0upxd9/gV1lbwX9N2yOwqbxz24cKXZWvOFBAGc3+gQFu8RrF6NAeQ96PlkuRsiNOKPPtJT3JNrLGvVKY8g==
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </xenc:EncryptedData>
+    </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
new file mode 100644
index 0000000..94a6fdb
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
@@ -0,0 +1,30 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+    <samlp:Extensions>
+        <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+        <what:ever xmlns:what="urn:keycloak:ext:what:1.0" what="ever"/>
+    </samlp:Extensions>
+    <samlp:Status>
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+    </samlp:Status>
+    <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+                <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+                    <xenc:CipherData>
+                        <xenc:CipherValue>
+                            OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+                        </xenc:CipherValue>
+                    </xenc:CipherData>
+                </xenc:EncryptedKey>
+            </ds:KeyInfo>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+                    RW2eu9nP2Ez9hfRlug9xC+kFfVF3HZpEb4kIFH33gmVbzrQjPk0l67uXkwRjC82FZZ482QnHCBIqNFlAryds/zTa6wdRvFmhQnIM6WxoAl8TM+e9h8MoKkalMc8J/Qfp+WQ7/XdmCg2pp9VvUZTK+g0+G4aGuL+S5+ssZq4rl9k7LrSYyp6vj+djgvISZiz5hPYJCN/WY/gWXfVuLHSpu4CmZt8D2APtT3ax1WmGcuzStAfTW8q3MFIDNV59hkpFmDb+gvyLNbZ95cDYxofiPXaC5cOTftnSBp68Ay1eienqdttEDo4fyakszdvq128KwXkH9azCg6sqLxli6B8l2xdq41MeuJO54VqmOhhLxwKy42NtnJvK/NkNwttH4yMwDPpPbC4vOKCXxT2r2F7jjvJQNB2VFv+oiUAWSSc3fGQcc2uNlx9YQVuzTmjqc7fXAWCGgYoogC8AeNWni204bnBoVpFrEo3gzuOe2fFsddJIclglmTH1hWf31FXUHDO2nl/lT4puQVTo+I+d6jpiV+qdp823NDntRxljRlUJO2AzSTXuIIGtF5q5KWyEi9Nj93BCWa1Llcddkn3ZEZMvDwR4MacwUj8G8hwoH73VvT3jAiakjSpNEIqYCzofeejdfN/gEuuAUfe8uNbTu+gBS+iP3QJe3Pc0Fs/lKJzd3frPNj7xb83wpOf865EQQoOozhnRIKKcMReSjakr/Px5NNooeiJcWEreDagQO2TbwTnHg1kCNG3BAXV/2lV3XBU4afZBoUfxAzYWFOl6xFCAPzhQCPL1SFJp1VRADY/1MU2Kaje5AZoJ4jjph8+yspxBvjic1vC1uYRGW8LWRind9w4eVhCm0LfPiFRCpP+jKPQOJzcNH580/nIMFXPHHnLKv/It7Qex1unDv/QjkuCFFHR6SWJm4WBrwDek+MyOIvgT6o878Cu0Ps472QpoYBQ+7l2WoylWdG1lHZV1UiHPj7PLHPNAL4rbbN3U88fS6N9OJHegQTfcX0i/1KPk4IN/5Z+/15dHI658BINjRvI/6O1QqaTVZkqM8ORcoGpn6BjAiz5rRhjWpOCwlmT+VzOAp3IqACURS1X+txjWE2mfVjlHLJsvyGRDLv1dUR3IeStDAEfsjR/ruRgn5XTFpYaccB/u//DJonJr5A+KFiLbYl+sbbSVAoQCAiAdxKdUpKPx7C473UJ2nYQGby5H5xwboa0Uj0SnJLYWdQ0jvVvzWpWFVWATc4UqnaxdoUDAmewrM6cSSIAmQBB34orCunFbriK9Z4efZ7gB9erQ1fpi3z/IjQBoTEpOUUIPW/qMAApIDPVM6UV9PumW7RL9zKEP5PuWJoGGnKbWGP/b9G4vMFiWMaSNHBYYMI6OLH4WJ3E+4QBGh2vjjfQ0gobhaLgIerIwCQFYEdl9KddAjaflUEFXal9fIQ8Bz9L3rDhQE5AGBZL6ULZmJe3GnkN6Cc+UWAGyD5zv2rsCG2lvR5ox4UE2mFi6nBJbC5Vj5m9Sz1l0QpRwUkH2kD2QQ5iV6nNmQOcU/mz7ulxluf8+FBJJimYVqK8UkJ6+W6j8Eft9Q8fTpEuEVLxqTWGgOAEUBf87RWDU+iF3A+AxFGsJLc5RC+5BKNTEDlV2qDCjHT7b5wqBKJ3FHulOih9EenlZiI51m6kg5yyxnMdbhasvSh6Az8Mp/4lFo/wSA/mXxNhBrEEmRhFiIE5yYUEYIj5F8fH+93tIuWQqyhXIwCntEOdSSmoei9EYFzj8deXcEzVf8y/N6HQErZcJjyg34caOsfRcJYoxEiCm4icA/btWhdjUNT02B20qnxGFndO4CRUQlyDqTbyVD8LRLK9/95L9+5v9zojLle8xQe30dsxKn7r9TTJH8QQai5iam9lU1ik50lwTKpZb18k4rNdO5cnnYoHzCXeCg38YZxyFt9G7um/MxlID5Qd5Ywq6thDzL7WxvanKeRhCuJ2MTVV0EoJxZKIj9Yv0Ars9mZHkoHoP0ikcW8d5ciDj1Onnbj+XDcYI3FZj0Y2vToZvYi/7eLWi8EnSjaIQrr/AHnrmZK1w3Uicd691U6r3Y0UdnzQEl4Ub/l1uhSaGAg2oEdDxkOdZ3Frvf/C4nTEBmunPlNvnJjVFssdeVVXKLBOZ5eRiJjasHUKnTeJVwolvd/dBI+ypfw1+5ae/0upxd9/gV1lbwX9N2yOwqbxz24cKXZWvOFBAGc3+gQFu8RrF6NAeQ96PlkuRsiNOKPPtJT3JNrLGvVKY8g==
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </xenc:EncryptedData>
+    </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
new file mode 100644
index 0000000..8c4ab20
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
@@ -0,0 +1,32 @@
+<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8081/auth/realms/saml-demo/protocol/saml" ID="ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4" IssueInstant="2016-11-01T14:36:43.194Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8080/sales-post-enc/</saml:Issuer>
+    <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
+        <dsig:SignedInfo>
+            <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+            <dsig:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+            <dsig:Reference URI="#ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4">
+                <dsig:Transforms>
+                    <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+                    <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+                </dsig:Transforms>
+                <dsig:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+                <dsig:DigestValue>zeWNo5eav5tFOOCEJ1YU9eINkPnBSfixzAr8AOC4R4c=</dsig:DigestValue>
+            </dsig:Reference>
+        </dsig:SignedInfo>
+        <dsig:SignatureValue>
+            pyOiS1LsV/XR08zhcN6IqSYuKTDln4otmCvZxCc07ORP1C9jragu8V8rEE09qt/zBcdw7Arb8eLNNC6oCnrnMxuvzRInVTwt7T5K3t0UlzRWOb3HMElhcWFEgDzh6uKw5Cr45A01XNpojtJWCML/qU2Enyyy80FBlCJNcbzyLxE=
+        </dsig:SignatureValue>
+        <dsig:KeyInfo>
+            <dsig:KeyValue>
+                <dsig:RSAKeyValue>
+                    <dsig:Modulus>
+                        2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEik=
+                    </dsig:Modulus>
+                    <dsig:Exponent>AQAB</dsig:Exponent>
+                </dsig:RSAKeyValue>
+            </dsig:KeyValue>
+        </dsig:KeyInfo>
+    </dsig:Signature>
+    <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">bburke</saml:NameID>
+    <samlp:SessionIndex>a3b2df1c-1095-487b-8b56-f62818c449e3</samlp:SessionIndex>
+</samlp:LogoutRequest>
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java
index e644a80..be5f551 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java
@@ -18,7 +18,6 @@
 package org.keycloak.models;
 
 import org.keycloak.common.ClientConnection;
-import org.keycloak.models.utils.RealmImporter;
 
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.UriInfo;
@@ -52,8 +51,6 @@ public interface KeycloakContext {
 
     void setConnection(ClientConnection connection);
 
-    RealmImporter getRealmManager();
-
     Locale resolveLocale(UserModel user);
 
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index cb10891..f3242eb 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -20,7 +20,6 @@ package org.keycloak.models;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.models.cache.UserCache;
 import org.keycloak.provider.Provider;
-import org.keycloak.scripting.ScriptingProvider;
 import org.keycloak.storage.federated.UserFederatedStorageProvider;
 
 import java.util.Set;
@@ -157,8 +156,4 @@ public interface KeycloakSession {
      */
     KeyManager keys();
 
-    /**
-     * Keycloak scripting support.
-     */
-    ScriptingProvider scripting();
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
index 95dfc53..8833c8a 100755
--- a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -17,10 +17,6 @@
 
 package org.keycloak.models;
 
-import org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory;
-import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
-import org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory;
-import org.keycloak.policy.HistoryPasswordPolicyProviderFactory;
 import org.keycloak.policy.PasswordPolicyProvider;
 
 import java.io.Serializable;
@@ -33,6 +29,18 @@ import java.util.Set;
  */
 public class PasswordPolicy implements Serializable {
 
+    public static final String HASH_ALGORITHM_ID = "hashAlgorithm";
+
+    public static final String HASH_ALGORITHM_DEFAULT = "pbkdf2";
+
+    public static final String HASH_ITERATIONS_ID = "hashIterations";
+
+    public static final int HASH_ITERATIONS_DEFAULT = 20000;
+
+    public static final String PASSWORD_HISTORY_ID = "passwordHistory";
+
+    public static final String FORCE_EXPIRED_ID = "forceExpiredPasswordChange";
+
     private String policyString;
     private Map<String, Object> policyConfig;
 
@@ -84,32 +92,32 @@ public class PasswordPolicy implements Serializable {
     }
 
     public String getHashAlgorithm() {
-        if (policyConfig.containsKey(HashAlgorithmPasswordPolicyProviderFactory.ID)) {
-            return getPolicyConfig(HashAlgorithmPasswordPolicyProviderFactory.ID);
+        if (policyConfig.containsKey(HASH_ALGORITHM_ID)) {
+            return getPolicyConfig(HASH_ALGORITHM_ID);
         } else {
-            return HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE;
+            return HASH_ALGORITHM_DEFAULT;
         }
     }
 
     public int getHashIterations() {
-        if (policyConfig.containsKey(HashIterationsPasswordPolicyProviderFactory.ID)) {
-            return getPolicyConfig(HashIterationsPasswordPolicyProviderFactory.ID);
+        if (policyConfig.containsKey(HASH_ITERATIONS_ID)) {
+            return getPolicyConfig(HASH_ITERATIONS_ID);
         } else {
-            return HashIterationsPasswordPolicyProviderFactory.DEFAULT_VALUE;
+            return HASH_ITERATIONS_DEFAULT;
         }
     }
 
     public int getExpiredPasswords() {
-        if (policyConfig.containsKey(HistoryPasswordPolicyProviderFactory.ID)) {
-            return getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
+        if (policyConfig.containsKey(PASSWORD_HISTORY_ID)) {
+            return getPolicyConfig(PASSWORD_HISTORY_ID);
         } else {
             return -1;
         }
     }
 
     public int getDaysToExpirePassword() {
-        if (policyConfig.containsKey(ForceExpiredPasswordPolicyProviderFactory.ID)) {
-            return getPolicyConfig(ForceExpiredPasswordPolicyProviderFactory.ID);
+        if (policyConfig.containsKey(FORCE_EXPIRED_ID)) {
+            return getPolicyConfig(FORCE_EXPIRED_ID);
         } else {
             return -1;
         }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index 1765bad..707b96a 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -19,8 +19,6 @@ package org.keycloak.models;
 
 import org.jboss.logging.Logger;
 import org.keycloak.component.ComponentModel;
-import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.services.managers.UserManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -74,7 +72,8 @@ public class UserFederationManager implements UserProvider {
     }
 
     public UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
-        return KeycloakModelUtils.getFederationProviderInstance(session, model);
+        UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
+        return factory.getInstance(session, model);
     }
 
     public UserFederationProvider getFederationLink(RealmModel realm, UserModel user) {
@@ -122,7 +121,7 @@ public class UserFederationManager implements UserProvider {
     }
 
     protected void deleteInvalidUser(final RealmModel realm, final UserModel user) {
-        KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+        runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
 
             @Override
             public void run(KeycloakSession session) {
@@ -136,6 +135,29 @@ public class UserFederationManager implements UserProvider {
         });
     }
 
+    private  static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) {
+        KeycloakSession session = factory.create();
+        KeycloakTransaction tx = session.getTransactionManager();
+        try {
+            tx.begin();
+            task.run(session);
+
+            if (tx.isActive()) {
+                if (tx.getRollbackOnly()) {
+                    tx.rollback();
+                } else {
+                    tx.commit();
+                }
+            }
+        } catch (RuntimeException re) {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+            throw re;
+        } finally {
+            session.close();
+        }
+    }
 
     protected UserModel validateAndProxyUser(RealmModel realm, UserModel user) {
         UserModel managed = managedUsers.get(user.getId());
diff --git a/server-spi/src/main/java/org/keycloak/models/UserManager.java b/server-spi/src/main/java/org/keycloak/models/UserManager.java
new file mode 100755
index 0000000..81b2b51
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models;
+
+import org.keycloak.models.session.UserSessionPersisterProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserManager {
+
+    private KeycloakSession session;
+
+    public UserManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+    public boolean removeUser(RealmModel realm, UserModel user) {
+        return removeUser(realm, user, session.users());
+    }
+
+    public boolean removeUser(RealmModel realm, UserModel user, UserProvider userProvider) {
+        UserSessionProvider sessions = session.sessions();
+        if (sessions != null) {
+            sessions.onUserRemoved(realm, user);
+        }
+
+        UserSessionPersisterProvider sessionsPersister = session.getProvider(UserSessionPersisterProvider.class);
+        if (sessionsPersister != null) {
+            sessionsPersister.onUserRemoved(realm, user);
+        }
+
+        if (userProvider.removeUser(realm, user)) {
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
new file mode 100644
index 0000000..a03a55d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RoleUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RoleModel;
+
+import java.util.Set;
+import java.util.stream.StreamSupport;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RoleUtils {
+
+    /**
+     *
+     * @param groups
+     * @param targetGroup
+     * @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
+     */
+    public static boolean isMember(Set<GroupModel> groups, GroupModel targetGroup) {
+        if (groups.contains(targetGroup)) return true;
+
+        for (GroupModel mapping : groups) {
+            GroupModel child = mapping;
+            while(child.getParent() != null) {
+                if (child.getParent().equals(targetGroup)) return true;
+                child = child.getParent();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param roles
+     * @param targetRole
+     * @return true if targetRole is in roles (directly or indirectly via composite role)
+     */
+    public static boolean hasRole(Set<RoleModel> roles, RoleModel targetRole) {
+        if (roles.contains(targetRole)) return true;
+
+        for (RoleModel mapping : roles) {
+            if (mapping.hasRole(targetRole)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks whether the {@code targetRole} is contained in the given group or its parents
+     * (if requested)
+     * @param group Group to check role for
+     * @param targetRole
+     * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
+     * @return true if targetRole is in roles (directly or indirectly via composite role)
+     */
+    public static boolean hasRoleFromGroup(GroupModel group, RoleModel targetRole, boolean checkParentGroup) {
+        if (group.hasRole(targetRole))
+            return true;
+
+        if (checkParentGroup) {
+            GroupModel parent = group.getParent();
+            return parent != null && hasRoleFromGroup(parent, targetRole, true);
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the {@code targetRole} is contained in any of the {@code groups} or their parents
+     * (if requested)
+     * @param groups
+     * @param targetRole
+     * @param checkParentGroup When {@code true}, also parent group is recursively checked for role
+     * @return true if targetRole is in roles (directly or indirectly via composite role)
+     */
+    public static boolean hasRoleFromGroup(Iterable<GroupModel> groups, RoleModel targetRole, boolean checkParentGroup) {
+        if (groups == null) {
+            return false;
+        }
+
+        return StreamSupport.stream(groups.spliterator(), false)
+                .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup));
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/provider/Spi.java b/server-spi/src/main/java/org/keycloak/provider/Spi.java
index b9c47f8..8043bcf 100644
--- a/server-spi/src/main/java/org/keycloak/provider/Spi.java
+++ b/server-spi/src/main/java/org/keycloak/provider/Spi.java
@@ -17,9 +17,6 @@
 
 package org.keycloak.provider;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
index df6c37a..c159020 100644
--- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java
@@ -26,7 +26,7 @@ import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.DefaultRoles;
-import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.storage.StorageId;
 
 import java.util.Collections;
@@ -135,7 +135,7 @@ public abstract class AbstractUserAdapter implements UserModel {
     @Override
     public boolean isMemberOf(GroupModel group) {
         Set<GroupModel> roles = getGroups();
-        return KeycloakModelUtils.isMember(roles, group);
+        return RoleUtils.isMember(roles, group);
     }
 
     @Override
@@ -172,8 +172,8 @@ public abstract class AbstractUserAdapter implements UserModel {
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        return KeycloakModelUtils.hasRole(roles, role)
-          || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+        return RoleUtils.hasRole(roles, role)
+          || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
index 865f454..c93dac4 100644
--- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java
@@ -25,7 +25,7 @@ import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.DefaultRoles;
-import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RoleUtils;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.federated.UserFederatedStorageProvider;
 
@@ -140,7 +140,7 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
     @Override
     public boolean isMemberOf(GroupModel group) {
         Set<GroupModel> roles = getGroups();
-        return KeycloakModelUtils.isMember(roles, group);
+        return RoleUtils.isMember(roles, group);
     }
 
     @Override
@@ -177,8 +177,8 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
     @Override
     public boolean hasRole(RoleModel role) {
         Set<RoleModel> roles = getRoleMappings();
-        return KeycloakModelUtils.hasRole(roles, role)
-          || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true);
+        return RoleUtils.hasRole(roles, role)
+          || RoleUtils.hasRoleFromGroup(getGroups(), role, true);
     }
 
     @Override
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index bbd588e..c7ee486 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -15,54 +15,21 @@
 # limitations under the License.
 #
 
-org.keycloak.models.UserFederationSpi
-org.keycloak.storage.UserStorageProviderSpi
-org.keycloak.storage.federated.UserFederatedStorageProviderSpi
-org.keycloak.mappers.UserFederationMapperSpi
-org.keycloak.models.RealmSpi
-org.keycloak.models.UserSessionSpi
-org.keycloak.models.UserSpi
-org.keycloak.models.session.UserSessionPersisterSpi
-org.keycloak.models.dblock.DBLockSpi
-org.keycloak.migration.MigrationSpi
-org.keycloak.events.EventListenerSpi
-org.keycloak.events.EventStoreSpi
-org.keycloak.exportimport.ExportSpi
-org.keycloak.exportimport.ImportSpi
-org.keycloak.timer.TimerSpi
-org.keycloak.scripting.ScriptingSpi
-org.keycloak.services.managers.BruteForceProtectorSpi
-org.keycloak.services.resource.RealmResourceSPI
-org.keycloak.protocol.ClientInstallationSpi
-org.keycloak.protocol.LoginProtocolSpi
-org.keycloak.protocol.ProtocolMapperSpi
-org.keycloak.broker.provider.IdentityProviderSpi
-org.keycloak.broker.provider.IdentityProviderMapperSpi
-org.keycloak.broker.social.SocialProviderSpi
-org.keycloak.forms.account.AccountSpi
-org.keycloak.forms.login.LoginFormsSpi
-org.keycloak.email.EmailSenderSpi
-org.keycloak.email.EmailTemplateSpi
-org.keycloak.theme.ThemeSpi
-org.keycloak.truststore.TruststoreSpi
-org.keycloak.connections.httpclient.HttpClientSpi
-org.keycloak.models.cache.CacheRealmProviderSpi
-org.keycloak.models.cache.CacheUserProviderSpi
-org.keycloak.authentication.AuthenticatorSpi
-org.keycloak.authentication.ClientAuthenticatorSpi
-org.keycloak.authentication.RequiredActionSpi
-org.keycloak.authentication.FormAuthenticatorSpi
-org.keycloak.authentication.FormActionSpi
-org.keycloak.cluster.ClusterSpi
-org.keycloak.authorization.policy.provider.PolicySpi
-org.keycloak.authorization.store.StoreFactorySpi
-org.keycloak.authorization.AuthorizationSpi
-org.keycloak.models.cache.authorization.CachedStoreFactorySpi
-org.keycloak.protocol.oidc.TokenIntrospectionSpi
-org.keycloak.policy.PasswordPolicySpi
-org.keycloak.policy.PasswordPolicyManagerSpi
-org.keycloak.transaction.TransactionManagerLookupSpi
-org.keycloak.credential.hash.PasswordHashSpi
-org.keycloak.credential.CredentialSpi
-org.keycloak.keys.PublicKeyStorageSpi
-org.keycloak.keys.KeySpi
\ No newline at end of file
+#
+# 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.
+#
+
+org.keycloak.storage.UserStorageProviderSpi
\ No newline at end of file
diff --git a/server-spi-private/pom.xml b/server-spi-private/pom.xml
new file mode 100755
index 0000000..a1d3df2
--- /dev/null
+++ b/server-spi-private/pom.xml
@@ -0,0 +1,98 @@
+<?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.
+  -->
+
+<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">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>2.4.0.CR1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-server-spi-private</artifactId>
+    <name>Keycloak Server Private SPI</name>
+    <description/>
+
+    <properties>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.transaction</groupId>
+            <artifactId>jboss-transaction-api_1.2_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+   </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
new file mode 100644
index 0000000..226949d
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreFactorySpi.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.authorization;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class CachedStoreFactorySpi implements Spi {
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "authz-fached-store-factory";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return CachedStoreFactoryProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return CachedStoreProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
new file mode 100644
index 0000000..b8563cb
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/cache/authorization/CachedStoreProviderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.authorization;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface CachedStoreProviderFactory extends ProviderFactory<CachedStoreFactoryProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockManager.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockManager.java
new file mode 100644
index 0000000..c41d371
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockManager.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.dblock;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RealmProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DBLockManager {
+
+    protected static final Logger logger = Logger.getLogger(DBLockManager.class);
+
+    private final KeycloakSession session;
+
+    public DBLockManager(KeycloakSession session) {
+        this.session = session;
+    }
+
+
+    public void checkForcedUnlock() {
+        if (Boolean.getBoolean("keycloak.dblock.forceUnlock")) {
+            DBLockProvider lock = getDBLock();
+            if (lock.supportsForcedUnlock()) {
+                logger.warn("Forced release of DB lock at startup requested by System property. Make sure to not use this in production environment! And especially when more cluster nodes are started concurrently.");
+                lock.releaseLock();
+            } else {
+                throw new IllegalStateException("Forced unlock requested, but provider " + lock + " doesn't support it");
+            }
+        }
+    }
+
+
+    // Try to detect ID from realmProvider
+    public DBLockProvider getDBLock() {
+        String realmProviderId = getRealmProviderId();
+        return session.getProvider(DBLockProvider.class, realmProviderId);
+    }
+
+    public DBLockProviderFactory getDBLockFactory() {
+        String realmProviderId = getRealmProviderId();
+        return (DBLockProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(DBLockProvider.class, realmProviderId);
+    }
+
+    private String getRealmProviderId() {
+        RealmProviderFactory realmProviderFactory = (RealmProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(RealmProvider.class);
+        return realmProviderFactory.getId();
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
new file mode 100644
index 0000000..d7d6053
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.dblock;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * Global database lock to ensure that some actions in DB can be done just be one cluster node at a time.
+ *
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface DBLockProvider extends Provider {
+
+
+    /**
+     * Try to retrieve DB lock or wait if retrieve was unsuccessful. Throw exception if lock can't be retrieved within specified timeout (900 seconds by default)
+     */
+    void waitForLock();
+
+
+    /**
+     * Release previously acquired lock
+     */
+    void releaseLock();
+
+    /**
+     * Check if I have lock
+     *
+     * @return
+     */
+    boolean hasLock();
+
+
+    /**
+     * @return true if provider supports forced unlock at startup
+     */
+    boolean supportsForcedUnlock();
+
+
+    /**
+     * Will destroy whole state of DB lock (drop table/collection to track locking).
+     * */
+    void destroyLockInfo();
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
new file mode 100644
index 0000000..747a168
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.dblock;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface DBLockProviderFactory extends ProviderFactory<DBLockProvider> {
+
+    /**
+     * Useful for testing to override provided configuration
+     */
+    void setTimeouts(long lockRecheckTimeMillis, long lockWaitTimeoutMillis);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockSpi.java b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
new file mode 100644
index 0000000..cd78f0f
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/dblock/DBLockSpi.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.models.dblock;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DBLockSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "dblock";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return DBLockProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return DBLockProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
new file mode 100644
index 0000000..f5e58d3
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.session;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Persistence of userSessions is disabled . Useful just if you never need survive of userSessions/clientSessions
+ * among server restart. Offline sessions / offline tokens will be invalid after server restart as well,
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DisabledUserSessionPersisterProvider implements UserSessionPersisterProviderFactory, UserSessionPersisterProvider {
+
+    public static final String ID = "disabled";
+
+    @Override
+    public UserSessionPersisterProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public void createUserSession(UserSessionModel userSession, boolean offline) {
+
+    }
+
+    @Override
+    public void createClientSession(ClientSessionModel clientSession, boolean offline) {
+
+    }
+
+    @Override
+    public void updateUserSession(UserSessionModel userSession, boolean offline) {
+
+    }
+
+    @Override
+    public void removeUserSession(String userSessionId, boolean offline) {
+
+    }
+
+    @Override
+    public void removeClientSession(String clientSessionId, boolean offline) {
+
+    }
+
+    @Override
+    public void onRealmRemoved(RealmModel realm) {
+
+    }
+
+    @Override
+    public void onClientRemoved(RealmModel realm, ClientModel client) {
+
+    }
+
+    @Override
+    public void onUserRemoved(RealmModel realm, UserModel user) {
+
+    }
+
+    @Override
+    public void clearDetachedUserSessions() {
+
+    }
+
+    @Override
+    public void updateAllTimestamps(int time) {
+
+    }
+
+    @Override
+    public List<UserSessionModel> loadUserSessions(int firstResult, int maxResults, boolean offline) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public int getUserSessionsCount(boolean offline) {
+        return 0;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
new file mode 100644
index 0000000..5990eea
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.session;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PersistentClientSessionModel {
+
+    private String clientSessionId;
+    private String userSessionId;
+    private String clientId;
+    private String userId;
+    private int timestamp;
+    private String data;
+
+    public String getClientSessionId() {
+        return clientSessionId;
+    }
+
+    public void setClientSessionId(String clientSessionId) {
+        this.clientSessionId = clientSessionId;
+    }
+
+    public String getUserSessionId() {
+        return userSessionId;
+    }
+
+    public void setUserSessionId(String userSessionId) {
+        this.userSessionId = userSessionId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getData() {
+        return data;
+    }
+
+    public void setData(String data) {
+        this.data = data;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
new file mode 100644
index 0000000..d7e0a04
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionModel.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.session;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PersistentUserSessionModel {
+
+    private String userSessionId;
+    private int lastSessionRefresh;
+
+    private String data;
+
+    public String getUserSessionId() {
+        return userSessionId;
+    }
+
+    public void setUserSessionId(String userSessionId) {
+        this.userSessionId = userSessionId;
+    }
+
+    public int getLastSessionRefresh() {
+        return lastSessionRefresh;
+    }
+
+    public void setLastSessionRefresh(int lastSessionRefresh) {
+        this.lastSessionRefresh = lastSessionRefresh;
+    }
+
+
+    public String getData() {
+        return data;
+    }
+
+    public void setData(String data) {
+        this.data = data;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
new file mode 100644
index 0000000..2c0b98c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.session;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UserSessionPersisterProviderFactory extends ProviderFactory<UserSessionPersisterProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.java
new file mode 100644
index 0000000..7e9dc4f
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterSpi.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.models.session;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserSessionPersisterSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "userSessionPersister";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return UserSessionPersisterProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return UserSessionPersisterProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
new file mode 100644
index 0000000..5efbb1c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.ComponentRepresentation;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ComponentUtil {
+
+    public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, ComponentRepresentation component) {
+        return getComponentConfigProperties(session, component.getProviderType(), component.getProviderId());
+    }
+
+    public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, ComponentModel component) {
+        return getComponentConfigProperties(session, component.getProviderType(), component.getProviderId());
+    }
+
+    public static ComponentFactory getComponentFactory(KeycloakSession session, ComponentRepresentation component) {
+        return getComponentFactory(session, component.getProviderType(), component.getProviderId());
+    }
+
+    public static ComponentFactory getComponentFactory(KeycloakSession session, ComponentModel component) {
+        return getComponentFactory(session, component.getProviderType(), component.getProviderId());
+    }
+
+    private static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
+        try {
+            ComponentFactory componentFactory = getComponentFactory(session, providerType, providerId);
+            List<ProviderConfigProperty> l = componentFactory.getConfigProperties();
+            Map<String, ProviderConfigProperty> properties = new HashMap<>();
+            for (ProviderConfigProperty p : l) {
+                properties.put(p.getName(), p);
+            }
+            List<ProviderConfigProperty> common = componentFactory.getCommonProviderConfigProperties();
+            for (ProviderConfigProperty p : common) {
+                properties.put(p.getName(), p);
+            }
+
+            return properties;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static ComponentFactory getComponentFactory(KeycloakSession session, String providerType, String providerId) {
+        Class<? extends Provider> provider = session.getProviderClass(providerType);
+        if (provider == null) {
+            throw new IllegalArgumentException("Invalid provider type '" + providerType + "'");
+        }
+
+        ProviderFactory<? extends Provider> f = session.getKeycloakSessionFactory().getProviderFactory(provider, providerId);
+        if (f == null) {
+            throw new IllegalArgumentException("No such provider '" + providerId + "'");
+        }
+
+        ComponentFactory cf = (ComponentFactory) f;
+        return cf;
+    }
+
+    public static void notifyCreated(KeycloakSession session, RealmModel realm, ComponentModel model) {
+        ComponentFactory factory = getComponentFactory(session, model);
+        factory.onCreate(session, realm, model);
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/CredentialValidation.java
new file mode 100755
index 0000000..598e3e9
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CredentialValidation {
+
+    public static boolean validOTP(RealmModel realm, String token, String secret) {
+        OTPPolicy policy = realm.getOTPPolicy();
+        if (policy.getType().equals(UserCredentialModel.TOTP)) {
+            TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+            return validator.validateTOTP(token, secret.getBytes());
+        } else {
+            HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+            int c = validator.validateHOTP(token, secret, policy.getInitialCounter());
+            return c > -1;
+        }
+
+    }
+
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
new file mode 100644
index 0000000..30ff7d7
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultKeyProviders {
+
+    public static void createProviders(RealmModel realm) {
+        ComponentModel generated = new ComponentModel();
+        generated.setName("rsa-generated");
+        generated.setParentId(realm.getId());
+        generated.setProviderId("rsa-generated");
+        generated.setProviderType(KeyProvider.class.getName());
+
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        config.putSingle("priority", "100");
+        generated.setConfig(config);
+
+        realm.addComponentModel(generated);
+    }
+
+    public static void createProviders(RealmModel realm, String privateKeyPem, String certificatePem) {
+        ComponentModel rsa = new ComponentModel();
+        rsa.setName("rsa");
+        rsa.setParentId(realm.getId());
+        rsa.setProviderId("rsa");
+        rsa.setProviderType(KeyProvider.class.getName());
+
+        MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+        config.putSingle("priority", "100");
+        config.putSingle("privateKey", privateKeyPem);
+        if (certificatePem != null) {
+            config.putSingle("certificate", certificatePem);
+        }
+        rsa.setConfig(config);
+
+        realm.addComponentModel(rsa);
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java
new file mode 100755
index 0000000..db38b64
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredActionProviderModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DefaultRequiredActions {
+    public static void addActions(RealmModel realm) {
+        if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.VERIFY_EMAIL.name()) == null) {
+            RequiredActionProviderModel verifyEmail = new RequiredActionProviderModel();
+            verifyEmail.setEnabled(true);
+            verifyEmail.setAlias(UserModel.RequiredAction.VERIFY_EMAIL.name());
+            verifyEmail.setName("Verify Email");
+            verifyEmail.setProviderId(UserModel.RequiredAction.VERIFY_EMAIL.name());
+            verifyEmail.setDefaultAction(false);
+            realm.addRequiredActionProvider(verifyEmail);
+
+        }
+
+        if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_PROFILE.name()) == null) {
+            RequiredActionProviderModel updateProfile = new RequiredActionProviderModel();
+            updateProfile.setEnabled(true);
+            updateProfile.setAlias(UserModel.RequiredAction.UPDATE_PROFILE.name());
+            updateProfile.setName("Update Profile");
+            updateProfile.setProviderId(UserModel.RequiredAction.UPDATE_PROFILE.name());
+            updateProfile.setDefaultAction(false);
+            realm.addRequiredActionProvider(updateProfile);
+        }
+
+        if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.CONFIGURE_TOTP.name()) == null) {
+            RequiredActionProviderModel totp = new RequiredActionProviderModel();
+            totp.setEnabled(true);
+            totp.setAlias(UserModel.RequiredAction.CONFIGURE_TOTP.name());
+            totp.setName("Configure OTP");
+            totp.setProviderId(UserModel.RequiredAction.CONFIGURE_TOTP.name());
+            totp.setDefaultAction(false);
+            realm.addRequiredActionProvider(totp);
+        }
+
+        if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_PASSWORD.name()) == null) {
+            RequiredActionProviderModel updatePassword = new RequiredActionProviderModel();
+            updatePassword.setEnabled(true);
+            updatePassword.setAlias(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+            updatePassword.setName("Update Password");
+            updatePassword.setProviderId(UserModel.RequiredAction.UPDATE_PASSWORD.name());
+            updatePassword.setDefaultAction(false);
+            realm.addRequiredActionProvider(updatePassword);
+        }
+
+        if (realm.getRequiredActionProviderByAlias("terms_and_conditions") == null) {
+            RequiredActionProviderModel termsAndConditions = new RequiredActionProviderModel();
+            termsAndConditions.setEnabled(false);
+            termsAndConditions.setAlias("terms_and_conditions");
+            termsAndConditions.setName("Terms and Conditions");
+            termsAndConditions.setProviderId("terms_and_conditions");
+            termsAndConditions.setDefaultAction(false);
+            realm.addRequiredActionProvider(termsAndConditions);
+        }
+
+
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/FormMessage.java b/server-spi-private/src/main/java/org/keycloak/models/utils/FormMessage.java
new file mode 100755
index 0000000..e3b6a23
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/FormMessage.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.utils;
+
+import java.util.Arrays;
+
+/**
+ * Message (eg. error) to be shown in form.
+ * 
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class FormMessage {
+
+	/**
+	 * Value used for {@link #field} if message is global (not tied to any specific form field)
+	 */
+	public static final String GLOBAL = "global";
+
+	private String field;
+	private String message;
+	private Object[] parameters;
+
+	public FormMessage() {
+	}
+
+	/**
+	 * Create message.
+	 * 
+	 * @param field this message is for. {@link #GLOBAL} is used if null
+	 * @param message key for the message
+	 * @param parameters to be formatted into message
+	 */
+	public FormMessage(String field, String message, Object... parameters) {
+		this(field, message);
+		this.parameters = parameters;
+	}
+
+    public FormMessage(String message, Object...parameters) {
+        this(null, message, parameters);
+    }
+	
+	/**
+     * Create message without parameters.
+     * 
+     * @param field this message is for. {@link #GLOBAL} is used if null
+     * @param message key for the message
+     */
+    public FormMessage(String field, String message) {
+        super();
+        if (field == null)
+            field = GLOBAL;
+        this.field = field;
+        this.message = message;
+    }
+
+	public String getField() {
+		return field;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public Object[] getParameters() {
+		return parameters;
+	}
+
+	@Override
+	public String toString() {
+		return "FormMessage [field=" + field + ", message=" + message + ", parameters=" + Arrays.toString(parameters) + "]";
+	}
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java b/server-spi-private/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
new file mode 100644
index 0000000..73889e0
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/PostMigrationEvent.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.provider.ProviderEvent;
+
+/**
+ * Executed at startup after model migration is finished
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PostMigrationEvent implements ProviderEvent {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RealmImporter.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RealmImporter.java
new file mode 100644
index 0000000..e2c3401
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RealmImporter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+
+/**
+ * Helper interface used just because RealmManager is in keycloak-services and not accessible for ImportUtils
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface RealmImporter {
+
+    RealmModel importRealm(RealmRepresentation rep);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
new file mode 100644
index 0000000..e55e065
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RealmInfoUtil {
+
+    public static int getDettachedClientSessionLifespan(RealmModel realm) {
+        int lifespan = realm.getAccessCodeLifespanLogin();
+        if (realm.getAccessCodeLifespanUserAction() > lifespan) {
+            lifespan = realm.getAccessCodeLifespanUserAction();
+        }
+        if (realm.getAccessCodeLifespan() > lifespan) {
+            lifespan = realm.getAccessCodeLifespan();
+        }
+        return lifespan;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
new file mode 100644
index 0000000..2dbec10
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * A criteria that matches a property based on its annotations
+ *
+ * @see PropertyCriteria
+ */
+public class AnnotatedPropertyCriteria implements PropertyCriteria {
+    private final Class<? extends Annotation> annotationClass;
+
+    public AnnotatedPropertyCriteria(Class<? extends Annotation> annotationClass) {
+        this.annotationClass = annotationClass;
+    }
+
+    @Override
+    public boolean methodMatches(Method m) {
+        return m.isAnnotationPresent(annotationClass);
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
new file mode 100644
index 0000000..fac6a89
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Method;
+
+public interface MethodProperty<V> extends Property<V> {
+
+    Method getAnnotatedElement();
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
new file mode 100644
index 0000000..e81734a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/NamedPropertyCriteria.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.beans.Introspector;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * A criteria that matches a property based on name
+ *
+ * @see PropertyCriteria
+ */
+public class NamedPropertyCriteria implements PropertyCriteria {
+    private final String[] propertyNames;
+
+    public NamedPropertyCriteria(String... propertyNames) {
+        this.propertyNames = propertyNames;
+    }
+
+    public boolean fieldMatches(Field f) {
+        for (String propertyName : propertyNames) {
+            if (propertyName.equals(f.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean methodMatches(Method m) {
+        String[] validPrefix = {"get", "is"};
+        for (String propertyName : propertyNames) {
+            for (String prefix : validPrefix) {
+                if (m.getName().startsWith(prefix) &&
+                        Introspector.decapitalize(m.getName().substring(prefix.length())).equals(propertyName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Properties.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Properties.java
new file mode 100755
index 0000000..ebb1381
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Properties.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Method;
+
+/**
+ * Utility class for working with JavaBean style properties
+ *
+ * @see Property
+ */
+public class Properties {
+
+    private Properties() {
+    }
+
+    /**
+     * Create a JavaBean style property from the specified method
+     *
+     * @param <V>
+     * @param method
+     *
+     * @return
+     *
+     * @throws IllegalArgumentException if the method does not match JavaBean conventions
+     * @see http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html
+     */
+    public static <V> MethodProperty<V> createProperty(Method method) {
+        return new MethodPropertyImpl<V>(method);
+    }
+
+    /**
+     * Indicates whether this method is a valid property method.
+     */
+    public static <V> boolean isProperty(Method method) {
+        try {
+            new MethodPropertyImpl<V>(method);
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+}
+
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Property.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Property.java
new file mode 100644
index 0000000..fcf6fe4
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/Property.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Member;
+import java.lang.reflect.Type;
+
+/**
+ * A representation of a JavaBean style property
+ *
+ * @param <V> the type of the properties value
+ *
+ * @see Properties
+ */
+public interface Property<V> {
+
+    /**
+     * Returns the name of the property. If the property is a field, then the field name is returned. Otherwise, if the
+     * property is a method, then the name that is returned is the getter method name without the "get" or "is" prefix,
+     * and a lower case first letter.
+     *
+     * @return The name of the property
+     */
+    String getName();
+
+    /**
+     * Returns the property type
+     *
+     * @return The property type
+     */
+    Type getBaseType();
+
+    /**
+     * Returns the property type
+     *
+     * @return The property type
+     */
+    Class<V> getJavaClass();
+
+    /**
+     * Get the element responsible for retrieving the property value
+     *
+     * @return
+     */
+    AnnotatedElement getAnnotatedElement();
+
+    /**
+     * Get the member responsible for retrieving the property value
+     *
+     * @return
+     */
+    Member getMember();
+
+    /**
+     * Returns the property value for the specified bean. The property to be returned is either a field or getter
+     * method.
+     *
+     * @param bean The bean to read the property from
+     *
+     * @return The property value
+     *
+     * @throws ClassCastException if the value is not of the type V
+     */
+    V getValue(Object instance);
+
+    /**
+     * This method sets the property value for a specified bean to the specified value. The property to be set is either
+     * a field or setter method.
+     *
+     * @param bean The bean containing the property to set
+     * @param value The new property value
+     */
+    void setValue(Object instance, V value);
+
+    /**
+     * Returns the class that declares the property
+     *
+     * @return
+     */
+    Class<?> getDeclaringClass();
+
+    /**
+     * Indicates whether this is a read-only property
+     *
+     * @return
+     */
+    boolean isReadOnly();
+
+    /**
+     * Calls the setAccessible method on the underlying member(s).
+     * <p/>
+     * The operation should be performed within a {@link PrivilegedAction}
+     */
+    void setAccessible();
+
+    /**
+     * Indicates whether the given <code>annotation</code> is defined for this property. This method will consider
+     * the annotations present in both field and accessor method.
+     *
+     * @param annotation The Annotation to check.
+     *
+     * @return True if the annotation is defined. Otherwise is false.
+     */
+    boolean isAnnotationPresent(Class<? extends Annotation> annotation);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java
new file mode 100755
index 0000000..2f3b8b1
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.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.models.utils.reflection;
+
+import java.lang.reflect.Method;
+
+/**
+ * <p> A property criteria can be used to filter the properties found by a {@link PropertyQuery} </p> <p/> <p>
+ * DeltaSpike provides a number of property queries ( {@link TypedPropertyCriteria}, {@link NamedPropertyCriteria} and
+ * {@link AnnotatedPropertyCriteria}), or you can create a custom query by implementing this interface. </p>
+ *
+ * @see PropertyQuery#addCriteria(PropertyCriteria)
+ * @see PropertyQueries
+ * @see TypedPropertyCriteria
+ * @see AnnotatedPropertyCriteria
+ * @see NamedPropertyCriteria
+ */
+public interface PropertyCriteria {
+
+    /**
+     * Tests whether the specified method matches the criteria
+     *
+     * @param m
+     *
+     * @return true if the method matches
+     */
+    boolean methodMatches(Method m);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
new file mode 100644
index 0000000..debabb7
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+/**
+ * Utilities for working with property queries
+ *
+ * @see PropertyQuery
+ */
+public class PropertyQueries {
+
+    private PropertyQueries() {
+    }
+
+    /**
+     * Create a new {@link PropertyQuery}
+     *
+     * @param <V>
+     * @param targetClass
+     *
+     * @return
+     */
+    public static <V> PropertyQuery<V> createQuery(Class<?> targetClass) {
+        return new PropertyQuery<V>(targetClass);
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
new file mode 100644
index 0000000..5d7cbf5
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p> Queries a target class for properties that match certain criteria. A property may either be a private or public
+ * field, declared by the target class or inherited from a superclass, or a public method declared by the target class
+ * or inherited from any of its superclasses. For properties that are exposed via a method, the property must be a
+ * JavaBean style property, i.e. it must provide both an accessor and mutator method according to the JavaBean
+ * specification. </p> <p/> <p> This class is not thread-safe, however the result returned by the getResultList() method
+ * is. </p>
+ *
+ * @see PropertyQueries
+ * @see PropertyCriteria
+ */
+public class PropertyQuery<V> {
+    private final Class<?> targetClass;
+    private final List<PropertyCriteria> criteria;
+
+    PropertyQuery(Class<?> targetClass) {
+        if (targetClass == null) {
+            throw new IllegalArgumentException("targetClass parameter may not be null");
+        }
+
+        this.targetClass = targetClass;
+        this.criteria = new ArrayList<PropertyCriteria>();
+    }
+
+    /**
+     * Add a criteria to query
+     *
+     * @param criteria the criteria to add
+     */
+    public PropertyQuery<V> addCriteria(PropertyCriteria criteria) {
+        this.criteria.add(criteria);
+        return this;
+    }
+
+    /**
+     * Get the first result from the query, causing the query to be run.
+     *
+     * @return the first result, or null if there are no results
+     */
+    public Property<V> getFirstResult() {
+        Map<String, Property<V>> results = getResultList();
+        return results.isEmpty() ? null : results.values().iterator().next();
+    }
+
+    /**
+     * Get the first result from the query that is not marked as read only, causing the query to be run.
+     *
+     * @return the first writable result, or null if there are no results
+     */
+    public Property<V> getFirstWritableResult() {
+        Map<String, Property<V>>  results = getWritableResultList();
+        return results.isEmpty() ? null : results.values().iterator().next();
+    }
+
+    /**
+     * Get a single result from the query, causing the query to be run. An exception is thrown if the query does not
+     * return exactly one result.
+     *
+     * @return the single result
+     *
+     * @throws RuntimeException if the query does not return exactly one result
+     */
+    public Property<V> getSingleResult() {
+        Map<String, Property<V>> results = getResultList();
+        if (results.size() == 1) {
+            return results.values().iterator().next();
+        } else if (results.isEmpty()) {
+            throw new RuntimeException(
+                    "Expected one property match, but the criteria did not match any properties on " +
+                            targetClass.getName());
+        } else {
+            throw new RuntimeException("Expected one property match, but the criteria matched " + results.size() +
+                    " properties on " + targetClass.getName());
+        }
+    }
+
+    /**
+     * Get a single result from the query that is not marked as read only, causing the query to be run. An exception is
+     * thrown if the query does not return exactly one result.
+     *
+     * @return the single writable result
+     *
+     * @throws RuntimeException if the query does not return exactly one result
+     */
+    public Property<V> getWritableSingleResult() {
+        Map<String, Property<V>> results = getWritableResultList();
+        if (results.size() == 1) {
+            return results.values().iterator().next();
+        } else if (results.isEmpty()) {
+            throw new RuntimeException(
+                    "Expected one property match, but the criteria did not match any properties on " +
+                            targetClass.getName());
+        } else {
+            throw new RuntimeException("Expected one property match, but the criteria matched " +
+                    results.size() + " properties on " + targetClass.getName());
+        }
+    }
+
+    /**
+     * Get the result from the query, causing the query to be run.
+     *
+     * @return the results, or an empty list if there are no results
+     */
+    public Map<String, Property<V>> getResultList() {
+        return getResultList(false);
+    }
+
+    /**
+     * Get the non read only results from the query, causing the query to be run.
+     *
+     * @return the results, or an empty list if there are no results
+     */
+    public Map<String, Property<V>> getWritableResultList() {
+        return getResultList(true);
+    }
+
+    /**
+     * Get the result from the query, causing the query to be run.
+     *
+     * @param writable if this query should only return properties that are not read only
+     *
+     * @return the results, or an empty list if there are no results
+     */
+    private Map<String, Property<V>> getResultList(boolean writable) {
+        Map<String, Property<V>> properties = new HashMap<String, Property<V>>();
+
+        // First check public accessor methods (we ignore private methods)
+        for (Method method : targetClass.getMethods()) {
+            if (!(method.getName().startsWith("is") || method.getName().startsWith("get"))) {
+                continue;
+            }
+
+            boolean match = true;
+            for (PropertyCriteria c : criteria) {
+                if (!c.methodMatches(method)) {
+                    match = false;
+                    break;
+                }
+            }
+
+            if (match) {
+                MethodProperty<V> property = Properties.<V>createProperty(method);
+
+                if (!writable || !property.isReadOnly()) {
+                    properties.put(property.getName(), property);
+                }
+            }
+        }
+
+        return Collections.unmodifiableMap(properties);
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
new file mode 100644
index 0000000..1fbafe1
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/reflection/TypedPropertyCriteria.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * A criteria that matches a property based on its type
+ *
+ * @see PropertyCriteria
+ */
+public class TypedPropertyCriteria implements PropertyCriteria {
+
+    /**
+     * <p> Different options can be used to match a specific property based on its type. Regardless of the option
+     * chosen, if the property type equals the <code>propertyClass</code> it will be selected. <p/> <ul> <li>SUB_TYPE:
+     * Also consider properties where its type is a subtype of <code>propertyClass</code>. .</li> <li>SUPER_TYPE: Also
+     * consider properties where its type is a superclass or superinterface of <code>propertyClass</code>. .</li> </ul>
+     * </p>
+     */
+    public static enum MatchOption {
+        SUB_TYPE, SUPER_TYPE, ALL
+    }
+
+    private final Class<?> propertyClass;
+    private final MatchOption matchOption;
+
+    public TypedPropertyCriteria(Class<?> propertyClass) {
+        this(propertyClass, null);
+    }
+
+    public TypedPropertyCriteria(Class<?> propertyClass, MatchOption matchOption) {
+        if (propertyClass == null) {
+            throw new IllegalArgumentException("Property class can not be null.");
+        }
+        this.propertyClass = propertyClass;
+        this.matchOption = matchOption;
+    }
+
+    public boolean fieldMatches(Field f) {
+        return match(f.getType());
+    }
+
+    public boolean methodMatches(Method m) {
+        return match(m.getReturnType());
+    }
+
+    private boolean match(Class<?> type) {
+        if (propertyClass.equals(type)) {
+            return true;
+        } else {
+            boolean matchSubType = propertyClass.isAssignableFrom(type);
+
+            if (MatchOption.SUB_TYPE == this.matchOption) {
+                return matchSubType;
+            }
+
+            boolean matchSuperType = type.isAssignableFrom(propertyClass);
+
+            if (MatchOption.SUPER_TYPE == this.matchOption) {
+                return matchSuperType;
+            }
+
+            if (MatchOption.ALL == this.matchOption) {
+                return matchSubType || matchSuperType;
+            }
+        }
+
+        return false;
+    }
+}
+
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java b/server-spi-private/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
new file mode 100755
index 0000000..8b4c25a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.common.util.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+
+/**
+ * <p>
+ * Password that uses SHA to encode passwords. You can always change the SHA strength by specifying a valid
+ * integer when creating a new instance.
+ * </p>
+ * <p>Passwords are returned with a Base64 encoding.</p>
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
+ *
+ */
+public class SHAPasswordEncoder {
+
+    private int strength;
+
+    public SHAPasswordEncoder(int strength) {
+        this.strength = strength;
+    }
+
+    public String encode(String rawPassword) {
+        MessageDigest messageDigest = getMessageDigest();
+
+        String encodedPassword = null;
+
+        try {
+            byte[] digest = messageDigest.digest(rawPassword.getBytes("UTF-8"));
+            encodedPassword = Base64.encodeBytes(digest);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Credential could not be encoded");
+        }
+
+        return encodedPassword;
+    }
+
+    public boolean verify(String rawPassword, String encodedPassword) {
+        return encode(rawPassword).equals(encodedPassword);
+    }
+
+    protected final MessageDigest getMessageDigest() throws IllegalArgumentException {
+        String algorithm = "SHA-" + this.strength;
+
+        try {
+            return MessageDigest.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("invalid credential encoding algorithm");
+        }
+    }
+
+    public int getStrength() {
+        return this.strength;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
new file mode 100644
index 0000000..26f5adc
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class StripSecretsUtils {
+
+    public static ComponentRepresentation strip(KeycloakSession session, ComponentRepresentation rep) {
+        Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, rep);
+        if (rep.getConfig() == null) {
+            return rep;
+        }
+
+        Iterator<Map.Entry<String, List<String>>> itr = rep.getConfig().entrySet().iterator();
+        while (itr.hasNext()) {
+            Map.Entry<String, List<String>> next = itr.next();
+            ProviderConfigProperty configProperty = configProperties.get(next.getKey());
+            if (configProperty != null) {
+                if (configProperty.isSecret()) {
+                    next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE));
+                }
+            } else {
+                itr.remove();
+            }
+        }
+        return rep;
+    }
+
+    public static RealmRepresentation strip(RealmRepresentation rep) {
+        if (rep.getSmtpServer() != null && rep.getSmtpServer().containsKey("password")) {
+            rep.getSmtpServer().put("password", ComponentRepresentation.SECRET_VALUE);
+        }
+        return rep;
+    }
+
+    public static IdentityProviderRepresentation strip(IdentityProviderRepresentation rep) {
+        if (rep.getConfig() != null && rep.getConfig().containsKey("clientSecret")) {
+            rep.getConfig().put("clientSecret", ComponentRepresentation.SECRET_VALUE);
+        }
+        return rep;
+    }
+
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java b/server-spi-private/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
new file mode 100755
index 0000000..d02bc55
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/TimeBasedOTP.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * TOTP: Time-based One-time Password Algorithm Based on http://tools.ietf.org/html/draft-mraihi-totp-timebased-06
+ *
+ * @author anil saldhana
+ * @since Sep 20, 2010
+ */
+public class TimeBasedOTP extends HmacOTP {
+
+    public static final int DEFAULT_INTERVAL_SECONDS = 30;
+    public static final int DEFAULT_DELAY_WINDOW = 1;
+
+    private Clock clock;
+
+    public TimeBasedOTP() {
+        this(DEFAULT_ALGORITHM, DEFAULT_NUMBER_DIGITS, DEFAULT_INTERVAL_SECONDS, DEFAULT_DELAY_WINDOW);
+    }
+
+    /**
+     * @param algorithm the encryption algorithm
+     * @param numberDigits the number of digits for tokens
+     * @param timeIntervalInSeconds the number of seconds a token is valid
+     * @param lookAheadWindow the number of previous intervals that should be used to validate tokens.
+     */
+    public TimeBasedOTP(String algorithm, int numberDigits, int timeIntervalInSeconds, int lookAheadWindow) {
+        super(numberDigits, algorithm, lookAheadWindow);
+        this.clock = new Clock(timeIntervalInSeconds);
+    }
+
+    /**
+     * <p>Generates a token.</p>
+     *
+     * @param secretKey the secret key to derive the token from.
+     */
+    public String generateTOTP(String secretKey) {
+        long T = this.clock.getCurrentInterval();
+
+        String steps = Long.toHexString(T).toUpperCase();
+
+        // Just get a 16 digit string
+        while (steps.length() < 16)
+            steps = "0" + steps;
+
+        return generateOTP(secretKey, steps, this.numberDigits, this.algorithm);
+    }
+
+    /**
+     * <p>Validates a token using a secret key.</p>
+     *
+     * @param token  OTP string to validate
+     * @param secret Shared secret
+     * @return
+     */
+    public boolean validateTOTP(String token, byte[] secret) {
+        long currentInterval = this.clock.getCurrentInterval();
+
+        for (int i = this.lookAheadWindow; i >= 0; --i) {
+            String steps = Long.toHexString(currentInterval - i).toUpperCase();
+
+            // Just get a 16 digit string
+            while (steps.length() < 16)
+                steps = "0" + steps;
+
+            String candidate = generateOTP(new String(secret), steps, this.numberDigits, this.algorithm);
+
+            if (candidate.equals(token)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void setCalendar(Calendar calendar) {
+        this.clock.setCalendar(calendar);
+    }
+
+    private class Clock {
+
+        private final int interval;
+        private Calendar calendar;
+
+        public Clock(int interval) {
+            this.interval = interval;
+        }
+
+        public long getCurrentInterval() {
+            Calendar currentCalendar = this.calendar;
+
+            if (currentCalendar == null) {
+                currentCalendar = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
+            }
+
+            return (currentCalendar.getTimeInMillis() / 1000) / this.interval;
+        }
+
+        public void setCalendar(Calendar calendar) {
+            this.calendar = calendar;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi-private/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
new file mode 100755
index 0000000..a08e18a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.utils;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserModelDelegate implements UserModel {
+    protected UserModel delegate;
+
+    public UserModelDelegate(UserModel delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public String getId() {
+        return delegate.getId();
+    }
+
+    @Override
+    public String getUsername() {
+        return delegate.getUsername();
+    }
+
+    @Override
+    public void setUsername(String username) {
+        delegate.setUsername(username);
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return delegate.isEnabled();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        delegate.setEnabled(enabled);
+    }
+
+    @Override
+    public void setSingleAttribute(String name, String value) {
+        delegate.setSingleAttribute(name, value);
+    }
+
+    @Override
+    public void setAttribute(String name, List<String> values) {
+        delegate.setAttribute(name, values);
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        delegate.removeAttribute(name);
+    }
+
+    @Override
+    public String getFirstAttribute(String name) {
+        return delegate.getFirstAttribute(name);
+    }
+
+    @Override
+    public List<String> getAttribute(String name) {
+        return delegate.getAttribute(name);
+    }
+
+    @Override
+    public Map<String, List<String>> getAttributes() {
+        return delegate.getAttributes();
+    }
+
+    @Override
+    public Set<String> getRequiredActions() {
+        return delegate.getRequiredActions();
+    }
+
+    @Override
+    public void addRequiredAction(String action) {
+        delegate.addRequiredAction(action);
+    }
+
+    @Override
+    public void removeRequiredAction(String action) {
+        delegate.removeRequiredAction(action);
+    }
+
+    @Override
+    public void addRequiredAction(RequiredAction action) {
+        delegate.addRequiredAction(action);
+    }
+
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        delegate.removeRequiredAction(action);
+    }
+
+    @Override
+    public String getFirstName() {
+        return delegate.getFirstName();
+    }
+
+    @Override
+    public void setFirstName(String firstName) {
+        delegate.setFirstName(firstName);
+    }
+
+    @Override
+    public String getLastName() {
+        return delegate.getLastName();
+    }
+
+    @Override
+    public void setLastName(String lastName) {
+        delegate.setLastName(lastName);
+    }
+
+    @Override
+    public String getEmail() {
+        return delegate.getEmail();
+    }
+
+    @Override
+    public void setEmail(String email) {
+        delegate.setEmail(email);
+    }
+
+    @Override
+    public boolean isEmailVerified() {
+        return delegate.isEmailVerified();
+    }
+
+    @Override
+    public void setEmailVerified(boolean verified) {
+        delegate.setEmailVerified(verified);
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoleMappings() {
+        return delegate.getRealmRoleMappings();
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoleMappings(ClientModel app) {
+        return delegate.getClientRoleMappings(app);
+    }
+
+    @Override
+    public boolean hasRole(RoleModel role) {
+        return delegate.hasRole(role);
+    }
+
+    @Override
+    public void grantRole(RoleModel role) {
+        delegate.grantRole(role);
+    }
+
+    @Override
+    public Set<RoleModel> getRoleMappings() {
+        return delegate.getRoleMappings();
+    }
+
+    @Override
+    public void deleteRoleMapping(RoleModel role) {
+        delegate.deleteRoleMapping(role);
+    }
+
+    @Override
+    public String getFederationLink() {
+        return delegate.getFederationLink();
+    }
+
+    @Override
+    public void setFederationLink(String link) {
+        delegate.setFederationLink(link);
+    }
+
+    @Override
+    public String getServiceAccountClientLink() {
+        return delegate.getServiceAccountClientLink();
+    }
+
+    @Override
+    public void setServiceAccountClientLink(String clientInternalId) {
+        delegate.setServiceAccountClientLink(clientInternalId);
+    }
+
+    public UserModel getDelegate() {
+        return delegate;
+    }
+    
+    @Override
+    public Long getCreatedTimestamp(){
+        return delegate.getCreatedTimestamp();
+    }
+    
+    @Override
+    public void setCreatedTimestamp(Long timestamp){
+        delegate.setCreatedTimestamp(timestamp);
+    }
+
+    @Override
+    public Set<GroupModel> getGroups() {
+        return delegate.getGroups();
+    }
+
+    @Override
+    public void joinGroup(GroupModel group) {
+        delegate.joinGroup(group);
+
+    }
+
+    @Override
+    public void leaveGroup(GroupModel group) {
+        delegate.leaveGroup(group);
+
+    }
+
+    @Override
+    public boolean isMemberOf(GroupModel group) {
+        return delegate.isMemberOf(group);
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
new file mode 100644
index 0000000..b1bfc0c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
@@ -0,0 +1,80 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManagerProvider {
+
+    private KeycloakSession session;
+
+    public DefaultPasswordPolicyManagerProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        for (PasswordPolicyProvider p : getProviders(realm, session)) {
+            PolicyError policyError = p.validate(realm, user, password);
+            if (policyError != null) {
+                return policyError;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        for (PasswordPolicyProvider p : getProviders(session)) {
+            PolicyError policyError = p.validate(user, password);
+            if (policyError != null) {
+                return policyError;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    private List<PasswordPolicyProvider> getProviders(KeycloakSession session) {
+        return getProviders(session.getContext().getRealm(), session);
+
+    }
+
+    private List<PasswordPolicyProvider> getProviders(RealmModel realm, KeycloakSession session) {
+        LinkedList<PasswordPolicyProvider> list = new LinkedList<>();
+        PasswordPolicy policy = realm.getPasswordPolicy();
+        for (String id : policy.getPolicies()) {
+            PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, id);
+            list.add(provider);
+        }
+        return list;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
new file mode 100644
index 0000000..b8aabd4
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultPasswordPolicyManagerProviderFactory implements PasswordPolicyManagerProviderFactory {
+
+    @Override
+    public PasswordPolicyManagerProvider create(KeycloakSession session) {
+        return new DefaultPasswordPolicyManagerProvider(session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "default";
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
new file mode 100644
index 0000000..391317e
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DigitsPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinDigitsMessage";
+
+    private KeycloakContext context;
+
+    public DigitsPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(DigitsPasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (Character.isDigit(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..d7fce9c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DigitsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "digits";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new DigitsPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Digits";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..b9f6f4c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
@@ -0,0 +1,91 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
+
+    public static final int DEFAULT_VALUE = 365;
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return PasswordPolicy.FORCE_EXPIRED_ID;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        return null;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Expire Password";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return String.valueOf(DEFAULT_VALUE);
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : DEFAULT_VALUE;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..303ba79
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
@@ -0,0 +1,89 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return PasswordPolicy.HASH_ALGORITHM_ID;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        return null;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Hashing Algorithm";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return PasswordPolicy.HASH_ALGORITHM_DEFAULT;
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? value : PasswordPolicy.HASH_ALGORITHM_DEFAULT;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..695ab28
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
@@ -0,0 +1,90 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HashIterationsPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
+
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getId() {
+        return PasswordPolicy.HASH_ITERATIONS_ID;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        return null;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : PasswordPolicy.HASH_ITERATIONS_DEFAULT;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Hashing Iterations";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return String.valueOf(PasswordPolicy.HASH_ITERATIONS_DEFAULT);
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
new file mode 100644
index 0000000..004d540
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.policy;
+
+import org.jboss.logging.Logger;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.hash.PasswordHashProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final Logger logger = Logger.getLogger(HistoryPasswordPolicyProvider.class);
+    private static final String ERROR_MESSAGE = "invalidPasswordHistoryMessage";
+
+    private KeycloakSession session;
+
+    public HistoryPasswordPolicyProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
+        int passwordHistoryPolicyValue = policy.getPolicyConfig(PasswordPolicy.PASSWORD_HISTORY_ID);
+        if (passwordHistoryPolicyValue != -1) {
+            List<CredentialModel> storedPasswords = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
+            for (CredentialModel cred : storedPasswords) {
+                PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
+                if (hash == null) continue;
+                if (hash.verify(password, cred)) {
+                    return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
+                }
+            }
+            List<CredentialModel> passwordHistory = session.userCredentialManager().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD_HISTORY);
+            for (CredentialModel cred : passwordHistory) {
+                PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, cred.getAlgorithm());
+                if (hash.verify(password, cred)) {
+                    return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
+                }
+
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..5f8a9d1
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
@@ -0,0 +1,74 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.PasswordPolicy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HistoryPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final Integer DEFAULT_VALUE = 3;
+
+    @Override
+    public String getId() {
+        return PasswordPolicy.PASSWORD_HISTORY_ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new HistoryPasswordPolicyProvider(session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Not Recently Used";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return String.valueOf(HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE);
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
new file mode 100644
index 0000000..5ba71fe
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
@@ -0,0 +1,57 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinLengthMessage";
+
+    private KeycloakContext context;
+
+    public LengthPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(LengthPasswordPolicyProviderFactory.ID);
+        return password.length() < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 8;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..a60c250
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LengthPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "length";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new LengthPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Minimum Length";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "8";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
new file mode 100644
index 0000000..f080d00
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
+
+    private KeycloakContext context;
+
+    public LowerCasePasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(LowerCasePasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (Character.isLowerCase(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..7e96dcf
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LowerCasePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "lowerCase";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new LowerCasePasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Lowercase Characters";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
new file mode 100644
index 0000000..f08edab
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class NotUsernamePasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordNotUsernameMessage";
+
+    private KeycloakContext context;
+
+    public NotUsernamePasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        if (username == null) {
+            return null;
+        }
+        return username.equals(password) ? new PolicyError(ERROR_MESSAGE) : null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return null;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..30ebbff
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class NotUsernamePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "notUsername";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new NotUsernamePasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Not Username";
+    }
+
+    @Override
+    public String getConfigType() {
+        return null;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return null;
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
new file mode 100644
index 0000000..e5e8497
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
@@ -0,0 +1,32 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyManagerProvider extends Provider {
+
+    PolicyError validate(RealmModel realm, UserModel user, String password);
+    PolicyError validate(String user, String password);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
new file mode 100644
index 0000000..f68701e
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.policy;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyManagerProviderFactory extends ProviderFactory<PasswordPolicyManagerProvider> {
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
new file mode 100644
index 0000000..266cf1f
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
@@ -0,0 +1,49 @@
+/*
+ * 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.policy;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordPolicyManagerSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "password-policy-manager";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return PasswordPolicyManagerProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return PasswordPolicyManagerProviderFactory.class;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..44714e3
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.policy;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyProviderFactory extends ProviderFactory<PasswordPolicyProvider> {
+
+    String getDisplayName();
+    String getConfigType();
+    String getDefaultConfigValue();
+    boolean isMultiplSupported();
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicySpi.java b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicySpi.java
new file mode 100644
index 0000000..97ad19a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/PasswordPolicySpi.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.policy;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public class PasswordPolicySpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "password-policy";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return PasswordPolicyProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return PasswordPolicyProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
new file mode 100644
index 0000000..52c83b8
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
@@ -0,0 +1,67 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordRegexPatternMessage";
+
+    private KeycloakContext context;
+
+    public RegexPatternsPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        Pattern pattern = context.getRealm().getPasswordPolicy().getPolicyConfig(RegexPatternsPasswordPolicyProviderFactory.ID);
+        Matcher matcher = pattern.matcher(password);
+        if (!matcher.matches()) {
+            return new PolicyError(ERROR_MESSAGE, pattern.pattern());
+        }
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Config required");
+        }
+        return Pattern.compile(value);
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..c0ce732
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RegexPatternsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "regexPattern";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new RegexPatternsPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Regular Expression";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return true;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
new file mode 100644
index 0000000..fa85137
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
+
+    private KeycloakContext context;
+
+    public SpecialCharsPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(SpecialCharsPasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (!Character.isLetterOrDigit(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..908cbee
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SpecialCharsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "specialChars";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new SpecialCharsPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Special Characters";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
new file mode 100644
index 0000000..16ac1ef
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
+
+    private KeycloakContext context;
+
+    public UpperCasePasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(UpperCasePasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (Character.isUpperCase(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(RealmModel realm, UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..8dce247
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpperCasePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "upperCase";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new UpperCasePasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Uppercase Characters";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java b/server-spi-private/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java
new file mode 100755
index 0000000..4d39f8a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/AbstractLoginProtocolFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractLoginProtocolFactory implements LoginProtocolFactory {
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+        factory.register(new ProviderEventListener() {
+            @Override
+            public void onEvent(ProviderEvent event) {
+                if (event instanceof RealmModel.ClientCreationEvent) {
+                    ClientModel client = ((RealmModel.ClientCreationEvent)event).getCreatedClient();
+                    addDefaults(client);
+                }
+            }
+        });
+    }
+
+    protected abstract void addDefaults(ClientModel realm);
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java b/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java
new file mode 100755
index 0000000..0c55c4f
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * Provides a template/sample client config adapter file.  For example keycloak.json for our OIDC adapter.  keycloak-saml.xml for our SAML client adapter
+ *
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ClientInstallationProvider extends Provider, ProviderFactory<ClientInstallationProvider> {
+    Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri);
+    String getProtocol();
+    String getDisplayType();
+    String getHelpText();
+    String getFilename();
+    String getMediaType();
+    boolean isDownloadOnly();
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java
new file mode 100755
index 0000000..abd86ff
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/ClientInstallationSpi.java
@@ -0,0 +1,49 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientInstallationSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "client-installation";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return ClientInstallationProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return ClientInstallationProvider.class;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
new file mode 100755
index 0000000..086a8ed
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -0,0 +1,84 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.provider.Provider;
+import org.keycloak.services.managers.ClientSessionCode;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface LoginProtocol extends Provider {
+
+    public static enum Error {
+
+        /**
+         * Login cancelled by the user
+         */
+        CANCELLED_BY_USER,
+        /**
+         * Consent denied by the user
+         */
+        CONSENT_DENIED,
+        /**
+         * Passive authentication mode requested but nobody is logged in
+         */
+        PASSIVE_LOGIN_REQUIRED,
+        /**
+         * Passive authentication mode requested, user is logged in, but some other user interaction is necessary (eg. some required login actions exist or Consent approval is necessary for logged in
+         * user)
+         */
+        PASSIVE_INTERACTION_REQUIRED;
+    }
+
+    LoginProtocol setSession(KeycloakSession session);
+
+    LoginProtocol setRealm(RealmModel realm);
+
+    LoginProtocol setUriInfo(UriInfo uriInfo);
+
+    LoginProtocol setHttpHeaders(HttpHeaders headers);
+
+    LoginProtocol setEventBuilder(EventBuilder event);
+
+    Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
+
+    Response sendError(ClientSessionModel clientSession, Error error);
+
+    void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
+    Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
+    Response finishLogout(UserSessionModel userSession);
+
+    /**
+     * @param userSession
+     * @param clientSession
+     * @return true if SSO cookie authentication can't be used. User will need to "actively" reauthenticate
+     */
+    boolean requireReauthentication(UserSessionModel userSession, ClientSessionModel clientSession);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
new file mode 100755
index 0000000..931a00d
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientTemplateRepresentation;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface LoginProtocolFactory extends ProviderFactory<LoginProtocol> {
+    /**
+     * List of built in protocol mappers that can be used to apply to clients.
+     *
+     * @return
+     */
+    List<ProtocolMapperModel> getBuiltinMappers();
+
+    /**
+     * List of mappers, which are added to new clients by default
+     * @return
+     */
+    List<ProtocolMapperModel> getDefaultBuiltinMappers();
+
+    Object createProtocolEndpoint(RealmModel realm, EventBuilder event);
+
+    /**
+     * Setup default values for new clients. This expects that the representation has already set up the client
+     *
+     * @param rep
+     * @param newClient
+     */
+    void setupClientDefaults(ClientRepresentation rep, ClientModel newClient);
+
+    /**
+     * Setup default values for new templates.  This expects that the representation has already set up the template
+     *
+     * @param clientRep
+     * @param newClient
+     */
+    void setupTemplateDefaults(ClientTemplateRepresentation clientRep, ClientTemplateModel newClient);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
new file mode 100755
index 0000000..84133a7
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/LoginProtocolSpi.java
@@ -0,0 +1,49 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginProtocolSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "login-protocol";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return LoginProtocol.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return LoginProtocolFactory.class;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
new file mode 100644
index 0000000..e30f5da
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProvider.java
@@ -0,0 +1,38 @@
+/*
+ *  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.protocol.oidc;
+
+import org.keycloak.provider.Provider;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Provides introspection for a determined OAuth2 token type.
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface TokenIntrospectionProvider extends Provider {
+
+    /**
+     * Introspect the <code>token</code>.
+     *
+     * @param token the token to introspect.
+     * @return the response with the information about the token
+     */
+    Response introspect(String token);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
new file mode 100644
index 0000000..48b7556
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionProviderFactory.java
@@ -0,0 +1,28 @@
+/*
+ *  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.protocol.oidc;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * A factory that creates {@link TokenIntrospectionProvider} instances.
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface TokenIntrospectionProviderFactory extends ProviderFactory<TokenIntrospectionProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
new file mode 100644
index 0000000..4eb6d39
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/oidc/TokenIntrospectionSpi.java
@@ -0,0 +1,49 @@
+/*
+ *  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.protocol.oidc;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * <p>A {@link Spi} to support additional tokens types to the OAuth2 Token Introspection Endpoint.
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class TokenIntrospectionSpi implements Spi {
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "oauth2-token-introspection";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return TokenIntrospectionProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return TokenIntrospectionProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
new file mode 100755
index 0000000..8055fae
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperContainerModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.provider.ConfiguredProvider;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper>,ConfiguredProvider {
+    String getProtocol();
+    String getDisplayCategory();
+    String getDisplayType();
+
+    /**
+     * Called when instance of mapperModel is created/updated for this protocolMapper through admin endpoint
+     *
+     * @param session
+     * @param realm
+     * @param client client or clientTemplate
+     * @param mapperModel
+     * @throws ProtocolMapperConfigException if configuration provided in mapperModel is not valid
+     */
+    default void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
+    };
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java
new file mode 100644
index 0000000..3f1f676
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java
@@ -0,0 +1,69 @@
+/*
+ * 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.protocol;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ProtocolMapperConfigException extends Exception {
+
+    private String messageKey;
+    private Object[] parameters;
+
+    public ProtocolMapperConfigException(String message) {
+        super(message);
+    }
+
+    public ProtocolMapperConfigException(String message, String messageKey) {
+        super(message);
+        this.messageKey = messageKey;
+    }
+
+    public ProtocolMapperConfigException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ProtocolMapperConfigException(String message, String messageKey, Throwable cause) {
+        super(message, cause);
+        this.messageKey = messageKey;
+    }
+
+    public ProtocolMapperConfigException(String message, Object ... parameters) {
+        super(message);
+        this.parameters = parameters;
+    }
+
+    public ProtocolMapperConfigException(String messageKey, String message, Object ... parameters) {
+        super(message);
+        this.messageKey = messageKey;
+        this.parameters = parameters;
+    }
+
+    public String getMessageKey() {
+        return messageKey;
+    }
+
+    public Object[] getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Object[] parameters) {
+        this.parameters = parameters;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java
new file mode 100755
index 0000000..5aa841c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/protocol/ProtocolMapperSpi.java
@@ -0,0 +1,49 @@
+/*
+ * 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.protocol;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProtocolMapperSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "protocol-mapper";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return ProtocolMapper.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return ProtocolMapper.class;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java b/server-spi-private/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
new file mode 100644
index 0000000..047c1bd
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/provider/ConfigurationValidationHelper.java
@@ -0,0 +1,121 @@
+/*
+ * 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.provider;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ConfigurationValidationHelper {
+
+    private ComponentModel model;
+
+    private ConfigurationValidationHelper(ComponentModel model) {
+        this.model = model;
+    }
+
+    public static ConfigurationValidationHelper check(ComponentModel model) {
+        return new ConfigurationValidationHelper(model);
+    }
+
+    public ConfigurationValidationHelper checkInt(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+        return checkInt(property.getName(), property.getLabel(), required);
+    }
+
+    public ConfigurationValidationHelper checkInt(String key, String label, boolean required) throws ComponentValidationException {
+        checkSingle(key, label, required);
+
+        String val = model.getConfig().getFirst(key);
+        if (val != null) {
+            try {
+                Integer.parseInt(val);
+            } catch (NumberFormatException e) {
+                throw new ComponentValidationException("''{0}'' should be a number", label);
+            }
+        }
+
+        return this;
+    }
+
+    public ConfigurationValidationHelper checkLong(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+        return checkLong(property.getName(), property.getLabel(), required);
+    }
+
+    public ConfigurationValidationHelper checkLong(String key, String label, boolean required) throws ComponentValidationException {
+        checkSingle(key, label, required);
+
+        String val = model.getConfig().getFirst(key);
+        if (val != null) {
+            try {
+                Long.parseLong(val);
+            } catch (NumberFormatException e) {
+                throw new ComponentValidationException("''{0}'' should be a number", label);
+            }
+        }
+
+        return this;
+    }
+
+    public ConfigurationValidationHelper checkSingle(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+        return checkSingle(property.getName(), property.getLabel(), required);
+    }
+
+    public ConfigurationValidationHelper checkSingle(String key, String label, boolean required) throws ComponentValidationException {
+        if (model.getConfig().containsKey(key) && model.getConfig().get(key).size() > 1) {
+            throw new ComponentValidationException("''{0}'' should be a single entry", label);
+        }
+
+        if (required) {
+            checkRequired(key, label);
+        }
+
+        return this;
+    }
+
+    public ConfigurationValidationHelper checkRequired(ProviderConfigProperty property) throws ComponentValidationException {
+        return checkRequired(property.getName(), property.getLabel());
+    }
+
+    public ConfigurationValidationHelper checkRequired(String key, String label) throws ComponentValidationException {
+        List<String> values = model.getConfig().get(key);
+        if (values == null) {
+            throw new ComponentValidationException("''{0}'' is required", label);
+        }
+
+        return this;
+    }
+
+    public ConfigurationValidationHelper checkBoolean(ProviderConfigProperty property, boolean required) throws ComponentValidationException {
+        return checkBoolean(property.getName(), property.getLabel(), required);
+    }
+
+    public ConfigurationValidationHelper checkBoolean(String key, String label, boolean required) {
+        checkSingle(key, label, required);
+
+        String val = model.getConfig().getFirst(key);
+        if (val != null && !(val.equals("true") || val.equals("false"))) {
+            throw new ComponentValidationException("''{0}'' should be ''true'' or ''false''", label);
+        }
+
+        return this;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java
new file mode 100644
index 0000000..b4e993a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.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.provider;
+
+/**
+ * Providers that are only supported in some environments can implement this interface to be able to determine if they
+ * should be available or not.
+ *
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface EnvironmentDependentProviderFactory {
+
+    /**
+     * @return <code>true</code> if the provider is supported and should be available, <code>false</code> otherwise
+     */
+    boolean isSupported();
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoader.java b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoader.java
new file mode 100644
index 0000000..2d7a07a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoader.java
@@ -0,0 +1,42 @@
+/*
+ * 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.provider;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderLoader {
+
+    /**
+     * Load the SPI definitions themselves.
+     *
+     * @return a list of Spi definition objects
+     */
+    List<Spi> loadSpis();
+
+    /**
+     * Load all provider factories of a specific SPI.
+     *
+     * @param spi the Spi definition
+     * @return a list of provider factories
+     */
+    List<ProviderFactory> load(Spi spi);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
new file mode 100644
index 0000000..85a5e17
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderLoaderFactory {
+
+    boolean supports(String type);
+
+    ProviderLoader create(ClassLoader baseClassLoader, String resource);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
new file mode 100644
index 0000000..a4e6718
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/provider/ServerInfoAwareProviderFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.provider;
+
+import java.util.Map;
+
+/**
+ * Marker interface for {@link ProviderFactory} of Provider which wants to show some info on "Server Info" page in Admin console.
+ * 
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public interface ServerInfoAwareProviderFactory {
+
+    /**
+     * Return actual info about the provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
+     * shown on "Server Info" page then.
+     * 
+     * @return Map with keys describing value and relevant values itself
+     */
+    public Map<String, String> getOperationalInfo();
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
new file mode 100644
index 0000000..c3859ab
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/InvocableScriptAdapter.java
@@ -0,0 +1,118 @@
+/*
+ * 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.scripting;
+
+import org.keycloak.models.ScriptModel;
+
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+/**
+ * Wraps a {@link ScriptModel} and makes it {@link Invocable}.
+ *
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+public class InvocableScriptAdapter implements Invocable {
+
+    /**
+     * Holds the {@ScriptModel}
+     */
+    private final ScriptModel scriptModel;
+
+    /**
+     * Holds the {@link ScriptEngine} instance initialized with the script code.
+     */
+    private final ScriptEngine scriptEngine;
+
+    /**
+     * Creates a new {@link InvocableScriptAdapter} instance.
+     *
+     * @param scriptModel  must not be {@literal null}
+     * @param scriptEngine must not be {@literal null}
+     */
+    public InvocableScriptAdapter(ScriptModel scriptModel, ScriptEngine scriptEngine) {
+
+        if (scriptModel == null) {
+            throw new IllegalArgumentException("scriptModel must not be null");
+        }
+
+        if (scriptEngine == null) {
+            throw new IllegalArgumentException("scriptEngine must not be null");
+        }
+
+        this.scriptModel = scriptModel;
+        this.scriptEngine = loadScriptIntoEngine(scriptModel, scriptEngine);
+    }
+
+    @Override
+    public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptExecutionException {
+
+        try {
+            return getInvocableEngine().invokeMethod(thiz, name, args);
+        } catch (ScriptException | NoSuchMethodException e) {
+            throw new ScriptExecutionException(scriptModel, e);
+        }
+    }
+
+    @Override
+    public Object invokeFunction(String name, Object... args) throws ScriptExecutionException {
+        try {
+            return getInvocableEngine().invokeFunction(name, args);
+        } catch (ScriptException | NoSuchMethodException e) {
+            throw new ScriptExecutionException(scriptModel, e);
+        }
+    }
+
+    @Override
+    public <T> T getInterface(Class<T> clazz) {
+        return getInvocableEngine().getInterface(clazz);
+    }
+
+    @Override
+    public <T> T getInterface(Object thiz, Class<T> clazz) {
+        return getInvocableEngine().getInterface(thiz, clazz);
+    }
+
+    /**
+     * Returns {@literal true} if the {@link ScriptEngine} has a definition with the given {@code name}.
+     *
+     * @param name
+     * @return
+     */
+    public boolean isDefined(String name) {
+
+        Object candidate = scriptEngine.getContext().getAttribute(name);
+
+        return candidate != null;
+    }
+
+    private ScriptEngine loadScriptIntoEngine(ScriptModel script, ScriptEngine engine) {
+
+        try {
+            engine.eval(script.getCode());
+        } catch (ScriptException se) {
+            throw new ScriptExecutionException(script, se);
+        }
+
+        return engine;
+    }
+
+    private Invocable getInvocableEngine() {
+        return (Invocable) scriptEngine;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/Script.java b/server-spi-private/src/main/java/org/keycloak/scripting/Script.java
new file mode 100644
index 0000000..ef86902
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/Script.java
@@ -0,0 +1,115 @@
+/*
+ * 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.scripting;
+
+import org.keycloak.models.ScriptModel;
+
+/**
+ * A {@link ScriptModel} which holds some meta-data.
+ *
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+public class Script implements ScriptModel {
+
+    private String id;
+
+    private String realmId;
+
+    private String name;
+
+    private String mimeType;
+
+    private String code;
+
+    private String description;
+
+    public Script(String id, String realmId, String name, String mimeType, String code, String description) {
+
+        this.id = id;
+        this.realmId = realmId;
+        this.name = name;
+        this.mimeType = mimeType;
+        this.code = code;
+        this.description = description;
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @Override
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getMimeType() {
+        return mimeType;
+    }
+
+    public void setMimeType(String mimeType) {
+        this.mimeType = mimeType;
+    }
+
+    @Override
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return "Script{" +
+                "id='" + id + '\'' +
+                ", realmId='" + realmId + '\'' +
+                ", name='" + name + '\'' +
+                ", type='" + mimeType + '\'' +
+                ", code='" + code + '\'' +
+                ", description='" + description + '\'' +
+                '}';
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java
new file mode 100644
index 0000000..9613eb6
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptBindingsConfigurer.java
@@ -0,0 +1,41 @@
+/*
+ * 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.scripting;
+
+import javax.script.Bindings;
+
+/**
+ * Callback interface for customization of {@link Bindings} for a {@link javax.script.ScriptEngine}.
+ * <p>Used by {@link ScriptingProvider}</p>
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+@FunctionalInterface
+public interface ScriptBindingsConfigurer {
+
+    /**
+     * A default {@link ScriptBindingsConfigurer} that provides no Bindings.
+     */
+    ScriptBindingsConfigurer EMPTY = new ScriptBindingsConfigurer() {
+
+        @Override
+        public void configureBindings(Bindings bindings) {
+            //NOOP
+        }
+    };
+
+    void configureBindings(Bindings bindings);
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptExecutionException.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptExecutionException.java
new file mode 100644
index 0000000..2063bd2
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptExecutionException.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.scripting;
+
+import org.keycloak.models.ScriptModel;
+
+import javax.script.ScriptException;
+
+/**
+ * Augments a {@link ScriptException} and adds additional metadata.
+ *
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+public class ScriptExecutionException extends RuntimeException {
+
+    public ScriptExecutionException(ScriptModel script, Exception ex) {
+        super("Could not execute script '" + script.getName() + "' problem was: " + ex.getMessage(), ex);
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
new file mode 100644
index 0000000..67bad5a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/ScriptingProvider.java
@@ -0,0 +1,51 @@
+/*
+ * 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.scripting;
+
+import org.keycloak.models.ScriptModel;
+import org.keycloak.provider.Provider;
+
+import javax.script.ScriptEngine;
+
+/**
+ * A {@link Provider} than provides Scripting capabilities.
+ *
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+public interface ScriptingProvider extends Provider {
+
+    /**
+     * Returns an {@link InvocableScriptAdapter} based on the given {@link ScriptModel}.
+     * <p>The {@code InvocableScriptAdapter} wraps a dedicated {@link ScriptEngine} that was populated with the provided {@link ScriptBindingsConfigurer}</p>
+     *
+     * @param scriptModel        the scriptModel to wrap
+     * @param bindingsConfigurer populates the {@link javax.script.Bindings}
+     * @return
+     */
+    InvocableScriptAdapter prepareInvocableScript(ScriptModel scriptModel, ScriptBindingsConfigurer bindingsConfigurer);
+
+    /**
+     * Creates a new {@link ScriptModel} instance.
+     *
+     * @param realmId
+     * @param scriptName
+     * @param scriptCode
+     * @param scriptDescription
+     * @return
+     */
+    ScriptModel createScript(String realmId, String mimeType, String scriptName, String scriptCode, String scriptDescription);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtector.java b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
new file mode 100755
index 0000000..e884b02
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
@@ -0,0 +1,34 @@
+/*
+ * 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.services.managers;
+
+import org.keycloak.common.ClientConnection;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface BruteForceProtector extends Provider {
+    void failedLogin(RealmModel realm, UserModel user, ClientConnection clientConnection);
+
+    boolean isTemporarilyDisabled(KeycloakSession session, RealmModel realm, UserModel user);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java
new file mode 100755
index 0000000..b76981f
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.services.managers;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface BruteForceProtectorFactory extends ProviderFactory<BruteForceProtector> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java
new file mode 100755
index 0000000..0d3e24c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/managers/BruteForceProtectorSpi.java
@@ -0,0 +1,49 @@
+/*
+ * 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.services.managers;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class BruteForceProtectorSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "bruteForceProtector";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return BruteForceProtector.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return BruteForceProtectorFactory.class;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/server-spi-private/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
new file mode 100755
index 0000000..2710174
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -0,0 +1,279 @@
+/*
+ * 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.services.managers;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.Time;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.KeyManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ClientSessionCode {
+
+    private static final Logger logger = Logger.getLogger(ClientSessionCode.class);
+
+    private static final String NEXT_CODE = ClientSessionCode.class.getName() + ".nextCode";
+
+    private KeycloakSession session;
+    private final RealmModel realm;
+    private final ClientSessionModel clientSession;
+
+    public enum ActionType {
+        CLIENT,
+        LOGIN,
+        USER
+    }
+
+    public ClientSessionCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+        this.session = session;
+        this.realm = realm;
+        this.clientSession = clientSession;
+    }
+
+    public static class ParseResult {
+        ClientSessionCode code;
+        boolean clientSessionNotFound;
+        boolean illegalHash;
+        ClientSessionModel clientSession;
+
+        public ClientSessionCode getCode() {
+            return code;
+        }
+
+        public boolean isClientSessionNotFound() {
+            return clientSessionNotFound;
+        }
+
+        public boolean isIllegalHash() {
+            return illegalHash;
+        }
+
+        public ClientSessionModel getClientSession() {
+            return clientSession;
+        }
+    }
+
+    public static ParseResult parseResult(String code, KeycloakSession session, RealmModel realm) {
+        ParseResult result = new ParseResult();
+        if (code == null) {
+            result.illegalHash = true;
+            return result;
+        }
+        try {
+            result.clientSession = getClientSession(code, session, realm);
+            if (result.clientSession == null) {
+                result.clientSessionNotFound = true;
+                return result;
+            }
+
+            if (!verifyCode(code, session, realm, result.clientSession)) {
+                result.illegalHash = true;
+                return result;
+            }
+
+            result.code = new ClientSessionCode(session, realm, result.clientSession);
+            return result;
+        } catch (RuntimeException e) {
+            result.illegalHash = true;
+            return result;
+        }
+    }
+
+    public static ClientSessionCode parse(String code, KeycloakSession session, RealmModel realm) {
+        try {
+            ClientSessionModel clientSession = getClientSession(code, session, realm);
+            if (clientSession == null) {
+                return null;
+            }
+
+            if (!verifyCode(code, session, realm, clientSession)) {
+                return null;
+            }
+
+            return new ClientSessionCode(session, realm, clientSession);
+        } catch (RuntimeException e) {
+            return null;
+        }
+    }
+
+    public static ClientSessionModel getClientSession(String code, KeycloakSession session, RealmModel realm) {
+        String[] parts = code.split("\\.");
+        String id = parts[1];
+        return session.sessions().getClientSession(realm, id);
+    }
+
+    public ClientSessionModel getClientSession() {
+        return clientSession;
+    }
+
+    public boolean isValid(String requestedAction, ActionType actionType) {
+        if (!isValidAction(requestedAction)) return false;
+        return isActionActive(actionType);
+    }
+
+    public boolean isActionActive(ActionType actionType) {
+        int timestamp = clientSession.getTimestamp();
+
+        int lifespan;
+        switch (actionType) {
+            case CLIENT:
+                lifespan = realm.getAccessCodeLifespan();
+                break;
+            case LOGIN:
+                lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
+                break;
+            case USER:
+                lifespan = realm.getAccessCodeLifespanUserAction();
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+
+        return timestamp + lifespan > Time.currentTime();
+    }
+
+    public boolean isValidAction(String requestedAction) {
+        String action = clientSession.getAction();
+        if (action == null) {
+            return false;
+        }
+        if (!action.equals(requestedAction)) {
+            return false;
+        }
+        return true;
+    }
+
+
+    public Set<RoleModel> getRequestedRoles() {
+        Set<RoleModel> requestedRoles = new HashSet<>();
+        for (String roleId : clientSession.getRoles()) {
+            RoleModel role = realm.getRoleById(roleId);
+            if (role != null) {
+                requestedRoles.add(role);
+            }
+        }
+        return requestedRoles;
+    }
+
+    public Set<ProtocolMapperModel> getRequestedProtocolMappers() {
+        Set<ProtocolMapperModel> requestedProtocolMappers = new HashSet<>();
+        Set<String> protocolMappers = clientSession.getProtocolMappers();
+        ClientModel client = clientSession.getClient();
+        ClientTemplateModel template = client.getClientTemplate();
+        if (protocolMappers != null) {
+            for (String protocolMapperId : protocolMappers) {
+                ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protocolMapperId);
+                if (protocolMapper == null && template != null) {
+                    protocolMapper = template.getProtocolMapperById(protocolMapperId);
+                }
+                if (protocolMapper != null) {
+                    requestedProtocolMappers.add(protocolMapper);
+                }
+            }
+        }
+        return requestedProtocolMappers;
+    }
+
+    public void setAction(String action) {
+        clientSession.setAction(action);
+        clientSession.setTimestamp(Time.currentTime());
+    }
+
+    public String getCode() {
+        String nextCode = (String) session.getAttribute(NEXT_CODE + "." + clientSession.getId());
+        if (nextCode == null) {
+            nextCode = generateCode(session, realm, clientSession);
+            session.setAttribute(NEXT_CODE + "." + clientSession.getId(), nextCode);
+        } else {
+            logger.debug("Code already generated for session, using code from session attributes");
+        }
+        return nextCode;
+    }
+
+    private static String generateCode(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+        try {
+            KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+
+            String secret = KeycloakModelUtils.generateSecret();
+
+            StringBuilder sb = new StringBuilder();
+            sb.append(secret);
+            sb.append('.');
+            sb.append(clientSession.getId());
+
+            String code = sb.toString();
+
+            Signature signature = RSAProvider.getSignature(Algorithm.RS256);
+            signature.initSign(keys.getPrivateKey());
+            signature.update(code.getBytes("utf-8"));
+
+            sb = new StringBuilder();
+
+            sb.append(Base64Url.encode(signature.sign()));
+            sb.append('.');
+            sb.append(keys.getKid());
+
+            clientSession.setNote(ClientSessionModel.ACTION_SIGNATURE, sb.toString());
+
+            return code;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static boolean verifyCode(String code, KeycloakSession session, RealmModel realm, ClientSessionModel clientSession) {
+        try {
+            String note = clientSession.getNote(ClientSessionModel.ACTION_SIGNATURE);
+            if (note == null) {
+                logger.debug("Action signature not found in client session");
+                return false;
+            }
+
+            clientSession.removeNote(ClientSessionModel.ACTION_SIGNATURE);
+
+            String[] signed = note.split("\\.");
+
+            PublicKey publicKey = session.keys().getPublicKey(realm, signed[1]);
+
+            Signature verifier = RSAProvider.getSignature(Algorithm.RS256);
+            verifier.initVerify(publicKey);
+            verifier.update(code.getBytes("utf-8"));
+            return verifier.verify(Base64Url.decode(signed[0]));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java
new file mode 100644
index 0000000..8f615c6
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProvider.java
@@ -0,0 +1,38 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual 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.services.resource;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * <p>A {@link RealmResourceProvider} creates JAX-RS <emphasis>sub-resource</emphasis> instances for paths relative
+ * to Realm's RESTful API that could not be resolved by the server.
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface RealmResourceProvider extends Provider {
+
+    /**
+     * <p>Returns a JAX-RS resource instance.
+     *
+     * @return a JAX-RS sub-resource instance
+     */
+    Object getResource();
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java
new file mode 100644
index 0000000..b39bc12
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceProviderFactory.java
@@ -0,0 +1,30 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual 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.services.resource;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * <p>A factory that creates {@link RealmResourceProvider} instances.
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface RealmResourceProviderFactory extends ProviderFactory<RealmResourceProvider> {
+
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java
new file mode 100644
index 0000000..04e8f6d
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/services/resource/RealmResourceSPI.java
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual 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.services.resource;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * <p>A {@link Spi} to plug additional sub-resources to Realms' RESTful API.
+ *
+ * <p>Implementors can use this {@link Spi} to provide additional services to the mentioned API and extend Keycloak capabilities by
+ * creating JAX-RS sub-resources for paths not known by the server.
+ *
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class RealmResourceSPI implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "realm-restapi-extension";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return RealmResourceProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return RealmResourceProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/theme/Theme.java b/server-spi-private/src/main/java/org/keycloak/theme/Theme.java
new file mode 100755
index 0000000..283f4d7
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/theme/Theme.java
@@ -0,0 +1,72 @@
+/*
+ * 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.theme;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Locale;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Theme {
+
+    public enum Type { LOGIN, ACCOUNT, ADMIN, EMAIL, WELCOME, COMMON };
+
+    public String getName();
+
+    public String getParentName();
+
+    public String getImportName();
+
+    public Type getType();
+
+    public URL getTemplate(String name) throws IOException;
+
+    public InputStream getTemplateAsStream(String name) throws IOException;
+
+    public URL getResource(String path) throws IOException;
+
+    public InputStream getResourceAsStream(String path) throws IOException;
+
+    /**
+     * Same as getMessages(baseBundlename, locale), but uses a default baseBundlename
+     * such as "messages".
+     *
+     * @param locale The locale of the desired message bundle.
+     * @return The localized messages from the bundle.
+     * @throws IOException If bundle can not be read.
+     */
+    public Properties getMessages(Locale locale) throws IOException;
+
+    /**
+     * Retrieve localized messages from a message bundle.
+     *
+     * @param baseBundlename The base name of the bundle, such as "messages" in
+     * messages_en.properties.
+     * @param locale The locale of the desired message bundle.
+     * @return The localized messages from the bundle.
+     * @throws IOException If bundle can not be read.
+     */
+    public Properties getMessages(String baseBundlename, Locale locale) throws IOException;
+
+    public Properties getProperties() throws IOException;
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/theme/ThemeProvider.java b/server-spi-private/src/main/java/org/keycloak/theme/ThemeProvider.java
new file mode 100755
index 0000000..34de97b
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/theme/ThemeProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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.theme;
+
+import org.keycloak.provider.Provider;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ThemeProvider extends Provider {
+
+    public int getProviderPriority();
+
+    public Theme getTheme(String name, Theme.Type type) throws IOException;
+
+    public Set<String> nameSet(Theme.Type type);
+
+    public boolean hasTheme(String name, Theme.Type type);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/theme/ThemeProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/theme/ThemeProviderFactory.java
new file mode 100755
index 0000000..d28fea5
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/theme/ThemeProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.theme;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ThemeProviderFactory extends ProviderFactory<ThemeProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/theme/ThemeSpi.java b/server-spi-private/src/main/java/org/keycloak/theme/ThemeSpi.java
new file mode 100755
index 0000000..b0f263c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/theme/ThemeSpi.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.theme;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ThemeSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "theme";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return ThemeProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return ThemeProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/timer/ScheduledTask.java b/server-spi-private/src/main/java/org/keycloak/timer/ScheduledTask.java
new file mode 100644
index 0000000..ba092ac
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/timer/ScheduledTask.java
@@ -0,0 +1,29 @@
+/*
+ * 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.timer;
+
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ScheduledTask {
+
+    public void run(KeycloakSession session);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/timer/TimerProvider.java b/server-spi-private/src/main/java/org/keycloak/timer/TimerProvider.java
new file mode 100644
index 0000000..5dbf69b
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/timer/TimerProvider.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.timer;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface TimerProvider extends Provider {
+
+    public void schedule(Runnable runnable, long intervalMillis, String taskName);
+
+    public void scheduleTask(ScheduledTask scheduledTask, long intervalMillis, String taskName);
+
+    public void cancelTask(String taskName);
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/timer/TimerProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/timer/TimerProviderFactory.java
new file mode 100644
index 0000000..a676242
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/timer/TimerProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.timer;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface TimerProviderFactory extends ProviderFactory<TimerProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/timer/TimerSpi.java b/server-spi-private/src/main/java/org/keycloak/timer/TimerSpi.java
new file mode 100644
index 0000000..b300a6b
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/timer/TimerSpi.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.timer;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TimerSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "timer";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return TimerProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return TimerProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java b/server-spi-private/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java
new file mode 100644
index 0000000..ebcf52e
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.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.transaction;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+import javax.transaction.TransactionManager;
+
+/**
+ * JTA TransactionManager lookup
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface JtaTransactionManagerLookup extends Provider, ProviderFactory<JtaTransactionManagerLookup> {
+    @Override
+    default void close() {
+
+    }
+
+    @Override
+    default JtaTransactionManagerLookup create(KeycloakSession session) {
+        return this;
+    }
+
+    TransactionManager getTransactionManager();
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java b/server-spi-private/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java
new file mode 100755
index 0000000..f45d897
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java
@@ -0,0 +1,49 @@
+/*
+ * 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.transaction;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TransactionManagerLookupSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "jta-lookup";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return JtaTransactionManagerLookup.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return JtaTransactionManagerLookup.class;
+    }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java b/server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java
new file mode 100755
index 0000000..4c4f069
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/truststore/HostnameVerificationPolicy.java
@@ -0,0 +1,36 @@
+/*
+ * 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.truststore;
+
+public enum HostnameVerificationPolicy {
+
+    /**
+     * Hostname verification is not done on the server's certificate
+     */
+    ANY,
+
+    /**
+     * Allows wildcards in subdomain names i.e. *.foo.com
+     */
+    WILDCARD,
+
+    /**
+     * CN must match hostname connecting to
+     */
+    STRICT
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
new file mode 100755
index 0000000..00b868a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
@@ -0,0 +1,32 @@
+/*
+ * 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.truststore;
+
+import org.keycloak.provider.Provider;
+
+import java.security.KeyStore;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public interface TruststoreProvider extends Provider {
+
+    HostnameVerificationPolicy getPolicy();
+
+    KeyStore getTruststore();
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java
new file mode 100755
index 0000000..83c79b2
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.truststore;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public interface TruststoreProviderFactory extends ProviderFactory<TruststoreProvider> {
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreSpi.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreSpi.java
new file mode 100755
index 0000000..160ccba
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreSpi.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.truststore;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class TruststoreSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "truststore";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return TruststoreProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return TruststoreProviderFactory.class;
+    }
+}
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
new file mode 100644
index 0000000..3cf430c
--- /dev/null
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.models.session.UserSessionPersisterProviderFactory
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.keycloak.models.session.DisabledUserSessionPersisterProvider
\ No newline at end of file
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
new file mode 100644
index 0000000..128272d
--- /dev/null
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.keycloak.policy.DefaultPasswordPolicyManagerProviderFactory
\ No newline at end of file
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
new file mode 100644
index 0000000..a436fe9
--- /dev/null
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+org.keycloak.policy.DigitsPasswordPolicyProviderFactory
+org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory
+org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory
+org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory
+org.keycloak.policy.HistoryPasswordPolicyProviderFactory
+org.keycloak.policy.LengthPasswordPolicyProviderFactory
+org.keycloak.policy.LowerCasePasswordPolicyProviderFactory
+org.keycloak.policy.NotUsernamePasswordPolicyProviderFactory
+org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory
+org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory
+org.keycloak.policy.UpperCasePasswordPolicyProviderFactory
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100755
index 0000000..bbd588e
--- /dev/null
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1,68 @@
+#
+# 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.
+#
+
+org.keycloak.models.UserFederationSpi
+org.keycloak.storage.UserStorageProviderSpi
+org.keycloak.storage.federated.UserFederatedStorageProviderSpi
+org.keycloak.mappers.UserFederationMapperSpi
+org.keycloak.models.RealmSpi
+org.keycloak.models.UserSessionSpi
+org.keycloak.models.UserSpi
+org.keycloak.models.session.UserSessionPersisterSpi
+org.keycloak.models.dblock.DBLockSpi
+org.keycloak.migration.MigrationSpi
+org.keycloak.events.EventListenerSpi
+org.keycloak.events.EventStoreSpi
+org.keycloak.exportimport.ExportSpi
+org.keycloak.exportimport.ImportSpi
+org.keycloak.timer.TimerSpi
+org.keycloak.scripting.ScriptingSpi
+org.keycloak.services.managers.BruteForceProtectorSpi
+org.keycloak.services.resource.RealmResourceSPI
+org.keycloak.protocol.ClientInstallationSpi
+org.keycloak.protocol.LoginProtocolSpi
+org.keycloak.protocol.ProtocolMapperSpi
+org.keycloak.broker.provider.IdentityProviderSpi
+org.keycloak.broker.provider.IdentityProviderMapperSpi
+org.keycloak.broker.social.SocialProviderSpi
+org.keycloak.forms.account.AccountSpi
+org.keycloak.forms.login.LoginFormsSpi
+org.keycloak.email.EmailSenderSpi
+org.keycloak.email.EmailTemplateSpi
+org.keycloak.theme.ThemeSpi
+org.keycloak.truststore.TruststoreSpi
+org.keycloak.connections.httpclient.HttpClientSpi
+org.keycloak.models.cache.CacheRealmProviderSpi
+org.keycloak.models.cache.CacheUserProviderSpi
+org.keycloak.authentication.AuthenticatorSpi
+org.keycloak.authentication.ClientAuthenticatorSpi
+org.keycloak.authentication.RequiredActionSpi
+org.keycloak.authentication.FormAuthenticatorSpi
+org.keycloak.authentication.FormActionSpi
+org.keycloak.cluster.ClusterSpi
+org.keycloak.authorization.policy.provider.PolicySpi
+org.keycloak.authorization.store.StoreFactorySpi
+org.keycloak.authorization.AuthorizationSpi
+org.keycloak.models.cache.authorization.CachedStoreFactorySpi
+org.keycloak.protocol.oidc.TokenIntrospectionSpi
+org.keycloak.policy.PasswordPolicySpi
+org.keycloak.policy.PasswordPolicyManagerSpi
+org.keycloak.transaction.TransactionManagerLookupSpi
+org.keycloak.credential.hash.PasswordHashSpi
+org.keycloak.credential.CredentialSpi
+org.keycloak.keys.PublicKeyStorageSpi
+org.keycloak.keys.KeySpi
\ No newline at end of file
diff --git a/server-spi-private/src/test/java/org/keycloak/models/HmacTest.java b/server-spi-private/src/test/java/org/keycloak/models/HmacTest.java
new file mode 100755
index 0000000..45aaf16
--- /dev/null
+++ b/server-spi-private/src/test/java/org/keycloak/models/HmacTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.models.utils.Base32;
+import org.keycloak.models.utils.HmacOTP;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HmacTest {
+
+    @Test
+    public void testHmac() throws Exception {
+        HmacOTP hmacOTP = new HmacOTP(6, HmacOTP.HMAC_SHA1, 10);
+        String secret = "JNSVMMTEKZCUGSKJIVGHMNSQOZBDA5JT";
+        String decoded = new String(Base32.decode(secret));
+        System.out.println(hmacOTP.generateHOTP(decoded, 0));
+        System.out.println(hmacOTP.validateHOTP("550233", decoded, 0));
+        Assert.assertEquals(1, hmacOTP.validateHOTP("550233", decoded, 0));
+    }
+}

services/pom.xml 5(+5 -0)

diff --git a/services/pom.xml b/services/pom.xml
index 78e3f42..f6fdb74 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -65,6 +65,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-ldap-federation</artifactId>
             <scope>provided</scope>
         </dependency>
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
index fdaa481..ef17476 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ConditionalOtpFormAuthenticator.java
@@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.browser;
 import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.RoleUtils;
 
 import javax.ws.rs.core.MultivaluedMap;
 import java.util.List;
@@ -30,7 +31,6 @@ import static org.keycloak.authentication.authenticators.browser.ConditionalOtpF
 import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OtpDecision.SHOW_OTP;
 import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OtpDecision.SKIP_OTP;
 import static org.keycloak.models.utils.KeycloakModelUtils.getRoleFromString;
-import static org.keycloak.models.utils.KeycloakModelUtils.hasRole;
 
 /**
  * An {@link OTPFormAuthenticator} that can conditionally require OTP authentication.
@@ -264,6 +264,6 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator {
         RoleModel role = getRoleFromString(context.getRealm(), roleName);
         UserModel user = context.getUser();
 
-        return hasRole(user.getRoleMappings(), role);
+        return RoleUtils.hasRole(user.getRoleMappings(), role);
     }
 }
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
index 85a217f..9bff3f9 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
@@ -142,7 +142,7 @@ public class ScriptBasedAuthenticator implements Authenticator {
 
         RealmModel realm = context.getRealm();
 
-        ScriptingProvider scripting = context.getSession().scripting();
+        ScriptingProvider scripting = context.getSession().getProvider(ScriptingProvider.class);
 
         //TODO lookup script by scriptId instead of creating it every time
         ScriptModel script = scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
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 997bc93..0ef5276 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -73,9 +73,13 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
-import java.security.PublicKey;
+import java.security.Key;
 import java.security.cert.X509Certificate;
+import java.util.LinkedList;
 import java.util.List;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -174,14 +178,20 @@ public class SAMLEndpoint {
         protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
         protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
         protected abstract SAMLDocumentHolder extractResponseDocument(String response);
-        protected PublicKey getIDPKey() {
-            X509Certificate certificate = null;
-            try {
-                certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
-            } catch (ProcessingException e) {
-                throw new RuntimeException(e);
+        
+        protected KeyLocator getIDPKeyLocator() {
+            List<Key> keys = new LinkedList<>();
+
+            for (String signingCertificate : config.getSigningCertificates()) {
+                try {
+                    X509Certificate cert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(signingCertificate.replaceAll("\\s", ""));
+                    keys.add(cert.getPublicKey());
+                } catch (ProcessingException e) {
+                    throw new RuntimeException(e);
+                }
             }
-            return certificate.getPublicKey();
+
+            return new HardcodedKeyLocator(keys);
         }
 
         public Response execute(String samlRequest, String samlResponse, String relayState) {
@@ -265,14 +275,18 @@ public class SAMLEndpoint {
             builder.issuer(issuerURL);
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
                         .relayState(relayState);
+            boolean postBinding = config.isPostBindingResponse();
             if (config.isWantAuthnRequestsSigned()) {
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-                binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
+                binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
                         .signatureAlgorithm(provider.getSignatureAlgorithm())
                         .signDocument();
+                if (! postBinding && config.isAddExtensionsElementWithKeyInfo()) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                    builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
             }
             try {
-                if (config.isPostBindingResponse()) {
+                if (postBinding) {
                     return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
                 } else {
                     return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
@@ -418,7 +432,7 @@ public class SAMLEndpoint {
     protected class PostBinding extends Binding {
         @Override
         protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
-            SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
+            SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKeyLocator());
         }
 
         @Override
@@ -440,8 +454,8 @@ public class SAMLEndpoint {
     protected class RedirectBinding extends Binding {
         @Override
         protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
-            PublicKey publicKey = getIDPKey();
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
+            KeyLocator locator = getIDPKeyLocator();
+            SamlProtocolUtils.verifyRedirectSignature(documentHolder, locator, uriInfo, key);
         }
 
 
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 104b8f8..f96f15a 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -50,8 +50,11 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * @author Pedro Igor
@@ -97,18 +100,22 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                     .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
                     .relayState(request.getState());
+            boolean postBinding = getConfig().isPostBindingAuthnRequest();
 
             if (getConfig().isWantAuthnRequestsSigned()) {
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
 
                 KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
 
-                binding.signWith(keypair);
+                binding.signWith(keys.getKid(), keypair);
                 binding.signatureAlgorithm(getSignatureAlgorithm());
                 binding.signDocument();
+                if (! postBinding && getConfig().isAddExtensionsElementWithKeyInfo()) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                    authnRequestBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
             }
 
-            if (getConfig().isPostBindingAuthnRequest()) {
+            if (postBinding) {
                 return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
             } else {
                 return binding.redirectBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
@@ -198,7 +205,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                 .relayState(userSession.getId());
         if (getConfig().isWantAuthnRequestsSigned()) {
             KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-            binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
+            binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
                     .signatureAlgorithm(getSignatureAlgorithm())
                     .signDocument();
         }
@@ -225,11 +232,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
         boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
         String entityId = getEntityId(uriInfo, realm);
         String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
-        String certificatePem = PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate());
-        String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
+
+        StringBuilder keysString = new StringBuilder();
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+        }
+        String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
         return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
     }
 
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("        ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, true));
+    }
+
     public SignatureAlgorithm getSignatureAlgorithm() {
         String alg = getConfig().getSignatureAlgorithm();
         if (alg != null) {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index 1b2fb67..59b46ca 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -62,14 +62,45 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
         getConfig().put("forceAuthn", String.valueOf(forceAuthn));
     }
 
+    /**
+     * @deprecated Prefer {@link #getSigningCertificates()}}
+     * @param signingCertificate
+     */
     public String getSigningCertificate() {
-        return getConfig().get("signingCertificate");
+        return getConfig().get(SIGNING_CERTIFICATE_KEY);
     }
 
+    /**
+     * @deprecated Prefer {@link #addSigningCertificate(String)}}
+     * @param signingCertificate
+     */
     public void setSigningCertificate(String signingCertificate) {
-        getConfig().put("signingCertificate", signingCertificate);
+        getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
     }
 
+    public void addSigningCertificate(String signingCertificate) {
+        String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+        if (crt == null || crt.isEmpty()) {
+            getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
+        } else {
+            // Note that "," is not coding character per PEM format specification:
+            // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+            getConfig().put(SIGNING_CERTIFICATE_KEY, crt + "," + signingCertificate);
+        }
+    }
+
+    public String[] getSigningCertificates() {
+        String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+        if (crt == null || crt.isEmpty()) {
+            return new String[] { };
+        }
+        // Note that "," is not coding character per PEM format specification:
+        // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+        return crt.split(",");
+    }
+
+    public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
+
     public String getNameIDPolicyFormat() {
         return getConfig().get("nameIDPolicyFormat");
     }
@@ -86,6 +117,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
         getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
     }
 
+    public boolean isAddExtensionsElementWithKeyInfo() {
+        return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
+    }
+
+    public void setAddExtensionsElementWithKeyInfo(boolean addExtensionsElementWithKeyInfo) {
+        getConfig().put("addExtensionsElementWithKeyInfo", String.valueOf(addExtensionsElementWithKeyInfo));
+    }
+
     public String getSignatureAlgorithm() {
         return getConfig().get("signatureAlgorithm");
     }
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index 714c47e..0cc72da 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -108,6 +108,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                     samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
                     samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
                     samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
+                    samlIdentityProviderConfig.setAddExtensionsElementWithKeyInfo(false);
                     samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
                     samlIdentityProviderConfig.setPostBindingResponse(postBinding);
                     samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
@@ -121,7 +122,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                             Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
 
                             if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
-                                samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
+                                samlIdentityProviderConfig.addSigningCertificate(x509KeyInfo.getTextContent());
                             } else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
                                 samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
                             } else if (keyDescriptorType.getUse() ==  null) {
@@ -131,8 +132,8 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                     }
 
                     if (defaultCertificate != null) {
-                        if (samlIdentityProviderConfig.getSigningCertificate() == null) {
-                            samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
+                        if (samlIdentityProviderConfig.getSigningCertificates().length == 0) {
+                            samlIdentityProviderConfig.addSigningCertificate(defaultCertificate);
                         }
 
                         if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index 84cd0f0..bdc32e7 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -140,7 +140,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
         PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, policy.getHashAlgorithm());
         if (hash == null) {
             logger.warnv("Realm PasswordPolicy PasswordHashProvider {0} not found", policy.getHashAlgorithm());
-            return session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
+            return session.getProvider(PasswordHashProvider.class, PasswordPolicy.HASH_ALGORITHM_DEFAULT);
         }
         return hash;
     }
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index 1ea4092..2357a65 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -108,7 +108,8 @@ public class ImportUtils {
             }
         }
 
-        RealmImporter realmManager = session.getContext().getRealmManager();
+        RealmManager realmManager = new RealmManager(session);
+        realmManager.setContextPath(session.getContext().getContextPath());
         realmManager.importRealm(rep);
 
         if (System.getProperty(ExportImportConfig.ACTION) != null) {
diff --git a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
index c5f09f1..f6bbaeb 100644
--- a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
@@ -23,7 +23,6 @@ import org.keycloak.models.RealmModel;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.Collections;
 import java.util.List;
diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
index d7fa875..8c98bb4 100644
--- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProvider.java
@@ -24,11 +24,17 @@ import org.keycloak.component.ComponentModel;
 import org.keycloak.models.RealmModel;
 
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.security.KeyPair;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 
 /**
@@ -61,8 +67,18 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
             String kid = KeyUtils.createKeyId(keyPair.getPublic());
 
             return new Keys(kid, keyPair, certificate);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to load keys", e);
+        } catch (KeyStoreException kse) {
+            throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
+        } catch (FileNotFoundException fnfe) {
+            throw new RuntimeException("File not found on server. " + fnfe.getMessage(), fnfe);
+        } catch (IOException ioe) {
+            throw new RuntimeException("IO error on server. " + ioe.getMessage(), ioe);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new RuntimeException("Algorithm not available on server. " + nsae.getMessage(), nsae);
+        } catch (CertificateException ce) {
+            throw new RuntimeException("Certificate error on server. " + ce.getMessage(), ce);
+        } catch (UnrecoverableKeyException uke) {
+            throw new RuntimeException("Keystore on server can not be recovered. " + uke.getMessage(), uke);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
index e6ed984..51b726a 100644
--- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
+++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java
@@ -27,6 +27,7 @@ import org.keycloak.provider.ConfigurationValidationHelper;
 import org.keycloak.provider.ProviderConfigProperty;
 
 import java.util.List;
+import org.jboss.logging.Logger;
 
 import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
 
@@ -34,6 +35,7 @@ import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
+    private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProviderFactory.class);
 
     public static final String ID = "java-keystore";
 
@@ -77,7 +79,8 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
             new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
                     .loadKeys(session.getContext().getRealm(), model);
         } catch (Throwable t) {
-            throw new ComponentValidationException("Failed to load keys", t);
+            logger.error("Failed to load keys.", t);
+            throw new ComponentValidationException("Failed to load keys. " + t.getMessage(), t);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
index 0128e4e..cce0fec 100755
--- a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
+++ b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java
@@ -24,7 +24,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.PartialImportRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 
 import java.util.HashMap;
 import java.util.List;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
index f00b2a7..60f5493 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
@@ -42,8 +42,9 @@ public class RedirectUtils {
     }
 
     public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
-        Set<String> validRedirects = client.getRedirectUris();
-        return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, validRedirects);
+        if (client != null)
+            return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, client.getRedirectUris());
+        return null;
     }
 
     public static Set<String> resolveValidRedirects(UriInfo uriInfo, String rootUrl, Set<String> validRedirects) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
index 00caa11..3d62a27 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
@@ -101,6 +101,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
         app.setFullScopeAllowed(true);
         app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
         attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
+        attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, SamlProtocol.ATTRIBUTE_FALSE_VALUE); // default to false
         attributes.put(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
         attributes.put(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
         SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
@@ -110,7 +111,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
         String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
         if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
         String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
-        if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
+        if (logoutRedirect != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
 
         String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
         if (assertionConsumerServicePostBinding != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
index 2175b32..14166ce 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
@@ -18,7 +18,6 @@
 package org.keycloak.protocol.saml.installation;
 
 import org.keycloak.Config;
-import org.keycloak.common.util.PemUtils;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
@@ -42,14 +41,14 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
     @Override
     public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
         SamlClient samlClient = new SamlClient(client);
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         buffer.append("<keycloak-saml-adapter>\n");
         baseXml(session, realm, client, baseUri, samlClient, buffer);
         buffer.append("</keycloak-saml-adapter>\n");
         return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
     }
 
-    public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
+    public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuilder buffer) {
         buffer.append("    <SP entityID=\"").append(client.getClientId()).append("\"\n");
         buffer.append("        sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
         buffer.append("        logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
@@ -113,15 +112,6 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
         buffer.append("                                 postBindingUrl=\"").append(bindingUrl).append("\"\n");
         buffer.append("                                 redirectBindingUrl=\"").append(bindingUrl).append("\"");
         buffer.append("/>\n");
-        if (samlClient.requiresRealmSignature()) {
-            buffer.append("            <Keys>\n");
-            buffer.append("                <Key signing=\"true\">\n");
-            buffer.append("                    <CertificatePem>\n");
-            buffer.append("                       ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
-            buffer.append("                    </CertificatePem>\n");
-            buffer.append("                </Key>\n");
-            buffer.append("            </Keys>\n");
-        }
         buffer.append("        </IDP>\n");
         buffer.append("    </SP>\n");
     }
@@ -138,7 +128,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
 
     @Override
     public String getHelpText() {
-        return "Keycloak SAML adapter configuration file.  Put this in WEB-INF directory if your WAR.";
+        return "Keycloak SAML adapter configuration file.  Put this in WEB-INF directory of your WAR.";
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
index ea77d47..bde0ccd 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
@@ -39,7 +39,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
     @Override
     public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
         SamlClient samlClient = new SamlClient(client);
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
         KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
         buffer.append("</secure-deployment>\n");
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
index 4b84363..3c451b3 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
@@ -32,6 +32,11 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import java.net.URI;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.saml.SPMetadataDescriptor;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,49 +46,61 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
     public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
         SamlClient samlClient = new SamlClient(client);
         String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
-        String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                 "<EntityDescriptor entityID=\"" + idpEntityId + "\"\n" +
-                "                   xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n" +
-                "                   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
-                "   <IDPSSODescriptor WantAuthnRequestsSigned=\"" + Boolean.toString(samlClient.requiresClientSignature()) + "\"\n" +
-                "      protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n";
+        StringBuilder sb = new StringBuilder();
+        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+          + "<EntityDescriptor entityID=\"").append(idpEntityId).append("\"\n"
+          + "                   xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n"
+          + "                   xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\"\n"
+          + "                   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+          + "   <IDPSSODescriptor WantAuthnRequestsSigned=\"")
+          .append(samlClient.requiresClientSignature())
+          .append("\"\n"
+            + "      protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n");
         if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) {
-            idp +=  "   <NameIDFormat>" + samlClient.getNameIDFormat() + "</NameIDFormat>\n";
+            sb.append("   <NameIDFormat>").append(samlClient.getNameIDFormat()).append("</NameIDFormat>\n");
         } else {
-            idp +=  "   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n" +
-                    "   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n" +
-                    "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>\n" +
-                    "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n";
+            sb.append("   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n"
+              + "   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n"
+              + "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>\n"
+              + "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n");
         }
         String bindUrl = RealmsResource.protocolUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
-        idp +=  "\n" +
-                "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
-                "         Location=\"" + bindUrl + "\" />\n";
-        if (!samlClient.forcePostBinding()) {
-           idp +=   "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n" +
-                    "         Location=\"" + bindUrl + "\" />\n";
+        sb.append("\n"
+          + "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
+          + "         Location=\"").append(bindUrl).append("\" />\n");
+        if (! samlClient.forcePostBinding()) {
+           sb.append("      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n"
+             + "         Location=\"").append(bindUrl).append("\" />\n");
 
         }
-        idp +=  "      <SingleLogoutService\n" +
-                "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
-                "         Location=\"" + bindUrl + "\" />\n";
-        if (!samlClient.forcePostBinding()) {
-            idp +=  "      <SingleLogoutService\n" +
-                    "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n" +
-                    "         Location=\"" + bindUrl + "\" />\n";
+        sb.append("      <SingleLogoutService\n"
+          + "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
+          + "         Location=\"").append(bindUrl).append("\" />\n");
+        if (! samlClient.forcePostBinding()) {
+            sb.append("      <SingleLogoutService\n"
+              + "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n"
+              + "         Location=\"").append(bindUrl).append("\" />\n");
         }
-        idp +=  "      <KeyDescriptor use=\"signing\">\n" +
-                "          <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
-                "              <dsig:X509Data>\n" +
-                "                  <dsig:X509Certificate>\n" +
-                "                      " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
-                "                  </dsig:X509Certificate>\n" +
-                "              </dsig:X509Data>\n" +
-                "          </dsig:KeyInfo>\n" +
-                "      </KeyDescriptor>\n" +
-                "   </IDPSSODescriptor>\n" +
-                "</EntityDescriptor>\n";
-        return idp;
+
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(sb, key, KeyTypes.SIGNING.value());
+        }
+
+        sb.append("   </IDPSSODescriptor>\n"
+          + "</EntityDescriptor>\n");
+        return sb.toString();
+    }
+
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("      ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
index 9d12242..6349953 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
@@ -31,6 +31,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.net.URI;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -45,7 +46,8 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
         if (logoutUrl == null) logoutUrl = client.getManagementUrl();
         String nameIdFormat = samlClient.getNameIDFormat();
         if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
-        return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, samlClient.getClientSigningCertificate());
+        String spCertificate = SPMetadataDescriptor.xmlKeyInfo("        ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
+        return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, spCertificate);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
index 0415a72..ee5aaba 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
@@ -23,6 +23,8 @@ import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 
 /**
+ * Configuration of a SAML-enabled client.
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
@@ -116,7 +118,14 @@ public class SamlClient extends ClientConfigResolver {
 
     public void setRequiresRealmSignature(boolean val) {
         client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+    }
+
+    public boolean addExtensionsElementWithKeyInfo() {
+        return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT));
+    }
 
+    public void setAddExtensionsElementWithKeyInfo(boolean val) {
+        client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, Boolean.toString(val));
     }
 
     public boolean forcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
index e5bc2fa..0af3be0 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.protocol.saml;
 
+import java.util.Objects;
 import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.saml.SignatureAlgorithm;
 
@@ -89,7 +90,14 @@ public class SamlClientTemplate {
 
     public void setRequiresRealmSignature(boolean val) {
         clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+    }
+
+    public boolean addExtensionsElementWithKeyInfo() {
+        return Objects.equals("true", clientTemplate.getAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT));
+    }
 
+    public void setAddExtensionsElementWithKeyInfo(boolean val) {
+        clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, Boolean.toString(val));
     }
 
     public boolean forcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
index 3356c31..9837179 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
@@ -31,6 +31,7 @@ public interface SamlConfigAttributes {
     String SAML_AUTHNSTATEMENT = "saml.authnstatement";
     String SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE = "saml_force_name_id_format";
     String SAML_SERVER_SIGNATURE = "saml.server.signature";
+    String SAML_SERVER_SIGNATURE_KEYINFO_EXT = "saml.server.signature.keyinfo.ext";
     String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
     String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
     String SAML_ENCRYPT = "saml.encrypt";
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 14726d3..486633f 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -74,8 +74,10 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -98,6 +100,7 @@ public class SamlProtocol implements LoginProtocol {
     public static final String SAML_REDIRECT_BINDING = "get";
     public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
     public static final String SAML_LOGOUT_BINDING = "saml.logout.binding";
+    public static final String SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO = "saml.logout.addExtensionsElementWithKeyInfo";
     public static final String SAML_LOGOUT_REQUEST_ID = "SAML_LOGOUT_REQUEST_ID";
     public static final String SAML_LOGOUT_RELAY_STATE = "SAML_LOGOUT_RELAY_STATE";
     public static final String SAML_LOGOUT_CANONICALIZATION = "SAML_LOGOUT_CANONICALIZATION";
@@ -373,7 +376,15 @@ public class SamlProtocol implements LoginProtocol {
         }
 
         Document samlDocument = null;
+        KeyManager keyManager = session.keys();
+        KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
+        boolean postBinding = isPostBinding(clientSession);
+
         try {
+            if ((! postBinding) && samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
+                builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+            }
+
             ResponseType samlModel = builder.buildModel();
             final AttributeStatementType attributeStatement = populateAttributeStatements(attributeStatementMappers, session, userSession, clientSession);
             populateRoles(roleListMapper, session, userSession, clientSession, attributeStatement);
@@ -394,22 +405,19 @@ public class SamlProtocol implements LoginProtocol {
         JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
         bindingBuilder.relayState(relayState);
 
-        KeyManager keyManager = session.keys();
-        KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
-
         if (samlClient.requiresRealmSignature()) {
             String canonicalization = samlClient.getCanonicalizationMethod();
             if (canonicalization != null) {
                 bindingBuilder.canonicalizationMethod(canonicalization);
             }
-            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
         }
         if (samlClient.requiresAssertionSignature()) {
             String canonicalization = samlClient.getCanonicalizationMethod();
             if (canonicalization != null) {
                 bindingBuilder.canonicalizationMethod(canonicalization);
             }
-            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
+            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
         }
         if (samlClient.requiresEncryption()) {
             PublicKey publicKey = null;
@@ -496,12 +504,17 @@ public class SamlProtocol implements LoginProtocol {
             if (isLogoutPostBindingForClient(clientSession)) {
                 String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
                 SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
+                // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
                 JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
                 return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri);
             } else {
                 logger.debug("frontchannel redirect binding");
                 String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
                 SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
+                if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
+                    KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+                    logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
                 JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
                 return binding.redirectBinding(logoutBuilder.buildDocument()).request(bindingUri);
             }
@@ -534,6 +547,7 @@ public class SamlProtocol implements LoginProtocol {
         JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
         binding.relayState(logoutRelayState);
         String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
+        boolean postBinding = isLogoutPostBindingForInitiator(userSession);
         if (signingAlgorithm != null) {
             SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
             String canonicalization = userSession.getNote(SAML_LOGOUT_CANONICALIZATION);
@@ -541,7 +555,11 @@ public class SamlProtocol implements LoginProtocol {
                 binding.canonicalizationMethod(canonicalization);
             }
             KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-            binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            boolean addExtension = (! postBinding) && Objects.equals("true", userSession.getNote(SamlProtocol.SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO));
+            if (addExtension) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+            }
         }
 
         try {
@@ -577,6 +595,7 @@ public class SamlProtocol implements LoginProtocol {
         String logoutRequestString = null;
         try {
             JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
+            // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
             logoutRequestString = binding.postBinding(logoutBuilder.buildDocument()).encoded();
         } catch (Exception e) {
             logger.warn("failed to send saml logout", e);
@@ -639,7 +658,7 @@ public class SamlProtocol implements LoginProtocol {
         JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
         if (samlClient.requiresRealmSignature()) {
             KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-            binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
         }
         return binding;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index e1a7c98..026a54a 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.protocol.saml;
 
+import java.security.Key;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.PemUtils;
 import org.keycloak.models.ClientModel;
@@ -33,6 +34,15 @@ import javax.ws.rs.core.UriInfo;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.cert.Certificate;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.w3c.dom.Element;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -40,20 +50,36 @@ import java.security.cert.Certificate;
  */
 public class SamlProtocolUtils {
 
-
+    /**
+     * Verifies a signature of the given SAML document using settings for the given client.
+     * Throws an exception if the client signature is expected to be present as per the client
+     * settings and it is invalid, otherwise returns back to the caller.
+     *
+     * @param client
+     * @param document
+     * @throws VerificationException
+     */
     public static void verifyDocumentSignature(ClientModel client, Document document) throws VerificationException {
         SamlClient samlClient = new SamlClient(client);
         if (!samlClient.requiresClientSignature()) {
             return;
         }
         PublicKey publicKey = getSignatureValidationKey(client);
-        verifyDocumentSignature(document, publicKey);
+        verifyDocumentSignature(document, new HardcodedKeyLocator(publicKey));
     }
 
-    public static void verifyDocumentSignature(Document document, PublicKey publicKey) throws VerificationException {
+    /**
+     * Verifies a signature of the given SAML document using keys obtained from the given key locator.
+     * Throws an exception if the client signature is invalid, otherwise returns back to the caller.
+     *
+     * @param document
+     * @param keyLocator
+     * @throws VerificationException
+     */
+    public static void verifyDocumentSignature(Document document, KeyLocator keyLocator) throws VerificationException {
         SAML2Signature saml2Signature = new SAML2Signature();
         try {
-            if (!saml2Signature.validate(document, publicKey)) {
+            if (!saml2Signature.validate(document, keyLocator)) {
                 throw new VerificationException("Invalid signature on document");
             }
         } catch (ProcessingException e) {
@@ -61,10 +87,22 @@ public class SamlProtocolUtils {
         }
     }
 
+    /**
+     * Returns public part of SAML signing key from the client settings.
+     * @param client
+     * @return Public key for signature validation.
+     * @throws VerificationException
+     */
     public static PublicKey getSignatureValidationKey(ClientModel client) throws VerificationException {
         return getPublicKey(new SamlClient(client).getClientSigningCertificate());
     }
 
+    /**
+     * Returns public part of SAML encryption key from the client settings.
+     * @param client
+     * @return Public key for encryption.
+     * @throws VerificationException
+     */
     public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
         return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
     }
@@ -85,7 +123,7 @@ public class SamlProtocolUtils {
         return cert.getPublicKey();
     }
 
-    public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation, String paramKey) throws VerificationException {
+    public static void verifyRedirectSignature(SAMLDocumentHolder documentHolder, KeyLocator locator, UriInfo uriInformation, String paramKey) throws VerificationException {
         MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
         String request = encodedParams.getFirst(paramKey);
         String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
@@ -96,10 +134,11 @@ public class SamlProtocolUtils {
         if (algorithm == null) throw new VerificationException("SigAlg was null");
         if (signature == null) throw new VerificationException("Signature was null");
 
+        String keyId = getMessageSigningKeyId(documentHolder.getSamlObject());
+
         // Shibboleth doesn't sign the document for redirect binding.
         // todo maybe a flag?
 
-
         UriBuilder builder = UriBuilder.fromPath("/")
                 .queryParam(paramKey, request);
         if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
@@ -113,8 +152,13 @@ public class SamlProtocolUtils {
 
             SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
             Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
-            validator.initVerify(publicKey);
-            validator.update(rawQuery.getBytes("UTF-8"));
+            Key key = locator.getKey(keyId);
+            if (key instanceof PublicKey) {
+                validator.initVerify((PublicKey) key);
+                validator.update(rawQuery.getBytes("UTF-8"));
+            } else {
+                throw new VerificationException("Invalid key locator for signature verification");
+            }
             if (!validator.verify(decodedSignature)) {
                 throw new VerificationException("Invalid query param signature");
             }
@@ -123,5 +167,32 @@ public class SamlProtocolUtils {
         }
     }
 
+    private static String getMessageSigningKeyId(SAML2Object doc) {
+        final ExtensionsType extensions;
+        if (doc instanceof RequestAbstractType) {
+            extensions = ((RequestAbstractType) doc).getExtensions();
+        } else if (doc instanceof StatusResponseType) {
+            extensions = ((StatusResponseType) doc).getExtensions();
+        } else {
+            return null;
+        }
+
+        if (extensions == null) {
+            return null;
+        }
+
+        for (Object ext : extensions.getAny()) {
+            if (! (ext instanceof Element)) {
+                continue;
+            }
+
+            String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
 
+            if (res != null) {
+                return res;
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
index b2b4ee4..a67374a 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
@@ -64,7 +64,11 @@ public class SamlRepresentationAttributes {
     public String getSamlServerSignature() {
         if (getAttributes() == null) return null;
         return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE);
+    }
 
+    public String getAddExtensionsElementWithKeyInfo() {
+        if (getAttributes() == null) return null;
+        return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT);
     }
 
     public String getForcePostBinding() {
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 ea01085..14c5503 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -74,6 +74,17 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.security.PublicKey;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.SPMetadataDescriptor;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * Resource class for the oauth/openid connect token service
@@ -336,6 +347,8 @@ public class SamlService extends AuthorizationEndpointBase {
                 String logoutBinding = getBindingType();
                 if ("true".equals(samlClient.forcePostBinding()))
                     logoutBinding = SamlProtocol.SAML_POST_BINDING;
+                boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
+
                 String bindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
                 UserSessionModel userSession = authResult.getSession();
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
@@ -347,6 +360,7 @@ public class SamlService extends AuthorizationEndpointBase {
                     userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
+                userSession.setNote(SamlProtocol.SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO, Boolean.toString((! postBinding) && samlClient.addExtensionsElementWithKeyInfo()));
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
                 userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
                 // remove client from logout requests
@@ -397,14 +411,17 @@ public class SamlService extends AuthorizationEndpointBase {
             builder.destination(logoutBindingUri);
             builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
+            boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
             if (samlClient.requiresRealmSignature()) {
                 SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-                binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
-
+                binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+                if (! postBinding && samlClient.addExtensionsElementWithKeyInfo()) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                    builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
             }
             try {
-                if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
+                if (postBinding) {
                     return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
                 } else {
                     return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
@@ -466,7 +483,8 @@ public class SamlService extends AuthorizationEndpointBase {
                 return;
             }
             PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
+            KeyLocator clientKeyLocator = new HardcodedKeyLocator(publicKey);
+            SamlProtocolUtils.verifyRedirectSignature(documentHolder, clientKeyLocator, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
         }
 
         @Override
@@ -541,12 +559,30 @@ public class SamlService extends AuthorizationEndpointBase {
     public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) throws IOException {
         InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
         String template = StreamUtil.readString(is);
-        template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
-        template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
-        return template;
+        Properties props = new Properties();
+        props.put("idp.entityID", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
+        props.put("idp.sso.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        StringBuilder keysString = new StringBuilder();
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+        }
+        props.put("idp.signing.certificates", keysString.toString());
+        return StringPropertyReplacer.replaceProperties(template, props);
+    }
+
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("                        ",
+          key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
     }
 
     @GET
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
index 99f2559..07313d8 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java
@@ -111,13 +111,6 @@ public class DefaultKeycloakContext implements KeycloakContext {
     }
 
     @Override
-    public RealmImporter getRealmManager() {
-        RealmManager manager = new RealmManager(session);
-        manager.setContextPath(getContextPath());
-        return manager;
-    }
-
-    @Override
     public Locale resolveLocale(UserModel user) {
         return LocaleHelper.getLocale(session, realm, user);
     }
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 7fbd7a3..4cc77a2 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -59,7 +59,6 @@ public class DefaultKeycloakSession implements KeycloakSession {
     private UserProvider userModel;
     private UserStorageManager userStorageManager;
     private UserCredentialStoreManager userCredentialStorageManager;
-    private ScriptingProvider scriptingProvider;
     private UserSessionProvider sessionProvider;
     private UserFederationManager federationManager;
     private UserFederatedStorageProvider userFederatedStorageProvider;
@@ -275,14 +274,4 @@ public class DefaultKeycloakSession implements KeycloakSession {
             }
         }
     }
-
-    @Override
-    public ScriptingProvider scripting() {
-
-        if (scriptingProvider == null) {
-            scriptingProvider = getProvider(ScriptingProvider.class);
-        }
-
-        return scriptingProvider;
-    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index d0eb66b..b3f2638 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -436,7 +436,11 @@ public class AuthenticationManager {
         // refresh the cookies!
         createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
         if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
-        if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
+        if (userSession.isRememberMe()) {
+            createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
+        } else {
+            expireRememberMeCookie(realm, uriInfo, clientConnection);
+        }
 
         // Update userSession note with authTime. But just if flag SSO_AUTH is not set
         if (!isSSOAuthentication(clientSession)) {
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index b648bbd..eb6fba6 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -27,6 +27,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserManager;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.session.UserSessionPersisterProvider;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index f9ae190..7a8964f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -66,7 +66,7 @@ import org.keycloak.services.Urls;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.BruteForceProtector;
 import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 import org.keycloak.services.managers.UserSessionManager;
 import org.keycloak.services.resources.AccountService;
 import org.keycloak.services.validation.Validation;
diff --git a/services/src/main/resources/idp-metadata-template.xml b/services/src/main/resources/idp-metadata-template.xml
index 0a53647..a4416cd 100755
--- a/services/src/main/resources/idp-metadata-template.xml
+++ b/services/src/main/resources/idp-metadata-template.xml
@@ -16,22 +16,12 @@
   ~ limitations under the License.
   -->
 
-<EntitiesDescriptor Name="urn:keycloak"
-					xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+<EntitiesDescriptor Name="urn:keycloak" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
 					xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
 	<EntityDescriptor entityID="${idp.entityID}">
 		<IDPSSODescriptor WantAuthnRequestsSigned="true"
 			protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
-
-			<KeyDescriptor use="signing">
-				 <dsig:KeyInfo>
-					 <dsig:X509Data>
-						 <dsig:X509Certificate>
-							 ${idp.signing.certificate}
-						 </dsig:X509Certificate>
-					 </dsig:X509Data>
-				 </dsig:KeyInfo>
-			</KeyDescriptor>
+${idp.signing.certificates}
 			<SingleLogoutService
 					Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
 					Location="${idp.sls.HTTP-POST}" />
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index d1311b5..873d99d 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -120,7 +120,7 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-server-spi</artifactId>
+            <artifactId>keycloak-server-spi-private</artifactId>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index dbf30e9..09ddb39 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -193,6 +193,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals(true, config.isPostBindingAuthnRequest());
         assertEquals(true, config.isPostBindingResponse());
         assertEquals(true, config.isValidateSignature());
+        assertEquals(false, config.isAddExtensionsElementWithKeyInfo());
     }
 
     private void assertOidcIdentityProviderConfig(IdentityProviderModel identityProvider) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java
index cafe113..6eccb21 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java
@@ -79,7 +79,7 @@ public class FederatedStorageExportImportTest {
     protected PasswordHashProvider getHashProvider(KeycloakSession session, PasswordPolicy policy) {
         PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, policy.getHashAlgorithm());
         if (hash == null) {
-            return session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
+            return session.getProvider(PasswordHashProvider.class, PasswordPolicy.HASH_ALGORITHM_DEFAULT);
         }
         return hash;
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index 431bff6..2db24ad 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -34,7 +34,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.UserSessionProviderFactory;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 import org.keycloak.services.managers.UserSessionManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
index 074438f..80f663f 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
@@ -33,7 +33,7 @@ import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
 import java.util.ArrayList;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
index 013f16c..b9f4f2a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
@@ -34,7 +34,7 @@ import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.services.managers.ClientManager;
 import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 import org.keycloak.services.managers.UserSessionManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.LoggingRule;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index d75e467..f2fc3aa 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -31,7 +31,7 @@ import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.services.managers.UserManager;
+import org.keycloak.models.UserManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
 import java.util.Arrays;
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-assertion-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/bad-assertion-signed-post/WEB-INF/keycloak-saml.xml
index 16f78d9..bc0e9af 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/bad-assertion-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/bad-assertion-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-assertion-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index 39caccd..a24431a 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index 6ec2379..ba20359 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml
index 5e8661c..b0b6ce8 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/ecp/ecp-sp/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/ecp-sp/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index 6021c4e..f075a20 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index e8598aa..2679264 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml
index dc60fda..7305b2f 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml
@@ -16,7 +16,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/missing-assertion-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml
index 1bd35d1..4a4976b 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml
@@ -16,7 +16,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-assertion-and-response-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index e6b64d1..b043e61 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 2d6fd74..dd2c0a1 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index a409d52..7b14a8d 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index b01112b..0949c23 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index 92125d7..bde0569 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 60445bd..5fd80f4 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 28dc1ba..8384ff3 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 61b2a07..df0bad3 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index e40bf58..138c8d3 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index de46826..5dd40a9 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml
index 6585841..87884a5 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-passive/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
index 28e4789..6f20838 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/org/keycloak/testsuite/integration-arquillian-testsuite-providers/main/module.xml
@@ -28,6 +28,7 @@
         <module name="org.keycloak.keycloak-common"/>
         <module name="org.keycloak.keycloak-core"/>
         <module name="org.keycloak.keycloak-server-spi"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.keycloak.keycloak-services"/>
         <module name="org.keycloak.keycloak-model-infinispan"/>
         <module name="org.keycloak.keycloak-model-jpa"/>
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java
index 7392dd0..d6d038a 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerDatabaseServlet.java
@@ -40,7 +40,13 @@ public class CustomerDatabaseServlet extends HttpServlet {
         pw.println("Bill Burke");
         pw.print("</body></html>");
         pw.flush();
-
-
+    }
+    
+    @Override
+    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        req.logout();
+        PrintWriter pw = resp.getWriter();
+        pw.println("servlet logout from database ok");
+        pw.flush();  
     }
 }
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
index e174d7e..b4fd9a5 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServlet.java
@@ -43,16 +43,38 @@ public class CustomerServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         PrintWriter pw = resp.getWriter();
+        KeycloakSecurityContext context = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
         if (req.getRequestURI().endsWith("logout")) {
             resp.setStatus(200);
             pw.println("servlet logout ok");
+            
+            //Clear principal form database-service by calling logout
+            StringBuilder result = new StringBuilder();
+            String urlBase;
+
+            if (System.getProperty("app.server.ssl.required", "false").equals("true")) {
+                urlBase = System.getProperty("app.server.ssl.base.url", "https://localhost:8643");
+            } else {
+                urlBase = System.getProperty("app.server.base.url", "http://localhost:8280");
+            }
 
+            URL url = new URL(urlBase + "/customer-db/");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("DELETE");
+            conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + context.getTokenString());
+            BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+            String line;
+            while ((line = rd.readLine()) != null) {
+              result.append(line);
+            }
+            rd.close();
+            pw.println(result.toString());
             // Call logout before pw.flush
             req.logout();
             pw.flush();
             return;
         }
-        KeycloakSecurityContext context = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
+       
 
         //try {
         StringBuilder result = new StringBuilder();
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServletNoConf.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServletNoConf.java
new file mode 100644
index 0000000..c353532
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/CustomerServletNoConf.java
@@ -0,0 +1,95 @@
+/*
+ * 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.servlet;
+
+import org.keycloak.KeycloakSecurityContext;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@WebServlet("/customer-portal-noconf")
+public class CustomerServletNoConf extends HttpServlet {
+    private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        PrintWriter pw = resp.getWriter();
+        if (req.getRequestURI().endsWith("logout")) {
+            resp.setStatus(200);
+            pw.println("servlet logout ok");
+
+            // Call logout before pw.flush
+            req.logout();
+            pw.flush();
+            return;
+        }
+        KeycloakSecurityContext context = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
+
+        //try {
+        StringBuilder result = new StringBuilder();
+        String urlBase;
+
+        if (System.getProperty("app.server.ssl.required", "false").equals("true")) {
+            urlBase = System.getProperty("app.server.ssl.base.url", "https://localhost:8643");
+        } else {
+            urlBase = System.getProperty("app.server.base.url", "http://localhost:8280");
+        }
+
+        URL url = new URL(urlBase + "/customer-db/");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("GET");
+        conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + context.getTokenString());
+        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+        String line;
+        while ((line = rd.readLine()) != null) {
+            result.append(line);
+        }
+        rd.close();
+        resp.setContentType("text/html");
+        pw.println(result.toString());
+        pw.flush();
+//
+//            Response response = target.request().get();
+//            if (response.getStatus() != 401) { // assert response status == 401
+//                throw new AssertionError("Response status code is not 401.");
+//            }
+//            response.close();
+//            String html = target.request()
+//                                .header(HttpHeaders.AUTHORIZATION, "Bearer " + context.getTokenString())
+//                                .get(String.class);
+//            pw.println(html);
+//            pw.flush();
+//        } finally {
+//            client.close();
+//        }
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortalNoConf.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortalNoConf.java
new file mode 100644
index 0000000..ab17aa7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/CustomerPortalNoConf.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+public class CustomerPortalNoConf extends AbstractPageWithInjectedUrl {
+
+    public static final String DEPLOYMENT_NAME = "customer-portal-noconf";
+
+    @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/adapter/page/EmployeeSigPostNoIdpKeyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigPostNoIdpKeyServlet.java
new file mode 100644
index 0000000..5ef40ae
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigPostNoIdpKeyServlet.java
@@ -0,0 +1,39 @@
+/*
+ * 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 java.net.URL;
+
+/**
+ * @author hmlnarik
+ */
+public class EmployeeSigPostNoIdpKeyServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "employee-sig-post-noidpkey";
+
+    @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/adapter/page/EmployeeSigRedirNoIdpKeyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirNoIdpKeyServlet.java
new file mode 100644
index 0000000..ac6d671
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirNoIdpKeyServlet.java
@@ -0,0 +1,39 @@
+/*
+ * 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 java.net.URL;
+
+/**
+ * @author hmlnarik
+ */
+public class EmployeeSigRedirNoIdpKeyServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "employee-sig-redir-noidpkey";
+
+    @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/adapter/page/EmployeeSigRedirOptNoIdpKeyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirOptNoIdpKeyServlet.java
new file mode 100644
index 0000000..e37c12f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirOptNoIdpKeyServlet.java
@@ -0,0 +1,39 @@
+/*
+ * 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 java.net.URL;
+
+/**
+ * @author hmlnarik
+ */
+public class EmployeeSigRedirOptNoIdpKeyServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "employee-sig-redir-opt-noidpkey";
+
+    @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/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 374585b..b8ed52d 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -132,21 +132,12 @@ public class OAuthClient {
 
     public AuthorizationEndpointResponse doLogin(String username, String password) {
         openLoginForm();
-        String src = driver.getPageSource();
-        try {
-            driver.findElement(By.id("username")).sendKeys(username);
-            driver.findElement(By.id("password")).sendKeys(password);
-            driver.findElement(By.name("login")).click();
-        } catch (Throwable t) {
-            System.err.println(src);
-            throw t;
-        }
+        fillLoginForm(username, password);
 
         return new AuthorizationEndpointResponse(this);
     }
 
-    public void doLoginGrant(String username, String password) {
-        openLoginForm();
+    public void fillLoginForm(String username, String password) {
         String src = driver.getPageSource();
         try {
             driver.findElement(By.id("username")).sendKeys(username);
@@ -158,6 +149,11 @@ public class OAuthClient {
         }
     }
 
+    public void doLoginGrant(String username, String password) {
+        openLoginForm();
+        fillLoginForm(username, password);
+    }
+
     public AccessTokenResponse doAccessTokenRequest(String code, String password) {
         CloseableHttpClient client = new DefaultHttpClient();
         try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 0dc7597..65a2e9b 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -813,4 +813,18 @@ public class AccountTest extends TestRealmKeycloakTest {
 
     }
 
+    @Test
+    public void testInvalidReferrer() {
+        driver.navigate().to(profilePage.getPath() + "?referrer=test-app");
+        loginPage.login("test-user@localhost", "password");
+        Assert.assertTrue(profilePage.isCurrent());
+        profilePage.backToApplication();
+
+        Assert.assertTrue(appPage.isCurrent());
+
+        driver.navigate().to(profilePage.getPath() + "?referrer=test-invalid&referrer_uri=http://localhost:8180/auth/realms/master/app/auth?test");
+        Assert.assertTrue(profilePage.isCurrent());
+
+        events.clear();
+    }
 }
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 e742b91..ed2191d 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
@@ -28,8 +28,10 @@ import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.common.Version;
+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;
@@ -37,6 +39,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.VersionRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
@@ -85,6 +88,7 @@ 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;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
@@ -101,6 +105,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
     @Page
     private CustomerPortal customerPortal;
     @Page
+    private CustomerPortalNoConf customerPortalNoConf;
+    @Page
     private CustomerPortalSubsystem customerPortalSubsystem;
     @Page
     private SecurePortal securePortal;
@@ -129,6 +135,11 @@ 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() {
@@ -245,25 +256,26 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         driver.navigate().to(logoutUri);
 
         // Generate new realm key
-        RealmRepresentation realmRep = testRealmResource().toRepresentation();
-        String oldPublicKey = realmRep.getPublicKey();
-        String oldPrivateKey = realmRep.getPrivateKey();
-        realmRep.setPublicKey(Constants.GENERATE);
-        testRealmResource().update(realmRep);
-
-        // Try to login again. It should fail now
-        tokenMinTTLPage.navigateTo();
-        testRealmLoginPage.form().waitForUsernameInputPresent();
-        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
-        testRealmLoginPage.form().login("bburke@redhat.com", "password");
-        URLAssert.assertCurrentUrlStartsWith(driver, tokenMinTTLPage.getInjectedUrl().toString());
-        assertNull(tokenMinTTLPage.getAccessToken());
+        String realmId = adminClient.realm(DEMO).toRepresentation().getId();
+        ComponentRepresentation keys = new ComponentRepresentation();
+        keys.setName("generated");
+        keys.setProviderType(KeyProvider.class.getName());
+        keys.setProviderId("rsa-generated");
+        keys.setParentId(realmId);
+        keys.setConfig(new MultivaluedHashMap<>());
+        keys.getConfig().putSingle("priority", "100");
+        Response response = adminClient.realm(DEMO).components().add(keys);
+        assertEquals(201, response.getStatus());
+        response.close();
 
         String adapterActionsUrl = tokenMinTTLPage.toString() + "/unsecured/foo";
         setAdapterAndServerTimeOffset(300, adapterActionsUrl);
 
         // Try to login. Should work now due to realm key change
         tokenMinTTLPage.navigateTo();
+        testRealmLoginPage.form().waitForUsernameInputPresent();
+        assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
         assertCurrentUrlEquals(tokenMinTTLPage);
         token = tokenMinTTLPage.getAccessToken();
         Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername());
@@ -387,7 +399,9 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
         // test logout
 
         driver.navigate().to(customerPortal + "/logout");
-        assertTrue(driver.getPageSource().contains("servlet logout ok"));
+        pageSource = driver.getPageSource();
+        assertTrue(pageSource.contains("servlet logout ok"));
+        assertTrue(pageSource.contains("servlet logout from database ok"));
 
         customerPortal.navigateTo();
         assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
@@ -829,6 +843,13 @@ 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();
+        String pageSource = driver.getPageSource();
+        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/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index 2906778..6dd3ee3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -24,6 +24,12 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.ProtocolMappersResource;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.keys.Attributes;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.RsaKeyProviderFactory;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
 import org.keycloak.protocol.saml.mappers.RoleListMapper;
 import org.keycloak.representations.idm.ClientRepresentation;
@@ -40,6 +46,9 @@ import org.keycloak.testsuite.adapter.page.BadRealmSalesPostSigServlet;
 import org.keycloak.testsuite.adapter.page.Employee2Servlet;
 import org.keycloak.testsuite.adapter.page.EmployeeServlet;
 import org.keycloak.testsuite.adapter.page.EmployeeSigFrontServlet;
+import org.keycloak.testsuite.adapter.page.EmployeeSigPostNoIdpKeyServlet;
+import org.keycloak.testsuite.adapter.page.EmployeeSigRedirNoIdpKeyServlet;
+import org.keycloak.testsuite.adapter.page.EmployeeSigRedirOptNoIdpKeyServlet;
 import org.keycloak.testsuite.adapter.page.EmployeeSigServlet;
 import org.keycloak.testsuite.adapter.page.InputPortal;
 import org.keycloak.testsuite.adapter.page.MissingAssertionSig;
@@ -80,6 +89,8 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
+import java.security.KeyPair;
+import java.security.PublicKey;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -110,6 +121,15 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
     protected EmployeeSigServlet employeeSigServletPage;
 
     @Page
+    protected EmployeeSigPostNoIdpKeyServlet employeeSigPostNoIdpKeyServletPage;
+
+    @Page
+    protected EmployeeSigRedirNoIdpKeyServlet employeeSigRedirNoIdpKeyServletPage;
+
+    @Page
+    protected EmployeeSigRedirOptNoIdpKeyServlet employeeSigRedirOptNoIdpKeyServletPage;
+
+    @Page
     protected EmployeeSigFrontServlet employeeSigFrontServletPage;
 
     @Page
@@ -184,6 +204,21 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         return samlServletDeployment(EmployeeSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
     }
 
+    @Deployment(name = EmployeeSigPostNoIdpKeyServlet.DEPLOYMENT_NAME)
+    protected static WebArchive employeeSigPostNoIdpKeyServlet() {
+        return samlServletDeployment(EmployeeSigPostNoIdpKeyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
+    @Deployment(name = EmployeeSigRedirNoIdpKeyServlet.DEPLOYMENT_NAME)
+    protected static WebArchive employeeSigRedirNoIdpKeyServlet() {
+        return samlServletDeployment(EmployeeSigRedirNoIdpKeyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
+    @Deployment(name = EmployeeSigRedirOptNoIdpKeyServlet.DEPLOYMENT_NAME)
+    protected static WebArchive employeeSigRedirOptNoIdpKeyServlet() {
+        return samlServletDeployment(EmployeeSigRedirOptNoIdpKeyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
     @Deployment(name = EmployeeSigFrontServlet.DEPLOYMENT_NAME)
     protected static WebArchive employeeSigFront() {
         return samlServletDeployment(EmployeeSigFrontServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -394,6 +429,69 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         testSuccessfulAndUnauthorizedLogin(employeeSigServletPage, testRealmSAMLRedirectLoginPage);
     }
 
+    private PublicKey createKeys(String priority) throws Exception {
+        KeyPair keyPair = KeyUtils.generateRsaKeyPair(1024);
+        String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
+        PublicKey publicKey = keyPair.getPublic();
+
+        ComponentRepresentation rep = new ComponentRepresentation();
+        rep.setName("mycomponent");
+        rep.setParentId("demo");
+        rep.setProviderId(RsaKeyProviderFactory.ID);
+        rep.setProviderType(KeyProvider.class.getName());
+
+        org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();
+        config.addFirst("priority", priority);
+        config.addFirst(Attributes.PRIVATE_KEY_KEY, privateKeyPem);
+        rep.setConfig(config);
+
+        testRealmResource().components().add(rep);
+
+        return publicKey;
+    }
+
+    private void dropKeys(String priority) {
+        for (ComponentRepresentation c : testRealmResource().components().query("demo", KeyProvider.class.getName())) {
+            if (c.getConfig().getFirst("priority").equals(priority)) {
+                testRealmResource().components().component(c.getId()).remove();
+                return;
+            }
+        }
+        throw new RuntimeException("Failed to find keys");
+    }
+
+    private void testRotatedKeysPropagated(SAMLServlet servletPage, Login loginPage) throws Exception {
+        boolean keyDropped = false;
+        try {
+            log.info("Creating new key");
+            createKeys("1000");
+            testSuccessfulAndUnauthorizedLogin(servletPage, loginPage);
+            log.info("Dropping new key");
+            dropKeys("1000");
+            keyDropped = true;
+            testSuccessfulAndUnauthorizedLogin(servletPage, loginPage);
+        } finally {
+            if (! keyDropped) {
+                dropKeys("1000");
+            }
+        }
+    }
+
+    @Test
+    public void employeeSigPostNoIdpKeyTest() throws Exception {
+        testRotatedKeysPropagated(employeeSigPostNoIdpKeyServletPage, testRealmSAMLPostLoginPage);
+    }
+
+    @Test
+    public void employeeSigRedirNoIdpKeyTest() throws Exception {
+        testRotatedKeysPropagated(employeeSigRedirNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage);
+    }
+
+    @Test
+    public void employeeSigRedirOptNoIdpKeyTest() throws Exception {
+        testRotatedKeysPropagated(employeeSigRedirOptNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage);
+    }
+
     @Test
     public void employeeSigFrontTest() {
         testSuccessfulAndUnauthorizedLogin(employeeSigFrontServletPage, testRealmSAMLRedirectLoginPage);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
index 50a5027..97edc00 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -83,7 +83,7 @@ public class ApiUtil {
 
     public static ClientResource findClientResourceByName(RealmResource realm, String name) {
         for (ClientRepresentation c : realm.clients().findAll()) {
-            if (c.getName().equals(name)) {
+            if (name.equals(c.getName())) {
                 return realm.clients().get(c.getId());
             }
         }
@@ -92,7 +92,7 @@ public class ApiUtil {
 
     public static ClientResource findClientByClientId(RealmResource realm, String clientId) {
         for (ClientRepresentation c : realm.clients().findAll()) {
-            if (c.getClientId().equals(clientId)) {
+            if (clientId.equals(c.getClientId())) {
                 return realm.clients().get(c.getId());
             }
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
index 75818b2..02a2cdb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
@@ -27,6 +27,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractAuthTest;
+import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.events.EventsListenerProviderFactory;
 import org.keycloak.testsuite.util.AdminEventPaths;
@@ -70,9 +71,16 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
 
     // returns UserRepresentation retrieved from server, with all fields, including id
     protected UserRepresentation getFullUserRep(String userName) {
+        // the search returns all users who has userName contained in their username.
         List<UserRepresentation> results = testRealmResource().users().search(userName, null, null, null, null, null);
-        if (results.size() != 1) throw new RuntimeException("Did not find single user with username " + userName);
-        return results.get(0);
+        UserRepresentation result = null;
+        for (UserRepresentation user : results) {
+            if (userName.equals(user.getUsername())) {
+                result = user;
+            }
+        }
+        Assert.assertNotNull("Did not find user with username " + userName, result);
+        return result;
     }
 
     protected String createOidcClient(String name) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
index 626c0d4..4328c8f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
@@ -21,13 +21,11 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
 
 /**
  * Test getting the installation/configuration files for OIDC and SAML.
@@ -71,7 +69,7 @@ public class InstallationTest extends AbstractClientTest {
     public void testOidcJBossXml() {
         String xml = oidcClient.getInstallationProvider("keycloak-oidc-jboss-subsystem");
         assertOidcInstallationConfig(xml);
-        assertTrue(xml.contains("<secure-deployment"));
+        assertThat(xml, containsString("<secure-deployment"));
     }
 
     @Test
@@ -81,43 +79,43 @@ public class InstallationTest extends AbstractClientTest {
     }
 
     private void assertOidcInstallationConfig(String config) {
-        assertTrue(config.contains("master"));
-        assertFalse(config.contains(ApiUtil.findActiveKey(testRealmResource()).getPublicKey()));
-        assertTrue(config.contains(authServerUrl()));
+        assertThat(config, containsString("master"));
+        assertThat(config, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getPublicKey())));
+        assertThat(config, containsString(authServerUrl()));
     }
 
     @Test
     public void testSamlMetadataIdpDescriptor() {
         String xml = samlClient.getInstallationProvider("saml-idp-descriptor");
-        assertTrue(xml.contains("<EntityDescriptor"));
-        assertTrue(xml.contains("<IDPSSODescriptor"));
-        assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
-        assertTrue(xml.contains(samlUrl()));
+        assertThat(xml, containsString("<EntityDescriptor"));
+        assertThat(xml, containsString("<IDPSSODescriptor"));
+        assertThat(xml, containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
+        assertThat(xml, containsString(samlUrl()));
     }
 
     @Test
     public void testSamlAdapterXml() {
         String xml = samlClient.getInstallationProvider("keycloak-saml");
-        assertTrue(xml.contains("<keycloak-saml-adapter>"));
-        assertTrue(xml.contains(SAML_NAME));
-        assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
-        assertTrue(xml.contains(samlUrl()));
+        assertThat(xml, containsString("<keycloak-saml-adapter>"));
+        assertThat(xml, containsString(SAML_NAME));
+        assertThat(xml, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
+        assertThat(xml, containsString(samlUrl()));
     }
 
     @Test
     public void testSamlMetadataSpDescriptor() {
         String xml = samlClient.getInstallationProvider("saml-sp-descriptor");
-        assertTrue(xml.contains("<EntityDescriptor"));
-        assertTrue(xml.contains("<SPSSODescriptor"));
-        assertTrue(xml.contains(SAML_NAME));
+        assertThat(xml, containsString("<EntityDescriptor"));
+        assertThat(xml, containsString("<SPSSODescriptor"));
+        assertThat(xml, containsString(SAML_NAME));
     }
 
     @Test
     public void testSamlJBossXml() {
         String xml = samlClient.getInstallationProvider("keycloak-saml-subsystem");
-        assertTrue(xml.contains("<secure-deployment"));
-        assertTrue(xml.contains(SAML_NAME));
-        assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
-        assertTrue(xml.contains(samlUrl()));
+        assertThat(xml, containsString("<secure-deployment"));
+        assertThat(xml, containsString(SAML_NAME));
+        assertThat(xml, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
+        assertThat(xml, containsString(samlUrl()));
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
index e3392b3..f550c17 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
@@ -57,17 +57,43 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import javax.xml.crypto.dsig.XMLSignature;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.hamcrest.Matchers.*;
+import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
+import org.w3c.dom.NodeList;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class IdentityProviderTest extends AbstractAdminTest {
 
+    // Certificate imported from
+    private static final String SIGNING_CERT_1 = "MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQY"
+      + "DVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXI"
+      + "wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGa"
+      + "cRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlI"
+      + "D6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXP"
+      + "ubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4"
+      + "RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/"
+      + "Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em"
+      + "1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNgg"
+      + "y6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/"
+      + "p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=";
+
+    private static final String SIGNING_CERT_2 = "MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAY"
+      + "DVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1"
+      + "sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQ"
+      + "u+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQ"
+      + "CAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhet"
+      + "vOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idf"
+      + "LXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=";
+
     @Test
     public void testFindAll() {
         create(createRep("google", "google"));
@@ -303,7 +329,45 @@ public class IdentityProviderTest extends AbstractAdminTest {
         form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata.xml");
 
         Map<String, String> result = realm.identityProviders().importFrom(form);
-        assertSamlImport(result);
+        assertSamlImport(result, SIGNING_CERT_1);
+
+        // Create new SAML identity provider using configuration retrieved from import-config
+        create(createRep("saml", "saml", result));
+
+        IdentityProviderResource provider = realm.identityProviders().get("saml");
+        IdentityProviderRepresentation rep = provider.toRepresentation();
+        assertCreatedSamlIdp(rep);
+
+        // Now list the providers - we should see the one just created
+        List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
+        Assert.assertNotNull("identityProviders not null", providers);
+        Assert.assertEquals("identityProviders instance count", 1, providers.size());
+        assertEqual(rep, providers.get(0));
+
+        // Perform export, and make sure some of the values are like they're supposed to be
+        Response response = realm.identityProviders().get("saml").export("xml");
+        Assert.assertEquals(200, response.getStatus());
+        body = response.readEntity(String.class);
+        response.close();
+
+        assertSamlExport(body);
+    }
+
+    @Test
+    public void testSamlImportAndExportMultipleSigningKeys() throws URISyntaxException, IOException, ParsingException {
+
+        // Use import-config to convert IDPSSODescriptor file into key value pairs
+        // to use when creating a SAML Identity Provider
+        MultipartFormDataOutput form = new MultipartFormDataOutput();
+        form.addFormData("providerId", "saml", MediaType.TEXT_PLAIN_TYPE);
+
+        URL idpMeta = getClass().getClassLoader().getResource("admin-test/saml-idp-metadata-two-signing-certs.xml");
+        byte [] content = Files.readAllBytes(Paths.get(idpMeta.toURI()));
+        String body = new String(content, Charset.forName("utf-8"));
+        form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata-two-signing-certs");
+
+        Map<String, String> result = realm.identityProviders().importFrom(form);
+        assertSamlImport(result, SIGNING_CERT_1 + "," + SIGNING_CERT_2);
 
         // Create new SAML identity provider using configuration retrieved from import-config
         create(createRep("saml", "saml", result));
@@ -464,18 +528,29 @@ public class IdentityProviderTest extends AbstractAdminTest {
         // import endpoint simply converts IDPSSODescriptor into key value pairs.
         // check that saml-idp-metadata.xml was properly converted into key value pairs
         //System.out.println(config);
-        Assert.assertEquals("Config size", 7, config.size());
-        Assert.assertEquals("validateSignature", "true", config.get("validateSignature"));
-        Assert.assertEquals("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleLogoutServiceUrl"));
-        Assert.assertEquals("postBindingResponse", "true", config.get("postBindingResponse"));
-        Assert.assertEquals("postBindingAuthnRequest", "true", config.get("postBindingAuthnRequest"));
-        Assert.assertEquals("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleSignOnServiceUrl"));
-        Assert.assertEquals("wantAuthnRequestsSigned", "true", config.get("wantAuthnRequestsSigned"));
-        Assert.assertNotNull("signingCertificate not null", config.get("signingCertificate"));
+        assertThat(config.keySet(), containsInAnyOrder(
+          "validateSignature",
+          "singleLogoutServiceUrl",
+          "postBindingResponse",
+          "postBindingAuthnRequest",
+          "singleSignOnServiceUrl",
+          "wantAuthnRequestsSigned",
+          "signingCertificate",
+          "addExtensionsElementWithKeyInfo"
+        ));
+        assertThat(config, hasEntry("validateSignature", "true"));
+        assertThat(config, hasEntry("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
+        assertThat(config, hasEntry("postBindingResponse", "true"));
+        assertThat(config, hasEntry("postBindingAuthnRequest", "true"));
+        assertThat(config, hasEntry("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
+        assertThat(config, hasEntry("wantAuthnRequestsSigned", "true"));
+        assertThat(config, hasEntry("addExtensionsElementWithKeyInfo", "false"));
+        assertThat(config, hasEntry(is("signingCertificate"), notNullValue()));
     }
 
-    private void assertSamlImport(Map<String, String> config) {
+    private void assertSamlImport(Map<String, String> config, String expectedSigningCertificates) {
         assertSamlConfig(config);
+        assertThat(config, hasEntry("signingCertificate", expectedSigningCertificates));
     }
 
     private void assertSamlExport(String body) throws ParsingException, URISyntaxException {
@@ -534,7 +609,11 @@ public class IdentityProviderTest extends AbstractAdminTest {
 
         Assert.assertNotNull("KeyDescriptor not null", desc.getKeyDescriptor());
         Assert.assertEquals("KeyDescriptor.size", 1, desc.getKeyDescriptor().size());
-        Assert.assertEquals("KeyDescriptor.Use", KeyTypes.SIGNING, desc.getKeyDescriptor().get(0).getUse());
+        KeyDescriptorType keyDesc = desc.getKeyDescriptor().get(0);
+        assertThat(keyDesc, notNullValue());
+        assertThat(keyDesc.getUse(), equalTo(KeyTypes.SIGNING));
+        NodeList cert = keyDesc.getKeyInfo().getElementsByTagNameNS(XMLSignature.XMLNS, "X509Certificate");
+        assertThat("KeyDescriptor.Signing.Cert existence", cert.getLength(), is(1));
     }
 
     private void assertProviderInfo(Map<String, String> info, String id, String name) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index c4522c6..adb6734 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -45,6 +45,7 @@ import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -481,6 +482,55 @@ public class LoginTest extends TestRealmKeycloakTest {
             setRememberMe(false);
         }
     }
+
+    //KEYCLOAK-2741
+    @Test
+    public void loginAgainWithoutRememberMe() {
+        setRememberMe(true);
+
+        try {
+            //login with remember me
+            loginPage.open();
+            assertFalse(loginPage.isRememberMeChecked());
+            loginPage.setRememberMe(true);
+            assertTrue(loginPage.isRememberMeChecked());
+            loginPage.login("login-test", "password");
+
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+            Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+            EventRepresentation loginEvent = events.expectLogin().user(userId)
+                                                   .detail(Details.USERNAME, "login-test")
+                                                   .detail(Details.REMEMBER_ME, "true")
+                                                   .assertEvent();
+            String sessionId = loginEvent.getSessionId();
+
+            // Expire session
+            testingClient.testing().removeUserSession("test", sessionId);
+
+            // Assert rememberMe checked and username/email prefilled
+            loginPage.open();
+            assertTrue(loginPage.isRememberMeChecked());
+            Assert.assertEquals("login-test", loginPage.getUsername());
+
+            //login without remember me
+            loginPage.setRememberMe(false);
+            loginPage.login("login-test", "password");
+            
+            // Expire session
+            loginEvent = events.expectLogin().user(userId)
+                                                   .detail(Details.USERNAME, "login-test")
+                                                   .assertEvent();
+            sessionId = loginEvent.getSessionId();
+            testingClient.testing().removeUserSession("test", sessionId);
+            
+            // Assert rememberMe not checked nor username/email prefilled
+            loginPage.open();
+            assertFalse(loginPage.isRememberMeChecked());
+            assertNotEquals("login-test", loginPage.getUsername());
+        } finally {
+            setRememberMe(false);
+        }
+    }
     
     @Test
     // KEYCLOAK-3181
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
index 1dd7aac..0810029 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java
@@ -29,8 +29,10 @@ import org.keycloak.testsuite.pages.LoginPage;
 import java.io.IOException;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import org.keycloak.testsuite.auth.page.account.AccountManagement;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -47,6 +49,9 @@ public class LogoutTest extends TestRealmKeycloakTest {
     @Page
     protected LoginPage loginPage;
 
+    @Page
+    protected AccountManagement accountManagementPage;
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
     }
@@ -130,4 +135,45 @@ public class LogoutTest extends TestRealmKeycloakTest {
         events.expectLogin().session(sessionId3).removeDetail(Details.USERNAME).assertEvent();
     }
 
+    //KEYCLOAK-2741
+    @Test
+    public void logoutWithRememberMe() {
+        setRememberMe(true);
+        
+        try {
+            loginPage.open();
+            assertFalse(loginPage.isRememberMeChecked());
+            loginPage.setRememberMe(true);
+            assertTrue(loginPage.isRememberMeChecked());
+            loginPage.login("test-user@localhost", "password");
+
+            String sessionId = events.expectLogin().assertEvent().getSessionId();
+
+            // Expire session
+            testingClient.testing().removeUserSession("test", sessionId);
+
+            // Assert rememberMe checked and username/email prefilled
+            loginPage.open();
+            assertTrue(loginPage.isRememberMeChecked());
+            assertEquals("test-user@localhost", loginPage.getUsername());
+
+            loginPage.login("test-user@localhost", "password");
+            
+            //log out
+            appPage.openAccount();
+            accountManagementPage.signOut();
+            // Assert rememberMe not checked nor username/email prefilled
+            assertTrue(loginPage.isCurrent());
+            assertFalse(loginPage.isRememberMeChecked());
+            assertNotEquals("test-user@localhost", loginPage.getUsername());
+        } finally {
+            setRememberMe(false);
+        }
+    }
+    
+    private void setRememberMe(boolean enabled) {
+        RealmRepresentation rep = adminClient.realm("test").toRepresentation();
+        rep.setRememberMe(enabled);
+        adminClient.realm("test").update(rep);
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java
index cac8a27..642a872 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java
@@ -22,15 +22,10 @@ import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.keycloak.common.util.KeyUtils;
 import org.keycloak.common.util.MultivaluedHashMap;
-import org.keycloak.common.util.PemUtils;
-import org.keycloak.keys.Attributes;
-import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
 import org.keycloak.keys.JavaKeystoreKeyProviderFactory;
 import org.keycloak.keys.KeyMetadata;
 import org.keycloak.keys.KeyProvider;
-import org.keycloak.keys.RsaKeyProviderFactory;
 import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.ErrorRepresentation;
 import org.keycloak.representations.idm.KeysMetadataRepresentation;
@@ -45,9 +40,6 @@ import javax.ws.rs.core.Response;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
-import java.security.KeyPair;
-import java.security.cert.Certificate;
-import java.security.interfaces.RSAPublicKey;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -123,7 +115,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
         rep.getConfig().putSingle("keystore", "/nosuchfile");
 
         Response response = adminClient.realm("test").components().add(rep);
-        assertErrror(response, "Failed to load keys");
+        assertErrror(response, "Failed to load keys. File not found on server.");
     }
 
     @Test
@@ -132,7 +124,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
         rep.getConfig().putSingle("keystore", "invalid");
 
         Response response = adminClient.realm("test").components().add(rep);
-        assertErrror(response, "Failed to load keys");
+        assertErrror(response, "Failed to load keys. File not found on server.");
     }
 
     @Test
@@ -141,7 +133,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
         rep.getConfig().putSingle("keyAlias", "invalid");
 
         Response response = adminClient.realm("test").components().add(rep);
-        assertErrror(response, "Failed to load keys");
+        assertErrror(response, "Failed to load keys. Error creating X509v1Certificate.");
     }
 
     @Test
@@ -150,7 +142,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
         rep.getConfig().putSingle("keyPassword", "invalid");
 
         Response response = adminClient.realm("test").components().add(rep);
-        assertErrror(response, "Failed to load keys");
+        assertErrror(response, "Failed to load keys. Keystore on server can not be recovered.");
     }
 
     protected void assertErrror(Response response, String error) {
@@ -159,7 +151,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
         }
 
         ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class);
-        assertEquals(error, errorRepresentation.getErrorMessage());
+        assertTrue(errorRepresentation.getErrorMessage().startsWith(error));
     }
 
     protected ComponentRepresentation createRep(String name, long priority) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java
index 2dc3506..5bfaef2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java
@@ -20,17 +20,13 @@ package org.keycloak.testsuite.keys;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Rule;
 import org.junit.Test;
-import org.keycloak.RSATokenVerifier;
-import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.CertificateUtils;
 import org.keycloak.common.util.KeyUtils;
 import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.common.util.PemUtils;
 import org.keycloak.keys.Attributes;
-import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
 import org.keycloak.keys.KeyMetadata;
 import org.keycloak.keys.KeyProvider;
-import org.keycloak.keys.RsaKeyProvider;
 import org.keycloak.keys.RsaKeyProviderFactory;
 import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.ErrorRepresentation;
@@ -40,16 +36,11 @@ import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.pages.AppPage;
-import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.util.OAuthClient;
 
-import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
 import java.security.KeyPair;
-import java.security.PublicKey;
 import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
 import java.util.List;
 
 import static org.junit.Assert.*;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
index d9c041c..72d0c70 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
@@ -36,9 +36,15 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
 import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
 import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientTemplateRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
-import static org.keycloak.testsuite.Assert.*;
+import static org.keycloak.testsuite.Assert.assertEquals;
+import static org.keycloak.testsuite.Assert.assertFalse;
+import static org.keycloak.testsuite.Assert.assertNames;
+import static org.keycloak.testsuite.Assert.assertTrue;
+import static org.keycloak.testsuite.Assert.fail;
 import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
 
 /**
@@ -71,12 +77,14 @@ public class MigrationTest extends AbstractKeycloakTest {
         testMigrationTo2_0_0();
         testMigrationTo2_1_0();
         testMigrationTo2_2_0();
+        testMigrationTo2_3_0();
     }
     
     @Test
     @Migration(versionFrom = "2.2.1.Final")
     public void migration2_2_1Test() {
         testMigratedData();
+        testMigrationTo2_3_0();
     }
     
     private void testMigratedData() {
@@ -120,6 +128,13 @@ public class MigrationTest extends AbstractKeycloakTest {
         //MigrateTo2_2_0#migrateRolePolicies is not relevant any more
     }
     
+    /**
+     * @see org.keycloak.migration.migrators.MigrateTo2_3_0
+     */
+    private void testMigrationTo2_3_0() {
+        testUpdateProtocolMappers(masterRealm, migrationRealm);
+    }
+    
     private void testAuthorizationServices(RealmResource... realms) {
         for (RealmResource realm : realms) {
             //test setup of authorization services
@@ -177,4 +192,26 @@ public class MigrationTest extends AbstractKeycloakTest {
             }
         }
     }
+
+    private void testUpdateProtocolMappers(RealmResource... realms) {
+        for (RealmResource realm : realms) {
+            for (ClientRepresentation client : realm.clients().findAll()) {
+                for (ProtocolMapperRepresentation protocolMapper : client.getProtocolMappers()) {
+                    testUpdateProtocolMapper(protocolMapper);
+                }
+            }
+            for (ClientTemplateRepresentation clientTemlate : realm.clientTemplates().findAll()) {
+                for (ProtocolMapperRepresentation protocolMapper : clientTemlate.getProtocolMappers()) {
+                    testUpdateProtocolMapper(protocolMapper);
+                }
+            }
+        }
+    }
+    
+    private void testUpdateProtocolMapper(ProtocolMapperRepresentation protocolMapper) {
+        if (protocolMapper.getConfig().get("id.token.claim") != null) {
+            assertEquals("ProtocolMapper's config should contain key 'userinfo.token.claim'.", 
+                    protocolMapper.getConfig().get("id.token.claim"), protocolMapper.getConfig().get("userinfo.token.claim"));
+        }
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index eaeeafd..7115f74 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -39,6 +39,7 @@ import org.keycloak.events.Errors;
 import org.keycloak.jose.jws.JWSHeader;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.models.Constants;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
@@ -56,6 +57,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
@@ -75,10 +77,8 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
 import java.net.URI;
-import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -89,14 +89,9 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
 import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
-import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
 import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
 import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
 import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
-import static org.keycloak.testsuite.util.ProtocolMapperUtil.createAddressMapper;
-import static org.keycloak.testsuite.util.ProtocolMapperUtil.createClaimMapper;
-import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim;
-import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedRole;
 import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMapper;
 
 /**
@@ -200,6 +195,30 @@ public class AccessTokenTest extends AbstractKeycloakTest {
 
     }
 
+    // KEYCLOAK-3692
+    @Test
+    public void accessTokenWrongCode() throws Exception {
+        oauth.clientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
+        oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
+        oauth.openLoginForm();
+
+        String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
+        actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+
+        String loginPageCode = actionUrl.split("code=")[1].split("&")[0];
+
+        driver.navigate().to(actionUrl);
+
+        oauth.fillLoginForm("test-user@localhost", "password");
+
+        events.expectLogin().client(Constants.ADMIN_CONSOLE_CLIENT_ID).detail(Details.REDIRECT_URI, AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html").assertEvent();
+
+        OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(loginPageCode, null);
+
+        assertEquals(400, response.getStatusCode());
+        assertNull(response.getRefreshToken());
+    }
+
     @Test
     public void accessTokenInvalidClientCredentials() throws Exception {
         oauth.doLogin("test-user@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal-noconf/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal-noconf/META-INF/context.xml
new file mode 100644
index 0000000..8d1c0d6
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal-noconf/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-noconf">
+    <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/customer-portal-noconf/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal-noconf/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/customer-portal-noconf/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/customer-portal-noconf/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal-noconf/WEB-INF/web.xml
new file mode 100644
index 0000000..71fd5cd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal-noconf/WEB-INF/web.xml
@@ -0,0 +1,76 @@
+<?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>customer-portal-noconf</module-name>
+
+    <servlet>
+        <servlet-name>Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.adapter.servlet.CustomerServletNoConf</servlet-class>
+    </servlet>
+    <servlet>
+        <servlet-name>Error Servlet</servlet-name>
+        <servlet-class>org.keycloak.testsuite.adapter.servlet.ErrorServlet</servlet-class>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Servlet</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+    <servlet-mapping>
+        <servlet-name>Error Servlet</servlet-name>
+        <url-pattern>/error.html</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>
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Errors</web-resource-name>
+            <url-pattern>/error.html</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
+    <login-config>
+        <auth-method>KEYCLOAK</auth-method>
+        <realm-name>demo</realm-name>
+        <form-login-config>
+            <form-login-page>/error.html</form-login-page>
+            <form-error-page>/error.html</form-error-page>
+        </form-login-config>
+    </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/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml
index 2ea7ff7..68a1ac8 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-assertion-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-sales-post-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-sales-post-sig/WEB-INF/keycloak-saml.xml
index bec4a5a..d5cc228 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-sales-post-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-sales-post-sig/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index 39caccd..a24431a 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-sales-post-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-sales-post-sig/WEB-INF/keycloak-saml.xml
index 649bc7b..a792f29 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-sales-post-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-sales-post-sig/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index 6ec2379..ba20359 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/keycloak-saml.xml
index 96f0b86..571d6b8 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml
index 3481528..14bd44e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig/WEB-INF/keycloak-saml.xml
index 1f926b7..a883abf 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-front/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-front/WEB-INF/keycloak-saml.xml
index ba0761c..5a720d5 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-front/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-front/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..64f8208
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,56 @@
+<!--
+  ~ 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.
+  -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+    <SP entityID="http://localhost:8081/employee-sig-post-noidpkey/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keystore.jks
new file mode 100644
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..73b8f4e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,56 @@
+<!--
+  ~ 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.
+  -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+    <SP entityID="http://localhost:8081/employee-sig-redir-noidpkey/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="REDIRECT"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="REDIRECT"
+                    redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keystore.jks
new file mode 100644
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..a12ddfe
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,56 @@
+<!--
+  ~ 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.
+  -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+    <SP entityID="http://localhost:8081/employee-sig-redir-opt-noidpkey/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="REDIRECT"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="REDIRECT"
+                    redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keystore.jks
new file mode 100644
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index 6021c4e..f075a20 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml
index e1cd76a..02bbe0f 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index e8598aa..2679264 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml
index 936fb21..3242e7d 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml
@@ -16,7 +16,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/missing-assertion-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-metadata/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-metadata/WEB-INF/keycloak-saml.xml
index 3580dfd..350a41e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post/WEB-INF/keycloak-saml.xml
index 5e33717..80f5ae9 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml
index eae5b14..acc80f2 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml
index 94c3f4d..c31eab1 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml
@@ -16,7 +16,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-assertion-and-response-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc/WEB-INF/keycloak-saml.xml
index e79e600..e1b8334 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-passive/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-passive/WEB-INF/keycloak-saml.xml
index 61a9e22..8dcb081 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-passive/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-passive/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-passive/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig/WEB-INF/keycloak-saml.xml
index 4656f4a..e8f5928 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-email/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-email/WEB-INF/keycloak-saml.xml
index 04e16e5..95880aa 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-persistent/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-persistent/WEB-INF/keycloak-saml.xml
index 382b835..3d57f03 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-transient/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-transient/WEB-INF/keycloak-saml.xml
index a068b10..9619bce 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-sig-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8081/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index 0e25d89..cca23fc 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -334,6 +334,61 @@
             }
         },
         {
+            "clientId": "http://localhost:8081/employee-sig-redir-noidpkey/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/employee-sig-redir-noidpkey",
+            "redirectUris": [
+                "http://localhost:8080/employee-sig-redir-noidpkey/*"
+            ],
+            "adminUrl": "http://localhost:8080/employee-sig-redir-noidpkey",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
+            "clientId": "http://localhost:8081/employee-sig-redir-opt-noidpkey/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/employee-sig-redir-opt-noidpkey",
+            "redirectUris": [
+                "http://localhost:8080/employee-sig-redir-opt-noidpkey/*"
+            ],
+            "adminUrl": "http://localhost:8080/employee-sig-redir-opt-noidpkey",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.server.signature.keyinfo.ext": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
+            "clientId": "http://localhost:8081/employee-sig-post-noidpkey/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/employee-sig-post-noidpkey",
+            "redirectUris": [
+                "http://localhost:8080/employee-sig-post-noidpkey/*"
+            ],
+            "adminUrl": "http://localhost:8080/employee-sig-post-noidpkey",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
             "clientId": "http://localhost:8081/employee/",
             "enabled": true,
             "protocol": "saml",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
index 2bcfc21..f28e206 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
                   xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
 >
    <IDPSSODescriptor WantAuthnRequestsSigned="true"
       protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml
new file mode 100644
index 0000000..dba0d5a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
+                  xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
+>
+   <IDPSSODescriptor WantAuthnRequestsSigned="true"
+      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
+   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
+   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
+
+      <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+         Location="http://localhost:8080/auth/realms/master/protocol/saml" />
+      <SingleLogoutService
+         Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+         Location="http://localhost:8080/auth/realms/master/protocol/saml" />
+         <KeyDescriptor use="signing">
+             <dsig:KeyInfo>
+                 <dsig:KeyName>hAoy_sBtpu6FdRVCk7ykihF6Ug-o0pKPK3LN9RYkeqs</dsig:KeyName>
+                 <dsig:X509Data>
+                     <dsig:X509Certificate>
+                         MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGacRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlID6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXPubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNggy6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=
+                     </dsig:X509Certificate>
+                 </dsig:X509Data>
+             </dsig:KeyInfo>
+         </KeyDescriptor>
+         <KeyDescriptor use="signing">
+             <dsig:KeyInfo>
+                 <dsig:KeyName>FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE</dsig:KeyName>
+                 <dsig:X509Data>
+                     <dsig:X509Certificate>
+                         MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhetvOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idfLXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=
+                     </dsig:X509Certificate>
+                 </dsig:X509Data>
+             </dsig:KeyInfo>
+         </KeyDescriptor>
+   </IDPSSODescriptor>
+</EntityDescriptor>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json
index 4194a95..ba783b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json
@@ -829,6 +829,7 @@
   }, {
     "id" : "d89c5b0f-bee6-4a97-86b1-118efa21e508",
     "clientId" : "master-test-client",
+    "name" : "master-test-client",
     "surrogateAuthRequired" : false,
     "enabled" : true,
     "clientAuthenticatorType" : "client-secret",
@@ -1898,6 +1899,7 @@
   }, {
     "id" : "d8262b3f-02e4-409e-97fc-ee5532e0801e",
     "clientId" : "migration-test-client",
+    "name" : "migration-test-client",
     "surrogateAuthRequired" : false,
     "enabled" : true,
     "clientAuthenticatorType" : "client-secret",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json
index 7011d1b..db1c768 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-2.2.1.Final.json
@@ -933,6 +933,7 @@
   }, {
     "id" : "6268e266-346b-46ba-8408-fe17b5792b10",
     "clientId" : "master-test-client",
+    "name" : "master-test-client",
     "surrogateAuthRequired" : false,
     "enabled" : true,
     "clientAuthenticatorType" : "client-secret",
@@ -2085,6 +2086,7 @@
   }, {
     "id" : "f66de6ed-4fd8-47b6-a2db-85ab8ed88874",
     "clientId" : "migration-test-client",
+    "name" : "migration-test-client",
     "surrogateAuthRequired" : false,
     "enabled" : true,
     "clientAuthenticatorType" : "client-secret",
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
index 79092cd..b8cbf4d 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
@@ -223,6 +223,7 @@ public class ClientSettingsForm extends CreateClientForm {
 	public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
 	public static final String SAML_MULTIVALUED_ROLES = "saml.multivalued.roles";
 	public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
+	public static final String SAML_SERVER_SIGNATURE_KEYINFO_EXT = "saml.server.signature.keyinfo.ext";
 	public static final String SAML_SIGNATURE_ALGORITHM = "saml.signature.algorithm";
 	public static final String SAML_ASSERTION_CONSUMER_URL_POST = "saml_assertion_consumer_url_post";
 	public static final String SAML_ASSERTION_CONSUMER_URL_REDIRECT = "saml_assertion_consumer_url_redirect";
@@ -236,6 +237,8 @@ public class ClientSettingsForm extends CreateClientForm {
         private OnOffSwitch samlAuthnStatement;
         @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='samlServerSignature']]")
         private OnOffSwitch samlServerSignature;
+        @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='samlServerSignatureEnableKeyInfoExtension']]")
+        private OnOffSwitch samlServerSignatureKeyInfoExt;
         @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='samlAssertionSignature']]")
         private OnOffSwitch samlAssertionSignature;
         @FindBy(id = "signatureAlgorithm")
@@ -277,6 +280,7 @@ public class ClientSettingsForm extends CreateClientForm {
             if (samlServerSignature.isOn() || samlAssertionSignature.isOn()) {
                 signatureAlgorithm.selectByVisibleText(attributes.get(SAML_SIGNATURE_ALGORITHM));
                 canonicalization.selectByValue("string:" + attributes.get(SAML_SIGNATURE_CANONICALIZATION_METHOD));
+                samlServerSignatureKeyInfoExt.setOn("true".equals(attributes.get(SAML_SERVER_SIGNATURE_KEYINFO_EXT)));
             }
             samlEncrypt.setOn("true".equals(attributes.get(SAML_ENCRYPT)));
             samlClientSignature.setOn("true".equals(attributes.get(SAML_CLIENT_SIGNATURE)));
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index ea63ca4..4f4f42d 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -458,42 +458,46 @@
                             </execution>
                         </executions>
                     </plugin>
-                    <plugin>
-                        <artifactId>maven-dependency-plugin</artifactId>
-                        <executions>
-                            <execution>
-                                <id>unpack-migrated-auth-server-jboss</id>
-                                <phase>generate-resources</phase>
-                                <goals>
-                                    <goal>unpack</goal>
-                                </goals>
-                                <configuration>
-                                    <artifactItems>
-                                        <artifactItem>
-                                            <groupId>org.keycloak.testsuite</groupId>
-                                            <artifactId>integration-arquillian-migration-server</artifactId>
-                                            <version>${project.version}</version>
-                                            <type>zip</type>
-                                        </artifactItem>
-                                    </artifactItems>
-                                    <outputDirectory>${containers.home}</outputDirectory>
-                                    <overWriteIfNewer>true</overWriteIfNewer>
-                                </configuration>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <plugin>
-                        <artifactId>maven-surefire-plugin</artifactId>
-                        <configuration>
-                            <systemPropertyVariables>
-                                <migrated.auth.server.version>${migrated.auth.server.version}</migrated.auth.server.version>
-                                <auth.server.jboss.migration>true</auth.server.jboss.migration>
-                                <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
-                                <migration.import.props.previous>${migration.import.props.previous}</migration.import.props.previous>
-                            </systemPropertyVariables>
-                        </configuration>
-                    </plugin>
                 </plugins>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-dependency-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>unpack-migrated-auth-server-jboss</id>
+                                    <phase>generate-resources</phase>
+                                    <goals>
+                                        <goal>unpack</goal>
+                                    </goals>
+                                    <configuration>
+                                        <artifactItems>
+                                            <artifactItem>
+                                                <groupId>org.keycloak.testsuite</groupId>
+                                                <artifactId>integration-arquillian-migration-server</artifactId>
+                                                <version>${project.version}</version>
+                                                <type>zip</type>
+                                            </artifactItem>
+                                        </artifactItems>
+                                        <outputDirectory>${containers.home}</outputDirectory>
+                                        <overWriteIfNewer>true</overWriteIfNewer>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                        <plugin>
+                            <artifactId>maven-surefire-plugin</artifactId>
+                            <configuration>
+                                <systemPropertyVariables>
+                                    <migrated.auth.server.version>${migrated.auth.server.version}</migrated.auth.server.version>
+                                    <auth.server.jboss.migration>true</auth.server.jboss.migration>
+                                    <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
+                                    <migration.import.props.previous>${migration.import.props.previous}</migration.import.props.previous>
+                                </systemPropertyVariables>
+                            </configuration>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
             </build>
         </profile>    
         
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100755
--- a/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty81/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100755
--- a/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty91/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100755
--- a/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty92/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100644
--- a/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/jetty/jetty93/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100755
--- a/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat6/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100755
--- a/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat7/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
index ecc7469..a933bcb 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-client-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-client-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
index fee4cfe..6c0548e 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/bad-realm-signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/bad-realm-sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
index eebd481..780843f 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/encrypted-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-enc/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
index 0495c3d..e658934 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/mappers/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
index 993277d..5f19f5b 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-front-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig-front/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
index 5e8dca8..6e76a0e 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-get/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/employee-sig/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
index 16a4d75..1f52f1c 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-metadata/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-metadata/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
index 32bb6b0..0cae188 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
index ff82567..a53b44d 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-email/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-email/"
         sslPolicy="EXTERNAL"
         logoutPage="/logout.jsp"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
index 626d239..a4bf71b 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-persistent/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-persistent/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
index 3182dc0..8bf32fa 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/signed-post-transient/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post-sig-transient/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
index 1af3a86..8cf6d98 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-input/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/input-portal/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
index f4cb21c..716be67 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml b/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
index 3f447c6..a09f312 100755
--- a/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
+++ b/testsuite/tomcat8/src/test/resources/keycloak-saml/simple-post2/WEB-INF/keycloak-saml.xml
@@ -15,7 +15,9 @@
   ~ limitations under the License.
   -->
 
-<keycloak-saml-adapter>
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                       xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
     <SP entityID="http://localhost:8082/sales-post2/"
         sslPolicy="EXTERNAL"
         nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index fcf02a4..6c696c9 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -209,6 +209,8 @@ include-authnstatement=Include AuthnStatement
 include-authnstatement.tooltip=Should a statement specifying the method and timestamp be included in login responses?
 sign-documents=Sign Documents
 sign-documents.tooltip=Should SAML documents be signed by the realm?
+sign-documents-redirect-enable-key-info-ext=Optimize REDIRECT signing key lookup
+sign-documents-redirect-enable-key-info-ext.tooltip=When signing SAML documents in REDIRECT binding for SP that is secured by Keycloak adapter, should the ID of the signing key be included in SAML protocol message in <Extensions> element? This optimizes validation of the signature as the validating party uses a single key instead of trying every known key for validation.
 sign-assertions=Sign Assertions
 sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting isn't needed if document is already being signed.
 signature-algorithm=Signature Algorithm
@@ -506,8 +508,8 @@ force-authentication=Force Authentication
 identity-provider.force-authentication.tooltip=Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.
 validate-signature=Validate Signature
 saml.validate-signature.tooltip=Enable/disable signature validation of SAML responses.
-validating-x509-certificate=Validating X509 Certificate
-validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures.
+validating-x509-certificate=Validating X509 Certificates
+validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures. Multiple certificates can be entered, separated by comma (,).
 saml.import-from-url.tooltip=Import metadata from a remote IDP SAML entity descriptor.
 social.client-id.tooltip=The client identifier registered with the identity provider.
 social.client-secret.tooltip=The client secret registered with the identity provider.
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 4503b2f..624e9a5 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -860,6 +860,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
     $scope.samlAuthnStatement = false;
     $scope.samlMultiValuedRoles = false;
     $scope.samlServerSignature = false;
+    $scope.samlServerSignatureEnableKeyInfoExtension = false;
     $scope.samlAssertionSignature = false;
     $scope.samlClientSignature = false;
     $scope.samlEncrypt = false;
@@ -908,6 +909,13 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
 
             }
         }
+        if ($scope.client.attributes["saml.server.signature.keyinfo.ext"]) {
+            if ($scope.client.attributes["saml.server.signature.keyinfo.ext"] == "true") {
+                $scope.samlServerSignatureEnableKeyInfoExtension = true;
+            } else {
+                $scope.samlServerSignatureEnableKeyInfoExtension = false;
+            }
+        }
         if ($scope.client.attributes["saml.assertion.signature"]) {
             if ($scope.client.attributes["saml.assertion.signature"] == "true") {
                 $scope.samlAssertionSignature = true;
@@ -1115,7 +1123,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
             $scope.client.attributes["saml.server.signature"] = "true";
         } else {
             $scope.client.attributes["saml.server.signature"] = "false";
-
+        }
+        if ($scope.samlServerSignatureEnableKeyInfoExtension == true) {
+            $scope.client.attributes["saml.server.signature.keyinfo.ext"] = "true";
+        } else {
+            $scope.client.attributes["saml.server.signature.keyinfo.ext"] = "false";
         }
         if ($scope.samlAssertionSignature == true) {
             $scope.client.attributes["saml.assertion.signature"] = "true";
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 8601770..6b0f0a6 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -118,7 +118,7 @@
                 </div>
             </div>
             <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
-                <label class="col-md-2 control-label" for="samlServerSignature">{{:: 'include-authnstatement' | translate}}</label>
+                <label class="col-md-2 control-label" for="samlAuthnStatement">{{:: 'include-authnstatement' | translate}}</label>
                 <div class="col-sm-6">
                     <input ng-model="samlAuthnStatement" ng-click="switchChange()" name="samlAuthnStatement" id="samlAuthnStatement" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
                 </div>
@@ -131,6 +131,13 @@
                 </div>
                 <kc-tooltip>{{:: 'sign-documents.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group clearfix block" data-ng-show="protocol == 'saml' && samlServerSignature == true">
+                <label class="col-md-2 control-label" for="samlServerSignatureEnableKeyInfoExtension">{{:: 'sign-documents-redirect-enable-key-info-ext' | translate}}</label>
+                <div class="col-sm-6">
+                    <input ng-model="samlServerSignatureEnableKeyInfoExtension" ng-click="switchChange()" name="samlServerSignatureEnableKeyInfoExtension" id="samlServerSignatureEnableKeyInfoExtension" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                </div>
+                <kc-tooltip>{{:: 'sign-documents-redirect-enable-key-info-ext.tooltip' | translate}}</kc-tooltip>
+            </div>
             <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
                 <label class="col-md-2 control-label" for="samlAssertionSignature">{{:: 'sign-assertions' | translate}}</label>
                 <div class="col-sm-6">

travis-run-tests.sh 30(+30 -0)

diff --git a/travis-run-tests.sh b/travis-run-tests.sh
new file mode 100755
index 0000000..fe7c88e
--- /dev/null
+++ b/travis-run-tests.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+if [ $1 == "old" ]; then
+    mvn test -B -f testsuite/integration
+fi
+
+if [ $1 == "group1" ]; then
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.a**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.b**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.c**.*Test
+fi
+
+if [ $1 == "group2" ]; then
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.d**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.e**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.f**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.i**.*Test
+fi
+
+if [ $1 == "group3" ]; then
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.k**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.m**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.testsuite.o**.*Test
+    mvn test -B -f testsuite/integration-arquillian/tests/base -Dtest=org.keycloak.*Test
+fi
+
+if [ $1 == "adapter" ]; then
+    mvn test -B -f testsuite/integration-arquillian/tests/other/adapters
+fi
+
diff --git a/wildfly/adduser/pom.xml b/wildfly/adduser/pom.xml
index b33eb5f..6a827f4 100755
--- a/wildfly/adduser/pom.xml
+++ b/wildfly/adduser/pom.xml
@@ -36,6 +36,10 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-common</artifactId>
         </dependency>
         <dependency>
diff --git a/wildfly/extensions/pom.xml b/wildfly/extensions/pom.xml
index 0cafd9c..9b677db 100755
--- a/wildfly/extensions/pom.xml
+++ b/wildfly/extensions/pom.xml
@@ -47,6 +47,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi-private</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-services</artifactId>
             <scope>provided</scope>
         </dependency>
diff --git a/wildfly/server-subsystem/pom.xml b/wildfly/server-subsystem/pom.xml
index 7710f9f..03ac1a7 100755
--- a/wildfly/server-subsystem/pom.xml
+++ b/wildfly/server-subsystem/pom.xml
@@ -108,7 +108,7 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-server-spi</artifactId>
+            <artifactId>keycloak-server-spi-private</artifactId>
             <scope>provided</scope>
         </dependency>