keycloak-uncached

Changes

pom.xml 1(+1 -0)

Details

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 8d8eed9..e03eb51 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
@@ -87,6 +87,11 @@ public class KeycloakDeploymentBuilder {
     }
 
     public static KeycloakDeployment build(InputStream is) {
+        AdapterConfig adapterConfig = loadAdapterConfig(is);
+        return new KeycloakDeploymentBuilder().internalBuild(adapterConfig);
+    }
+
+    public static AdapterConfig loadAdapterConfig(InputStream is) {
         ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
         mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
         AdapterConfig adapterConfig;
@@ -95,7 +100,7 @@ public class KeycloakDeploymentBuilder {
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
-        return new KeycloakDeploymentBuilder().internalBuild(adapterConfig);
+        return adapterConfig;
     }
 
 
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
new file mode 100755
index 0000000..b9761e1
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.util.Sessions;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractUndertowRequestAuthenticator extends RequestAuthenticator {
+    protected SecurityContext securityContext;
+    protected HttpServerExchange exchange;
+
+
+    public AbstractUndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
+                                                SecurityContext securityContext, HttpServerExchange exchange,
+                                                AdapterTokenStore tokenStore) {
+        super(facade, deployment, tokenStore, sslRedirectPort);
+        this.securityContext = securityContext;
+        this.exchange = exchange;
+    }
+
+    protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+        exchange.putAttachment(UndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
+    }
+
+    @Override
+    protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+        return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
+            @Override
+            protected void saveRequest() {
+                // todo
+            }
+        };
+    }
+
+    @Override
+    protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+        KeycloakUndertowAccount account = createAccount(principal);
+        securityContext.authenticationComplete(account, "KEYCLOAK", false);
+        propagateKeycloakContext(account);
+        tokenStore.saveAccountInfo(account);
+    }
+
+    @Override
+    protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+        KeycloakUndertowAccount account = createAccount(principal);
+        securityContext.authenticationComplete(account, "KEYCLOAK", false);
+        propagateKeycloakContext(account);
+    }
+
+    @Override
+    protected String getHttpSessionId(boolean create) {
+        if (create) {
+            Session session = Sessions.getOrCreateSession(exchange);
+            return session.getId();
+        } else {
+            Session session = Sessions.getSession(exchange);
+            return session != null ? session.getId() : null;
+        }
+    }
+
+    /**
+     * Subclasses need to be able to create their own version of the KeycloakUndertowAccount
+     * @return The account
+     */
+    protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
index 7f18e90..0ae5b6e 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -16,14 +16,10 @@
  */
 package org.keycloak.adapters.undertow;
 
-import io.undertow.security.api.NotificationReceiver;
 import io.undertow.security.api.SecurityContext;
-import io.undertow.security.api.SecurityNotification;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.servlet.api.ConfidentialPortManager;
-import io.undertow.servlet.handlers.ServletRequestContext;
 import org.jboss.logging.Logger;
-import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.adapters.AdapterDeploymentContext;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.HttpFacade;
@@ -32,15 +28,12 @@ import org.keycloak.adapters.NodesRegistrationManagement;
 import org.keycloak.adapters.RequestAuthenticator;
 import org.keycloak.enums.TokenStore;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
  * @version $Revision: 1 $
  */
-public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
+public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
     private static final Logger log = Logger.getLogger(ServletKeycloakAuthMech.class);
 
     protected NodesRegistrationManagement nodesRegistrationManagement;
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index ed13f56..d28ff29 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -34,7 +34,7 @@ import javax.servlet.http.HttpSession;
  * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
  * @version $Revision: 1 $
  */
-public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
+public class ServletRequestAuthenticator extends AbstractUndertowRequestAuthenticator {
 
 
     public ServletRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
old mode 100644
new mode 100755
index 70f0bee..2168d9d
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -63,7 +63,7 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
         if (account.checkActive()) {
             log.debug("Cached account found");
             securityContext.authenticationComplete(account, "KEYCLOAK", false);
-            ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+            ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
             return true;
         } else {
             log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
new file mode 100755
index 0000000..057fc50
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
@@ -0,0 +1,42 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAuthMech {
+    protected NodesRegistrationManagement nodesRegistrationManagement;
+    protected int confidentialPort;
+
+    public UndertowAuthenticationMechanism(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+                                           NodesRegistrationManagement nodesRegistrationManagement, int confidentialPort) {
+        super(deploymentContext, sessionManagement);
+        this.nodesRegistrationManagement = nodesRegistrationManagement;
+        this.confidentialPort = confidentialPort;
+    }
+
+    @Override
+    public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+        if (!deployment.isConfigured()) {
+            return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+        }
+
+        nodesRegistrationManagement.tryRegister(deployment);
+
+        AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
+        RequestAuthenticator authenticator = new UndertowRequestAuthenticator(facade, deployment, confidentialPort, securityContext, exchange, tokenStore);
+
+        return keycloakAuthenticate(exchange, securityContext, authenticator);
+    }
+
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
index 65b6ab2..ccd695f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
@@ -53,7 +53,7 @@ public class UndertowCookieTokenStore implements AdapterTokenStore {
         if (account.checkActive()) {
             log.debug("Cached account found");
             securityContext.authenticationComplete(account, "KEYCLOAK", false);
-            ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+            ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
             return true;
         } else {
             log.debug("Account was not active, removing cookie and returning false");
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index 54552cc..21738f5 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -96,6 +96,7 @@ public class UndertowHttpFacade implements HttpFacade {
 
         @Override
         public InputStream getInputStream() {
+            if (!exchange.isBlocking()) exchange.startBlocking();
             return exchange.getInputStream();
         }
 
@@ -142,6 +143,7 @@ public class UndertowHttpFacade implements HttpFacade {
 
         @Override
         public OutputStream getOutputStream() {
+            if (!exchange.isBlocking()) exchange.startBlocking();
             return exchange.getOutputStream();
         }
 
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
new file mode 100755
index 0000000..0a70c67
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.SessionManager;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.PreAuthActionsHandler;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowPreAuthActionsHandler implements HttpHandler {
+
+    private static final Logger log = Logger.getLogger(UndertowPreAuthActionsHandler.class);
+    protected HttpHandler next;
+    protected SessionManager sessionManager;
+    protected UndertowUserSessionManagement userSessionManagement;
+    protected AdapterDeploymentContext deploymentContext;
+
+    public UndertowPreAuthActionsHandler(AdapterDeploymentContext deploymentContext,
+                                            UndertowUserSessionManagement userSessionManagement,
+                                            SessionManager sessionManager,
+                                            HttpHandler next) {
+        this.next = next;
+        this.deploymentContext = deploymentContext;
+        this.sessionManager = sessionManager;
+        this.userSessionManagement = userSessionManagement;
+    }
+
+    @Override
+    public void handleRequest(HttpServerExchange exchange) throws Exception {
+        UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+        SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
+        PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
+        if (handler.handleRequest()) return;
+        next.handleRequest(exchange);
+    }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
index 6b9c351..91fe9e8 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -1,94 +1,27 @@
-/*
- * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
- * as indicated by the @author tags. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
 package org.keycloak.adapters.undertow;
 
 import io.undertow.security.api.SecurityContext;
 import io.undertow.server.HttpServerExchange;
-import io.undertow.server.session.Session;
-import io.undertow.util.Sessions;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.HttpFacade;
 import org.keycloak.adapters.KeycloakDeployment;
-import org.keycloak.adapters.OAuthRequestAuthenticator;
 import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.undertow.AbstractUndertowRequestAuthenticator;
+import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
- * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
  * @version $Revision: 1 $
  */
-public abstract class UndertowRequestAuthenticator extends RequestAuthenticator {
-    protected SecurityContext securityContext;
-    protected HttpServerExchange exchange;
-
-
+public class UndertowRequestAuthenticator extends AbstractUndertowRequestAuthenticator {
     public UndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
-                                        SecurityContext securityContext, HttpServerExchange exchange,
-                                        AdapterTokenStore tokenStore) {
-        super(facade, deployment, tokenStore, sslRedirectPort);
-        this.securityContext = securityContext;
-        this.exchange = exchange;
-    }
-
-    protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
-        exchange.putAttachment(UndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
-    }
-
-    @Override
-    protected OAuthRequestAuthenticator createOAuthAuthenticator() {
-        return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort) {
-            @Override
-            protected void saveRequest() {
-                // todo
-            }
-        };
+                                        SecurityContext securityContext, HttpServerExchange exchange, AdapterTokenStore tokenStore) {
+        super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
     }
 
     @Override
-    protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
-        KeycloakUndertowAccount account = createAccount(principal);
-        securityContext.authenticationComplete(account, "KEYCLOAK", false);
-        propagateKeycloakContext(account);
-        tokenStore.saveAccountInfo(account);
+    protected KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+        return new KeycloakUndertowAccount(principal);
     }
-
-    @Override
-    protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
-        KeycloakUndertowAccount account = createAccount(principal);
-        securityContext.authenticationComplete(account, "KEYCLOAK", false);
-        propagateKeycloakContext(account);
-    }
-
-    @Override
-    protected String getHttpSessionId(boolean create) {
-        if (create) {
-            Session session = Sessions.getOrCreateSession(exchange);
-            return session.getId();
-        } else {
-            Session session = Sessions.getSession(exchange);
-            return session != null ? session.getId() : null;
-        }
-    }
-
-    /**
-     * Subclasses need to be able to create their own version of the KeycloakUndertowAccount
-     * @return The account
-     */
-    protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
 }
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
index cc9e3d9..e5f013c 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -60,7 +60,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
         if (account.checkActive()) {
             log.debug("Cached account found");
             securityContext.authenticationComplete(account, "KEYCLOAK", false);
-            ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+            ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
             return true;
         } else {
             log.debug("Account was not active, returning false");

pom.xml 1(+1 -0)

diff --git a/pom.xml b/pom.xml
index ffeaa5e..ba351f1 100755
--- a/pom.xml
+++ b/pom.xml
@@ -108,6 +108,7 @@
         <module>events</module>
         <module>model</module>
         <module>integration</module>
+        <module>proxy/proxy-server</module>
         <module>picketlink</module>
         <module>federation</module>
         <module>services</module>
diff --git a/proxy/proxy-server/pom.xml b/proxy/proxy-server/pom.xml
new file mode 100755
index 0000000..4b4d12a
--- /dev/null
+++ b/proxy/proxy-server/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0.Beta2-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-proxy-server</artifactId>
+    <name>Keycloak Proxy Server</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>${jboss.logging.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${keycloak.apache.httpcomponents.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>net.iharder</groupId>
+            <artifactId>base64</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk16</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-xc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
new file mode 100755
index 0000000..bdbddc6
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
@@ -0,0 +1,42 @@
+package org.keycloak.proxy;
+
+import io.undertow.security.handlers.AuthenticationConstraintHandler;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import org.keycloak.KeycloakSecurityContext;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ConstraintMatcherHandler implements HttpHandler {
+    public static final AttachmentKey<SingleConstraintMatch> CONSTRAINT_KEY = AttachmentKey.create(SingleConstraintMatch.class);
+    protected SecurityPathMatches matcher;
+    protected HttpHandler securedHandler;
+    protected HttpHandler unsecuredHandler;
+
+    public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler) {
+        this.matcher = matcher;
+        this.securedHandler = securedHandler;
+        this.unsecuredHandler = unsecuredHandler;
+    }
+
+    @Override
+    public void handleRequest(HttpServerExchange exchange) throws Exception {
+        SingleConstraintMatch match = matcher.getSecurityInfo(exchange.getRelativePath(), exchange.getRequestMethod().toString()).getMergedConstraint();
+        if (match == null || (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.PERMIT)) {
+            unsecuredHandler.handleRequest(exchange);
+            return;
+        }
+
+        if (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.DENY) {
+            exchange.setResponseCode(403);
+            exchange.endExchange();
+        }
+        exchange.getSecurityContext().setAuthenticationRequired();
+        exchange.putAttachment(CONSTRAINT_KEY, match);
+        securedHandler.handleRequest(exchange);
+
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
new file mode 100755
index 0000000..f236579
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
@@ -0,0 +1,278 @@
+package org.keycloak.proxy;
+
+import io.undertow.Undertow;
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.AuthenticationMode;
+import io.undertow.security.handlers.AuthenticationCallHandler;
+import io.undertow.security.handlers.AuthenticationConstraintHandler;
+import io.undertow.security.handlers.AuthenticationMechanismsHandler;
+import io.undertow.security.handlers.SecurityInitialHandler;
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.security.impl.CachedAuthenticatedSessionMechanism;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.handlers.PathHandler;
+import io.undertow.server.handlers.ResponseCodeHandler;
+import io.undertow.server.handlers.proxy.ProxyHandler;
+import io.undertow.server.handlers.proxy.SimpleProxyClientProvider;
+import io.undertow.server.session.InMemorySessionManager;
+import io.undertow.server.session.SessionAttachmentHandler;
+import io.undertow.server.session.SessionCookieConfig;
+import io.undertow.server.session.SessionManager;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.undertow.UndertowAuthenticatedActionsHandler;
+import org.keycloak.adapters.undertow.UndertowAuthenticationMechanism;
+import org.keycloak.adapters.undertow.UndertowPreAuthActionsHandler;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+import org.keycloak.enums.SslRequired;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.xnio.Option;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ProxyServerBuilder {
+    public static final HttpHandler NOT_FOUND = new HttpHandler() {
+        @Override
+        public void handleRequest(HttpServerExchange exchange) throws Exception {
+            exchange.setResponseCode(404);
+            exchange.endExchange();
+        }
+    };
+
+    protected Undertow.Builder builder = Undertow.builder();
+
+    protected PathHandler root = new PathHandler(NOT_FOUND);
+    protected HttpHandler proxyHandler;
+
+    public ProxyServerBuilder target(String uri) {
+        SimpleProxyClientProvider provider = null;
+        try {
+            provider = new SimpleProxyClientProvider(new URI(uri));
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+        final HttpHandler handler = new ProxyHandler(provider, 30000, ResponseCodeHandler.HANDLE_404);
+        proxyHandler = new HttpHandler() {
+            @Override
+            public void handleRequest(HttpServerExchange exchange) throws Exception {
+                exchange.setRelativePath(exchange.getResolvedPath()); // need this otherwise proxy forwards to chopped off path
+                handler.handleRequest(exchange);
+            }
+        };
+        return this;
+    }
+
+    public ApplicationBuilder application(AdapterConfig config) {
+        return new ApplicationBuilder(config);
+    }
+
+    public class ApplicationBuilder {
+        protected NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
+        protected UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
+        protected AdapterDeploymentContext deploymentContext;
+        protected KeycloakDeployment deployment;
+        SessionManager sessionManager = new InMemorySessionManager(
+                "SESSION_MANAGER");
+        protected String base;
+        protected SecurityPathMatches.Builder constraintBuilder = new SecurityPathMatches.Builder();
+        protected SecurityPathMatches matches;
+
+        public ApplicationBuilder base(String base) {
+            this.base = base;
+            return this;
+        }
+
+        public ApplicationBuilder(AdapterConfig config) {
+            this.deployment = KeycloakDeploymentBuilder.build(config);
+            this.deploymentContext = new AdapterDeploymentContext(deployment);
+        }
+
+        public ProxyServerBuilder add() {
+            matches = constraintBuilder.build();
+            HttpHandler handler = sessionHandling(addSecurity(proxyHandler));
+            root.addPrefixPath(base, handler);
+            return ProxyServerBuilder.this;
+        }
+
+        public ConstraintBuilder constraint(String pattern) {
+            return new ConstraintBuilder(pattern);
+        }
+
+        public class ConstraintBuilder {
+            protected String pattern;
+            protected Set<String> rolesAllowed = new HashSet<String>();
+            protected Set<String> methods = new HashSet<String>();
+            protected Set<String> excludedMethods = new HashSet<String>();
+            protected SecurityInfo.EmptyRoleSemantic semantic = SecurityInfo.EmptyRoleSemantic.AUTHENTICATE;
+
+            public ConstraintBuilder(String pattern) {
+                this.pattern = pattern;
+
+            }
+
+            public ConstraintBuilder deny() {
+                semantic = SecurityInfo.EmptyRoleSemantic.DENY;
+                return this;
+            }
+            public ConstraintBuilder permit() {
+                semantic = SecurityInfo.EmptyRoleSemantic.PERMIT;
+                return this;
+            }
+            public ConstraintBuilder authenticate() {
+                semantic = SecurityInfo.EmptyRoleSemantic.AUTHENTICATE;
+                return this;
+            }
+
+            public ConstraintBuilder method(String method) {
+                methods.add(method);
+                return this;
+            }
+
+            public ConstraintBuilder excludeMethod(String method) {
+                excludedMethods.add(method);
+                return this;
+            }
+
+
+            public ConstraintBuilder roles(String... roles) {
+                for (String role : roles) role(role);
+                return this;
+            }
+
+            public ConstraintBuilder role(String role) {
+                rolesAllowed.add(role);
+                return this;
+            }
+
+            public ApplicationBuilder add() {
+                constraintBuilder.addSecurityConstraint(rolesAllowed, semantic, pattern, methods, excludedMethods);
+                return ApplicationBuilder.this;
+            }
+
+
+        }
+
+        private HttpHandler addSecurity(final HttpHandler toWrap) {
+            HttpHandler handler = toWrap;
+            handler = new UndertowAuthenticatedActionsHandler(deploymentContext, toWrap);
+            handler = new AuthenticationCallHandler(handler);
+            handler = new ConstraintMatcherHandler(matches, handler, toWrap);
+            final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
+            mechanisms.add(new CachedAuthenticatedSessionMechanism());
+            mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1));
+            handler = new AuthenticationMechanismsHandler(handler, mechanisms);
+            IdentityManager identityManager = new IdentityManager() {
+                @Override
+                public Account verify(Account account) {
+                    return account;
+                }
+
+                @Override
+                public Account verify(String id, Credential credential) {
+                    throw new IllegalStateException("Should never be called in Keycloak flow");
+                }
+
+                @Override
+                public Account verify(Credential credential) {
+                    throw new IllegalStateException("Should never be called in Keycloak flow");
+                }
+            };
+            handler = new UndertowPreAuthActionsHandler(deploymentContext, userSessionManagement, sessionManager, handler);
+            return new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, handler);
+        }
+
+        private HttpHandler sessionHandling(HttpHandler toWrap) {
+            SessionCookieConfig sessionConfig = new SessionCookieConfig();
+            sessionConfig.setCookieName("keycloak." + deployment.getResourceName() + ".session");
+            sessionConfig.setPath(base);
+            if (deployment.getSslRequired() == SslRequired.ALL) sessionConfig.setSecure(true);
+            toWrap = new SessionAttachmentHandler(
+                    toWrap, sessionManager, sessionConfig);
+            return toWrap;
+        }
+
+    }
+
+
+    public Undertow build() {
+        builder.setHandler(root);
+        return builder.build();
+    }
+
+    public ProxyServerBuilder addHttpListener(int port, String host) {
+        builder.addHttpListener(port, host);
+        return this;
+    }
+
+    public ProxyServerBuilder addHttpsListener(int port, String host, KeyManager[] keyManagers, TrustManager[] trustManagers) {
+        builder.addHttpsListener(port, host, keyManagers, trustManagers);
+        return this;
+    }
+
+    public ProxyServerBuilder addHttpsListener(int port, String host, SSLContext sslContext) {
+        builder.addHttpsListener(port, host, sslContext);
+        return this;
+    }
+
+    public ProxyServerBuilder setBufferSize(int bufferSize) {
+        builder.setBufferSize(bufferSize);
+        return this;
+    }
+
+    public ProxyServerBuilder setBuffersPerRegion(int buffersPerRegion) {
+        builder.setBuffersPerRegion(buffersPerRegion);
+        return this;
+    }
+
+    public ProxyServerBuilder setIoThreads(int ioThreads) {
+        builder.setIoThreads(ioThreads);
+        return this;
+    }
+
+    public ProxyServerBuilder setWorkerThreads(int workerThreads) {
+        builder.setWorkerThreads(workerThreads);
+        return this;
+    }
+
+    public ProxyServerBuilder setDirectBuffers(boolean directBuffers) {
+        builder.setDirectBuffers(directBuffers);
+        return this;
+    }
+
+    public ProxyServerBuilder setHandler(HttpHandler handler) {
+        builder.setHandler(handler);
+        return this;
+    }
+
+    public <T> ProxyServerBuilder setServerOption(Option<T> option, T value) {
+        builder.setServerOption(option, value);
+        return this;
+    }
+
+    public <T> ProxyServerBuilder setSocketOption(Option<T> option, T value) {
+        builder.setSocketOption(option, value);
+        return this;
+    }
+
+    public <T> ProxyServerBuilder setWorkerOption(Option<T> option, T value) {
+        builder.setWorkerOption(option, value);
+        return this;
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/RoleAuthHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/RoleAuthHandler.java
new file mode 100755
index 0000000..3beb870
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/RoleAuthHandler.java
@@ -0,0 +1,43 @@
+package org.keycloak.proxy;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
+
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleAuthHandler implements HttpHandler {
+
+    protected Collection<String> roles;
+    protected HttpHandler next;
+
+    public RoleAuthHandler(Collection<String> roles, HttpHandler next) {
+        this.roles = roles;
+        this.next = next;
+    }
+
+    @Override
+    public void handleRequest(HttpServerExchange exchange) throws Exception {
+        KeycloakUndertowAccount account = (KeycloakUndertowAccount)exchange.getSecurityContext().getAuthenticatedAccount();
+        SingleConstraintMatch match = exchange.getAttachment(ConstraintMatcherHandler.CONSTRAINT_KEY);
+        if (match == null || (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.AUTHENTICATE)) {
+            next.handleRequest(exchange);
+            return;
+        }
+        if (match != null) {
+            for (String role : match.getRequiredRoles()) {
+                if (account.getRoles().contains(role)) {
+                    next.handleRequest(exchange);
+                    return;
+                }
+            }
+        }
+        exchange.setResponseCode(403);
+        exchange.endExchange();
+
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java
new file mode 100755
index 0000000..5d29137
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java
@@ -0,0 +1,93 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.keycloak.proxy;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Stuart Douglas
+ */
+public class SecurityInfo<T extends SecurityInfo> implements Cloneable {
+
+    /**
+     * Equivalent to {@see ServletSecurity.EmptyRoleSemantic} but with an additional mode to require authentication but no role
+     * check.
+     */
+    public enum EmptyRoleSemantic {
+
+        /**
+         * Permit access to the resource without requiring authentication or role membership.
+         */
+        PERMIT,
+
+        /**
+         * Deny access to the resource regardless of the authentication state.
+         */
+        DENY,
+
+        /**
+         * Mandate authentication but authorize access as no roles to check against.
+         */
+        AUTHENTICATE;
+
+    }
+
+    private volatile EmptyRoleSemantic emptyRoleSemantic = EmptyRoleSemantic.DENY;
+    private final Set<String> rolesAllowed = new HashSet<String>();
+
+    public EmptyRoleSemantic getEmptyRoleSemantic() {
+        return emptyRoleSemantic;
+    }
+
+    public T setEmptyRoleSemantic(final EmptyRoleSemantic emptyRoleSemantic) {
+        this.emptyRoleSemantic = emptyRoleSemantic;
+        return (T)this;
+    }
+
+    public T addRoleAllowed(final String role) {
+        this.rolesAllowed.add(role);
+        return (T) this;
+    }
+
+    public T addRolesAllowed(final String ... roles) {
+        this.rolesAllowed.addAll(Arrays.asList(roles));
+        return (T) this;
+    }
+    public T addRolesAllowed(final Collection<String> roles) {
+        this.rolesAllowed.addAll(roles);
+        return (T) this;
+    }
+    public Set<String> getRolesAllowed() {
+        return new HashSet<String>(rolesAllowed);
+    }
+
+    @Override
+    public T clone() {
+        final SecurityInfo info = createInstance();
+        info.emptyRoleSemantic = emptyRoleSemantic;
+        info.rolesAllowed.addAll(rolesAllowed);
+        return (T) info;
+    }
+
+    protected T createInstance() {
+        return (T) new SecurityInfo();
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatch.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatch.java
new file mode 100755
index 0000000..857d8fd
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatch.java
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.keycloak.proxy;
+
+
+/**
+ * @author Stuart Douglas
+ */
+public class SecurityPathMatch {
+
+    private final SingleConstraintMatch mergedConstraint;
+
+    SecurityPathMatch(final SingleConstraintMatch mergedConstraint) {
+        this.mergedConstraint = mergedConstraint;
+    }
+
+
+    SingleConstraintMatch getMergedConstraint() {
+        return mergedConstraint;
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
new file mode 100755
index 0000000..c5c39a0
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
@@ -0,0 +1,250 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.keycloak.proxy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Stuart Douglas
+ */
+public class SecurityPathMatches {
+
+    private final boolean denyUncoveredHttpMethods;
+    private final PathSecurityInformation defaultPathSecurityInformation;
+    private final Map<String, PathSecurityInformation> exactPathRoleInformation;
+    private final Map<String, PathSecurityInformation> prefixPathRoleInformation;
+    private final Map<String, PathSecurityInformation> extensionRoleInformation;
+
+    private SecurityPathMatches(final boolean denyUncoveredHttpMethods, final PathSecurityInformation defaultPathSecurityInformation, final Map<String, PathSecurityInformation> exactPathRoleInformation, final Map<String, PathSecurityInformation> prefixPathRoleInformation, final Map<String, PathSecurityInformation> extensionRoleInformation) {
+        this.denyUncoveredHttpMethods = denyUncoveredHttpMethods;
+        this.defaultPathSecurityInformation = defaultPathSecurityInformation;
+        this.exactPathRoleInformation = exactPathRoleInformation;
+        this.prefixPathRoleInformation = prefixPathRoleInformation;
+        this.extensionRoleInformation = extensionRoleInformation;
+    }
+
+    /**
+     *
+     * @return <code>true</code> If no security path information has been defined
+     */
+    public boolean isEmpty() {
+        return defaultPathSecurityInformation.excludedMethodRoles.isEmpty() &&
+                defaultPathSecurityInformation.perMethodRequiredRoles.isEmpty() &&
+                defaultPathSecurityInformation.defaultRequiredRoles.isEmpty() &&
+                exactPathRoleInformation.isEmpty() &&
+                prefixPathRoleInformation.isEmpty() &&
+                extensionRoleInformation.isEmpty();
+    }
+
+    public SecurityPathMatch getSecurityInfo(final String path, final String method) {
+        RuntimeMatch currentMatch = new RuntimeMatch();
+        handleMatch(method, defaultPathSecurityInformation, currentMatch);
+        PathSecurityInformation match = exactPathRoleInformation.get(path);
+        if (match != null) {
+            handleMatch(method, match, currentMatch);
+            return new SecurityPathMatch(mergeConstraints(currentMatch));
+        }
+
+        match = prefixPathRoleInformation.get(path);
+        if (match != null) {
+            handleMatch(method, match, currentMatch);
+            return new SecurityPathMatch(mergeConstraints(currentMatch));
+        }
+
+        int qsPos = -1;
+        boolean extension = false;
+        for (int i = path.length() - 1; i >= 0; --i) {
+            final char c = path.charAt(i);
+            if (c == '?') {
+                //there was a query string, check the exact matches again
+                final String part = path.substring(0, i);
+                match = exactPathRoleInformation.get(part);
+                if (match != null) {
+                    handleMatch(method, match, currentMatch);
+                    return new SecurityPathMatch(mergeConstraints(currentMatch));
+                }
+                qsPos = i;
+                extension = false;
+            } else if (c == '/') {
+                extension = true;
+                final String part = path.substring(0, i);
+                match = prefixPathRoleInformation.get(part);
+                if (match != null) {
+                    handleMatch(method, match, currentMatch);
+                    return new SecurityPathMatch(mergeConstraints(currentMatch));
+                }
+            } else if (c == '.') {
+                if (!extension) {
+                    extension = true;
+                    final String ext;
+                    if (qsPos == -1) {
+                        ext = path.substring(i + 1, path.length());
+                    } else {
+                        ext = path.substring(i + 1, qsPos);
+                    }
+                    match = extensionRoleInformation.get(ext);
+                    if (match != null) {
+                        handleMatch(method, match, currentMatch);
+                        return new SecurityPathMatch(mergeConstraints(currentMatch));
+                    }
+                }
+            }
+        }
+        return new SecurityPathMatch(mergeConstraints(currentMatch));
+    }
+
+    /**
+     * merge all constraints, as per 13.8.1 Combining Constraints
+     */
+    private SingleConstraintMatch mergeConstraints(final RuntimeMatch currentMatch) {
+        if(currentMatch.uncovered && denyUncoveredHttpMethods) {
+            return new SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic.DENY, Collections.<String>emptySet());
+        }
+        final Set<String> allowedRoles = new HashSet<String>();
+        for(SingleConstraintMatch match : currentMatch.constraints) {
+            if(match.getRequiredRoles().isEmpty()) {
+                return new SingleConstraintMatch(match.getEmptyRoleSemantic(), Collections.<String>emptySet());
+            } else {
+                allowedRoles.addAll(match.getRequiredRoles());
+            }
+        }
+        return new SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic.PERMIT, allowedRoles);
+    }
+
+    private void handleMatch(final String method, final PathSecurityInformation exact, RuntimeMatch currentMatch) {
+        List<SecurityInformation> roles = exact.defaultRequiredRoles;
+        for (SecurityInformation role : roles) {
+            currentMatch.constraints.add(new SingleConstraintMatch(role.emptyRoleSemantic, role.roles));
+            if(role.emptyRoleSemantic == SecurityInfo.EmptyRoleSemantic.DENY || !role.roles.isEmpty()) {
+                currentMatch.uncovered = false;
+            }
+        }
+        List<SecurityInformation> methodInfo = exact.perMethodRequiredRoles.get(method);
+        if (methodInfo != null) {
+            currentMatch.uncovered = false;
+            for (SecurityInformation role : methodInfo) {
+                currentMatch.constraints.add(new SingleConstraintMatch(role.emptyRoleSemantic, role.roles));
+            }
+        }
+        for (ExcludedMethodRoles excluded : exact.excludedMethodRoles) {
+            if (!excluded.methods.contains(method)) {
+                currentMatch.uncovered = false;
+                currentMatch.constraints.add(new SingleConstraintMatch(excluded.securityInformation.emptyRoleSemantic, excluded.securityInformation.roles));
+            }
+        }
+    }
+
+     public static class Builder {
+        private final PathSecurityInformation defaultPathSecurityInformation = new PathSecurityInformation();
+        private final Map<String, PathSecurityInformation> exactPathRoleInformation = new HashMap<String, PathSecurityInformation>();
+        private final Map<String, PathSecurityInformation> prefixPathRoleInformation = new HashMap<String, PathSecurityInformation>();
+        private final Map<String, PathSecurityInformation> extensionRoleInformation = new HashMap<String, PathSecurityInformation>();
+
+        public void addSecurityConstraint(Set<String> roles, SecurityInfo.EmptyRoleSemantic emptyRoleSemantic, String pattern, Set<String> httpMethods, Set<String> excludedMethods) {
+            final SecurityInformation securityInformation = new SecurityInformation(roles, emptyRoleSemantic);
+            if (pattern.endsWith("/*") || pattern.endsWith("/")) {
+                String part = pattern.substring(0, pattern.lastIndexOf('/'));
+                PathSecurityInformation info = prefixPathRoleInformation.get(part);
+                if (info == null) {
+                    prefixPathRoleInformation.put(part, info = new PathSecurityInformation());
+                }
+                setupPathSecurityInformation(info, securityInformation, httpMethods, excludedMethods);
+            } else if (pattern.startsWith("*.")) {
+                String part = pattern.substring(2, pattern.length());
+                PathSecurityInformation info = extensionRoleInformation.get(part);
+                if (info == null) {
+                    extensionRoleInformation.put(part, info = new PathSecurityInformation());
+                }
+                setupPathSecurityInformation(info, securityInformation, httpMethods, excludedMethods);
+            } else {
+                PathSecurityInformation info = exactPathRoleInformation.get(pattern);
+                if (info == null) {
+                    exactPathRoleInformation.put(pattern, info = new PathSecurityInformation());
+                }
+                setupPathSecurityInformation(info, securityInformation, httpMethods, excludedMethods);
+            }
+
+        }
+
+        private Set<String> expandRolesAllowed(final Set<String> rolesAllowed) {
+            final Set<String> roles = new HashSet<String>(rolesAllowed);
+            return roles;
+        }
+
+        private void setupPathSecurityInformation(final PathSecurityInformation info, final SecurityInformation securityConstraint,
+                                                  Set<String> httpMethods, Set<String> excludedMethods) {
+            if (httpMethods.isEmpty() &&
+                    excludedMethods.isEmpty()) {
+                info.defaultRequiredRoles.add(securityConstraint);
+            } else if (!httpMethods.isEmpty()) {
+                for (String method : httpMethods) {
+                    List<SecurityInformation> securityInformations = info.perMethodRequiredRoles.get(method);
+                    if (securityInformations == null) {
+                        info.perMethodRequiredRoles.put(method, securityInformations = new ArrayList<SecurityInformation>());
+                    }
+                    securityInformations.add(securityConstraint);
+                }
+            } else if (!excludedMethods.isEmpty()) {
+                info.excludedMethodRoles.add(new ExcludedMethodRoles(excludedMethods, securityConstraint));
+            }
+        }
+
+        public SecurityPathMatches build() {
+            return new SecurityPathMatches(false, defaultPathSecurityInformation, exactPathRoleInformation, prefixPathRoleInformation, extensionRoleInformation);
+        }
+    }
+
+
+    private static class PathSecurityInformation {
+        final List<SecurityInformation> defaultRequiredRoles = new ArrayList<SecurityInformation>();
+        final Map<String, List<SecurityInformation>> perMethodRequiredRoles = new HashMap<String, List<SecurityInformation>>();
+        final List<ExcludedMethodRoles> excludedMethodRoles = new ArrayList<ExcludedMethodRoles>();
+    }
+
+    private static final class ExcludedMethodRoles {
+        final Set<String> methods;
+        final SecurityInformation securityInformation;
+
+        public ExcludedMethodRoles(final Set<String> methods, final SecurityInformation securityInformation) {
+            this.methods = methods;
+            this.securityInformation = securityInformation;
+        }
+    }
+
+    private static final class SecurityInformation {
+        final Set<String> roles;
+        final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic;
+
+        private SecurityInformation(final Set<String> roles, final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic) {
+            this.emptyRoleSemantic = emptyRoleSemantic;
+            this.roles = new HashSet<String>(roles);
+        }
+    }
+
+    private static final class RuntimeMatch {
+        final List<SingleConstraintMatch> constraints = new ArrayList<SingleConstraintMatch>();
+        boolean uncovered = true;
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SingleConstraintMatch.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SingleConstraintMatch.java
new file mode 100755
index 0000000..cd0b242
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SingleConstraintMatch.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2014 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.keycloak.proxy;
+
+import java.util.Set;
+
+/**
+ * Representation of a single security constrain matched for a single request.
+ *
+ * When performing any authentication/authorization check every constraint MUST be satisfied for the request to be allowed to
+ * proceed.
+ *
+ * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
+ */
+public class SingleConstraintMatch {
+
+    private final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic;
+    private final Set<String> requiredRoles;
+
+    public SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic emptyRoleSemantic, Set<String> requiredRoles) {
+        this.emptyRoleSemantic = emptyRoleSemantic;
+        this.requiredRoles = requiredRoles;
+    }
+
+    public SecurityInfo.EmptyRoleSemantic getEmptyRoleSemantic() {
+        return emptyRoleSemantic;
+    }
+
+    public Set<String> getRequiredRoles() {
+        return requiredRoles;
+    }
+
+}
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index db0fad4..2c582f6 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -26,6 +26,7 @@
     </build>
     <modules>
         <module>integration</module>
+        <module>proxy</module>
         <module>tomcat6</module>
         <module>tomcat7</module>
         <module>tomcat8</module>
diff --git a/testsuite/proxy/pom.xml b/testsuite/proxy/pom.xml
new file mode 100755
index 0000000..892b121
--- /dev/null
+++ b/testsuite/proxy/pom.xml
@@ -0,0 +1,509 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-testsuite-pom</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0.Beta2-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-testsuite-security-proxy</artifactId>
+    <name>Keycloak Security Proxy TestSuite</name>
+    <properties>
+        <!--<tomcat.version>8.0.14</tomcat.version>-->
+        <tomcat.version>7.0.54</tomcat.version>
+    </properties>
+    <description />
+
+   <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-dependencies-server-all</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+        </dependency>
+       <dependency>
+           <groupId>org.keycloak</groupId>
+           <artifactId>keycloak-admin-client</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+       <dependency>
+           <groupId>org.keycloak</groupId>
+           <artifactId>keycloak-proxy-server</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <version>${resteasy.version.latest}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-crypto</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-multipart-provider</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jackson-provider</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-undertow</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk16</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${keycloak.apache.httpcomponents.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-ldap-federation</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+       <dependency>
+           <groupId>org.keycloak</groupId>
+           <artifactId>keycloak-undertow-adapter</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+       <dependency>
+           <groupId>org.keycloak</groupId>
+           <artifactId>keycloak-tomcat7-adapter</artifactId>
+           <version>${project.version}</version>
+       </dependency>
+       <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-xc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.javax.persistence</groupId>
+            <artifactId>hibernate-jpa-2.0-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-entitymanager</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.icegreen</groupId>
+            <artifactId>greenmail</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.infinispan</groupId>
+            <artifactId>infinispan-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.seleniumhq.selenium</groupId>
+            <artifactId>selenium-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>xml-apis</groupId>
+            <artifactId>xml-apis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.seleniumhq.selenium</groupId>
+            <artifactId>selenium-chrome-driver</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.wildfly</groupId>
+            <artifactId>wildfly-undertow</artifactId>
+            <version>${wildfly.version}</version>
+            <scope>test</scope>
+        </dependency>
+       <dependency>
+           <groupId>org.keycloak</groupId>
+           <artifactId>keycloak-testsuite-integration</artifactId>
+           <version>${project.version}</version>
+           <scope>test</scope>
+       </dependency>
+       <dependency>
+           <groupId>org.keycloak</groupId>
+           <artifactId>keycloak-testsuite-integration</artifactId>
+           <version>${project.version}</version>
+           <type>test-jar</type>
+           <scope>test</scope>
+       </dependency>
+
+       <dependency>
+           <groupId>org.apache.tomcat</groupId>
+           <artifactId>tomcat-catalina</artifactId>
+           <version>${tomcat.version}</version>
+       </dependency>
+       <dependency>
+           <groupId>org.apache.tomcat</groupId>
+           <artifactId>tomcat-util</artifactId>
+           <version>${tomcat.version}</version>
+       </dependency>
+       <dependency>
+           <groupId>org.apache.tomcat.embed</groupId>
+           <artifactId>tomcat-embed-core</artifactId>
+           <version>${tomcat.version}</version>
+       </dependency>
+
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.2</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <workingDirectory>${project.basedir}</workingDirectory>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>keycloak-server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.KeycloakServer</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>mail-server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.MailServer</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>totp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testutils.TotpGenerator</mainClass>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>jpa</id>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <keycloak.realm.provider>jpa</keycloak.realm.provider>
+                                <keycloak.user.provider>jpa</keycloak.user.provider>
+                                <keycloak.eventStore.provider>jpa</keycloak.eventStore.provider>
+                                <keycloak.userSessions.provider>jpa</keycloak.userSessions.provider>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>mongo</id>
+
+            <properties>
+                <keycloak.connectionsMongo.host>localhost</keycloak.connectionsMongo.host>
+                <keycloak.connectionsMongo.port>27018</keycloak.connectionsMongo.port>
+                <keycloak.connectionsMongo.db>keycloak</keycloak.connectionsMongo.db>
+                <keycloak.connectionsMongo.clearOnStartup>true</keycloak.connectionsMongo.clearOnStartup>
+                <keycloak.connectionsMongo.bindIp>127.0.0.1</keycloak.connectionsMongo.bindIp>
+            </properties>
+
+            <build>
+                <plugins>
+
+                    <!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>test</id>
+                                <phase>integration-test</phase>
+                                <goals>
+                                    <goal>test</goal>
+                                </goals>
+                                <configuration>
+                                    <systemPropertyVariables>
+                                        <keycloak.realm.provider>mongo</keycloak.realm.provider>
+                                        <keycloak.user.provider>mongo</keycloak.user.provider>
+                                        <keycloak.audit.provider>mongo</keycloak.audit.provider>
+                                        <keycloak.userSessions.provider>mongo</keycloak.userSessions.provider>
+                                        <keycloak.connectionsMongo.host>${keycloak.connectionsMongo.host}</keycloak.connectionsMongo.host>
+                                        <keycloak.connectionsMongo.port>${keycloak.connectionsMongo.port}</keycloak.connectionsMongo.port>
+                                        <keycloak.connectionsMongo.db>${keycloak.connectionsMongo.db}</keycloak.connectionsMongo.db>
+                                        <keycloak.connectionsMongo.clearOnStartup>${keycloak.connectionsMongo.clearOnStartup}</keycloak.connectionsMongo.clearOnStartup>
+                                        <keycloak.connectionsMongo.bindIp>${keycloak.connectionsMongo.bindIp}</keycloak.connectionsMongo.bindIp>
+                                    </systemPropertyVariables>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>default-test</id>
+                                <configuration>
+                                    <skip>true</skip>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <!-- Embedded mongo -->
+                    <plugin>
+                        <groupId>com.github.joelittlejohn.embedmongo</groupId>
+                        <artifactId>embedmongo-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>start-mongodb</id>
+                                <phase>pre-integration-test</phase>
+                                <goals>
+                                    <goal>start</goal>
+                                </goals>
+                                <configuration>
+                                    <port>${keycloak.connectionsMongo.port}</port>
+                                    <logging>file</logging>
+                                    <logFile>${project.build.directory}/mongodb.log</logFile>
+                                    <bindIp>${keycloak.connectionsMongo.bindIp}</bindIp>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>stop-mongodb</id>
+                                <phase>post-integration-test</phase>
+                                <goals>
+                                    <goal>stop</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+
+        </profile>
+
+        <profile>
+            <id>infinispan</id>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <keycloak.realm.cache.provider>infinispan</keycloak.realm.cache.provider>
+                                <keycloak.user.cache.provider>infinispan</keycloak.user.cache.provider>
+                                <keycloak.userSessions.provider>infinispan</keycloak.userSessions.provider>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <!-- MySQL -->
+        <profile>
+            <activation>
+                <property>
+                    <name>keycloak.connectionsJpa.driver</name>
+                    <value>com.mysql.jdbc.Driver</value>
+                </property>
+            </activation>
+            <id>mysql</id>
+            <dependencies>
+                <dependency>
+                    <groupId>mysql</groupId>
+                    <artifactId>mysql-connector-java</artifactId>
+                    <version>${mysql.version}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+
+        <!-- PostgreSQL -->
+        <profile>
+            <activation>
+                <property>
+                    <name>keycloak.connectionsJpa.driver</name>
+                    <value>org.postgresql.Driver</value>
+                </property>
+            </activation>
+            <id>postgresql</id>
+            <dependencies>
+                <dependency>
+                    <groupId>org.postgresql</groupId>
+                    <artifactId>postgresql</artifactId>
+                    <version>${postgresql.version}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+
+        <profile>
+            <id>clean-jpa</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.liquibase</groupId>
+                        <artifactId>liquibase-maven-plugin</artifactId>
+                        <configuration>
+                            <changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
+
+                            <url>${keycloak.connectionsJpa.url}</url>
+                            <driver>${keycloak.connectionsJpa.driver}</driver>
+                            <username>${keycloak.connectionsJpa.user}</username>
+                            <password>${keycloak.connectionsJpa.password}</password>
+
+                            <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>clean-jpa</id>
+                                <phase>clean</phase>
+                                <goals>
+                                    <goal>dropAll</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
new file mode 100755
index 0000000..ecd0d59
--- /dev/null
+++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+import io.undertow.Undertow;
+import io.undertow.io.IoCallback;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.handlers.ResponseCodeHandler;
+import io.undertow.server.handlers.proxy.ProxyHandler;
+import io.undertow.server.handlers.proxy.SimpleProxyClientProvider;
+import io.undertow.util.Headers;
+import io.undertow.util.HttpString;
+import org.apache.catalina.startup.Tomcat;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.undertow.AbstractUndertowRequestAuthenticator;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.proxy.ProxyServerBuilder;
+import org.keycloak.representations.adapters.config.AdapterConfig;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+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.openqa.selenium.WebDriver;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.UriBuilder;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.security.Principal;
+import java.util.regex.Matcher;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProxyTest {
+    static String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+            .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString();
+
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/tomcat-test/demorealm.json"), RealmRepresentation.class);
+            RealmModel realm = manager.importRealm(representation);
+       }
+    };
+
+    public static class SendUsernameServlet extends HttpServlet {
+        @Override
+        protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            OutputStream stream = resp.getOutputStream();
+            stream.write(req.getRequestURL().toString().getBytes());
+            stream.write("\n".getBytes());
+            Integer count = (Integer)req.getSession().getAttribute("counter");
+            if (count == null) count = new Integer(0);
+            req.getSession().setAttribute("counter", new Integer(count.intValue() + 1));
+            stream.write(count.toString().getBytes());
+
+
+
+        }
+        @Override
+        protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            doGet(req, resp);
+        }
+    }
+
+    static Tomcat tomcat = null;
+
+    public static void initTomcat() throws Exception {
+        URL dir = ProxyTest.class.getResource("/tomcat-test/webapp/WEB-INF/web.xml");
+        File webappDir = new File(dir.getFile()).getParentFile().getParentFile();
+        tomcat = new Tomcat();
+        String baseDir = getBaseDirectory();
+        tomcat.setBaseDir(baseDir);
+        tomcat.setPort(8082);
+
+        tomcat.addWebapp("/customer-portal", webappDir.toString());
+        System.out.println("configuring app with basedir: " + webappDir.toString());
+
+        tomcat.start();
+        //tomcat.getServer().await();
+    }
+
+    public static void shutdownTomcat() throws Exception {
+        tomcat.stop();
+        tomcat.destroy();
+    }
+
+    static Undertow proxyServer = null;
+
+    //@BeforeClass
+    public static void initProxy() throws Exception {
+        initTomcat();
+        ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost");
+        InputStream is = ProxyTest.class.getResourceAsStream("/keycloak.json");
+        AdapterConfig config = KeycloakDeploymentBuilder.loadAdapterConfig(is);
+
+        builder.target("http://localhost:8082")
+                .application(config)
+                .base("/customer-portal")
+                .constraint("/*").add().add();
+        proxyServer = builder.build();
+        proxyServer.start();
+
+    }
+
+    @AfterClass
+    public static void shutdownProxy() throws Exception {
+        shutdownTomcat();
+        if (proxyServer != null) proxyServer.stop();
+    }
+
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+    @WebResource
+    protected WebDriver driver;
+    @WebResource
+    protected LoginPage loginPage;
+
+    public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
+
+    @Test
+    public void testLoginSSOAndLogout() throws Exception {
+        initProxy();
+        driver.navigate().to("http://localhost:8080/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:8080/customer-portal");
+        String pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("customer-portal"));
+        Assert.assertTrue(pageSource.contains("0"));
+        driver.navigate().to("http://localhost:8080/customer-portal");
+        Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal");
+        pageSource = driver.getPageSource();
+        System.out.println(pageSource);
+        Assert.assertTrue(pageSource.contains("customer-portal"));
+        Assert.assertTrue(pageSource.contains("1")); // test http session
+
+        // test logout
+
+        String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
+                .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString();
+        driver.navigate().to(logoutUri);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+        driver.navigate().to("http://localhost:8080/customer-portal");
+        String currentUrl = driver.getCurrentUrl();
+        Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
+
+
+    }
+
+    @Test
+    @Ignore
+    public void runit() throws Exception {
+        Thread.sleep(10000000);
+    }
+
+
+    private static String getBaseDirectory() {
+        String dirPath = null;
+        String relativeDirPath = "testsuite" + File.separator + "proxy" + File.separator + "target";
+
+        if (System.getProperties().containsKey("maven.home")) {
+            dirPath = System.getProperty("user.dir").replaceFirst("testsuite.proxy.*", Matcher.quoteReplacement(relativeDirPath));
+        } else {
+            for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) {
+                if (c.contains(File.separator + "testsuite" + File.separator + "proxy")) {
+                    dirPath = c.replaceFirst("testsuite.proxy.*", Matcher.quoteReplacement(relativeDirPath));
+                    break;
+                }
+            }
+        }
+
+        String absolutePath = new File(dirPath).getAbsolutePath();
+        return absolutePath;
+    }
+
+
+
+
+}
diff --git a/testsuite/proxy/src/test/resources/keycloak.json b/testsuite/proxy/src/test/resources/keycloak.json
new file mode 100755
index 0000000..e49233f
--- /dev/null
+++ b/testsuite/proxy/src/test/resources/keycloak.json
@@ -0,0 +1,11 @@
+{
+    "realm": "demo",
+    "resource": "customer-portal",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "http://localhost:8081/auth",
+    "ssl-required" : "external",
+    "principal-attribute": "name",
+    "credentials": {
+        "secret": "password"
+    }
+}
diff --git a/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json b/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json
new file mode 100755
index 0000000..a4a6ec9
--- /dev/null
+++ b/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json
@@ -0,0 +1,58 @@
+{
+    "id": "demo",
+    "realm": "demo",
+    "enabled": true,
+    "accessTokenLifespan": 3000,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslRequired": "external",
+    "registrationAllowed": false,
+    "social": false,
+    "passwordCredentialGrantAllowed": true,
+    "updateProfileOnInitialSocialLogin": false,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "bburke@redhat.com",
+            "enabled": true,
+            "email" : "bburke@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": [ "user", "admin" ],
+            "applicationRoles": {
+                "account": [ "manage-account" ]
+            }
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            },
+            {
+                "name": "admin",
+                "description": "Administrator privileges"
+            }
+        ]
+    },
+    "applications": [
+        {
+            "name": "customer-portal",
+            "enabled": true,
+            "fullScopeAllowed": true,
+            "adminUrl": "http://localhost:8080/customer-portal",
+            "baseUrl": "http://localhost:8080/customer-portal",
+            "redirectUris": [
+                "http://localhost:8080/customer-portal/*"
+            ],
+            "secret": "password"
+        }
+    ]
+}
diff --git a/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml b/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..7e0b269
--- /dev/null
+++ b/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+      version="3.0">
+
+	<module-name>adapter-test</module-name>
+
+    <servlet>
+        <servlet-name>SendUsername</servlet-name>
+        <servlet-class>org.keycloak.testsuite.ProxyTest$SendUsernameServlet</servlet-class>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>SendUsername</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+</web-app>