keycloak-uncached

further

7/15/2013 10:38:06 AM

Changes

integration/pom.xml 21(+21 -0)

pom.xml 1(+1 -0)

Details

diff --git a/integration/as7-eap6/adapter/pom.xml b/integration/as7-eap6/adapter/pom.xml
new file mode 100755
index 0000000..b4ad78f
--- /dev/null
+++ b/integration/as7-eap6/adapter/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-alpha-1</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-as7-adapter</artifactId>
+    <name>Keycloak AS7 Integration</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <version>3.1.2.GA</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jose-jwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <scope>provided</scope>
+            <version>1.0.0.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jboss.web</groupId>
+            <artifactId>jbossweb</artifactId>
+            <version>7.0.17.Final</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.as</groupId>
+            <artifactId>jboss-as-web</artifactId>
+            <version>7.1.2.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.picketbox</groupId>
+            <artifactId>picketbox</artifactId>
+            <scope>provided</scope>
+            <version>4.0.7.Final</version>
+        </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>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/Actions.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/Actions.java
new file mode 100755
index 0000000..65c9ddb
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/Actions.java
@@ -0,0 +1,14 @@
+package org.keycloak.adapters.as7;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Actions
+{
+   public static final String J_OAUTH_ADMIN_FORCED_LOGOUT = "j_oauth_admin_forced_logout";
+   public static final String J_OAUTH_LOGOUT = "j_oauth_logout";
+   public static final String J_OAUTH_RESOLVE_ACCESS_CODE = "j_oauth_resolve_access_code";
+   public static final String J_OAUTH_REMOTE_LOGOUT = "j_oauth_remote_logout";
+   public static final String J_OAUTH_TOKEN_GRANT = "j_oauth_token_grant";
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java
new file mode 100755
index 0000000..2f7e95c
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java
@@ -0,0 +1,87 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.authenticator.AuthenticatorBase;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.jboss.logging.Logger;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.adapters.as7.config.ManagedResourceConfig;
+import org.keycloak.adapters.as7.config.ManagedResourceConfigLoader;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Uses a configured remote auth server to do Bearer token authentication only.  SkeletonKeyTokens are used
+ * to provide user data and role mappings.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements LifecycleListener
+{
+   private static final Logger log = Logger.getLogger(BearerTokenAuthenticatorValve.class);
+   protected ManagedResourceConfig remoteSkeletonKeyConfig;
+   protected ResourceMetadata resourceMetadata;
+
+   @Override
+   public void start() throws LifecycleException
+   {
+      super.start();
+      StandardContext standardContext = (StandardContext)context;
+      standardContext.addLifecycleListener(this);
+   }
+
+   @Override
+   public void lifecycleEvent(LifecycleEvent event)
+   {
+      if (event.getType() == Lifecycle.AFTER_START_EVENT) init();
+   }
+
+   protected void init()
+   {
+      ManagedResourceConfigLoader managedResourceConfigLoader = new ManagedResourceConfigLoader(context);
+      resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
+      remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
+   }
+
+   @Override
+   public void invoke(Request request, Response response) throws IOException, ServletException
+   {
+      try
+      {
+         super.invoke(request, response);
+      }
+      finally
+      {
+         ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
+      }
+   }
+
+   @Override
+   protected boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException
+   {
+      try
+      {
+         CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, !remoteSkeletonKeyConfig.isCancelPropagation(), true);
+         if (bearer.login(request, response))
+         {
+            return true;
+       }
+         return false;
+      }
+      catch (LoginException e)
+      {
+      }
+      return false;
+   }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java
new file mode 100755
index 0000000..7d14c1d
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java
@@ -0,0 +1,163 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.connector.Request;
+import org.jboss.logging.Logger;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.SkeletonKeyPrincipal;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.VerificationException;
+import org.keycloak.representations.SkeletonKeyToken;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaBearerTokenAuthenticator
+{
+   protected ResourceMetadata resourceMetadata;
+   protected boolean challenge;
+   protected Logger log = Logger.getLogger(CatalinaBearerTokenAuthenticator.class);
+   protected String tokenString;
+   protected SkeletonKeyToken token;
+   private Principal principal;
+   protected boolean propagateToken;
+
+   public CatalinaBearerTokenAuthenticator(ResourceMetadata resourceMetadata, boolean propagateToken, boolean challenge)
+   {
+      this.resourceMetadata = resourceMetadata;
+      this.challenge = challenge;
+      this.propagateToken = propagateToken;
+   }
+
+   public ResourceMetadata getResourceMetadata()
+   {
+      return resourceMetadata;
+   }
+
+   public String getTokenString()
+   {
+      return tokenString;
+   }
+
+   public SkeletonKeyToken getToken()
+   {
+      return token;
+   }
+
+   public Principal getPrincipal()
+   {
+      return principal;
+   }
+
+   public boolean login(Request request, HttpServletResponse response) throws LoginException, IOException
+   {
+      String authHeader = request.getHeader("Authorization");
+      if (authHeader == null)
+      {
+         if (challenge)
+         {
+            challengeResponse(response, null, null);
+            return false;
+         }
+         else
+         {
+            return false;
+         }
+      }
+
+      String[] split = authHeader.trim().split("\\s+");
+      if (split == null || split.length != 2) challengeResponse(response, null, null);
+      if (!split[0].equalsIgnoreCase("Bearer")) challengeResponse(response, null, null);
+
+
+      tokenString = split[1];
+
+      try
+      {
+         token = RSATokenVerifier.verifyToken(tokenString, resourceMetadata);
+      }
+      catch (VerificationException e)
+      {
+         log.error("Failed to verify token", e);
+         challengeResponse(response, "invalid_token", e.getMessage());
+      }
+      boolean verifyCaller = false;
+      Set<String> roles = null;
+      if (resourceMetadata.getResourceName() != null)
+      {
+         SkeletonKeyToken.Access access = token.getResourceAccess(resourceMetadata.getResourceName());
+         if (access != null) roles = access.getRoles();
+         verifyCaller = token.isVerifyCaller(resourceMetadata.getResourceName());
+      }
+      else
+      {
+         verifyCaller = token.isVerifyCaller();
+         SkeletonKeyToken.Access access = token.getRealmAccess();
+         if (access != null) roles = access.getRoles();
+      }
+      String surrogate = null;
+      if (verifyCaller)
+      {
+         if (token.getTrustedCertificates() == null || token.getTrustedCertificates().size() == 0)
+         {
+            response.sendError(400);
+            throw new LoginException("No trusted certificates in token");
+         }
+         // for now, we just make sure JBoss Web did two-way SSL
+         // assume JBoss Web verifies the client cert
+         X509Certificate[] chain = request.getCertificateChain();
+         if (chain == null || chain.length == 0)
+         {
+            response.sendError(400);
+            throw new LoginException("No certificates provided by jboss web to verify the caller");
+         }
+         surrogate = chain[0].getSubjectX500Principal().getName();
+      }
+      SkeletonKeyPrincipal skeletonKeyPrincipal = new SkeletonKeyPrincipal(token.getPrincipal(), surrogate);
+      principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skeletonKeyPrincipal, roles);
+      request.setUserPrincipal(principal);
+      request.setAuthType("OAUTH_BEARER");
+      if (propagateToken)
+      {
+         SkeletonKeySession skSession = new SkeletonKeySession(tokenString, resourceMetadata);
+         request.setAttribute(SkeletonKeySession.class.getName(), skSession);
+         ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
+      }
+
+      return true;
+   }
+
+
+   protected void challengeResponse(HttpServletResponse response, String error, String description) throws LoginException
+   {
+      StringBuilder header = new StringBuilder("Bearer realm=\"");
+      header.append(resourceMetadata.getRealm()).append("\"");
+      if (error != null)
+      {
+         header.append(", error=\"").append(error).append("\"");
+      }
+      if (description != null)
+      {
+         header.append(", error_description=\"").append(description).append("\"");
+      }
+      response.setHeader("WWW-Authenticate", header.toString());
+      try
+      {
+         response.sendError(401);
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+      throw new LoginException("Challenged");
+   }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSecurityContextHelper.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSecurityContextHelper.java
new file mode 100755
index 0000000..bf783bd
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSecurityContextHelper.java
@@ -0,0 +1,138 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.as.web.security.JBossGenericPrincipal;
+import org.jboss.security.NestableGroup;
+import org.jboss.security.SecurityConstants;
+import org.jboss.security.SecurityContext;
+import org.jboss.security.SecurityContextAssociation;
+import org.jboss.security.SimpleGroup;
+import org.jboss.security.SimplePrincipal;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaSecurityContextHelper
+{
+   public GenericPrincipal createPrincipal(Realm realm, Principal identity, Collection<String> roleSet)
+   {
+      Subject subject = new Subject();
+      String credentials = "";
+      Set<Principal> principals = subject.getPrincipals();
+      principals.add(identity);
+      Group[] roleSets = getRoleSets(roleSet);
+      for(int g = 0; g < roleSets.length; g ++)
+      {
+         Group group = roleSets[g];
+         String name = group.getName();
+         Group subjectGroup = createGroup(name, principals);
+         if( subjectGroup instanceof NestableGroup)
+         {
+            /* A NestableGroup only allows Groups to be added to it so we
+            need to add a SimpleGroup to subjectRoles to contain the roles
+            */
+            SimpleGroup tmp = new SimpleGroup("Roles");
+            subjectGroup.addMember(tmp);
+            subjectGroup = tmp;
+         }
+         // Copy the group members to the Subject group
+         Enumeration<? extends Principal> members = group.members();
+         while( members.hasMoreElements() )
+         {
+            Principal role = (Principal) members.nextElement();
+            subjectGroup.addMember(role);
+         }
+      }
+      // add the CallerPrincipal group if none has been added in getRoleSets
+      Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
+      callerGroup.addMember(identity);
+      principals.add(callerGroup);
+      SecurityContext sc = SecurityContextAssociation.getSecurityContext();
+      Principal userPrincipal = getPrincipal(subject);
+      sc.getUtil().createSubjectInfo(userPrincipal, credentials, subject);
+      List<String> rolesAsStringList = new ArrayList<String>();
+      rolesAsStringList.addAll(roleSet);
+      return  new JBossGenericPrincipal(realm, userPrincipal.getName(), null, rolesAsStringList,
+              userPrincipal, null, credentials, null, subject);
+
+   }
+   /**
+    * Get the Principal given the authenticated Subject. Currently the first principal that is not of type {@code Group} is
+    * considered or the single principal inside the CallerPrincipal group.
+    *
+    * @param subject
+    * @return the authenticated principal
+    */
+   protected Principal getPrincipal(Subject subject) {
+      Principal principal = null;
+      Principal callerPrincipal = null;
+      if (subject != null) {
+         Set<Principal> principals = subject.getPrincipals();
+         if (principals != null && !principals.isEmpty()) {
+            for (Principal p : principals) {
+               if (!(p instanceof Group) && principal == null) {
+                  principal = p;
+               }
+               if (p instanceof Group) {
+                  Group g = Group.class.cast(p);
+                  if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
+                     Enumeration<? extends Principal> e = g.members();
+                     if (e.hasMoreElements())
+                        callerPrincipal = e.nextElement();
+                  }
+               }
+            }
+         }
+      }
+      return callerPrincipal == null ? principal : callerPrincipal;
+   }
+
+   protected Group createGroup(String name, Set<Principal> principals)
+   {
+      Group roles = null;
+      Iterator<Principal> iter = principals.iterator();
+      while( iter.hasNext() )
+      {
+         Object next = iter.next();
+         if( (next instanceof Group) == false )
+            continue;
+         Group grp = (Group) next;
+         if( grp.getName().equals(name) )
+         {
+            roles = grp;
+            break;
+         }
+      }
+      // If we did not find a group create one
+      if( roles == null )
+      {
+         roles = new SimpleGroup(name);
+         principals.add(roles);
+      }
+      return roles;
+   }
+
+   protected Group[] getRoleSets(Collection<String> roleSet)
+   {
+      SimpleGroup roles = new SimpleGroup("Roles");
+      Group[] roleSets = {roles};
+      for (String role : roleSet)
+      {
+         roles.addMember(new SimplePrincipal(role));
+      }
+      return roleSets;
+   }
+
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/AuthServerConfig.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/AuthServerConfig.java
new file mode 100755
index 0000000..73372e3
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/AuthServerConfig.java
@@ -0,0 +1,279 @@
+package org.keycloak.adapters.as7.config;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AuthServerConfig
+{
+   @JsonProperty("realm")
+   protected String realm;
+
+   @JsonProperty("realm-private-key")
+   protected String realmPrivateKey;
+
+   @JsonProperty("realm-public-key")
+   protected String realmPublicKey;
+
+   @JsonProperty("realm-keystore")
+   protected String realmKeyStore;
+
+   @JsonProperty("realm-keystore-password")
+   protected String realmKeystorePassword;
+
+   @JsonProperty("realm-key-alias")
+   protected String realmKeyAlias;
+
+   @JsonProperty("realm-private-key-password")
+   protected String realmPrivateKeyPassword;
+
+   @JsonProperty("access-code-lifetime")
+   protected int accessCodeLifetime;
+
+   @JsonProperty("token-lifetime")
+   protected int tokenLifetime;
+
+   @JsonProperty("admin-role")
+   protected String adminRole;
+
+   @JsonProperty("login-role")
+   protected String loginRole;
+
+   @JsonProperty("oauth-client-role")
+   protected String clientRole;
+
+   @JsonProperty("wildcard-role")
+   protected String wildcardRole;
+
+   @JsonProperty("cancel-propagation")
+   protected boolean cancelPropagation;
+
+   @JsonProperty("sso-disabled")
+   protected boolean ssoDisabled;
+
+   // these properties are optional and used to provide connection metadata when the server wants to make
+   // remote SSL connections
+
+   protected String truststore;
+   @JsonProperty("truststore-password")
+   protected String truststorePassword;
+   @JsonProperty("client-keystore")
+   protected String clientKeystore;
+   @JsonProperty("client-keystore-password")
+   protected String clientKeystorePassword;
+   @JsonProperty("client-key-password")
+   protected String clientKeyPassword;
+
+   protected List<String> resources = new ArrayList<String>();
+
+
+   public String getRealm()
+   {
+      return realm;
+   }
+
+   public void setRealm(String realm)
+   {
+      this.realm = realm;
+   }
+
+   public String getRealmPrivateKey()
+   {
+      return realmPrivateKey;
+   }
+
+   public void setRealmPrivateKey(String realmPrivateKey)
+   {
+      this.realmPrivateKey = realmPrivateKey;
+   }
+
+   public String getRealmPublicKey()
+   {
+      return realmPublicKey;
+   }
+
+   public void setRealmPublicKey(String realmPublicKey)
+   {
+      this.realmPublicKey = realmPublicKey;
+   }
+
+   public int getAccessCodeLifetime()
+   {
+      return accessCodeLifetime;
+   }
+
+   public void setAccessCodeLifetime(int accessCodeLifetime)
+   {
+      this.accessCodeLifetime = accessCodeLifetime;
+   }
+
+   public String getTruststore()
+   {
+      return truststore;
+   }
+
+   public void setTruststore(String truststore)
+   {
+      this.truststore = truststore;
+   }
+
+   public String getTruststorePassword()
+   {
+      return truststorePassword;
+   }
+
+   public void setTruststorePassword(String truststorePassword)
+   {
+      this.truststorePassword = truststorePassword;
+   }
+
+   public String getClientKeystore()
+   {
+      return clientKeystore;
+   }
+
+   public void setClientKeystore(String clientKeystore)
+   {
+      this.clientKeystore = clientKeystore;
+   }
+
+   public String getClientKeystorePassword()
+   {
+      return clientKeystorePassword;
+   }
+
+   public void setClientKeystorePassword(String clientKeystorePassword)
+   {
+      this.clientKeystorePassword = clientKeystorePassword;
+   }
+
+   public String getClientKeyPassword()
+   {
+      return clientKeyPassword;
+   }
+
+   public void setClientKeyPassword(String clientKeyPassword)
+   {
+      this.clientKeyPassword = clientKeyPassword;
+   }
+
+   public boolean isCancelPropagation()
+   {
+      return cancelPropagation;
+   }
+
+   public void setCancelPropagation(boolean cancelPropagation)
+   {
+      this.cancelPropagation = cancelPropagation;
+   }
+
+   public boolean isSsoDisabled()
+   {
+      return ssoDisabled;
+   }
+
+   public void setSsoDisabled(boolean ssoDisabled)
+   {
+      this.ssoDisabled = ssoDisabled;
+   }
+
+   public List<String> getResources()
+   {
+      return resources;
+   }
+
+   public String getAdminRole()
+   {
+      return adminRole;
+   }
+
+   public void setAdminRole(String adminRole)
+   {
+      this.adminRole = adminRole;
+   }
+
+   public String getLoginRole()
+   {
+      return loginRole;
+   }
+
+   public void setLoginRole(String loginRole)
+   {
+      this.loginRole = loginRole;
+   }
+
+   public String getClientRole()
+   {
+      return clientRole;
+   }
+
+   public void setClientRole(String clientRole)
+   {
+      this.clientRole = clientRole;
+   }
+
+   public String getWildcardRole()
+   {
+      return wildcardRole;
+   }
+
+   public void setWildcardRole(String wildcardRole)
+   {
+      this.wildcardRole = wildcardRole;
+   }
+
+   public String getRealmKeyStore()
+   {
+      return realmKeyStore;
+   }
+
+   public void setRealmKeyStore(String realmKeyStore)
+   {
+      this.realmKeyStore = realmKeyStore;
+   }
+
+   public String getRealmKeystorePassword()
+   {
+      return realmKeystorePassword;
+   }
+
+   public void setRealmKeystorePassword(String realmKeystorePassword)
+   {
+      this.realmKeystorePassword = realmKeystorePassword;
+   }
+
+   public String getRealmKeyAlias()
+   {
+      return realmKeyAlias;
+   }
+
+   public void setRealmKeyAlias(String realmKeyAlias)
+   {
+      this.realmKeyAlias = realmKeyAlias;
+   }
+
+   public String getRealmPrivateKeyPassword()
+   {
+      return realmPrivateKeyPassword;
+   }
+
+   public void setRealmPrivateKeyPassword(String realmPrivateKeyPassword)
+   {
+      this.realmPrivateKeyPassword = realmPrivateKeyPassword;
+   }
+
+   public int getTokenLifetime()
+   {
+      return tokenLifetime;
+   }
+
+   public void setTokenLifetime(int tokenLifetime)
+   {
+      this.tokenLifetime = tokenLifetime;
+   }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java
new file mode 100755
index 0000000..e1bef4b
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java
@@ -0,0 +1,213 @@
+package org.keycloak.adapters.as7.config;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.annotate.JsonPropertyOrder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@JsonPropertyOrder({"realm", "resource", "realm-public-key", "admin-role", "auth-url", "code-url", "truststore", "truststore-password", "client-id", "client-credentials"})
+public class ManagedResourceConfig
+{
+   @JsonProperty("realm")
+   protected String realm;
+   @JsonProperty("resource")
+   protected String resource;
+
+   @JsonProperty("realm-public-key")
+   protected String realmKey;
+
+   @JsonProperty("admin-role")
+   protected String adminRole;
+
+   @JsonProperty("auth-url")
+   protected String authUrl;
+   @JsonProperty("code-url")
+   protected String codeUrl;
+
+   @JsonProperty("allow-any-hostname")
+   protected boolean allowAnyHostname;
+
+   @JsonProperty("truststore")
+   protected String truststore;
+
+   @JsonProperty("truststore-password")
+   protected String truststorePassword;
+   @JsonProperty("client-id")
+   protected String clientId;
+   @JsonProperty("client-keystore")
+   protected String clientKeystore;
+   @JsonProperty("client-keystore-password")
+   protected String clientKeystorePassword;
+   @JsonProperty("client-key-password")
+   protected String clientKeyPassword;
+
+   @JsonProperty("client-credentials")
+   protected Map<String, String> clientCredentials = new HashMap<String, String>();
+
+   @JsonProperty("connection-pool-size")
+   protected int connectionPoolSize;
+
+   @JsonProperty("cancel-propagation")
+   protected boolean cancelPropagation;
+
+
+   public String getRealm()
+   {
+      return realm;
+   }
+
+   public void setRealm(String realm)
+   {
+      this.realm = realm;
+   }
+
+   public String getResource()
+   {
+      return resource;
+   }
+
+   public void setResource(String resource)
+   {
+      this.resource = resource;
+   }
+
+   public String getRealmKey()
+   {
+      return realmKey;
+   }
+
+   public void setRealmKey(String realmKey)
+   {
+      this.realmKey = realmKey;
+   }
+
+   public String getAuthUrl()
+   {
+      return authUrl;
+   }
+
+   public void setAuthUrl(String authUrl)
+   {
+      this.authUrl = authUrl;
+   }
+
+   public String getCodeUrl()
+   {
+      return codeUrl;
+   }
+
+   public void setCodeUrl(String codeUrl)
+   {
+      this.codeUrl = codeUrl;
+   }
+
+   public boolean isAllowAnyHostname()
+   {
+      return allowAnyHostname;
+   }
+
+   public void setAllowAnyHostname(boolean allowAnyHostname)
+   {
+      this.allowAnyHostname = allowAnyHostname;
+   }
+
+   public String getTruststore()
+   {
+      return truststore;
+   }
+
+   public void setTruststore(String truststore)
+   {
+      this.truststore = truststore;
+   }
+
+   public String getTruststorePassword()
+   {
+      return truststorePassword;
+   }
+
+   public void setTruststorePassword(String truststorePassword)
+   {
+      this.truststorePassword = truststorePassword;
+   }
+
+   public String getClientId()
+   {
+      return clientId;
+   }
+
+   public void setClientId(String clientId)
+   {
+      this.clientId = clientId;
+   }
+
+   public Map<String, String> getClientCredentials()
+   {
+      return clientCredentials;
+   }
+
+   public String getClientKeystore()
+   {
+      return clientKeystore;
+   }
+
+   public void setClientKeystore(String clientKeystore)
+   {
+      this.clientKeystore = clientKeystore;
+   }
+
+   public String getClientKeystorePassword()
+   {
+      return clientKeystorePassword;
+   }
+
+   public void setClientKeystorePassword(String clientKeystorePassword)
+   {
+      this.clientKeystorePassword = clientKeystorePassword;
+   }
+
+   public String getClientKeyPassword()
+   {
+      return clientKeyPassword;
+   }
+
+   public void setClientKeyPassword(String clientKeyPassword)
+   {
+      this.clientKeyPassword = clientKeyPassword;
+   }
+
+   public int getConnectionPoolSize()
+   {
+      return connectionPoolSize;
+   }
+
+   public void setConnectionPoolSize(int connectionPoolSize)
+   {
+      this.connectionPoolSize = connectionPoolSize;
+   }
+
+   public boolean isCancelPropagation()
+   {
+      return cancelPropagation;
+   }
+
+   public void setCancelPropagation(boolean cancelPropagation)
+   {
+      this.cancelPropagation = cancelPropagation;
+   }
+
+   public String getAdminRole()
+   {
+      return adminRole;
+   }
+
+   public void setAdminRole(String adminRole)
+   {
+      this.adminRole = adminRole;
+   }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java
new file mode 100755
index 0000000..75339d1
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java
@@ -0,0 +1,139 @@
+package org.keycloak.adapters.as7.config;
+
+import org.apache.catalina.Context;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.logging.Logger;
+import org.keycloak.EnvUtil;
+import org.keycloak.PemUtils;
+import org.keycloak.ResourceMetadata;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.PublicKey;
+
+public class ManagedResourceConfigLoader
+{
+   static final Logger log = Logger.getLogger(ManagedResourceConfigLoader.class);
+   protected ManagedResourceConfig remoteSkeletonKeyConfig;
+   protected ResourceMetadata resourceMetadata;
+
+   public ManagedResourceConfigLoader(Context context)
+   {
+      ObjectMapper mapper = new ObjectMapper();
+      mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
+      InputStream is = null;
+      String path = context.getServletContext().getInitParameter("skeleton.key.config.file");
+      if (path == null)
+      {
+         is = context.getServletContext().getResourceAsStream("/WEB-INF/resteasy-oauth.json");
+      }
+      else
+      {
+         try
+         {
+            is = new FileInputStream(path);
+         }
+         catch (FileNotFoundException e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+      remoteSkeletonKeyConfig = null;
+      try
+      {
+         remoteSkeletonKeyConfig = mapper.readValue(is, ManagedResourceConfig.class);
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+
+      String name = remoteSkeletonKeyConfig.getResource();
+      String realm = remoteSkeletonKeyConfig.getRealm();
+      if (realm == null) throw new RuntimeException("Must set 'realm' in config");
+
+      String realmKeyPem = remoteSkeletonKeyConfig.getRealmKey();
+      if (realmKeyPem == null)
+      {
+         throw new IllegalArgumentException("You must set the realm-public-key");
+      }
+
+      PublicKey realmKey = null;
+      try
+      {
+         realmKey = PemUtils.decodePublicKey(realmKeyPem);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+      resourceMetadata = new ResourceMetadata();
+      resourceMetadata.setRealm(realm);
+      resourceMetadata.setResourceName(name);
+      resourceMetadata.setRealmKey(realmKey);
+
+
+      String truststore = remoteSkeletonKeyConfig.getTruststore();
+      if (truststore != null)
+      {
+         truststore = EnvUtil.replace(truststore);
+         String truststorePassword = remoteSkeletonKeyConfig.getTruststorePassword();
+         KeyStore trust = null;
+         try
+         {
+            trust = loadKeyStore(truststore, truststorePassword);
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException("Failed to load truststore", e);
+         }
+         resourceMetadata.setTruststore(trust);
+      }
+      String clientKeystore = remoteSkeletonKeyConfig.getClientKeystore();
+      String clientKeyPassword = null;
+      if (clientKeystore != null)
+      {
+         clientKeystore = EnvUtil.replace(clientKeystore);
+         String clientKeystorePassword = remoteSkeletonKeyConfig.getClientKeystorePassword();
+         KeyStore serverKS = null;
+         try
+         {
+            serverKS = loadKeyStore(clientKeystore, clientKeystorePassword);
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException("Failed to load keystore", e);
+         }
+         resourceMetadata.setClientKeystore(serverKS);
+         clientKeyPassword = remoteSkeletonKeyConfig.getClientKeyPassword();
+         resourceMetadata.setClientKeyPassword(clientKeyPassword);
+      }
+
+   }
+   public static KeyStore loadKeyStore(String filename, String password) throws Exception
+   {
+      KeyStore trustStore = KeyStore.getInstance(KeyStore
+              .getDefaultType());
+      File truststoreFile = new File(filename);
+      FileInputStream trustStream = new FileInputStream(truststoreFile);
+      trustStore.load(trustStream, password.toCharArray());
+      trustStream.close();
+      return trustStore;
+   }
+
+   public ManagedResourceConfig getRemoteSkeletonKeyConfig()
+   {
+      return remoteSkeletonKeyConfig;
+   }
+
+   public ResourceMetadata getResourceMetadata()
+   {
+      return resourceMetadata;
+   }
+
+}
\ No newline at end of file
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java
new file mode 100755
index 0000000..abc1dca
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java
@@ -0,0 +1,1050 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.bouncycastle.openssl.PEMWriter;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.ObjectWriter;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.jose.jws.JWSBuilder;
+import org.jboss.resteasy.jose.jws.JWSInput;
+import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
+import org.jboss.resteasy.jwt.JsonSerialization;
+import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
+import org.jboss.resteasy.plugins.server.servlet.ServletUtil;
+import org.keycloak.EnvUtil;
+import org.keycloak.PemUtils;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.as7.config.AuthServerConfig;
+import org.keycloak.adapters.as7.config.ManagedResourceConfig;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.SkeletonKeyToken;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.jboss.resteasy.spi.ResteasyUriInfo;
+import org.jboss.resteasy.util.BasicAuthHelper;
+
+import javax.security.auth.login.LoginException;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriBuilder;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Turns a web deployment into an authentication server that follwos the OAuth 2 protocol and Skeleton Key bearer tokens.
+ * Authentication store is backed by a JBoss security domain.
+ * <p/>
+ * Servlet FORM authentication that uses the local security domain to authenticate and for role mappings.
+ * <p/>
+ * Supports bearer token creation and authentication.  The client asking for access must be set up as a valid user
+ * within the security domain.
+ * <p/>
+ * If no an OAuth access request, this works like normal FORM authentication and authorization.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OAuthAuthenticationServerValve extends FormAuthenticator implements LifecycleListener
+{
+
+
+   public static class AccessCode
+   {
+      protected String id = UUID.randomUUID().toString() + System.currentTimeMillis();
+      protected long expiration;
+      protected SkeletonKeyToken token;
+      protected String client;
+      protected boolean sso;
+      protected String redirect;
+
+      public boolean isExpired()
+      {
+         return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration;
+      }
+
+      public String getId()
+      {
+         return id;
+      }
+
+      public long getExpiration()
+      {
+         return expiration;
+      }
+
+      public void setExpiration(long expiration)
+      {
+         this.expiration = expiration;
+      }
+
+      public SkeletonKeyToken getToken()
+      {
+         return token;
+      }
+
+      public void setToken(SkeletonKeyToken token)
+      {
+         this.token = token;
+      }
+
+      public String getClient()
+      {
+         return client;
+      }
+
+      public void setClient(String client)
+      {
+         this.client = client;
+      }
+
+      public boolean isSso()
+      {
+         return sso;
+      }
+
+      public void setSso(boolean sso)
+      {
+         this.sso = sso;
+      }
+
+      public String getRedirect()
+      {
+         return redirect;
+      }
+
+      public void setRedirect(String redirect)
+      {
+         this.redirect = redirect;
+      }
+   }
+
+   protected ConcurrentHashMap<String, AccessCode> accessCodeMap = new ConcurrentHashMap<String, AccessCode>();
+   private static final Logger log = Logger.getLogger(OAuthAuthenticationServerValve.class);
+
+   private static AtomicLong counter = new AtomicLong(1);
+
+   private static String generateId()
+   {
+      return counter.getAndIncrement() + "." + UUID.randomUUID().toString();
+   }
+
+   protected AuthServerConfig skeletonKeyConfig;
+   protected PrivateKey realmPrivateKey;
+   protected PublicKey realmPublicKey;
+   protected String realmPublicKeyPem;
+   protected ResteasyProviderFactory providers;
+   protected ResourceMetadata resourceMetadata;
+   protected UserSessionManagement userSessionManagement = new UserSessionManagement();
+   protected ObjectMapper mapper;
+   protected ObjectWriter accessTokenResponseWriter;
+   protected ObjectWriter mapWriter;
+
+   private static KeyStore loadKeyStore(String filename, String password) throws Exception
+   {
+      KeyStore trustStore = KeyStore.getInstance(KeyStore
+              .getDefaultType());
+      File truststoreFile = new File(filename);
+      FileInputStream trustStream = new FileInputStream(truststoreFile);
+      trustStore.load(trustStream, password.toCharArray());
+      trustStream.close();
+      return trustStore;
+   }
+
+   @Override
+   public void start() throws LifecycleException
+   {
+      super.start();
+      StandardContext standardContext = (StandardContext) context;
+      standardContext.addLifecycleListener(this);
+   }
+
+   @Override
+   public void lifecycleEvent(LifecycleEvent event)
+   {
+      if (event.getType() == Lifecycle.AFTER_START_EVENT) init();
+   }
+
+   protected void init()
+   {
+      mapper = new ObjectMapper();
+      mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
+      accessTokenResponseWriter = mapper.writerWithType(AccessTokenResponse.class);
+      mapWriter = mapper.writerWithType(mapper.getTypeFactory().constructMapType(Map.class, String.class, String.class));
+
+      InputStream is = null;
+      String path = context.getServletContext().getInitParameter("skeleton.key.config.file");
+      if (path == null)
+      {
+         is = context.getServletContext().getResourceAsStream("/WEB-INF/resteasy-oauth.json");
+      }
+      else
+      {
+         try
+         {
+            is = new FileInputStream(path);
+         }
+         catch (FileNotFoundException e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+      try
+      {
+         skeletonKeyConfig = mapper.readValue(is, AuthServerConfig.class);
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+      if (skeletonKeyConfig.getLoginRole() == null)
+      {
+         throw new RuntimeException("You must define the login-role in your config file");
+      }
+      if (skeletonKeyConfig.getClientRole() == null)
+      {
+         throw new RuntimeException("You must define the oauth-client-role in your config file");
+      }
+      if (skeletonKeyConfig.getRealmPrivateKey() != null)
+      {
+         try
+         {
+            realmPrivateKey = PemUtils.decodePrivateKey(skeletonKeyConfig.getRealmPrivateKey());
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+      if (skeletonKeyConfig.getRealmPublicKey() != null)
+      {
+         try
+         {
+            realmPublicKey = PemUtils.decodePublicKey(skeletonKeyConfig.getRealmPublicKey());
+            realmPublicKeyPem = skeletonKeyConfig.getRealmPublicKey();
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+      if (skeletonKeyConfig.getRealmKeyStore() != null)
+      {
+         if (skeletonKeyConfig.getRealmKeyAlias() == null) throw new RuntimeException("Must define realm-key-alias");
+         String keystorePath = EnvUtil.replace(skeletonKeyConfig.getRealmKeyStore());
+         try
+         {
+            KeyStore ks = loadKeyStore(keystorePath, skeletonKeyConfig.getRealmKeystorePassword());
+            if (realmPrivateKey == null)
+            {
+               realmPrivateKey = (PrivateKey) ks.getKey(skeletonKeyConfig.getRealmKeyAlias(), skeletonKeyConfig.getRealmPrivateKeyPassword().toCharArray());
+            }
+            if (realmPublicKey == null)
+            {
+               Certificate cert = ks.getCertificate(skeletonKeyConfig.getRealmKeyAlias());
+               realmPublicKey = cert.getPublicKey();
+            }
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException(e);
+         }
+      }
+      if (realmPublicKey == null) throw new RuntimeException("You have not declared a keystore or public key");
+      if (realmPrivateKey == null) throw new RuntimeException("You have not declared a keystore or private key");
+      if (realmPublicKeyPem == null)
+      {
+         StringWriter sw = new StringWriter();
+         PEMWriter writer = new PEMWriter(sw);
+         try
+         {
+            writer.writeObject(realmPublicKey);
+            writer.flush();
+         }
+         catch (IOException e)
+         {
+            throw new RuntimeException(e);
+         }
+         realmPublicKeyPem = sw.toString();
+         realmPublicKeyPem = PemUtils.removeBeginEnd(realmPublicKeyPem);
+      }
+      providers = new ResteasyProviderFactory();
+      ClassLoader old = Thread.currentThread().getContextClassLoader();
+      Thread.currentThread().setContextClassLoader(OAuthAuthenticationServerValve.class.getClassLoader());
+      try
+      {
+         ResteasyProviderFactory.getInstance(); // initialize builtins
+         RegisterBuiltin.register(providers);
+      }
+      finally
+      {
+         Thread.currentThread().setContextClassLoader(old);
+      }
+      resourceMetadata = new ResourceMetadata();
+      resourceMetadata.setRealm(skeletonKeyConfig.getRealm());
+      resourceMetadata.setRealmKey(realmPublicKey);
+      String truststore = skeletonKeyConfig.getTruststore();
+      if (truststore != null)
+      {
+         truststore = EnvUtil.replace(truststore);
+         String truststorePassword = skeletonKeyConfig.getTruststorePassword();
+         KeyStore trust = null;
+         try
+         {
+            trust = loadKeyStore(truststore, truststorePassword);
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException("Failed to load truststore", e);
+         }
+         resourceMetadata.setTruststore(trust);
+      }
+      String clientKeystore = skeletonKeyConfig.getClientKeystore();
+      String clientKeyPassword = null;
+      if (clientKeystore != null)
+      {
+         clientKeystore = EnvUtil.replace(clientKeystore);
+         String clientKeystorePassword = skeletonKeyConfig.getClientKeystorePassword();
+         KeyStore serverKS = null;
+         try
+         {
+            serverKS = loadKeyStore(clientKeystore, clientKeystorePassword);
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException("Failed to load keystore", e);
+         }
+         resourceMetadata.setClientKeystore(serverKS);
+         clientKeyPassword = skeletonKeyConfig.getClientKeyPassword();
+         resourceMetadata.setClientKeyPassword(clientKeyPassword);
+      }
+   }
+
+   @Override
+   public void invoke(Request request, Response response) throws IOException, ServletException
+   {
+      try
+      {
+         String contextPath = request.getContextPath();
+         String requestURI = request.getDecodedRequestURI();
+         log.debug("--- invoke: " + requestURI);
+         if (request.getMethod().equalsIgnoreCase("GET")
+                 && context.getLoginConfig().getLoginPage().equals(request.getRequestPathMB().toString()))
+         {
+            if (handleLoginPage(request, response)) return;
+         }
+         else if (request.getMethod().equalsIgnoreCase("GET")
+                 && requestURI.endsWith(Actions.J_OAUTH_LOGOUT))
+         {
+            logoutCurrentUser(request, response);
+            return;
+         }
+         else if (request.getMethod().equalsIgnoreCase("POST")
+                 && requestURI.endsWith(Actions.J_OAUTH_ADMIN_FORCED_LOGOUT))
+         {
+            adminLogout(request, response);
+            return;
+         }
+         else if (request.getMethod().equalsIgnoreCase("POST")
+                 && requestURI.startsWith(contextPath) &&
+                 requestURI.endsWith(Constants.FORM_ACTION)
+                 && request.getParameter("client_id") != null)
+         {
+            handleOAuth(request, response);
+            return;
+         }
+         else if (request.getMethod().equalsIgnoreCase("POST")
+                 && requestURI.endsWith(Actions.J_OAUTH_TOKEN_GRANT))
+         {
+            tokenGrant(request, response);
+            return;
+         }
+         else if (request.getMethod().equalsIgnoreCase("POST")
+                 && requestURI.startsWith(contextPath) &&
+                 requestURI.endsWith(Actions.J_OAUTH_RESOLVE_ACCESS_CODE))
+         {
+            resolveAccessCode(request, response);
+            return;
+         }
+         else if (request.getMethod().equalsIgnoreCase("GET")
+                 && requestURI.startsWith(contextPath) &&
+                 requestURI.endsWith("j_oauth_realm_info.html"))
+         {
+            publishRealmInfoHtml(request, response);
+            return;
+         }
+         // propagate the skeleton key token string?
+         if (!skeletonKeyConfig.isCancelPropagation())
+         {
+            if (request.getAttribute(SkeletonKeySession.class.getName()) == null && request.getSessionInternal() != null)
+            {
+               SkeletonKeySession skSession = (SkeletonKeySession) request.getSessionInternal().getNote(SkeletonKeySession.class.getName());
+               if (skSession != null)
+               {
+                  request.setAttribute(SkeletonKeySession.class.getName(), skSession);
+                  ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
+               }
+            }
+         }
+         request.setAttribute("OAUTH_FORM_ACTION", "j_security_check");
+         super.invoke(request, response);
+      }
+      finally
+      {
+         ResteasyProviderFactory.clearContextData();  // to clear push of SkeletonKeySession
+      }
+   }
+
+   protected boolean handleLoginPage(Request request, Response response) throws IOException, ServletException
+   {
+      String client_id = request.getParameter("client_id");
+      // if this is not an OAUTH redirect, just return and let the default flow happen
+      if (client_id == null) return false;
+
+      String redirect_uri = request.getParameter("redirect_uri");
+      String state = request.getParameter("state");
+
+      if (redirect_uri == null)
+      {
+         response.sendError(400, "No oauth redirect query parameter set");
+         return true;
+      }
+      // only bypass authentication if our session is authenticated,
+      // the login query parameter is on request URL,
+      // and we have configured the login-role
+      else if (!skeletonKeyConfig.isSsoDisabled()
+              && request.getSessionInternal() != null
+              && request.getSessionInternal().getPrincipal() != null
+              && request.getParameter("login") != null)
+      {
+         log.debug("We're ALREADY LOGGED IN!!!");
+         GenericPrincipal gp = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+         redirectAccessCode(true, response, redirect_uri, client_id, state, gp);
+      }
+      else
+      {
+         UriBuilder builder = UriBuilder.fromUri("j_security_check")
+                 .queryParam("redirect_uri", redirect_uri)
+                 .queryParam("client_id", client_id);
+         if (state != null) builder.queryParam("state", state);
+         String loginAction = builder.build().toString();
+         request.setAttribute("OAUTH_FORM_ACTION", loginAction);
+         getNext().invoke(request, response);
+      }
+      return true;
+   }
+
+   protected GenericPrincipal checkLoggedIn(Request request, HttpServletResponse response)
+   {
+      if (request.getPrincipal() != null)
+      {
+         return (GenericPrincipal) request.getPrincipal();
+      }
+      else if (request.getSessionInternal() != null && request.getSessionInternal().getPrincipal() != null)
+      {
+         return (GenericPrincipal) request.getSessionInternal().getPrincipal();
+      }
+      return null;
+   }
+
+
+   protected void adminLogout(Request request, HttpServletResponse response) throws IOException
+   {
+      log.debug("<< adminLogout");
+      GenericPrincipal gp = checkLoggedIn(request, response);
+      if (gp == null)
+      {
+         if (bearer(request, response, false))
+         {
+            gp = (GenericPrincipal) request.getPrincipal();
+         }
+         else
+         {
+            response.sendError(403);
+            return;
+         }
+      }
+      if (!gp.hasRole(skeletonKeyConfig.getAdminRole()))
+      {
+         response.sendError(403);
+         return;
+      }
+      String logoutUser = request.getParameter("user");
+      if (logoutUser != null)
+      {
+         userSessionManagement.logout(logoutUser);
+         logoutResources(logoutUser, gp.getName());
+      }
+      else
+      {
+         userSessionManagement.logoutAllBut(gp.getName());
+         logoutResources(null, gp.getName());
+      }
+      String forwardTo = request.getParameter("forward");
+      if (forwardTo == null)
+      {
+         response.setStatus(204);
+         return;
+      }
+      RequestDispatcher disp =
+              context.getServletContext().getRequestDispatcher(forwardTo);
+      try
+      {
+         disp.forward(request.getRequest(), response);
+      }
+      catch (Throwable t)
+      {
+         request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                 "failed to forward");
+      }
+
+
+   }
+
+
+   protected void logoutCurrentUser(Request request, HttpServletResponse response) throws IOException
+   {
+      if (request.getSessionInternal() == null || request.getSessionInternal().getPrincipal() == null)
+      {
+         redirectToWelcomePage(request, response);
+         return;
+      }
+      GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+      String username = principal.getName();
+      String admin = username;
+      userSessionManagement.logout(username);
+      request.setUserPrincipal(null);
+      request.setAuthType(null);
+      // logout user on all declared authenticated resources
+      logoutResources(username, admin);
+      redirectToWelcomePage(request, response);
+   }
+
+   protected void logoutResources(String username, String admin)
+   {
+      if (skeletonKeyConfig.getResources().size() != 0)
+      {
+         SkeletonKeyToken token = new SkeletonKeyToken();
+         token.id(generateId());
+         token.principal(admin);
+         token.audience(skeletonKeyConfig.getRealm());
+         SkeletonKeyToken.Access realmAccess = new SkeletonKeyToken.Access();
+         realmAccess.addRole(skeletonKeyConfig.getAdminRole());
+         token.setRealmAccess(realmAccess);
+         String tokenString = buildTokenString(realmPrivateKey, token);
+         ResteasyClient client = new ResteasyClientBuilder()
+                 .providerFactory(providers)
+                 .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
+                 .trustStore(resourceMetadata.getTruststore())
+                 .keyStore(resourceMetadata.getClientKeystore(), resourceMetadata.getClientKeyPassword())
+                 .build();
+         try
+         {
+            for (String resource : skeletonKeyConfig.getResources())
+            {
+               try
+               {
+                  log.debug("logging out: " + resource);
+                  WebTarget target = client.target(resource).path(Actions.J_OAUTH_REMOTE_LOGOUT);
+                  if (username != null) target = target.queryParam("user", username);
+                  javax.ws.rs.core.Response response = target.request()
+                          .header("Authorization", "Bearer " + tokenString)
+                          .put(null);
+                  if (response.getStatus() != 204) log.error("Failed to log out");
+                  response.close();
+               }
+               catch (Exception ignored)
+               {
+                  log.error("Failed to log out", ignored);
+               }
+            }
+         }
+         finally
+         {
+            client.close();
+         }
+      }
+   }
+
+   protected void redirectToWelcomePage(Request request, HttpServletResponse response) throws IOException
+   {
+      ResteasyUriInfo uriInfo = ServletUtil.extractUriInfo(request, null);
+      String[] welcomes = context.findWelcomeFiles();
+      if (welcomes.length > 0)
+      {
+         UriBuilder welcome = uriInfo.getBaseUriBuilder().path(welcomes[0]);
+         response.sendRedirect(welcome.toTemplate());
+      }
+      else
+      {
+         response.setStatus(204);
+      }
+   }
+
+
+   protected void publishRealmInfoHtml(Request request, HttpServletResponse response) throws IOException
+   {
+      ManagedResourceConfig rep = getRealmRepresentation(request);
+      StringWriter writer;
+      String json;
+
+      ObjectMapper mapper = new ObjectMapper();
+      mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
+      mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
+
+      StringBuffer html = new StringBuffer();
+      html.append("<html><body bgcolor=\"#CED8F6\">");
+      html.append("<h1>Realm: ").append(rep.getRealm()).append("</h1>");
+
+      ManagedResourceConfig bearer = new ManagedResourceConfig();
+      bearer.setRealm(rep.getRealm());
+      bearer.setRealmKey(rep.getRealmKey());
+      writer = new StringWriter();
+      mapper.writeValue(writer, bearer);
+      json = writer.toString();
+
+      html.append("<h3>BearerTokenAuthValve Json Config</h3>");
+      html.append("<form><textarea rows=\"7\" cols=\"80\">").append(json).append("</textarea></form>");
+
+      html.append("<br>");
+
+      writer = new StringWriter();
+      rep.getClientCredentials().put("password", "REQUIRED");
+      rep.setClientId("REQUIRED");
+      rep.setTruststore("REQUIRED");
+      rep.setTruststorePassword("REQUIRED");
+      mapper.writeValue(writer, rep);
+      json = writer.toString();
+      html.append("<h3>OAuthManagedResourceValve Json Config</h3>");
+      html.append("<form><textarea rows=\"20\" cols=\"80\">").append(json).append("</textarea></form>");
+
+      html.append("</body></html>");
+
+      response.setStatus(200);
+      response.setContentType("text/html");
+      response.getOutputStream().println(html.toString());
+      response.getOutputStream().flush();
+
+   }
+
+
+   protected ManagedResourceConfig getRealmRepresentation(Request request)
+   {
+      ManagedResourceConfig rep = new ManagedResourceConfig();
+      ResteasyUriInfo uriInfo = ServletUtil.extractUriInfo(request, null);
+      UriBuilder authUrl = uriInfo.getBaseUriBuilder().path(context.getLoginConfig().getLoginPage());
+      UriBuilder codeUrl = uriInfo.getBaseUriBuilder().path(Actions.J_OAUTH_RESOLVE_ACCESS_CODE);
+      rep.setRealm(skeletonKeyConfig.getRealm());
+      rep.setRealmKey(realmPublicKeyPem);
+      rep.setAuthUrl(authUrl.toTemplate());
+      rep.setCodeUrl(codeUrl.toTemplate());
+      rep.setAdminRole(skeletonKeyConfig.getAdminRole());
+      return rep;
+   }
+
+   public boolean bearer(Request request, HttpServletResponse response, boolean propagate) throws IOException
+   {
+      if (request.getHeader("Authorization") != null)
+      {
+         CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, true, false);
+         try
+         {
+            if (bearer.login(request, response))
+            {
+               return true;
+            }
+         }
+         catch (LoginException e)
+         {
+         }
+      }
+      return false;
+   }
+
+   @Override
+   protected void register(Request request, HttpServletResponse response, Principal principal, String authType, String username, String password)
+   {
+      super.register(request, response, principal, authType, username, password);
+      log.debug("authenticate userSessionManage.login(): " + principal.getName());
+      userSessionManagement.login(request.getSessionInternal(), principal.getName());
+      if (!skeletonKeyConfig.isCancelPropagation())
+      {
+         GenericPrincipal gp = (GenericPrincipal) request.getPrincipal();
+         if (gp != null)
+         {
+            SkeletonKeyToken token = buildToken(gp);
+            String stringToken = buildTokenString(realmPrivateKey, token);
+            SkeletonKeySession skSession = new SkeletonKeySession(stringToken, resourceMetadata);
+            request.setAttribute(SkeletonKeySession.class.getName(), skSession);
+            ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
+            request.getSessionInternal(true).setNote(SkeletonKeySession.class.getName(), skSession);
+         }
+      }
+   }
+
+   @Override
+   public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException
+   {
+      if (bearer(request, response, true))
+      {
+         return true;
+      }
+      return super.authenticate(request, response, config);
+   }
+
+
+   protected void resolveAccessCode(Request request, Response response) throws IOException
+   {
+      if (!request.isSecure())
+      {
+         response.sendError(400);
+         return;
+      }
+      // always verify code and remove access code from map before authenticating user
+      // if user authentication fails, we want the code to be removed irreguardless just in case we're under attack
+      String code = request.getParameter("code");
+      JWSInput input = new JWSInput(code, providers);
+      boolean verifiedCode = false;
+      try
+      {
+         verifiedCode = RSAProvider.verify(input, realmPublicKey);
+      }
+      catch (Exception ignored)
+      {
+         log.error("Failed to verify signature", ignored);
+      }
+      if (!verifiedCode)
+      {
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Unable to verify code signature");
+         response.sendError(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+      }
+      String key = input.readContent(String.class);
+      AccessCode accessCode = accessCodeMap.remove(key);
+      String redirect = request.getParameter("redirect_uri");
+
+      GenericPrincipal gp = basicAuth(request, response);
+      if (gp == null)
+      {
+         log.error("Failed to authenticate client_id");
+         return;
+      }
+      if (accessCode == null)
+      {
+         log.error("No access code: " + code);
+         response.sendError(400);
+         return;
+      }
+      if (accessCode.isExpired())
+      {
+         log.debug("Access code expired");
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Code is expired");
+         response.setStatus(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+      }
+      if (!accessCode.getToken().isActive())
+      {
+         log.debug("token not active");
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Token expired");
+         response.setStatus(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+      }
+      if (!gp.getName().equals(accessCode.getClient()))
+      {
+         log.debug("not equal client");
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Auth error");
+         response.setStatus(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+      }
+      if (!accessCode.getRedirect().equals(redirect))
+      {
+         log.debug("not equal redirect");
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Auth error");
+         response.setStatus(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+      }
+      if (accessCode.isSso() && !gp.hasRole(skeletonKeyConfig.getLoginRole()))
+      {
+         // we did not authenticate user on an access code request because a session was already established
+         // but, the client_id does not have permission to bypass this on a simple grant.  We want
+         // to always ask for credentials from a simple oath request
+
+         log.debug("does not have login permission");
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Auth error");
+         response.setStatus(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+      }
+      else if (!gp.hasRole(skeletonKeyConfig.getClientRole()) && !gp.hasRole(skeletonKeyConfig.getLoginRole()))
+      {
+         log.debug("does not have login or client role permission for access token request");
+         Map<String, String> res = new HashMap<String, String>();
+         res.put("error", "invalid_grant");
+         res.put("error_description", "Auth error");
+         response.setStatus(400);
+         response.setContentType("application/json");
+         mapWriter.writeValue(response.getOutputStream(), res);
+         response.getOutputStream().flush();
+         return;
+
+      }
+      String wildcard = skeletonKeyConfig.getWildcardRole() == null ? "*" : skeletonKeyConfig.getWildcardRole();
+      Set<String> codeRoles = accessCode.getToken().getRealmAccess().getRoles();
+      if (codeRoles != null &&
+              (codeRoles.contains(skeletonKeyConfig.getClientRole()) || codeRoles.contains(skeletonKeyConfig.getLoginRole())))
+      {
+         // we store roles a oauth client is granted in the user role mapping, remove those roles as we don't want those clients with those
+         // permissions if they are logging in.
+         Set<String> newRoles = new HashSet<String>();
+         if (codeRoles.contains(skeletonKeyConfig.getClientRole())) newRoles.add(skeletonKeyConfig.getClientRole());
+         if (codeRoles.contains(skeletonKeyConfig.getLoginRole())) newRoles.add(skeletonKeyConfig.getLoginRole());
+         if (codeRoles.contains(wildcard)) newRoles.add(wildcard);
+         codeRoles.clear();
+         codeRoles.addAll(newRoles);
+      }
+
+      // is we have a login role, then we don't need to filter out roles, just grant all the roles the user has
+      // Also, if the client has the "wildcard" role, then we don't need to filter out roles
+      if (codeRoles != null
+              && !gp.hasRole(wildcard)
+              && !gp.hasRole(skeletonKeyConfig.getLoginRole()))
+      {
+         Set<String> clientAllowed = new HashSet<String>();
+         for (String role : gp.getRoles())
+         {
+            clientAllowed.add(role);
+         }
+         Set<String> newRoles = new HashSet<String>();
+         newRoles.addAll(codeRoles);
+         for (String role : newRoles)
+         {
+            if (!clientAllowed.contains(role))
+            {
+               codeRoles.remove(role);
+            }
+         }
+      }
+      AccessTokenResponse res = accessTokenResponse(realmPrivateKey, accessCode.getToken());
+      response.setStatus(200);
+      response.setContentType("application/json");
+      accessTokenResponseWriter.writeValue(response.getOutputStream(), res);
+      response.getOutputStream().flush();
+   }
+
+   protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token)
+   {
+      String encodedToken = buildTokenString(privateKey, token);
+
+      AccessTokenResponse res = new AccessTokenResponse();
+      res.setToken(encodedToken);
+      res.setTokenType("bearer");
+      if (token.getExpiration() != 0)
+      {
+         long time = token.getExpiration() - (System.currentTimeMillis() / 1000);
+         res.setExpiresIn(time);
+      }
+      return res;
+   }
+
+   protected String buildTokenString(PrivateKey privateKey, SkeletonKeyToken token)
+   {
+      byte[] tokenBytes = null;
+      try
+      {
+         tokenBytes = JsonSerialization.toByteArray(token, false);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+      return new JWSBuilder()
+              .content(tokenBytes)
+              .rsa256(privateKey);
+   }
+
+
+   protected void handleOAuth(Request request, Response response) throws IOException
+   {
+      log.debug("<--- Begin oauthAuthenticate");
+      String redirect_uri = request.getParameter("redirect_uri");
+      String client_id = request.getParameter("client_id");
+      String state = request.getParameter("state");
+      String username = request.getParameter(Constants.FORM_USERNAME);
+      String password = request.getParameter(Constants.FORM_PASSWORD);
+      Principal principal = context.getRealm().authenticate(username, password);
+      if (principal == null)
+      {
+         UriBuilder builder = UriBuilder.fromUri(redirect_uri).queryParam("error", "unauthorized_client");
+         if (state != null) builder.queryParam("state", state);
+         response.sendRedirect(builder.toTemplate());
+         return;
+      }
+      GenericPrincipal gp = (GenericPrincipal) principal;
+      register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password);
+      userSessionManagement.login(request.getSessionInternal(), username);
+      redirectAccessCode(false, response, redirect_uri, client_id, state, gp);
+
+      return;
+   }
+
+   protected void tokenGrant(Request request, Response response) throws IOException
+   {
+      if (!request.isSecure())
+      {
+         response.sendError(400);
+         return;
+      }
+      GenericPrincipal gp = basicAuth(request, response);
+      if (gp == null) return;
+      SkeletonKeyToken token = buildToken(gp);
+      AccessTokenResponse res = accessTokenResponse(realmPrivateKey, token);
+      response.setStatus(200);
+      response.setContentType("application/json");
+      accessTokenResponseWriter.writeValue(response.getOutputStream(), res);
+      response.getOutputStream().flush();
+   }
+
+   protected GenericPrincipal basicAuth(Request request, Response response) throws IOException
+   {
+      String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
+      if (authHeader == null)
+      {
+         basicAuthError(response);
+         return null;
+      }
+      String[] creds = BasicAuthHelper.parseHeader(authHeader);
+      if (creds == null)
+      {
+         basicAuthError(response);
+         return null;
+      }
+      String username = creds[0];
+      String password = creds[1];
+      GenericPrincipal gp = (GenericPrincipal) context.getRealm().authenticate(username, password);
+      if (gp == null)
+      {
+         basicAuthError(response);
+         return null;
+      }
+      return gp;
+   }
+
+   protected void basicAuthError(Response response) throws IOException
+   {
+      response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getLoginConfig().getRealmName() + "\"");
+      response.sendError(401);
+   }
+
+   protected void redirectAccessCode(boolean sso, Response response, String redirect_uri, String client_id, String state, GenericPrincipal gp) throws IOException
+   {
+      SkeletonKeyToken token = buildToken(gp);
+      AccessCode code = new AccessCode();
+      code.setToken(token);
+      code.setClient(client_id);
+      code.setSso(sso);
+      code.setRedirect(redirect_uri);
+      int expiration = skeletonKeyConfig.getAccessCodeLifetime() == 0 ? 300 : skeletonKeyConfig.getAccessCodeLifetime();
+      code.setExpiration((System.currentTimeMillis() / 1000) + expiration);
+      accessCodeMap.put(code.getId(), code);
+      log.debug("--- sign access code");
+      String accessCode = null;
+      try
+      {
+         accessCode = new JWSBuilder().content(code.getId().getBytes("UTF-8")).rsa256(realmPrivateKey);
+      }
+      catch (UnsupportedEncodingException e)
+      {
+         throw new RuntimeException(e);
+      }
+      log.debug("--- build redirect");
+      UriBuilder redirectUri = UriBuilder.fromUri(redirect_uri).queryParam("code", accessCode);
+      if (state != null) redirectUri.queryParam("state", state);
+      response.sendRedirect(redirectUri.toTemplate());
+      log.debug("<--- end oauthAuthenticate");
+   }
+
+   protected SkeletonKeyToken buildToken(GenericPrincipal gp)
+   {
+      SkeletonKeyToken token = new SkeletonKeyToken();
+      token.id(generateId());
+      token.principal(gp.getName());
+      token.audience(skeletonKeyConfig.getRealm());
+      int expiration = skeletonKeyConfig.getAccessCodeLifetime() == 0 ? 3600 : skeletonKeyConfig.getAccessCodeLifetime();
+      if (skeletonKeyConfig.getTokenLifetime() > 0)
+      {
+         token.expiration((System.currentTimeMillis() / 1000) + expiration);
+      }
+      SkeletonKeyToken.Access realmAccess = new SkeletonKeyToken.Access();
+      for (String role : gp.getRoles())
+      {
+         realmAccess.addRole(role);
+      }
+      token.setRealmAccess(realmAccess);
+      return token;
+   }
+
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java
new file mode 100755
index 0000000..772607c
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java
@@ -0,0 +1,303 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Session;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
+import org.keycloak.RealmConfiguration;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.SkeletonKeyPrincipal;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.as7.config.ManagedResourceConfig;
+import org.keycloak.adapters.as7.config.ManagedResourceConfigLoader;
+import org.keycloak.representations.SkeletonKeyToken;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Web deployment whose security is managed by a remote OAuth Skeleton Key authentication server
+ * <p/>
+ * Redirects browser to remote authentication server if not logged in.  Also allows OAuth Bearer Token requests
+ * that contain a Skeleton Key bearer tokens.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OAuthManagedResourceValve extends FormAuthenticator implements LifecycleListener
+{
+   protected RealmConfiguration realmConfiguration;
+   private static final Logger log = Logger.getLogger(OAuthManagedResourceValve.class);
+   protected UserSessionManagement userSessionManagement = new UserSessionManagement();
+   protected ManagedResourceConfig remoteSkeletonKeyConfig;
+   protected ResourceMetadata resourceMetadata;
+
+
+   @Override
+   public void start() throws LifecycleException
+   {
+      super.start();
+      StandardContext standardContext = (StandardContext) context;
+      standardContext.addLifecycleListener(this);
+   }
+
+   @Override
+   public void lifecycleEvent(LifecycleEvent event)
+   {
+      if (event.getType() == Lifecycle.AFTER_START_EVENT) init();
+   }
+
+   protected void init()
+   {
+      ManagedResourceConfigLoader managedResourceConfigLoader = new ManagedResourceConfigLoader(context);
+      resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
+      remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
+      String client_id = remoteSkeletonKeyConfig.getClientId();
+      if (client_id == null)
+      {
+         throw new IllegalArgumentException("Must set client-id to use with auth server");
+      }
+      realmConfiguration = new RealmConfiguration();
+      String authUrl = remoteSkeletonKeyConfig.getAuthUrl();
+      if (authUrl == null)
+      {
+         throw new RuntimeException("You must specify auth-url");
+      }
+      String tokenUrl = remoteSkeletonKeyConfig.getCodeUrl();
+      if (tokenUrl == null)
+      {
+         throw new RuntimeException("You mut specify code-url");
+      }
+      realmConfiguration.setMetadata(resourceMetadata);
+      realmConfiguration.setClientId(client_id);
+
+      for (Map.Entry<String, String> entry : managedResourceConfigLoader.getRemoteSkeletonKeyConfig().getClientCredentials().entrySet())
+      {
+         realmConfiguration.getCredentials().param(entry.getKey(), entry.getValue());
+      }
+      int size = 10;
+      if (managedResourceConfigLoader.getRemoteSkeletonKeyConfig().getConnectionPoolSize() > 0)
+         size = managedResourceConfigLoader.getRemoteSkeletonKeyConfig().getConnectionPoolSize();
+      ResteasyClientBuilder.HostnameVerificationPolicy policy = ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD;
+      if (managedResourceConfigLoader.getRemoteSkeletonKeyConfig().isAllowAnyHostname())
+         policy = ResteasyClientBuilder.HostnameVerificationPolicy.ANY;
+      ResteasyProviderFactory providerFactory = new ResteasyProviderFactory();
+      ClassLoader old = Thread.currentThread().getContextClassLoader();
+      Thread.currentThread().setContextClassLoader(OAuthManagedResourceValve.class.getClassLoader());
+      try
+      {
+         ResteasyProviderFactory.getInstance(); // initialize builtins
+         RegisterBuiltin.register(providerFactory);
+      }
+      finally
+      {
+         Thread.currentThread().setContextClassLoader(old);
+      }
+      ResteasyClient client = new ResteasyClientBuilder()
+              .providerFactory(providerFactory)
+              .connectionPoolSize(size)
+              .hostnameVerification(policy)
+              .trustStore(resourceMetadata.getTruststore())
+              .keyStore(resourceMetadata.getClientKeystore(), resourceMetadata.getClientKeyPassword())
+              .build();
+      realmConfiguration.setClient(client);
+      realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", client_id));
+      realmConfiguration.setCodeUrl(client.target(tokenUrl));
+   }
+
+   @Override
+   public void invoke(Request request, Response response) throws IOException, ServletException
+   {
+      try
+      {
+         String requestURI = request.getDecodedRequestURI();
+         if (requestURI.endsWith("j_oauth_remote_logout"))
+         {
+            remoteLogout(request, response);
+            return;
+         }
+         super.invoke(request, response);
+      }
+      finally
+      {
+         ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
+      }
+   }
+
+   @Override
+   public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException
+   {
+      try
+      {
+         if (bearer(false, request, response)) return true;
+         else if (checkLoggedIn(request, response))
+         {
+            if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null)
+            {
+               if (restoreRequest(request, request.getSessionInternal()))
+               {
+                  log.debug("restoreRequest");
+                  return (true);
+               }
+               else
+               {
+                  log.debug("Restore of original request failed");
+                  response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                  return (false);
+               }
+            }
+            else
+            {
+               return true;
+            }
+         }
+
+         // initiate or continue oauth2 protocol
+         oauth(request, response);
+      }
+      catch (LoginException e)
+      {
+      }
+      return false;
+   }
+
+   protected void remoteLogout(Request request, HttpServletResponse response) throws IOException
+   {
+      try
+      {
+         log.debug("->> remoteLogout: ");
+         if (!bearer(true, request, response))
+         {
+            log.debug("remoteLogout: bearer auth failed");
+            return;
+         }
+         GenericPrincipal gp = (GenericPrincipal) request.getPrincipal();
+         if (!gp.hasRole(remoteSkeletonKeyConfig.getAdminRole()))
+         {
+            log.debug("remoteLogout: role failure");
+            response.sendError(403);
+            return;
+         }
+         String user = request.getParameter("user");
+         if (user != null)
+         {
+            userSessionManagement.logout(user);
+         }
+         else
+         {
+            userSessionManagement.logoutAll();
+         }
+      }
+      catch (Exception e)
+      {
+         log.error("failed to logout", e);
+      }
+      response.setStatus(204);
+   }
+
+   protected boolean bearer(boolean challenge, Request request, HttpServletResponse response) throws LoginException, IOException
+   {
+      CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(realmConfiguration.getMetadata(), !remoteSkeletonKeyConfig.isCancelPropagation(), challenge);
+      if (bearer.login(request, response))
+      {
+         return true;
+      }
+      return false;
+   }
+
+   protected boolean checkLoggedIn(Request request, HttpServletResponse response)
+   {
+      if (request.getSessionInternal() == null || request.getSessionInternal().getPrincipal() == null)
+         return false;
+      log.debug("remote logged in already");
+      GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+      request.setUserPrincipal(principal);
+      request.setAuthType("OAUTH");
+      Session session = request.getSessionInternal();
+      if (session != null && !remoteSkeletonKeyConfig.isCancelPropagation())
+      {
+         SkeletonKeySession skSession = (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
+         if (skSession != null)
+         {
+            request.setAttribute(SkeletonKeySession.class.getName(), skSession);
+            ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
+
+         }
+      }
+      return true;
+   }
+
+   /**
+    * This method always set the HTTP response, so do not continue after invoking
+    */
+   protected void oauth(Request request, HttpServletResponse response) throws IOException
+   {
+      ServletOAuthLogin oauth = new ServletOAuthLogin(realmConfiguration, request, response, request.getConnector().getRedirectPort());
+      String code = oauth.getCode();
+      if (code == null)
+      {
+         String error = oauth.getError();
+         if (error != null)
+         {
+            response.sendError(400, "OAuth " + error);
+            return;
+         }
+         else
+         {
+            saveRequest(request, request.getSessionInternal(true));
+            oauth.loginRedirect();
+         }
+         return;
+      }
+      else
+      {
+         if (!oauth.resolveCode(code)) return;
+
+         SkeletonKeyToken token = oauth.getToken();
+         Set<String> roles = null;
+         if (resourceMetadata.getResourceName() != null)
+         {
+            SkeletonKeyToken.Access access = token.getResourceAccess(resourceMetadata.getResourceName());
+            if (access != null) roles = access.getRoles();
+         }
+         else
+         {
+            SkeletonKeyToken.Access access = token.getRealmAccess();
+            if (access != null) roles = access.getRoles();
+         }
+         SkeletonKeyPrincipal skp = new SkeletonKeyPrincipal(token.getPrincipal(), null);
+         GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(context.getRealm(), skp, roles);
+         Session session = request.getSessionInternal(true);
+         session.setPrincipal(principal);
+         session.setAuthType("OAUTH");
+         if (!remoteSkeletonKeyConfig.isCancelPropagation())
+         {
+            SkeletonKeySession skSession = new SkeletonKeySession(oauth.getTokenString(), realmConfiguration.getMetadata());
+            session.setNote(SkeletonKeySession.class.getName(), skSession);
+         }
+
+         String username = token.getPrincipal();
+         log.debug("userSessionManage.login: " + username);
+         userSessionManagement.login(session, username);
+      }
+   }
+
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
new file mode 100755
index 0000000..4a7d8c1
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java
@@ -0,0 +1,320 @@
+package org.keycloak.adapters.as7;
+
+import org.jboss.logging.Logger;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.RealmConfiguration;
+import org.keycloak.VerificationException;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.SkeletonKeyToken;
+import org.jboss.resteasy.util.BasicAuthHelper;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletOAuthLogin
+{
+   private static final Logger log = Logger.getLogger(ServletOAuthLogin.class);
+   protected HttpServletRequest request;
+   protected HttpServletResponse response;
+   protected boolean codePresent;
+   protected RealmConfiguration realmInfo;
+   protected int redirectPort;
+   protected String tokenString;
+   protected SkeletonKeyToken token;
+
+   public ServletOAuthLogin(RealmConfiguration realmInfo, HttpServletRequest request, HttpServletResponse response, int redirectPort)
+   {
+      this.request = request;
+      this.response = response;
+      this.realmInfo = realmInfo;
+      this.redirectPort = redirectPort;
+   }
+
+   public String getTokenString()
+   {
+      return tokenString;
+   }
+
+   public SkeletonKeyToken getToken()
+   {
+      return token;
+   }
+
+   public RealmConfiguration getRealmInfo()
+   {
+      return realmInfo;
+   }
+
+   protected String getDefaultCookiePath()
+   {
+      String path = request.getContextPath();
+      if ("".equals(path) || path == null) path = "/";
+      return path;
+   }
+
+   protected String getRequestUrl()
+   {
+      return request.getRequestURL().toString();
+   }
+
+   protected boolean isRequestSecure()
+   {
+      return request.isSecure();
+   }
+
+   protected void sendError(int code)
+   {
+      try
+      {
+         response.sendError(code);
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   protected void sendRedirect(String url)
+   {
+      try
+      {
+         response.sendRedirect(url);
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   protected Cookie getCookie(String cookieName)
+   {
+      if (request.getCookies() == null) return null;
+      for (Cookie cookie : request.getCookies())
+      {
+         if (cookie.getName().equals(cookieName))
+         {
+            return cookie;
+         }
+      }
+      return null;
+   }
+
+   protected String getCookieValue(String cookieName)
+   {
+      Cookie cookie = getCookie(cookieName);
+      if (cookie == null) return null;
+      return cookie.getValue();
+   }
+
+   protected String getQueryParamValue(String paramName)
+   {
+      String query = request.getQueryString();
+      if (query == null) return null;
+      String[] params = query.split("&");
+      for (String param : params)
+      {
+         int eq = param.indexOf('=');
+         if (eq == -1) continue;
+         String name = param.substring(0, eq);
+         if (!name.equals(paramName)) continue;
+         return param.substring(eq + 1);
+      }
+      return null;
+   }
+
+   public String getError()
+   {
+      return getQueryParamValue("error");
+   }
+
+   public String getCode()
+   {
+      return getQueryParamValue("code");
+   }
+
+   protected void setCookie(String name, String value, String domain, String path, boolean secure)
+   {
+      Cookie cookie = new Cookie(name, value);
+      if (domain != null) cookie.setDomain(domain);
+      if (path != null) cookie.setPath(path);
+      if (secure) cookie.setSecure(true);
+      response.addCookie(cookie);
+   }
+
+   protected String getRedirectUri(String state)
+   {
+      String url = getRequestUrl();
+      if (!isRequestSecure() && realmInfo.isSslRequired())
+      {
+         int port = redirectPort;
+         if (port < 0)
+         {
+            // disabled?
+            return null;
+         }
+         UriBuilder secureUrl = UriBuilder.fromUri(url).scheme("https").port(-1);
+         if (port != 443) secureUrl.port(port);
+         url = secureUrl.build().toString();
+      }
+      return realmInfo.getAuthUrl().clone()
+              .queryParam("client_id", realmInfo.getClientId())
+              .queryParam("redirect_uri", url)
+              .queryParam("state", state)
+              .queryParam("login", "true")
+              .build().toString();
+   }
+
+   protected static final AtomicLong counter = new AtomicLong();
+
+   protected String getStateCode()
+   {
+      return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
+   }
+
+   public void loginRedirect()
+   {
+      String state = getStateCode();
+      String redirect = getRedirectUri(state);
+      if (redirect == null)
+      {
+         sendError(Response.Status.FORBIDDEN.getStatusCode());
+         return;
+      }
+      setCookie(realmInfo.getStateCookieName(), state, null, getDefaultCookiePath(), realmInfo.isSslRequired());
+      sendRedirect(redirect);
+   }
+
+   public boolean checkStateCookie()
+   {
+      Cookie stateCookie = getCookie(realmInfo.getStateCookieName());
+
+      if (stateCookie == null)
+      {
+         sendError(400);
+         log.warn("No state cookie");
+         return false;
+      }
+      // reset the cookie
+      Cookie reset = new Cookie(stateCookie.getName(), stateCookie.getValue());
+      reset.setPath(stateCookie.getPath());
+      reset.setMaxAge(0);
+      response.addCookie(reset);
+
+      String stateCookieValue = getCookieValue(realmInfo.getStateCookieName());
+      // its ok to call request.getParameter() because this should be a redirect
+      String state = request.getParameter("state");
+      if (state == null)
+      {
+         sendError(400);
+         log.warn("state parameter was null");
+         return false;
+      }
+      if (!state.equals(stateCookieValue))
+      {
+         sendError(400);
+         log.warn("state parameter invalid");
+         log.warn("cookie: " + stateCookieValue);
+         log.warn("queryParam: " + state);
+         return false;
+      }
+      return true;
+
+   }
+
+   /**
+    * Start or continue the oauth login process.
+    *
+    * if code query parameter is not present, then browser is redirected to authUrl.  The redirect URL will be
+    * the URL of the current request.
+    *
+    * If code query parameter is present, then an access token is obtained by invoking a secure request to the codeUrl.
+    * If the access token is obtained, the browser is again redirected to the current request URL, but any OAuth
+    * protocol specific query parameters are removed.
+    *
+    * @return true if an access token was obtained
+    */
+   public boolean resolveCode(String code)
+   {
+      // abort if not HTTPS
+      if (realmInfo.isSslRequired() && !isRequestSecure())
+      {
+         log.error("SSL is required");
+         sendError(Response.Status.FORBIDDEN.getStatusCode());
+         return false;
+      }
+
+      if (!checkStateCookie()) return false;
+
+      String client_id = realmInfo.getClientId();
+      String password = realmInfo.getCredentials().asMap().getFirst("password");
+      String authHeader = BasicAuthHelper.createHeader(client_id, password);
+      String redirectUri = stripOauthParametersFromRedirect();
+      Form form = new Form();
+      form.param("grant_type", "authorization_code")
+              .param("code", code)
+              .param("redirect_uri", redirectUri);
+
+      Response res = realmInfo.getCodeUrl().request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(form));
+      AccessTokenResponse tokenResponse;
+      try
+      {
+         if (res.getStatus() != 200)
+         {
+            log.error("failed to turn code into token");
+            sendError(Response.Status.FORBIDDEN.getStatusCode());
+            return false;
+         }
+         log.debug("media type: " + res.getMediaType());
+         log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
+         tokenResponse = res.readEntity(AccessTokenResponse.class);
+      }
+      finally
+      {
+         res.close();
+      }
+
+      tokenString = tokenResponse.getToken();
+      try
+      {
+         token = RSATokenVerifier.verifyToken(tokenString, realmInfo.getMetadata());
+         log.debug("Verification succeeded!");
+      }
+      catch (VerificationException e)
+      {
+         log.error("failed verification of token");
+         sendError(Response.Status.FORBIDDEN.getStatusCode());
+         return false;
+      }
+      // redirect to URL without oauth query parameters
+      sendRedirect(redirectUri);
+      return true;
+   }
+
+   /**
+    * strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
+    */
+   protected String stripOauthParametersFromRedirect()
+   {
+      StringBuffer buf = request.getRequestURL().append("?").append(request.getQueryString());
+      UriBuilder builder = UriBuilder.fromUri(buf.toString())
+              .replaceQueryParam("code", null)
+              .replaceQueryParam("state", null);
+      return builder.build().toString();
+   }
+
+
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java
new file mode 100755
index 0000000..081df77
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/UserSessionManagement.java
@@ -0,0 +1,111 @@
+package org.keycloak.adapters.as7;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.SessionEvent;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Manages relationship to users and sessions so that forced admin logout can be implemented
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserSessionManagement implements SessionListener
+{
+   private static final Logger log = Logger.getLogger(UserSessionManagement.class);
+   protected ConcurrentHashMap<String, Map<String, Session>> userSessionMap = new ConcurrentHashMap<String, Map<String, Session>>();
+
+   protected void login(Session session, String username)
+   {
+      Map<String, Session> map = userSessionMap.get(username);
+      if (map == null)
+      {
+         final Map<String, Session> value = new HashMap<String, Session>();
+         map = userSessionMap.putIfAbsent(username, value);
+         if (map == null)
+         {
+            map = value;
+         }
+      }
+      synchronized (map)
+      {
+         map.put(session.getId(), session);
+      }
+      session.addSessionListener(this);
+   }
+
+   public void logoutAll()
+   {
+      List<String> users = new ArrayList<String>();
+      users.addAll(userSessionMap.keySet());
+      for (String user : users) logout(user);
+   }
+
+   public void logoutAllBut(String but)
+   {
+      List<String> users = new ArrayList<String>();
+      users.addAll(userSessionMap.keySet());
+      for (String user : users)
+      {
+         if (!but.equals(user)) logout(user);
+      }
+   }
+
+
+   public void logout(String user)
+   {
+      log.debug("logoutUser: " + user);
+      Map<String, Session> map = userSessionMap.remove(user);
+      if (map == null)
+      {
+         log.debug("no session for user: " + user);
+         return;
+      }
+      log.debug("found session for user");
+      synchronized (map)
+      {
+         for (Session session : map.values())
+         {
+            log.debug("invalidating session for user: " + user);
+            session.setPrincipal(null);
+            session.setAuthType(null);
+            session.getSession().invalidate();
+         }
+      }
+
+   }
+
+   public void sessionEvent(SessionEvent event)
+   {
+      // We only care about session destroyed events
+      if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
+              && (!Session.SESSION_PASSIVATED_EVENT.equals(event.getType())))
+         return;
+
+      // Look up the single session id associated with this session (if any)
+      Session session = event.getSession();
+      GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
+      if (principal == null) return;
+      session.setPrincipal(null);
+      session.setAuthType(null);
+
+      String username = principal.getUserPrincipal().getName();
+      Map<String, Session> map = userSessionMap.get(username);
+      if (map == null) return;
+      synchronized (map)
+      {
+         map.remove(session.getId());
+         if (map.isEmpty()) userSessionMap.remove(username);
+      }
+
+
+   }
+}

integration/pom.xml 21(+21 -0)

diff --git a/integration/pom.xml b/integration/pom.xml
new file mode 100755
index 0000000..5028c1d
--- /dev/null
+++ b/integration/pom.xml
@@ -0,0 +1,21 @@
+<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.0-alpha-1</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <name>Keycloak Integration</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.keycloak</groupId>
+    <artifactId>integration-pom</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>as7-eap6/adapter</module>
+        <!-- <module>as7-eap6/jboss-modules</module> -->
+    </modules>
+</project>

pom.xml 1(+1 -0)

diff --git a/pom.xml b/pom.xml
index 53b9656..bcb889c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,7 @@
     <modules>
         <module>core</module>
         <module>services</module>
+        <module>integration</module>
     </modules>
 
     <dependencyManagement>
diff --git a/services/src/main/java/org/keycloak/services/managers/InstallationManager.java b/services/src/main/java/org/keycloak/services/managers/InstallationManager.java
index 4550ce6..6154547 100755
--- a/services/src/main/java/org/keycloak/services/managers/InstallationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/InstallationManager.java
@@ -20,10 +20,14 @@ public class InstallationManager {
         defaultRealm.setAccessCodeLifespan(60);
         defaultRealm.setSslNotRequired(false);
         defaultRealm.setCookieLoginAllowed(true);
+        defaultRealm.setRegistrationAllowed(true);
         manager.generateRealmKeys(defaultRealm);
         defaultRealm.updateRealm();
         defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
         defaultRealm.getIdm().add(new SimpleRole(RegistrationService.REALM_CREATOR_ROLE));
+    }
 
+    public boolean isInstalled(RealmManager manager) {
+        return manager.defaultRealm() != null;
     }
 }
diff --git a/services/src/main/java/org/keycloak/services/models/RealmModel.java b/services/src/main/java/org/keycloak/services/models/RealmModel.java
index 235bba1..0cdf45c 100755
--- a/services/src/main/java/org/keycloak/services/models/RealmModel.java
+++ b/services/src/main/java/org/keycloak/services/models/RealmModel.java
@@ -17,7 +17,6 @@ import org.picketlink.idm.model.Attribute;
 import org.picketlink.idm.model.Grant;
 import org.picketlink.idm.model.Realm;
 import org.picketlink.idm.model.Role;
-import org.picketlink.idm.model.SimpleAgent;
 import org.picketlink.idm.model.Tier;
 import org.picketlink.idm.model.User;
 import org.picketlink.idm.query.IdentityQuery;
@@ -48,21 +47,22 @@ public class RealmModel {
     public static final String REALM_PUBLIC_KEY = "publicKey";
     public static final String REALM_IS_SSL_NOT_REQUIRED = "isSSLNotRequired";
     public static final String REALM_IS_COOKIE_LOGIN_ALLOWED = "isCookieLoginAllowed";
+    public static final String REALM_IS_REGISTRATION_ALLOWED = "isRegistrationAllowed";
 
     protected Realm realm;
     protected Agent realmAgent;
-    protected IdentitySession IdentitySession;
+    protected IdentitySession identitySession;
     protected volatile transient PublicKey publicKey;
     protected volatile transient PrivateKey privateKey;
 
-    public RealmModel(Realm realm, IdentitySession factory) {
+    public RealmModel(Realm realm, IdentitySession session) {
         this.realm = realm;
-        this.IdentitySession = factory;
+        this.identitySession = session;
         realmAgent = getIdm().getAgent(REALM_AGENT_ID);
     }
 
     public IdentityManager getIdm() {
-        return IdentitySession.createIdentityManager(realm);
+        return identitySession.createIdentityManager(realm);
     }
 
     public void updateRealm() {
@@ -105,6 +105,14 @@ public class RealmModel {
         realmAgent.setAttribute(new Attribute<Boolean>(REALM_IS_COOKIE_LOGIN_ALLOWED, cookieLoginAllowed));
     }
 
+    public boolean isRegistrationAllowed() {
+        return (Boolean) realmAgent.getAttribute(REALM_IS_REGISTRATION_ALLOWED).getValue();
+    }
+
+    public void setRegistrationAllowed(boolean registrationAllowed) {
+        realmAgent.setAttribute(new Attribute<Boolean>(REALM_IS_REGISTRATION_ALLOWED, registrationAllowed));
+    }
+
     public long getTokenLifespan() {
         return (Long) realmAgent.getAttribute(REALM_TOKEN_LIFESPAN).getValue();
     }
@@ -269,8 +277,8 @@ public class RealmModel {
         List<ResourceRelationship> results = query.getResultList();
         List<ResourceModel> resources = new ArrayList<ResourceModel>();
         for (ResourceRelationship relationship : results) {
-            Tier resourceTier = IdentitySession.findTier(relationship.getResourceId());
-            ResourceModel model = new ResourceModel(resourceTier,relationship, this, IdentitySession);
+            Tier resourceTier = identitySession.findTier(relationship.getResourceId());
+            ResourceModel model = new ResourceModel(resourceTier,relationship, this, identitySession);
             resources.add(model);
         }
 
@@ -278,14 +286,14 @@ public class RealmModel {
     }
 
     public ResourceModel addResource(String name) {
-        Tier newTier = IdentitySession.createTier(RealmManager.generateId());
+        Tier newTier = identitySession.createTier(RealmManager.generateId());
         IdentityManager idm = getIdm();
         ResourceRelationship relationship = new ResourceRelationship();
         relationship.setResourceName(name);
         relationship.setRealmAgent(realmAgent);
         relationship.setResourceId(newTier.getId());
         idm.add(relationship);
-        return new ResourceModel(newTier, relationship, this, IdentitySession);
+        return new ResourceModel(newTier, relationship, this, identitySession);
     }
 
     public Set<String> getRoleMappings(User user) {
@@ -322,7 +330,7 @@ public class RealmModel {
     }
 
     public boolean isRealmAdmin(Agent agent) {
-        IdentityManager idm = new RealmManager(IdentitySession).defaultRealm().getIdm();
+        IdentityManager idm = new RealmManager(identitySession).defaultRealm().getIdm();
         RelationshipQuery<RealmAdminRelationship> query = idm.createRelationshipQuery(RealmAdminRelationship.class);
         query.setParameter(RealmAdminRelationship.REALM, realm.getId());
         query.setParameter(RealmAdminRelationship.ADMIN, agent);
@@ -331,7 +339,7 @@ public class RealmModel {
     }
 
     public void addRealmAdmin(Agent agent) {
-        IdentityManager idm = new RealmManager(IdentitySession).defaultRealm().getIdm();
+        IdentityManager idm = new RealmManager(identitySession).defaultRealm().getIdm();
         RealmAdminRelationship relationship = new RealmAdminRelationship();
         relationship.setAdmin(agent);
         relationship.setRealm(realm.getId());
diff --git a/services/src/main/java/org/keycloak/services/resources/RegistrationService.java b/services/src/main/java/org/keycloak/services/resources/RegistrationService.java
index 3718f76..55c576c 100755
--- a/services/src/main/java/org/keycloak/services/resources/RegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RegistrationService.java
@@ -11,6 +11,7 @@ import org.picketlink.idm.model.SimpleUser;
 import org.picketlink.idm.model.User;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.ForbiddenException;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.core.Context;
@@ -32,15 +33,21 @@ public class RegistrationService {
     protected UriInfo uriInfo;
 
     @Context
-    protected IdentitySession IdentitySession;
+    protected IdentitySession identitySession;
 
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response register(UserRepresentation newUser) {
-        IdentitySession.getTransaction().begin();
+        identitySession.getTransaction().begin();
         try {
-            RealmManager realmManager = new RealmManager(IdentitySession);
+            RealmManager realmManager = new RealmManager(identitySession);
             RealmModel defaultRealm = realmManager.defaultRealm();
+            if (!defaultRealm.isEnabled()) {
+                throw new ForbiddenException();
+            }
+            if (!defaultRealm.isRegistrationAllowed()) {
+                throw new ForbiddenException();
+            }
             User user = defaultRealm.getIdm().getUser(newUser.getUsername());
             if (user != null) {
                 return Response.status(400).type("text/plain").entity("user exists").build();
@@ -56,12 +63,12 @@ public class RegistrationService {
             }
             Role realmCreator = defaultRealm.getIdm().getRole(REALM_CREATOR_ROLE);
             defaultRealm.getIdm().grantRole(user, realmCreator);
-            IdentitySession.getTransaction().commit();
+            identitySession.getTransaction().commit();
             URI uri = uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(user.getLoginName()).build();
             return Response.created(uri).build();
         } catch (RuntimeException e) {
             logger.error("Failed to register", e);
-            IdentitySession.getTransaction().rollback();
+            identitySession.getTransaction().rollback();
             throw e;
         }
     }