keycloak-aplcache

merge

10/7/2014 7:17:44 PM

Changes

Details

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 acb4243..fd54ce2 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
@@ -10,14 +10,14 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
  * @version $Revision: 1 $
  */
 @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
-        "resource", "credentials",
+        "resource", "public-client", "credentials",
         "use-resource-role-mappings",
         "enable-cors", "cors-max-age", "cors-allowed-methods",
         "expose-token", "bearer-only",
         "connection-pool-size",
         "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
         "client-keystore", "client-keystore-password", "client-key-password",
-        "use-hostname-for-local-requests", "local-requests-scheme", "local-requests-port"
+        "auth-server-url-for-backend-requests"
 })
 public class AdapterConfig extends BaseAdapterConfig {
 
@@ -37,12 +37,8 @@ public class AdapterConfig extends BaseAdapterConfig {
     protected String clientKeyPassword;
     @JsonProperty("connection-pool-size")
     protected int connectionPoolSize = 20;
-    @JsonProperty("use-hostname-for-local-requests")
-    protected boolean useHostnameForLocalRequests;
-    @JsonProperty("local-requests-scheme")
-    protected String localRequestsScheme = "http";
-    @JsonProperty("local-requests-port")
-    protected int localRequestsPort = 8080;
+    @JsonProperty("auth-server-url-for-backend-requests")
+    protected String authServerUrlForBackendRequests;
 
     public boolean isAllowAnyHostname() {
         return allowAnyHostname;
@@ -108,27 +104,11 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.connectionPoolSize = connectionPoolSize;
     }
 
-    public boolean isUseHostnameForLocalRequests() {
-        return useHostnameForLocalRequests;
+    public String getAuthServerUrlForBackendRequests() {
+        return authServerUrlForBackendRequests;
     }
 
-    public void setUseHostnameForLocalRequests(boolean useHostnameForLocalRequests) {
-        this.useHostnameForLocalRequests = useHostnameForLocalRequests;
-    }
-
-    public String getLocalRequestsScheme() {
-        return localRequestsScheme;
-    }
-
-    public void setLocalRequestsScheme(String localRequestsScheme) {
-        this.localRequestsScheme = localRequestsScheme;
-    }
-
-    public int getLocalRequestsPort() {
-        return localRequestsPort;
-    }
-
-    public void setLocalRequestsPort(int localRequestsPort) {
-        this.localRequestsPort = localRequestsPort;
+    public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) {
+        this.authServerUrlForBackendRequests = authServerUrlForBackendRequests;
     }
 }
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index d0bf6a0..49a4502 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -17,6 +17,7 @@ import java.io.OutputStream;
 public class JsonSerialization {
     public static final ObjectMapper mapper = new ObjectMapper();
     public static final ObjectMapper prettyMapper = new ObjectMapper();
+    public static final ObjectMapper sysPropertiesAwareMapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
 
     static {
         mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
@@ -46,7 +47,15 @@ public class JsonSerialization {
     }
 
     public static <T> T readValue(InputStream bytes, Class<T> type) throws IOException {
-        return mapper.readValue(bytes, type);
+        return readValue(bytes, type, false);
+    }
+
+    public static <T> T readValue(InputStream bytes, Class<T> type, boolean replaceSystemProperties) throws IOException {
+        if (replaceSystemProperties) {
+            return sysPropertiesAwareMapper.readValue(bytes, type);
+        } else {
+            return mapper.readValue(bytes, type);
+        }
     }
 
 
diff --git a/core/src/main/java/org/keycloak/util/SystemPropertiesJsonParserFactory.java b/core/src/main/java/org/keycloak/util/SystemPropertiesJsonParserFactory.java
new file mode 100644
index 0000000..52f893d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/SystemPropertiesJsonParserFactory.java
@@ -0,0 +1,51 @@
+package org.keycloak.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.io.IOContext;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.codehaus.jackson.util.JsonParserDelegate;
+
+/**
+ * Provides replacing of system properties for parsed values
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SystemPropertiesJsonParserFactory extends MappingJsonFactory {
+
+    @Override
+    protected JsonParser _createJsonParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException {
+        JsonParser delegate = super._createJsonParser(data, offset, len, ctxt);
+        return new SystemPropertiesAwareJsonParser(delegate);
+    }
+
+    @Override
+    protected JsonParser _createJsonParser(Reader r, IOContext ctxt) throws IOException {
+        JsonParser delegate = super._createJsonParser(r, ctxt);
+        return new SystemPropertiesAwareJsonParser(delegate);
+    }
+
+    @Override
+    protected JsonParser _createJsonParser(InputStream in, IOContext ctxt) throws IOException {
+        JsonParser delegate = super._createJsonParser(in, ctxt);
+        return new SystemPropertiesAwareJsonParser(delegate);
+    }
+
+
+
+    public static class SystemPropertiesAwareJsonParser extends JsonParserDelegate {
+
+        public SystemPropertiesAwareJsonParser(JsonParser d) {
+            super(d);
+        }
+
+        @Override
+        public String getText() throws IOException {
+            String orig = super.getText();
+            return StringPropertyReplacer.replaceProperties(orig);
+        }
+    }
+}
diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java
new file mode 100644
index 0000000..265b401
--- /dev/null
+++ b/core/src/test/java/org/keycloak/JsonParserTest.java
@@ -0,0 +1,33 @@
+package org.keycloak;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JsonParserTest {
+
+    @Test
+    public void testParsingSystemProps() throws IOException {
+        System.setProperty("my.host", "foo");
+        System.setProperty("con.pool.size", "200");
+        System.setProperty("allow.any.hostname", "true");
+
+        InputStream is = getClass().getClassLoader().getResourceAsStream("keycloak.json");
+
+        AdapterConfig config = JsonSerialization.readValue(is, AdapterConfig.class, true);
+        Assert.assertEquals("http://foo:8080/auth", config.getAuthServerUrl());
+        Assert.assertEquals("external", config.getSslRequired());
+        Assert.assertEquals("angular-product${non.existing}", config.getResource());
+        Assert.assertTrue(config.isPublicClient());
+        Assert.assertTrue(config.isAllowAnyHostname());
+        Assert.assertEquals(100, config.getCorsMaxAge());
+        Assert.assertEquals(200, config.getConnectionPoolSize());
+    }
+}
diff --git a/core/src/test/resources/keycloak.json b/core/src/test/resources/keycloak.json
new file mode 100644
index 0000000..b0a8935
--- /dev/null
+++ b/core/src/test/resources/keycloak.json
@@ -0,0 +1,9 @@
+{
+  "auth-server-url" : "http://${my.host}:8080/auth",
+  "ssl-required" : "external",
+  "resource" : "angular-product${non.existing}",
+  "public-client" : true,
+  "allow-any-hostname": "${allow.any.hostname}",
+  "cors-max-age": 100,
+  "connection-pool-size": "${con.pool.size}"
+}
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index 39515ef..3487d3c 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -32,6 +32,7 @@
                 <!ENTITY ExportImport SYSTEM "modules/export-import.xml">
                 <!ENTITY ServerCache SYSTEM "modules/cache.xml">
                 <!ENTITY SecurityVulnerabilities SYSTEM "modules/security-vulnerabilities.xml">
+                <!ENTITY Clustering SYSTEM "modules/clustering.xml">
                 ]>
 
 <book>
@@ -121,6 +122,7 @@ This one is short
     &ExportImport;
     &ServerCache;
     &SecurityVulnerabilities;
+    &Clustering;
     &Migration;
 
 </book>
diff --git a/docbook/reference/en/en-US/modules/adapter-config.xml b/docbook/reference/en/en-US/modules/adapter-config.xml
index c5cb55a..8f66847 100755
--- a/docbook/reference/en/en-US/modules/adapter-config.xml
+++ b/docbook/reference/en/en-US/modules/adapter-config.xml
@@ -19,10 +19,10 @@
   "expose-token" : true,
    "credentials" : {
       "secret" : "234234-234234-234234"
-   }
+   },
 
    "connection-pool-size" : 20,
-   "disable-trust-manager" false,
+   "disable-trust-manager": false,
    "allow-any-hostname" : false,
    "truststore" : "path/to/truststore.jks",
    "truststore-password" : "geheim",
diff --git a/docbook/reference/en/en-US/modules/clustering.xml b/docbook/reference/en/en-US/modules/clustering.xml
new file mode 100755
index 0000000..613316f
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/clustering.xml
@@ -0,0 +1,217 @@
+<chapter id="clustering">
+    <title>Clustering</title>
+
+    <para>To improve availability and scalability Keycloak can be deployed in a cluster.</para>
+
+    <para>It's fairly straightforward to configure a Keycloak cluster, the steps required are:
+        <itemizedlist>
+            <listitem>
+                <para>
+                    Configure a shared database
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Configure Infinispan
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Enable realm and user cache invalidation
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Enable distributed user sessions
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    Start in HA mode
+                </para>
+            </listitem>
+        </itemizedlist>
+    </para>
+
+    <section>
+        <title>Configure a shared database</title>
+        <para>
+            Keycloak doesn't replicate realms and users, but instead relies on all nodes using the same
+            database. This can be a relational database or Mongo. To make sure your database doesn't become a single
+            point of failure you may also want to deploy your database to a cluster.
+        </para>
+    </section>
+
+    <section>
+        <title id="cluster-configure-infinispan">Configure Infinispan</title>
+        <para>
+            Keycloak uses <ulink url="http://www.infinispan.org/">Infinispan</ulink> caches to share information between nodes.
+        </para>
+        <para>
+            For realm and users Keycloak uses a invalidation cache. An invalidation cache doesn't share any data, but simply
+            removes stale data from remote caches. This reduces network traffic, as well as preventing sensitive data (such as
+            realm keys and password hashes) from being sent between the nodes.
+        </para>
+        <para>
+            User sessions supports either distributed caches or fully replicated caches. We recommend using a distributed
+            cache.
+        </para>
+        <para>
+            To configure the required Infinspan caches open <literal>standalone/configuration/standalone-ha.xml</literal> and add:
+<programlisting>
+<![CDATA[
+<subsystem xmlns="urn:jboss:domain:infinispan:2.0">
+    <cache-container name="keycloak" jndi-name="infinispan/Keycloak" start="EAGER">
+        <invalidation-cache name="realms" mode="SYNC"/>
+        <invalidation-cache name="users" mode="SYNC"/>
+        <distributed-cache name="sessions" mode="SYNC" owners="1" />
+    </cache-container>
+    ...
+</subsystem>
+]]>
+</programlisting>
+        </para>
+        <para>
+            For more advanced options refer to the
+            <ulink url="http://docs.jboss.org/author/display/WFLY8/Infinispan+Subsystem">Infinispan Subsystem</ulink>
+            and
+            <ulink url="http://www.infinispan.org/docs/6.0.x/user_guide/user_guide.html">Infinispan</ulink>
+            documentation.
+        </para>
+        <para>
+            Next open <literal>standalone/configuration/keycloak-server.json</literal> and add:
+<programlisting>
+"connectionsInfinispan": {
+    "default" : {
+        "cacheContainer" : "java:jboss/infinispan/Keycloak"
+    }
+}
+</programlisting>
+        </para>
+    </section>
+
+    <section>
+        <title>Enable realm and user cache invalidation</title>
+        <para>
+            To reduce number of requests to the database Keycloak caches realm and user data. In cluster mode
+            Keycloak uses an Infinispan invalidation cache to make sure all nodes re-load data from the database
+            when it is changed. Using an invalidation cache instead of a replicated cache reduces the network traffic
+            generated by the cluster, but more importantly prevents sensitive data from being sent.
+        </para>
+        <para>
+            To enable realm and user cache invalidation open <literal>keycloak-server.json</literal> and change
+            the <literal>realmCache</literal> and <literal>userCache</literal> providers to <literal>infinispan</literal>:
+<programlisting>
+"realmCache": {
+    "provider": "infinispan"
+},
+
+"userCache": {
+    "provider": "infinispan"
+}
+</programlisting>
+        </para>
+    </section>
+
+    <section>
+        <title>Enable distributed user sessions</title>
+        <para>
+            To help distribute the load of user sessions Keycloak uses an Infinispan distributed cache. A distributed
+            cache splits user sessions into segments where each node holds one or more segment. It is possible
+            to replicate each segment to multiple nodes, but this is not strictly necessary since the failure of a node
+            will only result in users having to log in again. If you need to prevent node failures from requiring users to
+            log in again, set the <literal>owners</literal> attribute to 2 or more for the <literal>sessions</literal> cache
+             (see <link linkend='cluster-configure-infinispan'>Configure Infinispan</link>).
+        </para>
+        <para>
+            To enable the Infinispan user sessions provider open <literal>keycloak-server.json</literal> and change the
+            userSessions provider to <literal>infinispan</literal>:
+<programlisting>
+"userSessions": {
+    "provider": "infinispan"
+}
+</programlisting>
+        </para>
+    </section>
+
+    <section>
+        <title>Start in HA mode</title>
+        <para>
+            To start the server in HA mode, start it with:
+            <programlisting># bin/standalone --server-config=standalone-ha.xml</programlisting>
+        </para>
+        <para>
+            Alternatively you can copy <literal>standalone/config/standalone-ha.xml</literal> to <literal>standalone/config/standalone.xml</literal>
+            to make it the default server config.
+        </para>
+    </section>
+
+    <section>
+        <title>Enabling cluster security</title>
+        <para>
+            By default there's nothing to prevent unauthorized nodes from joining the cluster and sending potentially malicious
+            messages to the cluster. However, as there's no sensitive data sent there's not much that can be achieved.
+            For realms and users all that can be done is to send invalidation messages to make nodes load data from the
+            database more frequently. For user sessions it would be possible to modify existing user sessions, but creating
+            new sessions would have no affect as they would not be linked to any access tokens. There's not to much that
+            can be achieved by modifying user sessions. For example it would be possible to prevent sessions from expiring,
+            by changing the creation time. However, it would for example have no effect adding additional permissions to the
+            sessions as these are rechecked against the user and application when the token is created or refreshed.
+        </para>
+        <para>
+            In either case your cluster nodes should be in a private network, with a firewall protecting them from outside
+            attacks. Ideally isolated from workstations and laptops. You can also enable encryption of cluster messages,
+            this could for example be useful if you can't isolate cluster nodes from workstations and laptops on your private
+            network. However, encryption will obviously come at a cost of reduced performance.
+        </para>
+        <para>
+            To enable encryption of cluster messages you first have to create a shared keystore (change the key and store passwords!):
+<programlisting>
+<![CDATA[
+# keytool -genseckey -alias keycloak -keypass <PASSWORD> -storepass <PASSWORD> \
+ -keyalg Blowfish -keysize 56 -keystore defaultStore.keystore -storetype JCEKS
+]]>
+</programlisting>
+        </para>
+        <para>
+            Copy this keystore to all nodes (for example to standalone/configuration). Then configure JGroups to encrypt all
+            messages by adding the <literal>ENCRYPT</literal> protocol to the JGroups sub-system (this should be added after
+            the <literal>pbcast.GMS</literal> protocol):
+<programlisting>
+<![CDATA[
+<subsystem xmlns="urn:jboss:domain:jgroups:2.0" default-stack="udp">
+    <stack name="udp">
+        ...
+        <protocol type="pbcast.GMS"/>
+        <protocol type="ENCRYPT">
+            <property name="key_store_name">
+                ${jboss.server.config.dir}/defaultStore.keystore
+            </property>
+            <property name="key_password">PASSWORD</property>
+            <property name="store_password">PASSWORD</property>
+            <property name="alias">keycloak</property>
+        </protocol>
+        ...
+    </stack>
+    <stack name="tcp">
+        ...
+        <protocol type="pbcast.GMS"/>
+        <protocol type="ENCRYPT">
+            <property name="key_store_name">
+                ${jboss.server.config.dir}/defaultStore.keystore
+            </property>
+            <property name="key_password">PASSWORD</property>
+            <property name="store_password">PASSWORD</property>
+            <property name="alias">keycloak</property>
+        </protocol>
+        ...
+    </stack>
+    ...
+</subsystem>
+]]>
+</programlisting>
+            See the <ulink url="http://www.jgroups.org/manual/index.html#ENCRYPT">JGroups manual</ulink> for more details.
+        </para>
+    </section>
+
+</chapter>
diff --git a/docbook/reference/en/en-US/modules/server-installation.xml b/docbook/reference/en/en-US/modules/server-installation.xml
index aadfba8..9e595a1 100755
--- a/docbook/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/reference/en/en-US/modules/server-installation.xml
@@ -122,7 +122,7 @@ keycloak-war-dist-all-&project.version;/
         </para>
     </section>
     <section>
-        <title>Configuring the Server</title>
+        <title id="configure-server">Configuring the Server</title>
         <para>
             Although the Keycloak Server is designed to run out of the box, there's some things you'll need
             to configure before you go into production.  Specifically:
diff --git a/docbook/reference/en/en-US/modules/themes.xml b/docbook/reference/en/en-US/modules/themes.xml
index 4d1e621..af30453 100755
--- a/docbook/reference/en/en-US/modules/themes.xml
+++ b/docbook/reference/en/en-US/modules/themes.xml
@@ -7,11 +7,37 @@
     </para>
 
     <section>
+        <title>Theme types</title>
+        <para>
+            There are several types of themes in Keycloak:
+            <itemizedlist>
+                <listitem>Account - Account management</listitem>
+                <listitem>Admin - Admin console</listitem>
+                <listitem>Common - Shared resources for themes</listitem>
+                <listitem>Email - Emails</listitem>
+                <listitem>Login - Login forms</listitem>
+                <listitem>Welcome - Welcome pages</listitem>
+            </itemizedlist>
+        </para>
+    </section>
+
+    <section>
         <title>Configure theme</title>
         <para>
-            To configure the theme used by a realm open the <literal>Keycloak Admin Console</literal>, select your realm
+            All theme types, except welcome, is configured through <literal>Keycloak Admin Console</literal>. To change
+            the theme used for a realm open the open the <literal>Keycloak Admin Console</literal>, select your realm
             from the drop-down box in the top left corner. Under <literal>Settings</literal> click on <literal>Theme</literal>.
         </para>
+        <para>
+            To change the welcome theme you need to edit <literal>standalone/configuration/keycloak-server.json</literal>
+            and add <literal>welcomeTheme</literal> to the theme element, for example:
+<programlisting>
+"theme": {
+    ...
+    "welcomeTheme": "custom-theme"
+}
+</programlisting>
+        </para>
     </section>
 
     <section>
@@ -26,25 +52,14 @@
     <section>
         <title>Creating a theme</title>
         <para>
-            There are several types of themes in Keycloak:
-            <itemizedlist>
-                <listitem>Account - Account management</listitem>
-                <listitem>Admin - Admin console</listitem>
-                <listitem>Common - Shared resources for themes</listitem>
-                <listitem>Email - Emails</listitem>
-                <listitem>Login - Login forms</listitem>
-            </itemizedlist>
-        </para>
-
-        <para>
             A theme consists of:
             <itemizedlist>
-                <listitem><para><ulink url="http://freemarker.org">FreeMarker</ulink> templates</para></listitem>
-                <listitem><para>Stylesheets</para></listitem>
-                <listitem><para>Scripts</para></listitem>
-                <listitem><para>Images</para></listitem>
-                <listitem><para>Message bundles</para></listitem>
-                <listitem><para>Theme properties</para></listitem>
+                <listitem><ulink url="http://freemarker.org">FreeMarker</ulink> templates</listitem>
+                <listitem>Stylesheets</listitem>
+                <listitem>Scripts</listitem>
+                <listitem>Images</listitem>
+                <listitem>Message bundles</listitem>
+                <listitem>Theme properties</listitem>
             </itemizedlist>
         </para>
         <para>
@@ -132,10 +147,9 @@
         <section>
             <title>Theme SPI</title>
             <para>
-                The Theme SPI allows creating different mechanisms to providing themes for the default FreeMarker based
+                The Theme SPI allows creating different mechanisms to load themes for the default FreeMarker based
                 implementations of login forms and account management. To create a theme provider you will need to implement
-                <literal>org.keycloak.freemarker.ThemeProvider</literal> and <literal>org.keycloak.freemarker.Theme</literal> in
-                <literal>forms/common-freemarker</literal>.
+                <literal>org.keycloak.freemarker.ThemeProviderFactory</literal> and <literal>org.keycloak.freemarker.ThemeProvider</literal>.
             </para>
             <para>
                 Keycloak comes with two theme providers, one that loads themes from the classpath (used by default themes)
@@ -149,12 +163,15 @@
             <para>
                 The Account SPI allows implementing the account management pages using whatever web framework or templating
                 engine you want. To create an Account provider implement <literal>org.keycloak.account.AccountProviderFactory</literal>
-                and <literal>org.keycloak.account.AccountProvider</literal> in <literal>forms/account-api</literal>.
+                and <literal>org.keycloak.account.AccountProvider</literal>.
             </para>
             <para>
-                Keycloaks default account management provider is built on the FreeMarker template engine (<literal>forms/account-freemarker</literal>).
-                To make sure your provider is loaded you will either need to delete <literal>standalone/deployments/auth-server.war/WEB-INF/lib/keycloak-account-freemarker-&project.version;.jar</literal>
-                or disable it with the system property <literal>org.keycloak.account.freemarker.FreeMarkerAccountProviderFactory</literal>.
+                Once you have deployed your account provider to Keycloak you need to configure <literal>keycloak-server.json</literal>to specify which provider should be used:
+<programlisting>
+"account": {
+    "provider": "custom-provider"
+}
+</programlisting>
             </para>
         </section>
         <section>
@@ -165,9 +182,12 @@
                 and <literal>org.keycloak.login.LoginFormsProvider</literal> in <literal>forms/login-api</literal>.
             </para>
             <para>
-                Keycloaks default login forms provider is built on the FreeMarker template engine (<literal>forms/login-freemarker</literal>).
-                To make sure your provider is loaded you will either need to delete <literal>standalone/deployments/auth-server.war/WEB-INF/lib/keycloak-login-freemarker-&project.version;.jar</literal>
-                or disable it with the system property <literal>org.keycloak.login.freemarker.FreeMarkerLoginFormsProviderFactory</literal>.
+                Once you have deployed your account provider to Keycloak you need to configure <literal>keycloak-server.json</literal>to specify which provider should be used:
+<programlisting>
+"login": {
+    "provider": "custom-provider"
+}
+</programlisting>
             </para>
         </section>
     </section>
diff --git a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
index ca0707e..c2241b3 100755
--- a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
@@ -7,6 +7,5 @@
     "expose-token": true,
     "credentials": {
         "secret": "password"
-    },
-    "use-hostname-for-local-requests": false
+    }
 }
diff --git a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
index c1ae517..0a86c04 100755
--- a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
@@ -6,6 +6,5 @@
   "ssl-required" : "external",
   "credentials" : {
       "secret": "password"
-  },
-  "use-hostname-for-local-requests": false
+  }
 }
diff --git a/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
index 14bbd79..559df05 100755
--- a/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
@@ -5,6 +5,5 @@
   "ssl-required" : "external",
    "credentials" : {
        "secret": "password"
-   },
-   "use-hostname-for-local-requests": false
+   }
 }
\ No newline at end of file
diff --git a/examples/demo-template/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json
index 14bbd79..559df05 100755
--- a/examples/demo-template/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json
@@ -5,6 +5,5 @@
   "ssl-required" : "external",
    "credentials" : {
        "secret": "password"
-   },
-   "use-hostname-for-local-requests": false
+   }
 }
\ No newline at end of file
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
index eacf266..533b631 100644
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
@@ -1,5 +1,6 @@
 package org.keycloak.freemarker;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
 
@@ -21,17 +22,17 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 public class ExtendingThemeManager implements ThemeProvider {
 
+    private static final Logger log = Logger.getLogger(ExtendingThemeManager.class);
+
     private final KeycloakSession session;
     private final ConcurrentHashMap<ExtendingThemeManagerFactory.ThemeKey, Theme> themeCache;
     private List<ThemeProvider> providers;
     private String defaultTheme;
-    private int staticMaxAge;
 
     public ExtendingThemeManager(KeycloakSession session, ConcurrentHashMap<ExtendingThemeManagerFactory.ThemeKey, Theme> themeCache) {
         this.session = session;
         this.themeCache = themeCache;
         this.defaultTheme = Config.scope("theme").get("default", "keycloak");
-        this.staticMaxAge = Config.scope("theme").getInt("staticMaxAge", -1);
     }
 
     private List<ThemeProvider> getProviders() {
@@ -57,10 +58,6 @@ public class ExtendingThemeManager implements ThemeProvider {
         return providers;
     }
 
-    public int getStaticMaxAge() {
-        return staticMaxAge;
-    }
-
     @Override
     public int getProviderPriority() {
         return 0;
@@ -77,7 +74,13 @@ public class ExtendingThemeManager implements ThemeProvider {
             Theme theme = themeCache.get(key);
             if (theme == null) {
                 theme = loadTheme(name, type);
-                if (themeCache.putIfAbsent(key, theme) != null) {
+                if (theme == null) {
+                    theme = loadTheme("keycloak", type);
+                    if (theme == null) {
+                        theme = loadTheme("base", type);
+                    }
+                    log.errorv("Failed to find {0} theme {1}, using built-in themes", type, name);
+                } else if (themeCache.putIfAbsent(key, theme) != null) {
                     theme = themeCache.get(key);
                 }
             }
@@ -89,7 +92,7 @@ public class ExtendingThemeManager implements ThemeProvider {
 
     private Theme loadTheme(String name, Theme.Type type) throws IOException {
         Theme theme = findTheme(name, type);
-        if (theme.getParentName() != null) {
+        if (theme != null && theme.getParentName() != null) {
             List<Theme> themes = new LinkedList<Theme>();
             themes.add(theme);
 
@@ -144,11 +147,11 @@ public class ExtendingThemeManager implements ThemeProvider {
                 try {
                     return p.getTheme(name, type);
                 } catch (IOException e) {
-                    throw new RuntimeException("Failed to create " + type.toString().toLowerCase() + " theme", e);
+                    log.errorv(e, p.getClass() + " failed to load theme, type={0}, name={1}", type, name);
                 }
             }
         }
-        throw new RuntimeException(type.toString().toLowerCase() + " theme '" + name + "' not found");
+        return null;
     }
 
     public static class ExtendingTheme implements Theme {
diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
index 6c711d3..9dfa90a 100755
--- a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
@@ -38,4 +38,6 @@ socialRedirectError=Failed to redirect to social provider
 socialProviderRemoved=Social provider removed successfully
 
 accountDisabled=Account is disabled, contact admin\
-accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
\ No newline at end of file
+accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later
+
+logOutAllSessions=Log out all sessions
diff --git a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
index 2fa5400..85c7a16 100755
--- a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
@@ -42,6 +42,6 @@
 
     </table>
 
-    <a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">Logout all sessions</a>
+    <a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${rb.logOutAllSessions}</a>
 
-</@layout.mainLayout>
\ No newline at end of file
+</@layout.mainLayout>
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 9709294..db284b1 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -7,7 +7,6 @@ import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.KeycloakUriBuilder;
-import org.keycloak.util.UriUtils;
 
 import java.net.URI;
 import java.security.PublicKey;
@@ -87,15 +86,18 @@ public class KeycloakDeployment {
 
         URI uri = URI.create(authServerBaseUrl);
         if (uri.getHost() == null) {
-            if (config.isUseHostnameForLocalRequests()) {
+            String authServerURLForBackendReqs = config.getAuthServerUrlForBackendRequests();
+            if (authServerURLForBackendReqs != null) {
                 relativeUrls = RelativeUrlsUsed.BROWSER_ONLY;
 
-                KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
-                serverBuilder.host(UriUtils.getHostName()).port(config.getLocalRequestsPort()).scheme(config.getLocalRequestsScheme());
+                KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
+                if (serverBuilder.getHost() == null || serverBuilder.getScheme() == null) {
+                    throw new IllegalStateException("Relative URL not supported for auth-server-url-for-backend-requests option. URL used: "
+                            + authServerURLForBackendReqs + ", Client: " + config.getResource());
+                }
                 resolveNonBrowserUrls(serverBuilder);
             } else {
                 relativeUrls = RelativeUrlsUsed.ALL_REQUESTS;
-                return;
             }
         } else {
             // We have absolute URI in config
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 0a3ab33..5aa996b 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -6,6 +6,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.PemUtils;
+import org.keycloak.util.SystemPropertiesJsonParserFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -79,7 +80,7 @@ public class KeycloakDeploymentBuilder {
     }
 
     public static KeycloakDeployment build(InputStream is) {
-        ObjectMapper mapper = new ObjectMapper();
+        ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
         mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
         AdapterConfig adapterConfig = null;
         try {
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
index e1ccd5d..e9013fd 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
@@ -7,7 +7,6 @@ import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.KeycloakUriBuilder;
-import org.keycloak.util.UriUtils;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,7 +24,7 @@ public class ServletOAuthClientBuilder {
 
     public static AdapterConfig getAdapterConfig(InputStream is) {
         try {
-            return JsonSerialization.readValue(is, AdapterConfig.class);
+            return JsonSerialization.readValue(is, AdapterConfig.class, true);
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
@@ -57,13 +56,17 @@ public class ServletOAuthClientBuilder {
 
         String authUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(adapterConfig.getRealm()).toString();
 
-        KeycloakUriBuilder tokenUrlBuilder = serverBuilder.clone();
-        KeycloakUriBuilder refreshUrlBuilder = serverBuilder.clone();
+        KeycloakUriBuilder tokenUrlBuilder;
+        KeycloakUriBuilder refreshUrlBuilder;
 
         if (useRelative == RelativeUrlsUsed.BROWSER_ONLY) {
             // Use absolute URI for refreshToken and codeToToken requests
-            tokenUrlBuilder.scheme(adapterConfig.getLocalRequestsScheme()).host(UriUtils.getHostName()).port(adapterConfig.getLocalRequestsPort());
-            refreshUrlBuilder.scheme(adapterConfig.getLocalRequestsScheme()).host(UriUtils.getHostName()).port(adapterConfig.getLocalRequestsPort());
+            KeycloakUriBuilder nonBrowsersServerBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrlForBackendRequests());
+            tokenUrlBuilder = nonBrowsersServerBuilder.clone();
+            refreshUrlBuilder = nonBrowsersServerBuilder.clone();
+        } else {
+            tokenUrlBuilder = serverBuilder.clone();
+            refreshUrlBuilder = serverBuilder.clone();
         }
         String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString();
         String refreshUrl = refreshUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString();
@@ -74,7 +77,7 @@ public class ServletOAuthClientBuilder {
 
     private static RelativeUrlsUsed relativeUrls(KeycloakUriBuilder serverBuilder, AdapterConfig adapterConfig) {
         if (serverBuilder.clone().getHost() == null) {
-            return (adapterConfig.isUseHostnameForLocalRequests()) ? RelativeUrlsUsed.BROWSER_ONLY : RelativeUrlsUsed.ALL_REQUESTS;
+            return (adapterConfig.getAuthServerUrlForBackendRequests() != null) ? RelativeUrlsUsed.BROWSER_ONLY : RelativeUrlsUsed.ALL_REQUESTS;
         } else {
             return RelativeUrlsUsed.NEVER;
         }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
index b7112d7..f39fe6a 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
@@ -19,6 +19,7 @@ package org.keycloak.adapters.undertow;
 import io.undertow.security.api.AuthenticatedSessionManager;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.server.session.Session;
+import io.undertow.server.session.SessionConfig;
 import io.undertow.server.session.SessionListener;
 import io.undertow.server.session.SessionManager;
 import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler;
@@ -108,7 +109,7 @@ public class UndertowUserSessionManagement implements SessionListener {
             log.debug("invalidating session for user: " + user);
             String sessionId = entry.getKey();
             String keycloakSessionId = entry.getValue();
-            Session session = manager.getSession(sessionId);
+            Session session = getSessionById(manager, sessionId);
             try {
                 session.invalidate(null);
             } catch (Exception e) {
@@ -131,7 +132,7 @@ public class UndertowUserSessionManagement implements SessionListener {
 
         }
         sessions.httpSessionToKeycloakSession.remove(sessionId);
-        Session session = manager.getSession(sessionId);
+        Session session = getSessionById(manager, sessionId);
         try {
             session.invalidate(null);
         } catch (Exception e) {
@@ -142,6 +143,41 @@ public class UndertowUserSessionManagement implements SessionListener {
         }
     }
 
+    protected Session getSessionById(SessionManager manager, final String sessionId) {
+        // TODO: Workaround for WFLY-3345. Remove this once we move to wildfly 8.2
+        if (manager.getClass().getName().equals("org.wildfly.clustering.web.undertow.session.DistributableSessionManager")) {
+            return manager.getSession(null, new SessionConfig() {
+
+                @Override
+                public void setSessionId(HttpServerExchange exchange, String sessionId) {
+                }
+
+                @Override
+                public void clearSession(HttpServerExchange exchange, String sessionId) {
+                }
+
+                @Override
+                public String findSessionId(HttpServerExchange exchange) {
+                    return sessionId;
+                }
+
+                @Override
+                public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) {
+                    return null;
+                }
+
+                @Override
+                public String rewriteUrl(String originalUrl, String sessionId) {
+                    return null;
+                }
+
+            });
+
+        } else {
+            return manager.getSession(sessionId);
+        }
+    }
+
 
     @Override
     public void sessionCreated(Session session, HttpServerExchange exchange) {
@@ -151,6 +187,7 @@ public class UndertowUserSessionManagement implements SessionListener {
     public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) {
         // Look up the single session id associated with this session (if any)
         String username = getUsernameFromSession(session);
+        log.debugf("Session destroyed for user: %s, sessionId: %s", username, session.getId());
         if (username == null) return;
         String sessionId = session.getId();
         UserSessions userSessions = userSessionMap.get(username);
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 033fe53..60bcb59 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -395,7 +395,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
                         throw new IllegalStateException("Can't remove session: task in progress for session");
                 }
             } else {
-                tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
+                tasks.put(key, new CacheTask(cache, CacheOperation.REPLACE, key, value));
             }
         }
 
@@ -426,6 +426,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
                         break;
                     case REPLACE:
                         cache.replace(key, value);
+                        break;
                 }
             }
         }
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
index 7329d3f..369b7a6 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
@@ -80,7 +80,9 @@ public class ClientSessionMapper implements Mapper<String, SessionEntity, String
                 collector.emit(key, entity);
                 break;
             case USER_SESSION_AND_TIMESTAMP:
-                collector.emit(entity.getUserSession(), entity.getTimestamp());
+                if (entity.getUserSession() != null) {
+                    collector.emit(entity.getUserSession(), entity.getTimestamp());
+                }
                 break;
         }
     }
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index 26ddb0a..b43f827 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -121,7 +121,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
         for (ClientSessionEntity s : clientSessions.values()) {
             String realmId = realm.getId();
             String clientId = client.getId();
-            if (s.getSession().getRealm().equals(realmId) && s.getClientId().equals(clientId)) {
+            if (s.getSession() != null && s.getSession().getRealm().equals(realmId) && s.getClientId().equals(clientId)) {
                 if (!userSessionEntities.contains(s.getSession())) {
                     userSessionEntities.add(s.getSession());
                 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
index b310403..b88648e 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java
@@ -28,6 +28,7 @@ import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.RefreshToken;
@@ -276,7 +277,7 @@ public class OpenIDConnectService {
         }
         event.detail(Details.USERNAME, username);
 
-        UserModel user = session.users().getUserByUsername(username, realm);
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
         if (user != null) event.user(user);
 
         ClientModel client = authorizeClient(authorizationHeader, form, event);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 761b69f..33485b0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -99,7 +99,7 @@ public class TokenManager {
         RefreshToken refreshToken = null;
         try {
             if (!RSAProvider.verify(jws, realm.getPublicKey())) {
-                throw new RuntimeException("Invalid refresh token");
+                throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
             }
             refreshToken = jws.readJsonContent(RefreshToken.class);
         } catch (IOException e) {
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 8b55f00..105751f 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -23,6 +23,7 @@ import org.keycloak.representations.adapters.action.UserStats;
 import org.keycloak.representations.adapters.action.UserStatsAction;
 import org.keycloak.services.util.HttpClientBuilder;
 import org.keycloak.services.util.ResolveRelative;
+import org.keycloak.util.StringPropertyReplacer;
 import org.keycloak.util.Time;
 
 import javax.ws.rs.core.MediaType;
@@ -108,8 +109,10 @@ public class ResourceAdminManager {
         }
 
         // this is to support relative admin urls when keycloak and applications are deployed on the same machine
-        return ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl);
+        String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl);
 
+        // this is for resolving URI like "http://${jboss.home.name}:8080/..." in order to send request to same machine and avoid LB in cluster env
+        return StringPropertyReplacer.replaceProperties(absoluteURI);
     }
 
     public UserStats getUserStats(URI requestUri, RealmModel realm, ApplicationModel application, UserModel user) {
@@ -242,7 +245,8 @@ public class ResourceAdminManager {
             try {
                 response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post(UserStats.class);
             } catch (Exception e) {
-                throw new RuntimeException(e);
+                logger.warn("Logout for application '" + resource.getName() + "' failed", e);
+                return false;
             }
             try {
                 boolean success = response.getStatus() == 204;
diff --git a/testsuite/docker-cluster/as7/Dockerfile b/testsuite/docker-cluster/as7/Dockerfile
new file mode 100644
index 0000000..f216f84
--- /dev/null
+++ b/testsuite/docker-cluster/as7/Dockerfile
@@ -0,0 +1,13 @@
+FROM mposolda/as7
+
+ADD keycloak-as7-trigger.sh /keycloak-as7-trigger.sh
+RUN chmod u+x /keycloak-as7-trigger.sh
+
+ENV JBOSS_HOME /opt/as7
+ENV JBOSS_MODULES_HOME $JBOSS_HOME/modules
+ENV JBOSS_TYPE as7
+ENV NODE_PREFIX as
+
+EXPOSE 8787
+
+CMD [ "/keycloak-as7-trigger.sh" ]
\ No newline at end of file
diff --git a/testsuite/docker-cluster/as7/keycloak-as7-trigger.sh b/testsuite/docker-cluster/as7/keycloak-as7-trigger.sh
new file mode 100644
index 0000000..a59408c
--- /dev/null
+++ b/testsuite/docker-cluster/as7/keycloak-as7-trigger.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+chmod u+x /keycloak-docker-cluster/shared-files/keycloak-run-node.sh
+chmod u+x /keycloak-docker-cluster/shared-files/keycloak-base-prepare.sh
+chmod u+x /keycloak-docker-cluster/shared-files/deploy-examples.sh
+
+echo "Permissions changed. Triggering keycloak-run-node.sh"
+/keycloak-docker-cluster/shared-files/keycloak-run-node.sh
diff --git a/testsuite/docker-cluster/as7-image/Dockerfile b/testsuite/docker-cluster/as7-image/Dockerfile
new file mode 100644
index 0000000..6c17097
--- /dev/null
+++ b/testsuite/docker-cluster/as7-image/Dockerfile
@@ -0,0 +1,32 @@
+FROM jboss/wildfly
+
+USER root
+
+# Update yum and install required programs
+RUN yum install -y unzip && yum install -y wget && yum install -y mc && yum -y install nc
+RUN yum clean all
+
+# Download mysql driver
+RUN cd /tmp
+RUN wget http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.32/mysql-connector-java-5.1.32.jar
+RUN mv *.jar mysql-connector-java-5.1.32.jar
+
+# Drop wildfly
+RUN rm -rf /opt/wildfly*
+
+ENV AS7_VERSION 7.1.1.Final
+
+# Download and unpack AS7 distribution
+RUN cd /opt
+RUN wget http://download.jboss.org/jbossas/7.1/jboss-as-$AS7_VERSION/jboss-as-$AS7_VERSION.zip
+RUN sleep 3
+RUN unzip -q jboss-as-$AS7_VERSION.zip
+
+# Make sure the distribution is available from a well-known place
+RUN mv jboss-as-$AS7_VERSION /opt/as7
+
+RUN rm -rf jboss-as-$AS7_VERSION.zip
+
+EXPOSE 8787
+
+CMD [ "/bin/bash" ]
diff --git a/testsuite/docker-cluster/assembly.xml b/testsuite/docker-cluster/assembly.xml
index d7c98cf..da0580d 100644
--- a/testsuite/docker-cluster/assembly.xml
+++ b/testsuite/docker-cluster/assembly.xml
@@ -23,8 +23,20 @@
             </excludes>
         </fileSet>
         <fileSet>
-            <directory>target/modules</directory>
-            <outputDirectory>modules</outputDirectory>
+            <directory>target/wildfly-adapter</directory>
+            <outputDirectory>wildfly-adapter</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>target/as7-adapter</directory>
+            <outputDirectory>as7-adapter</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>target/eap63-adapter</directory>
+            <outputDirectory>eap63-adapter</outputDirectory>
+        </fileSet>
+        <fileSet>
+            <directory>shared-files</directory>
+            <outputDirectory>shared-files</outputDirectory>
         </fileSet>
     </fileSets>
 </assembly>
diff --git a/testsuite/docker-cluster/eap63/Dockerfile b/testsuite/docker-cluster/eap63/Dockerfile
new file mode 100644
index 0000000..136bc38
--- /dev/null
+++ b/testsuite/docker-cluster/eap63/Dockerfile
@@ -0,0 +1,13 @@
+FROM mposolda/eap63
+
+ADD keycloak-eap63-trigger.sh /keycloak-eap63-trigger.sh
+RUN chmod u+x /keycloak-eap63-trigger.sh
+
+ENV JBOSS_HOME /opt/eap63
+ENV JBOSS_MODULES_HOME $JBOSS_HOME/modules/system/layers/base
+ENV JBOSS_TYPE eap63
+ENV NODE_PREFIX eap
+
+EXPOSE 8787
+
+CMD [ "/keycloak-eap63-trigger.sh" ]
\ No newline at end of file
diff --git a/testsuite/docker-cluster/eap63/keycloak-eap63-trigger.sh b/testsuite/docker-cluster/eap63/keycloak-eap63-trigger.sh
new file mode 100644
index 0000000..a59408c
--- /dev/null
+++ b/testsuite/docker-cluster/eap63/keycloak-eap63-trigger.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+chmod u+x /keycloak-docker-cluster/shared-files/keycloak-run-node.sh
+chmod u+x /keycloak-docker-cluster/shared-files/keycloak-base-prepare.sh
+chmod u+x /keycloak-docker-cluster/shared-files/deploy-examples.sh
+
+echo "Permissions changed. Triggering keycloak-run-node.sh"
+/keycloak-docker-cluster/shared-files/keycloak-run-node.sh
diff --git a/testsuite/docker-cluster/eap63-image/Dockerfile b/testsuite/docker-cluster/eap63-image/Dockerfile
new file mode 100644
index 0000000..fa8a626
--- /dev/null
+++ b/testsuite/docker-cluster/eap63-image/Dockerfile
@@ -0,0 +1,30 @@
+FROM jboss/wildfly
+
+USER root
+
+# Update yum and install required programs
+RUN yum install -y unzip && yum install -y wget && yum install -y mc && yum -y install nc
+RUN yum clean all
+
+# Download mysql driver
+RUN cd /tmp
+RUN wget http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.32/mysql-connector-java-5.1.32.jar
+RUN mv *.jar mysql-connector-java-5.1.32.jar
+
+# Drop wildfly
+RUN rm -rf /opt/wildfly*
+
+# Download and unpack EAP63 distribution TODO: Check if it's an issue for EAP 6.3
+RUN cd /
+RUN wget https://dl.dropboxusercontent.com/u/5525920/jboss-eap-6.3.0.zip
+RUN sleep 3
+RUN unzip -q jboss-eap-6.3.0.zip
+
+# Make sure the distribution is available from a well-known place
+RUN mv jboss-eap-6.3 /opt/eap63
+
+RUN rm -rf jboss-eap-6.3.0.zip
+
+EXPOSE 8787
+
+CMD [ "/bin/bash" ]
\ No newline at end of file
diff --git a/testsuite/docker-cluster/fig.yml b/testsuite/docker-cluster/fig.yml
index 046d73b..e75c7c5 100644
--- a/testsuite/docker-cluster/fig.yml
+++ b/testsuite/docker-cluster/fig.yml
@@ -15,9 +15,9 @@ mysql:
       - /apachelogs
    ports:
       - "33306:3306"
-node:
+wfnode:
    build: wildfly
-   command: /keycloak-run-node.sh
+   command: /keycloak-wildfly-trigger.sh
    volumes:
       - target/keycloak-docker-cluster:/keycloak-docker-cluster
    volumes_from:
diff --git a/testsuite/docker-cluster/fig-as7.yml b/testsuite/docker-cluster/fig-as7.yml
new file mode 100644
index 0000000..9d11d69
--- /dev/null
+++ b/testsuite/docker-cluster/fig-as7.yml
@@ -0,0 +1,31 @@
+httpd:
+   build: httpd
+   ports:
+      - "8000:80"
+      - "10001:10001"
+   volumes_from:
+      - mysql
+mysql:
+   image: mysql:5.6.20
+   environment:
+      - MYSQL_ROOT_PASSWORD=mysecretpassword
+      - MYSQL_DATABASE=keycloak_db
+   volumes:
+      - /keycloak-docker-shared
+      - /apachelogs
+   ports:
+      - "33306:3306"
+asnode:
+   build: as7
+   command: /keycloak-as7-trigger.sh
+   volumes:
+      - target/keycloak-docker-cluster:/keycloak-docker-cluster
+   volumes_from:
+      - mysql
+   links:
+      - httpd:httpd
+      - mysql:mysql
+   ports:
+      - "8787"
+      - "8080"
+      - "9990"
\ No newline at end of file
diff --git a/testsuite/docker-cluster/fig-eap63.yml b/testsuite/docker-cluster/fig-eap63.yml
new file mode 100644
index 0000000..b7ed7fe
--- /dev/null
+++ b/testsuite/docker-cluster/fig-eap63.yml
@@ -0,0 +1,31 @@
+httpd:
+   build: httpd
+   ports:
+      - "8000:80"
+      - "10001:10001"
+   volumes_from:
+      - mysql
+mysql:
+   image: mysql:5.6.20
+   environment:
+      - MYSQL_ROOT_PASSWORD=mysecretpassword
+      - MYSQL_DATABASE=keycloak_db
+   volumes:
+      - /keycloak-docker-shared
+      - /apachelogs
+   ports:
+      - "33306:3306"
+eapnode:
+   build: eap63
+   command: /keycloak-eap63-trigger.sh
+   volumes:
+      - target/keycloak-docker-cluster:/keycloak-docker-cluster
+   volumes_from:
+      - mysql
+   links:
+      - httpd:httpd
+      - mysql:mysql
+   ports:
+      - "8787"
+      - "8080"
+      - "9990"
\ No newline at end of file
diff --git a/testsuite/docker-cluster/pom.xml b/testsuite/docker-cluster/pom.xml
index d552ddf..d3a4dbc 100644
--- a/testsuite/docker-cluster/pom.xml
+++ b/testsuite/docker-cluster/pom.xml
@@ -51,7 +51,21 @@
                                     <artifactId>keycloak-wildfly-adapter-dist</artifactId>
                                     <version>${project.version}</version>
                                     <type>zip</type>
-                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                    <outputDirectory>${project.build.directory}/wildfly-adapter</outputDirectory>
+                                </artifactItem>
+                                <artifactItem>
+                                    <groupId>org.keycloak</groupId>
+                                    <artifactId>keycloak-as7-adapter-dist</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${project.build.directory}/as7-adapter</outputDirectory>
+                                </artifactItem>
+                                <artifactItem>
+                                    <groupId>org.keycloak</groupId>
+                                    <artifactId>keycloak-eap6-adapter-dist</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${project.build.directory}/eap63-adapter</outputDirectory>
                                 </artifactItem>
                             </artifactItems>
                         </configuration>
diff --git a/testsuite/docker-cluster/README.md b/testsuite/docker-cluster/README.md
index 2aaa4bf..c51cd80 100644
--- a/testsuite/docker-cluster/README.md
+++ b/testsuite/docker-cluster/README.md
@@ -3,7 +3,7 @@ How to test Keycloak cluster with Docker
 Docker+Fig allows to easily setup and test the whole environment with:
 * Apache HTTPD 2.4 + modcluster 1.3 as Load Balancer
 * MySQL 5.6.1 as database
-* Various number of Keycloak cluster nodes running on WildFly (with "demo" examples deployed)
+* Various number of Keycloak cluster nodes running on WildFly with "demo" examples deployed. (See below for EAP 6.3 and AS7)
 
 You don't need to setup Apache with modcluster + MySQL on your laptop as Docker will do it for you and all will run in Docker containers.
 
@@ -42,7 +42,6 @@ be able to access Apache modCluster status page: [http://localhost:10001/mod_clu
 with deployed "auth-server.war" and few other WARs (keycloak demo). 
 
 Also you can access Keycloak admin console via loadBalancer on [http://localhost:8000/auth/admin](http://localhost:8000/auth/admin) and similarly Account mgmt. 
-TODO: Examples currently doesn't work and I am looking at it..
 
 MySQL can be directly accessed from your machine (if you have MySQL client installed):
 ```shell
@@ -74,7 +73,7 @@ Scale / more cluster nodes
 
 Run this in separate terminal to add more (in this case 2) cluster nodes:
 ```shell
-$ fig scale node=2
+$ fig scale wfnode=2
 ````
 
 Now it should be visible on mod_cluster_manager page that they are 2 nodes.
@@ -89,7 +88,7 @@ to see output of MySql and Keycloak server consoles.
 
 To see Apache and debug logs of keycloak server:
 ```shell
-$ fig run node /bin/bash
+$ fig run wfnode /bin/bash
 ````
   
 Then you're in shell inside docker container, which has some mounted volumes with apache logs and keycloak nodes. Apache logs are at:
@@ -133,3 +132,26 @@ In this case you might need to stop and remove existing containers. Then start f
 changed jars, then rebuild distribution and testsuite/docker-cluster 
 (or just copy changed JAR into $KEYCLOAK_HOME/testsuite/docker-cluster/target/keycloak-docker-cluster/deployments/auth-server.war/WEB-INF/lib if it's not adapter stuff. 
 But 'fig rm' is safer to call anyway)
+
+Test with Keycloak and examples on EAP 6.3
+------------------------------------------
+Steps are quite similar like for WildFly but we need to pass different file "fig-eap63.yml" instead of default "fig.yml" which is used for WildFly. 
+Also name of the node is "eapnode" instead of "wfnode". 
+ 
+So your commands will look like
+```shell 
+$ fig -f fig-eap63.yml build
+$ fig -f fig-eap63.yml up
+$ fig -f fig-eap63.yml scale eapnode=2
+```` 
+and viceversa.
+ 
+Test with Keycloak and examples on AS 7.1.1
+-------------------------------------------
+Also arguments need to be passed with different fig file and node name: TODO: AS7 cluster setup doesn't work correctly yet
+ 
+ ```shell 
+$ fig -f fig-as7.yml build
+$ fig -f fig-as7.yml up
+$ fig -f fig-as7.yml scale asnode=2
+````
\ No newline at end of file
diff --git a/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh b/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh
new file mode 100644
index 0000000..137e5c6
--- /dev/null
+++ b/testsuite/docker-cluster/shared-files/keycloak-base-prepare.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Copy MySQL driver
+cd /tmp
+mkdir -p mysql/main && mv /mysql-connector-java-5.1.32.jar mysql/main/
+cp /keycloak-docker-cluster/shared-files/mysql-module.xml mysql/main/module.xml
+mv mysql $JBOSS_MODULES_HOME/com/
+
+if [ $JBOSS_TYPE == "eap63" ]; then
+  EXT="as7";
+else
+  EXT=$JBOSS_TYPE;
+fi;
+
+sed -i -e "s/<extensions>/&\n <extension module=\"org.keycloak.keycloak-$EXT-subsystem\"\/>/" $JBOSS_HOME/standalone/configuration/standalone-ha.xml
+sed -i -e 's/<profile>/&\n <subsystem xmlns="urn:jboss:domain:keycloak:1.0"\/>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<security-domains>/&\n <security-domain name="keycloak">\n  <authentication>\n   <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"\/>\n  <\/authentication>\n <\/security-domain>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<drivers>/&\n <driver name="mysql" module="com.mysql">\n  <xa-datasource-class>com.mysql.jdbc.Driver<\/xa-datasource-class>\n  <driver-class>com.mysql.jdbc.Driver<\/driver-class>\n <\/driver>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<\/periodic-rotating-file-handler>/&\n <logger category=\"org.keycloak\">\n  <level name=\"DEBUG\" \/> \n <\/logger>\n <logger category=\"org.jboss.resteasy.core.ResourceLocator\">\n  <level name=\"ERROR\" \/> \n <\/logger>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml
+
+sed -i -e 's/<subsystem xmlns=\"urn:jboss:domain:infinispan:[0-9]\.[0-9]\">/&\n <cache-container name=\"keycloak\" jndi-name=\"infinispan\/Keycloak\" start=\"EAGER\"> \
+\n  <transport lock-timeout=\"60000\"\/>\n  <distributed-cache name=\"sessions\" mode=\"SYNC\" owners=\"2\" segments=\"60\"\/> \
+\n  <invalidation-cache name=\"realms\" mode=\"SYNC\"\/>\n <\/cache-container>/' $JBOSS_HOME/standalone/configuration/standalone-ha.xml
+
+sed -i "s|<mod-cluster-config .*>|<mod-cluster-config advertise-socket=\"modcluster\" proxy-list=\"\$\{httpd.proxyList\}\" proxy-url=\"\/\" balancer=\"mycluster\" advertise=\"false\" connector=\"ajp\" sticky-session=\"true\">|" $JBOSS_HOME/standalone/configuration/standalone-ha.xml
+
+sed -i "s|#JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|" $JBOSS_HOME/bin/standalone.conf
+
+cp /keycloak-docker-cluster/shared-files/mysql-keycloak-ds.xml $JBOSS_HOME/standalone/deployments/
diff --git a/testsuite/docker-cluster/wildfly/Dockerfile b/testsuite/docker-cluster/wildfly/Dockerfile
index 3fde5bc..6aff50b 100644
--- a/testsuite/docker-cluster/wildfly/Dockerfile
+++ b/testsuite/docker-cluster/wildfly/Dockerfile
@@ -1,37 +1,25 @@
 FROM jboss/wildfly
 
 USER root
+
+# Update yum and install required programs
 RUN yum install -y unzip && yum install -y wget && yum install -y mc && yum -y install nc
 RUN yum clean all
 
+# Download mysql driver
 RUN cd /tmp
 RUN wget http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.32/mysql-connector-java-5.1.32.jar
-RUN mv *.jar mysql-connector-java-5.1.32.jar
-
-RUN mkdir -p mysql/main && mv mysql-connector-java-5.1.32.jar mysql/main/ 
-ADD mysql-module.xml mysql/main/module.xml
-RUN mv mysql /opt/wildfly/modules/system/layers/base/com/
-
-RUN sed -i -e 's/<extensions>/&\n <extension module="org.keycloak.keycloak-wildfly-subsystem"\/>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
-sed -i -e 's/<profile>/&\n <subsystem xmlns="urn:jboss:domain:keycloak:1.0"\/>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
-sed -i -e 's/<security-domains>/&\n <security-domain name="keycloak">\n  <authentication>\n   <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"\/>\n  <\/authentication>\n <\/security-domain>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
-sed -i -e 's/<drivers>/&\n <driver name="mysql" module="com.mysql">\n  <xa-datasource-class>com.mysql.jdbc.Driver<\/xa-datasource-class>\n <\/driver>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
-sed -i -e 's/<\/periodic-rotating-file-handler>/&\n <logger category=\"org.keycloak\">\n  <level name=\"DEBUG\" \/> \n <\/logger>/' /opt/wildfly/standalone/configuration/standalone-ha.xml
+RUN mv *.jar /mysql-connector-java-5.1.32.jar
 
-RUN sed -i -e 's/<subsystem xmlns=\"urn:jboss:domain:infinispan:2\.0\">/&\n <cache-container name=\"keycloak\" jndi-name=\"infinispan\/Keycloak\" start=\"EAGER\"> \
-\n  <transport lock-timeout=\"60000\"\/>\n  <distributed-cache name=\"sessions\" mode=\"SYNC\" owners=\"2\" segments=\"60\"\/> \
-\n  <invalidation-cache name=\"realms\" mode=\"SYNC\"\/>\n <\/cache-container>/' /opt/wildfly/standalone/configuration/standalone-ha.xml
+ADD keycloak-wildfly-trigger.sh /keycloak-wildfly-trigger.sh
+RUN chmod u+x /keycloak-wildfly-trigger.sh
 
-RUN sed -i "s|<mod-cluster-config .*>|<mod-cluster-config advertise-socket=\"modcluster\" proxy-list=\"\$\{httpd.proxyList\}\" proxy-url=\"\/\" balancer=\"mycluster\" advertise=\"false\" connector=\"ajp\" sticky-session=\"true\">|" /opt/wildfly/standalone/configuration/standalone-ha.xml
-
-RUN sed -i "s|#JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|" /opt/wildfly/bin/standalone.conf
-
-ADD mysql-keycloak-ds.xml /opt/wildfly/standalone/deployments/
-ADD keycloak-run-node.sh /keycloak-run-node.sh
-RUN chmod u+x /keycloak-run-node.sh
-ADD deploy-examples.sh /deploy-examples.sh
-RUN chmod u+x /deploy-examples.sh
+ENV JBOSS_HOME /opt/wildfly-8.1.0.Final
+ENV JBOSS_MODULES_HOME $JBOSS_HOME/modules/system/layers/base
+ENV JBOSS_TYPE wildfly
+ENV NODE_PREFIX wf
 
 EXPOSE 8787
 
-CMD [ "/keycloak-run-node.sh" ]
+CMD [ "/keycloak-wildfly-trigger.sh" ]
+
diff --git a/testsuite/docker-cluster/wildfly/keycloak-wildfly-trigger.sh b/testsuite/docker-cluster/wildfly/keycloak-wildfly-trigger.sh
new file mode 100644
index 0000000..a59408c
--- /dev/null
+++ b/testsuite/docker-cluster/wildfly/keycloak-wildfly-trigger.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+chmod u+x /keycloak-docker-cluster/shared-files/keycloak-run-node.sh
+chmod u+x /keycloak-docker-cluster/shared-files/keycloak-base-prepare.sh
+chmod u+x /keycloak-docker-cluster/shared-files/deploy-examples.sh
+
+echo "Permissions changed. Triggering keycloak-run-node.sh"
+/keycloak-docker-cluster/shared-files/keycloak-run-node.sh
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 4fe0131..5aa52c3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -453,20 +453,24 @@ public class AccountTest {
 
         // Create second session
         WebDriver driver2 = WebRule.createWebDriver();
-        OAuthClient oauth2 = new OAuthClient(driver2);
-        oauth2.state("mystate");
-        oauth2.doLogin("view-sessions", "password");
+        try {
+            OAuthClient oauth2 = new OAuthClient(driver2);
+            oauth2.state("mystate");
+            oauth2.doLogin("view-sessions", "password");
 
-        Event login2Event = events.expectLogin().user(userId).detail(Details.USERNAME, "view-sessions").assertEvent();
+            Event login2Event = events.expectLogin().user(userId).detail(Details.USERNAME, "view-sessions").assertEvent();
 
-        sessionsPage.open();
-        sessions = sessionsPage.getSessions();
-        Assert.assertEquals(2, sessions.size());
+            sessionsPage.open();
+            sessions = sessionsPage.getSessions();
+            Assert.assertEquals(2, sessions.size());
 
-        sessionsPage.logoutAll();
+            sessionsPage.logoutAll();
 
-        events.expectLogout(registerEvent.getSessionId());
-        events.expectLogout(login2Event.getSessionId());
+            events.expectLogout(registerEvent.getSessionId());
+            events.expectLogout(login2Event.getSessionId());
+        } finally {
+            driver2.close();
+        }
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 4001dfa..28bd4b6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -1,429 +1,480 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2012, Red Hat, Inc., and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.keycloak.testsuite.adapter;
-
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.Config;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.Version;
-import org.keycloak.adapters.AdapterConstants;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.Constants;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.oidc.OpenIDConnectService;
-import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.adapters.action.SessionStats;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.managers.ResourceAdminManager;
-import org.keycloak.services.resources.admin.AdminRoot;
-import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.rule.AbstractKeycloakRule;
-import org.keycloak.testsuite.rule.WebResource;
-import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.testutils.KeycloakServer;
-import org.keycloak.util.BasicAuthHelper;
-import org.openqa.selenium.WebDriver;
-
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.Form;
-import javax.ws.rs.core.GenericType;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import java.net.URI;
-import java.net.URL;
-import java.security.PublicKey;
-import java.util.Map;
-
-/**
- * Tests Undertow Adapter
- *
- * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
- */
-public class AdapterTest {
-
-    public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
-    public static PublicKey realmPublicKey;
-    @ClassRule
-    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
-        @Override
-        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
-            RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
-            RealmModel realm = manager.importRealm(representation);
-
-            realmPublicKey = realm.getPublicKey();
-
-            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
-            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
-            deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false);
-            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
-            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
-            url = getClass().getResource("/adapter-test/product-keycloak.json");
-            deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user");
-
-        }
-    };
-
-    private static String createToken() {
-        KeycloakSession session = keycloakRule.startSession();
-        try {
-            RealmManager manager = new RealmManager(session);
-
-            RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
-            ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
-            TokenManager tm = new TokenManager();
-            UserModel admin = session.users().getUserByUsername("admin", adminRealm);
-            UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
-            AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
-            return tm.encodeToken(adminRealm, token);
-        } finally {
-            keycloakRule.stopSession(session, true);
-        }
-    }
-
-
-    @Rule
-    public WebRule webRule = new WebRule(this);
-
-    @WebResource
-    protected WebDriver driver;
-
-    @WebResource
-    protected OAuthClient oauth;
-
-    @WebResource
-    protected LoginPage loginPage;
-
-    @Test
-    public void testLoginSSOAndLogout() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
-        pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
-
-        // View stats
-        String adminToken = createToken();
-
-        Client client = ClientBuilder.newClient();
-        UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
-        WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
-        Map<String, SessionStats> stats = adminTarget.path("session-stats").request()
-                .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
-                .get(new GenericType<Map<String, SessionStats>>() {
-                });
-
-        SessionStats custStats = stats.get("customer-portal");
-        Assert.assertNotNull(custStats);
-        Assert.assertEquals(1, custStats.getActiveSessions());
-        SessionStats prodStats = stats.get("product-portal");
-        Assert.assertNotNull(prodStats);
-        Assert.assertEquals(1, prodStats.getActiveSessions());
-
-        client.close();
-
-
-        // test logout
-
-        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
-                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/customer-portal").build("demo").toString();
-        driver.navigate().to(logoutUri);
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-
-    }
-
-    @Test
-    public void testServletRequestLogout() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
-        pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
-
-        // back
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-        // test logout
-
-        driver.navigate().to("http://localhost:8081/customer-portal/logout");
-
-
-
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        String currentUrl = driver.getCurrentUrl();
-        Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-
-    }
-
-    @Test
-    public void testLoginSSOIdle() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        KeycloakSession session = keycloakRule.startSession();
-        RealmModel realm = session.realms().getRealmByName("demo");
-        int originalIdle = realm.getSsoSessionIdleTimeout();
-        realm.setSsoSessionIdleTimeout(1);
-        session.getTransaction().commit();
-        session.close();
-
-        Thread.sleep(2000);
-
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        realm.setSsoSessionIdleTimeout(originalIdle);
-        session.getTransaction().commit();
-        session.close();
-    }
-
-    @Test
-    public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        KeycloakSession session = keycloakRule.startSession();
-        RealmModel realm = session.realms().getRealmByName("demo");
-        int originalIdle = realm.getSsoSessionIdleTimeout();
-        realm.setSsoSessionIdleTimeout(1);
-        session.getTransaction().commit();
-        session.close();
-
-        Thread.sleep(2000);
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        session.sessions().removeExpiredUserSessions(realm);
-        session.getTransaction().commit();
-        session.close();
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        // need to cleanup so other tests don't fail, so invalidate http sessions on remote clients.
-        UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm);
-        new ResourceAdminManager().logoutUser(null, realm, user.getId(), null);
-        realm.setSsoSessionIdleTimeout(originalIdle);
-        session.getTransaction().commit();
-        session.close();
-
-
-    }
-
-    @Test
-    public void testLoginSSOMax() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/customer-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        KeycloakSession session = keycloakRule.startSession();
-        RealmModel realm = session.realms().getRealmByName("demo");
-        int original = realm.getSsoSessionMaxLifespan();
-        realm.setSsoSessionMaxLifespan(1);
-        session.getTransaction().commit();
-        session.close();
-
-        Thread.sleep(2000);
-
-
-        // test SSO
-        driver.navigate().to("http://localhost:8081/product-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-
-        session = keycloakRule.startSession();
-        realm = session.realms().getRealmByName("demo");
-        realm.setSsoSessionMaxLifespan(original);
-        session.getTransaction().commit();
-        session.close();
-    }
-
-    /**
-     * KEYCLOAK-518
-     * @throws Exception
-     */
-    @Test
-    public void testNullBearerToken() throws Exception {
-        Client client = ClientBuilder.newClient();
-        WebTarget target = client.target("http://localhost:8081/customer-db");
-        Response response = target.request().get();
-        Assert.assertEquals(401, response.getStatus());
-        response.close();
-        response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get();
-        Assert.assertEquals(401, response.getStatus());
-        response.close();
-        client.close();
-
-    }
-
-    /**
-     * KEYCLOAK-518
-     * @throws Exception
-     */
-    @Test
-    public void testBadUser() throws Exception {
-        Client client = ClientBuilder.newClient();
-        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
-        URI uri = OpenIDConnectService.grantAccessTokenUrl(builder).build("demo");
-        WebTarget target = client.target(uri);
-        String header = BasicAuthHelper.createHeader("customer-portal", "password");
-        Form form = new Form();
-        form.param("username", "monkey@redhat.com")
-            .param("password", "password");
-        Response response = target.request()
-                .header(HttpHeaders.AUTHORIZATION, header)
-                .post(Entity.form(form));
-        Assert.assertEquals(400, response.getStatus());
-        response.close();
-        client.close();
-
-    }
-
-    @Test
-    public void testVersion() throws Exception {
-        Client client = ClientBuilder.newClient();
-        WebTarget target = client.target(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT).path("version");
-        Version version = target.request().get(Version.class);
-        Assert.assertNotNull(version);
-        Assert.assertNotNull(version.getVersion());
-        Assert.assertNotNull(version.getBuildTime());
-        Assert.assertNotEquals(version.getVersion(), Version.UNKNOWN);
-        Assert.assertNotEquals(version.getBuildTime(), Version.UNKNOWN);
-
-        Version version2 = client.target("http://localhost:8081/secure-portal").path(AdapterConstants.K_VERSION).request().get(Version.class);
-        Assert.assertNotNull(version2);
-        Assert.assertNotNull(version2.getVersion());
-        Assert.assertNotNull(version2.getBuildTime());
-        Assert.assertEquals(version.getVersion(), version2.getVersion());
-        Assert.assertEquals(version.getBuildTime(), version2.getBuildTime());
-        client.close();
-
-    }
-
-
-
-    @Test
-    public void testAuthenticated() throws Exception {
-        // test login to customer-portal which does a bearer request to customer-db
-        driver.navigate().to("http://localhost:8081/secure-portal");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        loginPage.login("bburke@redhat.com", "password");
-        System.out.println("Current url: " + driver.getCurrentUrl());
-        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/secure-portal");
-        String pageSource = driver.getPageSource();
-        System.out.println(pageSource);
-        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
-
-        // test logout
-
-        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
-                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/secure-portal").build("demo").toString();
-        driver.navigate().to(logoutUri);
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-        driver.navigate().to("http://localhost:8081/secure-portal");
-        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
-    }
-
-
-
-}
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.adapter;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.Version;
+import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.adapters.action.SessionStats;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.ResourceAdminManager;
+import org.keycloak.services.resources.admin.AdminRoot;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testutils.KeycloakServer;
+import org.keycloak.util.BasicAuthHelper;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.URL;
+import java.security.PublicKey;
+import java.util.Map;
+
+/**
+ * Tests Undertow Adapter
+ *
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class AdapterTest {
+
+    public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
+    public static PublicKey realmPublicKey;
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
+            RealmModel realm = manager.importRealm(representation);
+
+            realmPublicKey = realm.getPublicKey();
+
+            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+            deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
+            deployApplication("secure-portal", "/secure-portal", CallAuthenticatedServlet.class, url.getPath(), "user", false);
+            url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
+            deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
+            url = getClass().getResource("/adapter-test/product-keycloak.json");
+            deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user");
+
+            // Test that replacing system properties works for adapters
+            System.setProperty("my.host.name", "localhost");
+            url = getClass().getResource("/adapter-test/session-keycloak.json");
+            deployApplication("session-portal", "/session-portal", SessionServlet.class, url.getPath(), "user");
+        }
+    };
+
+    private static String createToken() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            RealmManager manager = new RealmManager(session);
+
+            RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
+            ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
+            TokenManager tm = new TokenManager();
+            UserModel admin = session.users().getUserByUsername("admin", adminRealm);
+            UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
+            AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
+            return tm.encodeToken(adminRealm, token);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @Test
+    public void testLoginSSOAndLogout() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
+
+        // View stats
+        String adminToken = createToken();
+
+        Client client = ClientBuilder.newClient();
+        UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
+        WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
+        Map<String, SessionStats> stats = adminTarget.path("session-stats").request()
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
+                .get(new GenericType<Map<String, SessionStats>>() {
+                });
+
+        SessionStats custStats = stats.get("customer-portal");
+        Assert.assertNotNull(custStats);
+        Assert.assertEquals(1, custStats.getActiveSessions());
+        SessionStats prodStats = stats.get("product-portal");
+        Assert.assertNotNull(prodStats);
+        Assert.assertEquals(1, prodStats.getActiveSessions());
+
+        client.close();
+
+
+        // test logout
+
+        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/customer-portal").build("demo").toString();
+        driver.navigate().to(logoutUri);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+
+    }
+
+    @Test
+    public void testServletRequestLogout() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
+
+        // back
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+        // test logout
+
+        driver.navigate().to("http://localhost:8081/customer-portal/logout");
+
+
+
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        String currentUrl = driver.getCurrentUrl();
+        Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+
+    }
+
+    @Test
+    public void testLoginSSOIdle() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int originalIdle = realm.getSsoSessionIdleTimeout();
+        realm.setSsoSessionIdleTimeout(1);
+        session.getTransaction().commit();
+        session.close();
+
+        Thread.sleep(2000);
+
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        realm.setSsoSessionIdleTimeout(originalIdle);
+        session.getTransaction().commit();
+        session.close();
+    }
+
+    @Test
+    public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int originalIdle = realm.getSsoSessionIdleTimeout();
+        realm.setSsoSessionIdleTimeout(1);
+        session.getTransaction().commit();
+        session.close();
+
+        Thread.sleep(2000);
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        session.sessions().removeExpiredUserSessions(realm);
+        session.getTransaction().commit();
+        session.close();
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        // need to cleanup so other tests don't fail, so invalidate http sessions on remote clients.
+        UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm);
+        new ResourceAdminManager().logoutUser(null, realm, user.getId(), null);
+        realm.setSsoSessionIdleTimeout(originalIdle);
+        session.getTransaction().commit();
+        session.close();
+    }
+
+    @Test
+    public void testLoginSSOMax() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/customer-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        KeycloakSession session = keycloakRule.startSession();
+        RealmModel realm = session.realms().getRealmByName("demo");
+        int original = realm.getSsoSessionMaxLifespan();
+        realm.setSsoSessionMaxLifespan(1);
+        session.getTransaction().commit();
+        session.close();
+
+        Thread.sleep(2000);
+
+
+        // test SSO
+        driver.navigate().to("http://localhost:8081/product-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+        session = keycloakRule.startSession();
+        realm = session.realms().getRealmByName("demo");
+        realm.setSsoSessionMaxLifespan(original);
+        session.getTransaction().commit();
+        session.close();
+    }
+
+    /**
+     * KEYCLOAK-518
+     * @throws Exception
+     */
+    @Test
+    public void testNullBearerToken() throws Exception {
+        Client client = ClientBuilder.newClient();
+        WebTarget target = client.target("http://localhost:8081/customer-db");
+        Response response = target.request().get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+        response = target.request().header(HttpHeaders.AUTHORIZATION, "Bearer null").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+        client.close();
+
+    }
+
+    /**
+     * KEYCLOAK-518
+     * @throws Exception
+     */
+    @Test
+    public void testBadUser() throws Exception {
+        Client client = ClientBuilder.newClient();
+        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+        URI uri = OpenIDConnectService.grantAccessTokenUrl(builder).build("demo");
+        WebTarget target = client.target(uri);
+        String header = BasicAuthHelper.createHeader("customer-portal", "password");
+        Form form = new Form();
+        form.param("username", "monkey@redhat.com")
+            .param("password", "password");
+        Response response = target.request()
+                .header(HttpHeaders.AUTHORIZATION, header)
+                .post(Entity.form(form));
+        Assert.assertEquals(400, response.getStatus());
+        response.close();
+        client.close();
+
+    }
+
+    @Test
+    public void testVersion() throws Exception {
+        Client client = ClientBuilder.newClient();
+        WebTarget target = client.target(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT).path("version");
+        Version version = target.request().get(Version.class);
+        Assert.assertNotNull(version);
+        Assert.assertNotNull(version.getVersion());
+        Assert.assertNotNull(version.getBuildTime());
+        Assert.assertNotEquals(version.getVersion(), Version.UNKNOWN);
+        Assert.assertNotEquals(version.getBuildTime(), Version.UNKNOWN);
+
+        Version version2 = client.target("http://localhost:8081/secure-portal").path(AdapterConstants.K_VERSION).request().get(Version.class);
+        Assert.assertNotNull(version2);
+        Assert.assertNotNull(version2.getVersion());
+        Assert.assertNotNull(version2.getBuildTime());
+        Assert.assertEquals(version.getVersion(), version2.getVersion());
+        Assert.assertEquals(version.getBuildTime(), version2.getBuildTime());
+        client.close();
+
+    }
+
+
+
+    @Test
+    public void testAuthenticated() throws Exception {
+        // test login to customer-portal which does a bearer request to customer-db
+        driver.navigate().to("http://localhost:8081/secure-portal");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/secure-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+
+        // test logout
+
+        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/secure-portal").build("demo").toString();
+        driver.navigate().to(logoutUri);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8081/secure-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+    }
+
+    @Test
+    public void testSingleSessionInvalidated() throws Throwable {
+        AdapterTest browser1 = this;
+        AdapterTest browser2 = new AdapterTest();
+
+        loginAndCheckSession(browser1.driver, browser1.loginPage);
+
+        // Open browser2
+        browser2.webRule.before();
+        try {
+            browser2.loginAndCheckSession(browser2.driver, browser2.loginPage);
+
+            // Logout in browser1
+            String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+                    .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/session-portal").build("demo").toString();
+            browser1.driver.navigate().to(logoutUri);
+            Assert.assertTrue(browser1.driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+            // Assert that I am logged out in browser1
+            browser1.driver.navigate().to("http://localhost:8081/session-portal");
+            Assert.assertTrue(browser1.driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+            // Assert that I am still logged in browser2 and same session is still preserved
+            browser2.driver.navigate().to("http://localhost:8081/session-portal");
+            Assert.assertEquals(browser2.driver.getCurrentUrl(), "http://localhost:8081/session-portal");
+            String pageSource = browser2.driver.getPageSource();
+            Assert.assertTrue(pageSource.contains("Counter=3"));
+
+            browser2.driver.navigate().to(logoutUri);
+            Assert.assertTrue(browser2.driver.getCurrentUrl().startsWith(LOGIN_URL));
+        } finally {
+            browser2.webRule.after();
+        }
+    }
+
+    private static void loginAndCheckSession(WebDriver driver, LoginPage loginPage) {
+        driver.navigate().to("http://localhost:8081/session-portal");
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        loginPage.login("bburke@redhat.com", "password");
+        System.out.println("Current url: " + driver.getCurrentUrl());
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/session-portal");
+        String pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("Counter=1"));
+
+        // Counter increased now
+        driver.navigate().to("http://localhost:8081/session-portal");
+        pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("Counter=2"));
+
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
index e77d0f8..eafe55b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
@@ -7,6 +7,7 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.client.WebTarget;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
index 228461c..0c3dfb8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
@@ -35,7 +35,6 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.OpenIDConnectService;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.representations.AccessToken;
-import org.keycloak.representations.adapters.action.SessionStats;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.admin.AdminRoot;
@@ -132,16 +131,16 @@ public class RelativeUriAdapterTest {
         Client client = ClientBuilder.newClient();
         UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
         WebTarget adminTarget = client.target(AdminRoot.realmsUrl(authBase)).path("demo");
-        Map<String, SessionStats> stats = adminTarget.path("session-stats").request()
+        Map<String, Integer> stats = adminTarget.path("application-session-stats").request()
                 .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
-                .get(new GenericType<Map<String, SessionStats>>(){});
-
-        SessionStats custStats = stats.get("customer-portal");
-        Assert.assertNotNull(custStats);
-        Assert.assertEquals(1, custStats.getActiveSessions());
-        SessionStats prodStats = stats.get("product-portal");
-        Assert.assertNotNull(prodStats);
-        Assert.assertEquals(1, prodStats.getActiveSessions());
+                .get(new GenericType<Map<String, Integer>>(){});
+
+        Integer custSessionsCount = stats.get("customer-portal");
+        Assert.assertNotNull(custSessionsCount);
+        Assert.assertTrue(1 == custSessionsCount);
+        Integer prodStatsCount = stats.get("product-portal");
+        Assert.assertNotNull(prodStatsCount);
+        Assert.assertTrue(1 == prodStatsCount);
 
         client.close();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java
new file mode 100644
index 0000000..c7c4d85
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/SessionServlet.java
@@ -0,0 +1,38 @@
+package org.keycloak.testsuite.adapter;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SessionServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String counter = increaseAndGetCounter(req);
+
+        resp.setContentType("text/html");
+        PrintWriter pw = resp.getWriter();
+        pw.printf("<html><head><title>%s</title></head><body>", "Session Test");
+        pw.printf("Counter=%s", counter);
+        pw.print("</body></html>");
+        pw.flush();
+
+
+    }
+
+    private String increaseAndGetCounter(HttpServletRequest req) {
+        HttpSession session = req.getSession();
+        Integer counter = (Integer)session.getAttribute("counter");
+        counter = (counter == null) ? 1 : counter + 1;
+        session.setAttribute("counter", counter);
+        return String.valueOf(counter);
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
index 30c10a7..2f78c6d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
@@ -27,6 +27,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.events.Details;
+import org.keycloak.events.Event;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
@@ -40,6 +41,7 @@ import org.openqa.selenium.WebDriver;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -88,7 +90,7 @@ public class SSOTest {
 
         profilePage.open();
 
-        Assert.assertTrue(profilePage.isCurrent());
+        assertTrue(profilePage.isCurrent());
 
         String sessionId2 = events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId();
 
@@ -105,4 +107,51 @@ public class SSOTest {
         events.clear();
     }
 
+    @Test
+    public void multipleSessions() {
+        loginPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        Event login1 = events.expectLogin().assertEvent();
+
+        WebDriver driver2 = WebRule.createWebDriver();
+        try {
+            OAuthClient oauth2 = new OAuthClient(driver2);
+            oauth2.state("mystate");
+            oauth2.doLogin("test-user@localhost", "password");
+
+            Event login2 = events.expectLogin().assertEvent();
+
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, RequestType.valueOf(driver2.getTitle()));
+            Assert.assertNotNull(oauth2.getCurrentQuery().get(OAuth2Constants.CODE));
+
+            assertNotEquals(login1.getSessionId(), login2.getSessionId());
+
+            oauth.openLogout();
+            events.expectLogout(login1.getSessionId()).assertEvent();
+
+            oauth.openLoginForm();
+
+            assertTrue(loginPage.isCurrent());
+
+            oauth2.openLoginForm();
+
+            events.expectLogin().session(login2.getSessionId()).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
+            Assert.assertEquals(RequestType.AUTH_RESPONSE, RequestType.valueOf(driver2.getTitle()));
+            Assert.assertNotNull(oauth2.getCurrentQuery().get(OAuth2Constants.CODE));
+
+            oauth2.openLogout();
+            events.expectLogout(login2.getSessionId()).assertEvent();
+
+            oauth2.openLoginForm();
+
+            assertTrue(driver2.getTitle().equals("Log in to test"));
+        } finally {
+            driver2.close();
+        }
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 803133e..fcd51b3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -33,9 +33,11 @@ import org.keycloak.events.Event;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.protocol.oidc.OpenIDConnectService;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
@@ -56,6 +58,8 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import java.net.URI;
+import java.security.PrivateKey;
+import java.security.PublicKey;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertEquals;
@@ -175,6 +179,54 @@ public class RefreshTokenTest {
         Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
     }
 
+    PrivateKey privateKey;
+    PublicKey publicKey;
+
+    @Test
+    public void refreshTokenRealmKeysChanged() throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        Event loginEvent = events.expectLogin().assertEvent();
+
+        String sessionId = loginEvent.getSessionId();
+        String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+        AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+        String refreshTokenString = response.getRefreshToken();
+        RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
+
+        events.expectCodeToToken(codeId, sessionId).assertEvent();
+
+        try {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    privateKey = appRealm.getPrivateKey();
+                    publicKey = appRealm.getPublicKey();
+                    KeycloakModelUtils.generateRealmKeys(appRealm);
+                }
+            });
+
+            response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
+
+            assertEquals(400, response.getStatusCode());
+            assertEquals("invalid_grant", response.getError());
+
+            events.expectRefresh(refreshToken.getId(), sessionId).user((String) null).session((String) null).clearDetails().error(Errors.INVALID_TOKEN).assertEvent();
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPrivateKey(privateKey);
+                    appRealm.setPublicKey(publicKey);
+                }
+            });
+
+        }
+    }
+
     @Test
     public void refreshTokenUserSessionExpired() {
         oauth.doLogin("test-user@localhost", "password");
@@ -417,5 +469,4 @@ public class RefreshTokenTest {
     }
 
 
-
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index f59c8ef..f172dec 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -8,6 +8,8 @@ import org.keycloak.events.Details;
 import org.keycloak.events.Errors;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.managers.RealmManager;
@@ -32,6 +34,14 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
             ApplicationModel app = appRealm.addApplication("resource-owner");
             app.setSecret("secret");
             appRealm.setPasswordCredentialGrantAllowed(true);
+
+            UserModel user = session.users().addUser(appRealm, "direct-login");
+            user.setEmail("direct-login@localhost");
+            user.setEnabled(true);
+
+            userId = user.getId();
+
+            session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
         }
     });
 
@@ -47,11 +57,22 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
     @WebResource
     protected OAuthClient oauth;
 
+    private static String userId;
+
     @Test
-    public void grantAccessToken() throws Exception {
+    public void grantAccessTokenUsername() throws Exception {
+        grantAccessToken("direct-login");
+    }
+
+    @Test
+    public void grantAccessTokenEmail() throws Exception {
+        grantAccessToken("direct-login@localhost");
+    }
+
+    private void grantAccessToken(String login) throws Exception {
         oauth.clientId("resource-owner");
 
-        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
+        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password");
 
         assertEquals(200, response.getStatusCode());
 
@@ -60,11 +81,13 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
 
         events.expectLogin()
                 .client("resource-owner")
+                .user(userId)
                 .session(accessToken.getSessionState())
                 .detail(Details.AUTH_METHOD, "oauth_credentials")
                 .detail(Details.RESPONSE_TYPE, "token")
                 .detail(Details.TOKEN_ID, accessToken.getId())
                 .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
+                .detail(Details.USERNAME, login)
                 .removeDetail(Details.CODE_ID)
                 .removeDetail(Details.REDIRECT_URI)
                 .assertEvent();
@@ -79,7 +102,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
         assertEquals(accessToken.getSessionState(), refreshedAccessToken.getSessionState());
         assertEquals(accessToken.getSessionState(), refreshedRefreshToken.getSessionState());
 
-        events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).client("resource-owner").assertEvent();
+        events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).user(userId).client("resource-owner").assertEvent();
     }
 
     @Test
@@ -187,4 +210,27 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
                 .assertEvent();
     }
 
+    @Test
+    public void grantAccessTokenUserNotFound() throws Exception {
+        oauth.clientId("resource-owner");
+
+        OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "invalid", "invalid");
+
+        assertEquals(400, response.getStatusCode());
+
+        assertEquals("invalid_grant", response.getError());
+
+        events.expectLogin()
+                .client("resource-owner")
+                .user((String) null)
+                .session((String) null)
+                .detail(Details.AUTH_METHOD, "oauth_credentials")
+                .detail(Details.RESPONSE_TYPE, "token")
+                .detail(Details.USERNAME, "invalid")
+                .removeDetail(Details.CODE_ID)
+                .removeDetail(Details.REDIRECT_URI)
+                .error(Errors.INVALID_USER_CREDENTIALS)
+                .assertEvent();
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
index 35454dc..dab37f7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
@@ -46,7 +46,7 @@ public class WebRule extends ExternalResource {
     }
 
     @Override
-    protected void before() throws Throwable {
+    public void before() throws Throwable {
         driver = createWebDriver();
         oauth = new OAuthClient(driver);
         initWebResources(test);
@@ -121,7 +121,7 @@ public class WebRule extends ExternalResource {
     }
 
     @Override
-    protected void after() {
+    public void after() {
         driver.manage().deleteAllCookies();
         driver.close();
     }
diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
index ed538f2..abc8e40 100755
--- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
@@ -105,6 +105,16 @@
                 "http://localhost:8081/secure-portal/*"
             ],
             "secret": "password"
+        },
+        {
+            "name": "session-portal",
+            "enabled": true,
+            "adminUrl": "http://localhost:8081/session-portal",
+            "baseUrl": "http://localhost:8081/session-portal",
+            "redirectUris": [
+                "http://localhost:8081/session-portal/*"
+            ],
+            "secret": "password"
         }
     ],
     "oauthClients": [
diff --git a/testsuite/integration/src/test/resources/adapter-test/session-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/session-keycloak.json
new file mode 100644
index 0000000..6a7f60b
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/session-keycloak.json
@@ -0,0 +1,10 @@
+{
+  "realm" : "demo",
+  "resource" : "session-portal",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://${my.host.name}:8081/auth",
+  "ssl-required" : "external",
+  "credentials" : {
+      "secret": "password"
+   }
+}
\ No newline at end of file