keycloak-aplcache
Changes
examples/wildfly-demo/customer-app/pom.xml 73(+73 -0)
examples/wildfly-demo/customer-app/src/main/java/org/jboss/resteasy/example/oauth/CustomerDatabaseClient.java 36(+36 -0)
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml 11(+11 -0)
examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java 26(+26 -0)
examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java 13(+13 -0)
examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java 26(+26 -0)
examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-deployment-structure.xml 9(+9 -0)
examples/wildfly-demo/pom.xml 43(+43 -0)
examples/wildfly-demo/product-app/pom.xml 73(+73 -0)
examples/wildfly-demo/product-app/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java 36(+36 -0)
examples/wildfly-demo/README.md 79(+79 -0)
examples/wildfly-demo/server/pom.xml 142(+142 -0)
examples/wildfly-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java 59(+59 -0)
examples/wildfly-demo/third-party/pom.xml 61(+61 -0)
examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java 69(+69 -0)
examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java 69(+69 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java 3(+2 -1)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java 7(+4 -3)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/AuthServerConfig.java 237(+0 -237)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaManagedResourceConfigLoader.java 27(+27 -0)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java 2(+1 -1)
integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java 29(+5 -24)
integration/pom.xml 1(+1 -0)
integration/undertow/pom.xml 78(+78 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java 116(+116 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/BearerTokenAuthenticator.java 149(+149 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java 142(+142 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java 75(+75 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java 294(+294 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/PreflightCorsHandler.java 80(+80 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAuthenticatedActionsHandler.java 48(+48 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java 48(+48 -0)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletOAuthAuthenticator.java 23(+23 -0)
Details
diff --git a/core/src/main/java/org/keycloak/RealmConfiguration.java b/core/src/main/java/org/keycloak/RealmConfiguration.java
index 5e4a7a4..90275fe 100755
--- a/core/src/main/java/org/keycloak/RealmConfiguration.java
+++ b/core/src/main/java/org/keycloak/RealmConfiguration.java
@@ -2,6 +2,7 @@ package org.keycloak;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
+import org.keycloak.adapters.config.ManagedResourceConfig;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.UriBuilder;
@@ -19,6 +20,12 @@ public class RealmConfiguration {
protected boolean sslRequired = true;
protected String stateCookieName = "OAuth_Token_Request_State";
+ public RealmConfiguration() {
+ }
+
+ public RealmConfiguration(ManagedResourceConfig config) {
+ }
+
public ResourceMetadata getMetadata() {
return metadata;
}
examples/wildfly-demo/customer-app/pom.xml 73(+73 -0)
diff --git a/examples/wildfly-demo/customer-app/pom.xml b/examples/wildfly-demo/customer-app/pom.xml
new file mode 100755
index 0000000..5465e55
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak.example.wildfly.demo</groupId>
+ <artifactId>customer-portal-example</artifactId>
+ <packaging>war</packaging>
+ <name>Customer Portal - Secured via Undertow</name>
+ <description/>
+
+ <repositories>
+ <repository>
+ <id>jboss</id>
+ <name>jboss repo</name>
+ <url>http://repository.jboss.org/nexus/content/groups/public/</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>customer-portal</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <version>7.4.Final</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/wildfly-demo/customer-app/src/main/java/org/jboss/resteasy/example/oauth/CustomerDatabaseClient.java b/examples/wildfly-demo/customer-app/src/main/java/org/jboss/resteasy/example/oauth/CustomerDatabaseClient.java
new file mode 100755
index 0000000..2da2c84
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/java/org/jboss/resteasy/example/oauth/CustomerDatabaseClient.java
@@ -0,0 +1,36 @@
+package org.jboss.resteasy.example.oauth;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.keycloak.SkeletonKeySession;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CustomerDatabaseClient
+{
+ public static List<String> getCustomers(HttpServletRequest request)
+ {
+ SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
+ ResteasyClient client = new ResteasyClientBuilder()
+ .trustStore(session.getMetadata().getTruststore())
+ .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
+ try
+ {
+ Response response = client.target("http://localhost:8080/database/customers").request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getTokenString()).get();
+ return response.readEntity(new GenericType<List<String>>(){});
+ }
+ finally
+ {
+ client.close();
+ }
+ }
+}
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/admin/admin.jsp b/examples/wildfly-demo/customer-app/src/main/webapp/admin/admin.jsp
new file mode 100644
index 0000000..e132e37
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/admin/admin.jsp
@@ -0,0 +1,11 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<html>
+<head>
+ <title>Customer Admin Iterface</title>
+</head>
+<body bgcolor="#E3F6CE">
+<h1>Customer Admin Interface</h1>
+User <b><%=request.getUserPrincipal().getName()%></b> made this request.
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/customers/cors-test.html b/examples/wildfly-demo/customer-app/src/main/webapp/customers/cors-test.html
new file mode 100755
index 0000000..254b2ae
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/customers/cors-test.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html lang="en">
+
+<body>
+
+<script type="text/javascript">
+ console.log('here!!!!!');
+ var xhr1 = new XMLHttpRequest();
+ xhr1.open('GET', '/customer-portal/K_QUERY_BEARER_TOKEN');
+ xhr1.onreadystatechange = function () {
+ console.log('got here');
+ if (this.status == 200 && this.readyState == 4) {
+ var token = this.responseText;
+ console.log('Access token: ' + token);
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', 'http://localhost:8080/database/customers');
+ xhr.withCredentials = true;
+ xhr.setRequestHeader('Authorization', 'Bearer ' + token);
+ xhr.onreadystatechange = function () {
+ console.log('got auth success');
+ if (this.status == 200 && this.readyState == 4) {
+ console.log('db response: ' + this.responseText);
+ } else if (this.status != 200) {
+ console.log('there was an error:' + this.status);
+ }
+ };
+ xhr.send();
+ } else if (this.status != 200) {
+ console.log('there was an error on get bearer token:' + this.status);
+ }
+ };
+ xhr1.send();
+
+
+</script>
+
+</body>
+</html>
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/customers/view.jsp b/examples/wildfly-demo/customer-app/src/main/webapp/customers/view.jsp
new file mode 100755
index 0000000..6e825f7
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/customers/view.jsp
@@ -0,0 +1,28 @@
+<%@ page import="javax.ws.rs.core.UriBuilder" language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<html>
+<head>
+ <title>Customer View Page</title>
+</head>
+<body bgcolor="#E3F6CE">
+<%
+
+ String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
+ .queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString();
+%>
+<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="<%=logoutUri%>">logout</a></p>
+User <b><%=request.getUserPrincipal().getName()%></b> made this request.
+<h2>Customer Listing</h2>
+<%
+java.util.List<String> list = org.jboss.resteasy.example.oauth.CustomerDatabaseClient.getCustomers(request);
+for (String cust : list)
+{
+ out.print("<p>");
+ out.print(cust);
+ out.println("</p>");
+
+}
+%>
+<br><br>
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/index.html b/examples/wildfly-demo/customer-app/src/main/webapp/index.html
new file mode 100644
index 0000000..7b164df
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title></title>
+</head>
+<body bgcolor="#E3F6CE">
+<h1>Customer Portal</h1>
+
+<p><a href="customers/view.jsp">Customer Listing</a></p>
+<p><a href="admin/admin.html">Customer Admin Interface</a></p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..1469973
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,11 @@
+<jboss-deployment-structure>
+ <deployment>
+ <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+ <dependencies>
+ <module name="org.bouncycastle"/>
+ <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
+ <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
+ <module name="org.jboss.resteasy.jose-jwt" />
+ </dependencies>
+ </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-web.xml b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-web.xml
new file mode 100755
index 0000000..3cec19c
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-web.xml
@@ -0,0 +1,5 @@
+<jboss-web>
+ <valve>
+ <class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
+ </valve>
+</jboss-web>
\ No newline at end of file
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/keycloak.json b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/keycloak.json
new file mode 100755
index 0000000..9be971b
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,12 @@
+{
+ "realm" : "demo",
+ "resource" : "customer-portal",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login",
+ "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes",
+ "ssl-not-required" : true,
+ "expose-token" : true,
+ "credentials" : {
+ "password" : "password"
+ }
+}
diff --git a/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/web.xml b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..ac37d2e
--- /dev/null
+++ b/examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>customer-portal</module-name>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Admins</web-resource-name>
+ <url-pattern>/admin/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>admin</role-name>
+ </auth-constraint>
+ </security-constraint>
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Customers</web-resource-name>
+ <url-pattern>/customers/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <!--
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint>
+ </security-constraint> -->
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ <realm-name>commerce</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+</web-app>
diff --git a/examples/wildfly-demo/database-service/pom.xml b/examples/wildfly-demo/database-service/pom.xml
new file mode 100755
index 0000000..be20e37
--- /dev/null
+++ b/examples/wildfly-demo/database-service/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak.example.wildfly.demo</groupId>
+ <artifactId>database-service</artifactId>
+ <packaging>war</packaging>
+ <name>JAX-RS Database Service Using OAuth Bearer Tokens</name>
+ <description/>
+ <url>http://maven.apache.org</url>
+
+ <repositories>
+ <repository>
+ <id>jboss</id>
+ <name>jboss repo</name>
+ <url>http://repository.jboss.org/nexus/content/groups/public/</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>database</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <version>7.5.Final</version>
+ </plugin>
+ <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/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java b/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java
new file mode 100644
index 0000000..c6a0efc
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java
@@ -0,0 +1,26 @@
+package org.jboss.resteasy.example.oauth;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Path("customers")
+public class CustomerService
+{
+ @GET
+ @Produces("application/json")
+ public List<String> getCustomers()
+ {
+ ArrayList<String> rtn = new ArrayList<String>();
+ rtn.add("Bill Burke");
+ rtn.add("Ron Sigal");
+ rtn.add("Weinan Li");
+ return rtn;
+ }
+}
diff --git a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java b/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java
new file mode 100644
index 0000000..673ad16
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java
@@ -0,0 +1,13 @@
+package org.jboss.resteasy.example.oauth;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@ApplicationPath("/")
+public class DataApplication extends Application
+{
+}
diff --git a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java b/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java
new file mode 100644
index 0000000..8515dfe
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java
@@ -0,0 +1,26 @@
+package org.jboss.resteasy.example.oauth;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Path("products")
+public class ProductService
+{
+ @GET
+ @Produces("application/json")
+ public List<String> getProducts()
+ {
+ ArrayList<String> rtn = new ArrayList<String>();
+ rtn.add("iphone");
+ rtn.add("ipad");
+ rtn.add("ipod");
+ return rtn;
+ }
+}
diff --git a/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..f1f1ffa
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,9 @@
+<jboss-deployment-structure>
+ <deployment>
+ <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+ <dependencies>
+ <module name="org.bouncycastle"/>
+ <module name="org.jboss.resteasy.jose-jwt" />
+ </dependencies>
+ </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-web.xml b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-web.xml
new file mode 100755
index 0000000..d1ca393
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/jboss-web.xml
@@ -0,0 +1,5 @@
+<jboss-web>
+ <valve>
+ <class-name>org.keycloak.adapters.as7.BearerTokenAuthenticatorValve</class-name>
+ </valve>
+</jboss-web>
\ No newline at end of file
diff --git a/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/keycloak.json
new file mode 100755
index 0000000..6b707d9
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,8 @@
+{
+ "realm" : "demo",
+ "resource" : "database-service",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "enable-cors" : true,
+ "bearer-only" : true
+
+}
diff --git a/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/web.xml b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..4ef7108
--- /dev/null
+++ b/examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>database</module-name>
+
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+<!-- <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint> -->
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ <realm-name>commerce</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+</web-app>
examples/wildfly-demo/pom.xml 43(+43 -0)
diff --git a/examples/wildfly-demo/pom.xml b/examples/wildfly-demo/pom.xml
new file mode 100755
index 0000000..3b13f42
--- /dev/null
+++ b/examples/wildfly-demo/pom.xml
@@ -0,0 +1,43 @@
+<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>Examples</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.keycloak</groupId>
+ <artifactId>wildfly-demo-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <version>7.5.Final</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <modules>
+ <module>server</module>
+ <module>customer-app</module>
+ <module>product-app</module>
+ <module>database-service</module>
+ <module>third-party</module>
+ </modules>
+</project>
examples/wildfly-demo/product-app/pom.xml 73(+73 -0)
diff --git a/examples/wildfly-demo/product-app/pom.xml b/examples/wildfly-demo/product-app/pom.xml
new file mode 100755
index 0000000..7e6e482
--- /dev/null
+++ b/examples/wildfly-demo/product-app/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak.example.wildfly.demo</groupId>
+ <artifactId>product-portal-example</artifactId>
+ <packaging>war</packaging>
+ <name>Product Portal - Secured via Undertow</name>
+ <description/>
+
+ <repositories>
+ <repository>
+ <id>jboss</id>
+ <name>jboss repo</name>
+ <url>http://repository.jboss.org/nexus/content/groups/public/</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>product-portal</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <version>7.5.Final</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/wildfly-demo/product-app/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java b/examples/wildfly-demo/product-app/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
new file mode 100755
index 0000000..1111268
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
@@ -0,0 +1,36 @@
+package org.jboss.resteasy.example.oauth;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.keycloak.SkeletonKeySession;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ProductDatabaseClient
+{
+ public static List<String> getProducts(HttpServletRequest request)
+ {
+ SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
+ ResteasyClient client = new ResteasyClientBuilder()
+ .trustStore(session.getMetadata().getTruststore())
+ .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
+ try
+ {
+ Response response = client.target("http://localhost:8080/database/products").request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getTokenString()).get();
+ return response.readEntity(new GenericType<List<String>>(){});
+ }
+ finally
+ {
+ client.close();
+ }
+ }
+}
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/admin/admin.jsp b/examples/wildfly-demo/product-app/src/main/webapp/admin/admin.jsp
new file mode 100644
index 0000000..b6448d7
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/admin/admin.jsp
@@ -0,0 +1,11 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<html>
+<head>
+ <title>Product Admin Interface</title>
+</head>
+<body bgcolor="#F5F6CE">
+<h1>Product Admin Interface</h1>
+User <b><%=request.getUserPrincipal().getName()%></b> made this request.
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/index.html b/examples/wildfly-demo/product-app/src/main/webapp/index.html
new file mode 100644
index 0000000..e30ebc5
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title></title>
+</head>
+<body bgcolor="#F5F6CE">
+<h1>Product Portal</h1>
+
+<p><a href="products/view.jsp">Product Listing</a></p>
+<p><a href="admin/admin.html">Admin Interface</a></p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/products/view.jsp b/examples/wildfly-demo/product-app/src/main/webapp/products/view.jsp
new file mode 100755
index 0000000..bf1ca5a
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/products/view.jsp
@@ -0,0 +1,28 @@
+<%@ page import="javax.ws.rs.core.UriBuilder" language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<html>
+<head>
+ <title>Product View Page</title>
+</head>
+<body bgcolor="#F5F6CE">
+<%
+ String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
+ .queryParam("redirect_uri", "http://localhost:8080/product-portal").build().toString();
+%>
+
+<p>Goto: <a href="http://localhost:8080/customer-portal">customers</a> | <a href="<%=logoutUri%>">logout</a></p>
+User <b><%=request.getUserPrincipal().getName()%></b> made this request.
+<h2>Product Listing</h2>
+<%
+java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
+for (String cust : list)
+{
+ out.print("<p>");
+ out.print(cust);
+ out.println("</p>");
+
+}
+%>
+<br><br>
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..1469973
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,11 @@
+<jboss-deployment-structure>
+ <deployment>
+ <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+ <dependencies>
+ <module name="org.bouncycastle"/>
+ <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
+ <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
+ <module name="org.jboss.resteasy.jose-jwt" />
+ </dependencies>
+ </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-web.xml b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-web.xml
new file mode 100755
index 0000000..3cec19c
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-web.xml
@@ -0,0 +1,5 @@
+<jboss-web>
+ <valve>
+ <class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
+ </valve>
+</jboss-web>
\ No newline at end of file
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/keycloak.json b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/keycloak.json
new file mode 100755
index 0000000..26bc1fe
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,11 @@
+{
+ "realm" : "demo",
+ "resource" : "product-portal",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login",
+ "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes",
+ "ssl-not-required" : true,
+ "credentials" : {
+ "password" : "password"
+ }
+}
diff --git a/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/web.xml b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..bfcffb8
--- /dev/null
+++ b/examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>product-portal</module-name>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Admins</web-resource-name>
+ <url-pattern>/admin/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>admin</role-name>
+ </auth-constraint>
+ </security-constraint>
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Products</web-resource-name>
+ <url-pattern>/products/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+<!--
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint>
+ </security-constraint>
+ -->
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ <realm-name>commerce</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+</web-app>
examples/wildfly-demo/README.md 79(+79 -0)
diff --git a/examples/wildfly-demo/README.md b/examples/wildfly-demo/README.md
new file mode 100755
index 0000000..0193e93
--- /dev/null
+++ b/examples/wildfly-demo/README.md
@@ -0,0 +1,79 @@
+Login, Distributed SSO, Distributed Logout, and Oauth Token Grant AS7 Examples
+===================================
+The following examples requires JBoss AS7 or EAP 6.1, and Resteasy 3.0.2 and has been tested on version EAP 6.1. Here's the highlights of the examples
+* Delegating authentication of a web app to the remote authentication server via OAuth 2 protocols
+* Distributed Single-Sign-On and Single-Logout
+* Transferring identity and role mappings via a special bearer token (Skeleton Key Token).
+* Bearer token authentication and authorization of JAX-RS services
+* Obtaining bearer tokens via the OAuth2 protocol
+
+There are 5 WAR projects. These all will run on the same jboss instance, but pretend each one is running on a different
+machine on the network or Internet.
+* **auth-server**: This is the keycloak SSO auth server
+* **customer-app** A WAR applications that does remote login using OAUTH2 browser redirects with the auth server
+* **product-app** A WAR applications that does remote login using OAUTH2 browser redirects with the auth server
+* **database-service** JAX-RS services authenticated by bearer tokens only. The customer and product app invoke on it
+ to get data
+* **third-party** Simple WAR that obtain a bearer token using OAuth2 using browser redirects to the auth-server.
+
+The UI of each of these applications is very crude and exists just to show our OAuth2 implementation in action.
+
+
+Step 1: Make sure you've upgraded Resteasy
+--------------------------------------
+The first thing you is upgrade Resteasy to 3.0.4 within JBoss as described [here](http://docs.jboss.org/resteasy/docs/3.0.4.Final/userguide/html/Installation_Configuration.html#upgrading-as7)
+
+
+Step 2: Boot JBoss
+---------------------------------------
+Boot JBoss in 'standalone' mode.
+
+Step 3: Build and deploy
+---------------------------------------
+next you must build and deploy
+
+1. cd as7-eap-demo
+2. mvn clean install
+3. mvn jboss-as:deploy
+
+Step 4: Login and Observe Apps
+---------------------------------------
+Try going to the customer app and viewing customer data:
+
+[http://localhost:8080/customer-portal/customers/view.jsp](http://localhost:8080/customer-portal/customers/view.jsp)
+
+This should take you to the auth-server login screen. Enter username: bburke@redhat.com and password: password.
+
+If you click on the products link, you'll be take to the products app and show a product listing. The redirects
+are still happening, but the auth-server knows you are already logged in so the login is bypassed.
+
+If you click on the logout link of either of the product or customer app, you'll be logged out of all the applications.
+
+Step 5: Traditional OAuth2 Example
+----------------------------------
+The customer and product apps are logins. The third-party app is the traditional OAuth2 usecase of a client wanting
+to get permission to access a user's data. To run this example
+
+[http://localhost:8080/oauth-client](http://localhost:8080/oauth-client)
+
+If you area already logged in, you will not be asked for a username and password, but you will be redirected to
+an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
+
+Admin Console
+==========================
+
+1. Register or login
+
+You'll have to first register and create an account
+
+Login:
+[http://localhost:8080/auth-server/rest/saas/login](http://localhost:8080/auth-server/rest/saas/login)
+
+Register:
+[http://localhost:8080/auth-server/rest/saas/registrations](http://localhost:8080/auth-server/rest/saas/registrations)
+
+2. Next you'll be brought to the admin console. Click "New Realm" button and start doing stuff.
+
+
+
+
examples/wildfly-demo/server/pom.xml 142(+142 -0)
diff --git a/examples/wildfly-demo/server/pom.xml b/examples/wildfly-demo/server/pom.xml
new file mode 100755
index 0000000..fc030e0
--- /dev/null
+++ b/examples/wildfly-demo/server/pom.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak.example.wildfly.demo</groupId>
+ <artifactId>keycloak-server</artifactId>
+ <packaging>war</packaging>
+ <name>Keycloak Demo</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jose-jwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-services</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-google</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-twitter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-social-facebook</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-forms</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-admin-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-admin-ui-styles</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxrs</artifactId>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>jaxrs-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>de.flapdoodle.embed</groupId>
+ <artifactId>de.flapdoodle.embed.mongo</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>auth-server</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <version>7.5.Final</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/wildfly-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java b/examples/wildfly-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java
new file mode 100755
index 0000000..0b7b49d
--- /dev/null
+++ b/examples/wildfly-demo/server/src/main/java/org/keycloak/example/demo/DemoApplication.java
@@ -0,0 +1,59 @@
+package org.keycloak.example.demo;
+
+import org.jboss.resteasy.jwt.JsonSerialization;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.ApplianceBootstrap;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.KeycloakApplication;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DemoApplication extends KeycloakApplication {
+
+ public DemoApplication(@Context ServletContext servletContext) {
+ super(servletContext);
+ KeycloakSession session = factory.createSession();
+ session.getTransaction().begin();
+ ApplianceBootstrap bootstrap = new ApplianceBootstrap();
+ bootstrap.bootstrap(session);
+ install(new RealmManager(session));
+ session.getTransaction().commit();
+ }
+
+ public void install(RealmManager manager) {
+ RealmRepresentation rep = loadJson("META-INF/testrealm.json");
+ RealmModel realm = manager.createRealm("demo", rep.getRealm());
+ manager.importRealm(rep, realm);
+
+ }
+
+ public static RealmRepresentation loadJson(String path)
+ {
+ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ int c;
+ try {
+ while ( (c = is.read()) != -1)
+ {
+ os.write(c);
+ }
+ byte[] bytes = os.toByteArray();
+ //System.out.println(new String(bytes));
+
+ return JsonSerialization.fromBytes(RealmRepresentation.class, bytes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+}
diff --git a/examples/wildfly-demo/server/src/main/resources/META-INF/persistence.xml b/examples/wildfly-demo/server/src/main/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..b949715
--- /dev/null
+++ b/examples/wildfly-demo/server/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,31 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+ version="1.0">
+ <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+ <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
+
+ <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
+ <class>org.keycloak.models.jpa.entities.ApplicationScopeMappingEntity</class>
+ <class>org.keycloak.models.jpa.entities.ApplicationUserRoleMappingEntity</class>
+ <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+ <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
+ <class>org.keycloak.models.jpa.entities.RealmEntity</class>
+ <class>org.keycloak.models.jpa.entities.RealmScopeMappingEntity</class>
+ <class>org.keycloak.models.jpa.entities.RealmUserRoleMappingEntity</class>
+ <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
+ <class>org.keycloak.models.jpa.entities.RoleEntity</class>
+ <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+ <class>org.keycloak.models.jpa.entities.UserEntity</class>
+ <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
+
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+ <properties>
+ <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+ <property name="hibernate.show_sql" value="false" />
+ <property name="hibernate.format_sql" value="false" />
+ </properties>
+ </persistence-unit>
+
+</persistence>
diff --git a/examples/wildfly-demo/server/src/main/resources/META-INF/testrealm.json b/examples/wildfly-demo/server/src/main/resources/META-INF/testrealm.json
new file mode 100755
index 0000000..284a4be
--- /dev/null
+++ b/examples/wildfly-demo/server/src/main/resources/META-INF/testrealm.json
@@ -0,0 +1,91 @@
+{
+ "realm": "demo",
+ "enabled": true,
+ "tokenLifespan": 300,
+ "accessCodeLifespan": 10,
+ "accessCodeLifespanUserAction": 600,
+ "sslNotRequired": true,
+ "cookieLoginAllowed": true,
+ "registrationAllowed": true,
+ "social": true,
+ "automaticRegistrationAfterSocialLogin": false,
+ "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "requiredCredentials": [ "password" ],
+ "requiredApplicationCredentials": [ "password" ],
+ "requiredOAuthClientCredentials": [ "password" ],
+ "defaultRoles": [ "user" ],
+ "users" : [
+ {
+ "username" : "bburke@redhat.com",
+ "enabled": true,
+ "attributes" : {
+ "email" : "bburke@redhat.com"
+ },
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ]
+ },
+ {
+ "username" : "third-party",
+ "enabled": true,
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ]
+ }
+ ],
+ "roles": [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Have Administrator privileges"
+ }
+ ],
+ "roleMappings": [
+ {
+ "username": "bburke@redhat.com",
+ "roles": ["user"]
+ },
+ {
+ "username": "third-party",
+ "roles": ["KEYCLOAK_IDENTITY_REQUESTER"]
+ }
+ ],
+ "scopeMappings": [
+ {
+ "username": "third-party",
+ "roles": ["user"]
+ }
+ ],
+ "applications": [
+ {
+ "name": "customer-portal",
+ "enabled": true,
+ "adminUrl": "http://localhost:8080/customer-portal/j_admin_request",
+ "useRealmMappings": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
+ ]
+ },
+ {
+ "name": "product-portal",
+ "enabled": true,
+ "adminUrl": "http://localhost:8080/product-portal/j_admin_request",
+ "useRealmMappings": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/wildfly-demo/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/wildfly-demo/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..8caa96f
--- /dev/null
+++ b/examples/wildfly-demo/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,10 @@
+<jboss-deployment-structure>
+ <deployment>
+ <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+ <dependencies>
+ <module name="org.jboss.resteasy.jose-jwt"/>
+ <module name="org.jboss.resteasy.resteasy-crypto"/>
+ <module name="org.bouncycastle"/>
+ </dependencies>
+ </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/examples/wildfly-demo/server/src/main/webapp/WEB-INF/web.xml b/examples/wildfly-demo/server/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..fafd744
--- /dev/null
+++ b/examples/wildfly-demo/server/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>auth-server</module-name>
+
+ <servlet>
+ <servlet-name>Resteasy</servlet-name>
+ <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.keycloak.example.demo.DemoApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>resteasy.servlet.mapping.prefix</param-name>
+ <param-value>/rest</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ </servlet>
+
+ <listener>
+ <listener-class>org.keycloak.services.listeners.MongoRunnerListener</listener-class>
+ </listener>
+
+ <filter>
+ <filter-name>Keycloak Session Management</filter-name>
+ <filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>Keycloak Session Management</filter-name>
+ <url-pattern>/rest/*</url-pattern>
+ </filter-mapping>
+
+ <servlet-mapping>
+ <servlet-name>Resteasy</servlet-name>
+ <url-pattern>/rest/*</url-pattern>
+ </servlet-mapping>
+
+ <!--
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint>
+ </security-constraint> -->
+
+</web-app>
examples/wildfly-demo/third-party/pom.xml 61(+61 -0)
diff --git a/examples/wildfly-demo/third-party/pom.xml b/examples/wildfly-demo/third-party/pom.xml
new file mode 100755
index 0000000..4cdfbf1
--- /dev/null
+++ b/examples/wildfly-demo/third-party/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak.example.wildfly.demo</groupId>
+ <artifactId>oauth-client-example</artifactId>
+ <packaging>war</packaging>
+ <name>Simple OAuth Wildfly Client</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <version>1.0.1.Final</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>oauth-client</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <version>7.5.Final</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java b/examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java
new file mode 100755
index 0000000..717cd3e
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java
@@ -0,0 +1,69 @@
+package org.jboss.resteasy.example.oauth;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.keycloak.servlet.ServletOAuthClient;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.KeyStore;
+
+/**
+ * Stupid init code to load up the truststore so we can make appropriate SSL connections
+ * You really should use a better way of initializing this stuff.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class Bootstrap implements ServletContextListener {
+
+ private ServletOAuthClient client;
+
+ 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 contextInitialized(ServletContextEvent sce) {
+ client = new ServletOAuthClient();
+/*
+ // hardcoded, WARNING, you should really have a better way of doing this
+ // configuration. Either use something like Spring or CDI, or even pull
+ // config vales from context-params
+ String truststorePath = "${jboss.server.config.dir}/client-truststore.ts";
+ String truststorePassword = "password";
+ truststorePath = EnvUtil.replace(truststorePath);
+ KeyStore truststore = null;
+ try
+ {
+ truststore = loadKeyStore(truststorePath, truststorePassword);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ client.setTruststore(truststore);
+ */
+ client.setClientId("third-party");
+ client.setPassword("password");
+ client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login");
+ client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes");
+ client.setClient(new ResteasyClientBuilder().build());
+ client.start();
+ sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client);
+
+
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ client.stop();
+ }
+}
diff --git a/examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java b/examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
new file mode 100755
index 0000000..d21c823
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
@@ -0,0 +1,69 @@
+package org.jboss.resteasy.example.oauth;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.keycloak.servlet.ServletOAuthClient;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ProductDatabaseClient {
+ public static void redirect(HttpServletRequest request, HttpServletResponse response) {
+ // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
+ // that is set in the Bootstrap context listenr in this project.
+ // You really should come up with a better way to initialize
+ // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
+ // and take a look how it works.
+ ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
+ try {
+ oAuthClient.redirectRelative("pull_data.jsp", request, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static List<String> getProducts(HttpServletRequest request) {
+ // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
+ // that is set in the Bootstrap context listenr in this project.
+ // You really should come up with a better way to initialize
+ // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
+ // and take a look how it works.
+ ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
+ String token = oAuthClient.getBearerToken(request);
+ ResteasyClient client = new ResteasyClientBuilder()
+ .trustStore(oAuthClient.getTruststore())
+ .hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
+ try {
+ // invoke without the Authorization header
+ Response response = client.target("http://localhost:8080/database/products").request().get();
+ response.close();
+ if (response.getStatus() != 401) {
+ response.close();
+ client.close();
+ throw new RuntimeException("Expecting an auth status code: " + response.getStatus());
+ }
+ } finally {
+ }
+ try {
+ Response response = client.target("http://localhost:8080/database/products").request()
+ .header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
+ if (response.getStatus() != 200) {
+ response.close();
+ throw new RuntimeException("Failed to access!: " + response.getStatus());
+ }
+ return response.readEntity(new GenericType<List<String>>() {
+ });
+ } finally {
+ client.close();
+ }
+ }
+}
diff --git a/examples/wildfly-demo/third-party/src/main/webapp/index.html b/examples/wildfly-demo/third-party/src/main/webapp/index.html
new file mode 100644
index 0000000..dbd7d7a
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/webapp/index.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+<h1>Third Party App That Pulls Data Using OAuth</h1>
+<a href="redirect.jsp">Pull Data</a>
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/third-party/src/main/webapp/pull_data.jsp b/examples/wildfly-demo/third-party/src/main/webapp/pull_data.jsp
new file mode 100644
index 0000000..63ad9d9
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/webapp/pull_data.jsp
@@ -0,0 +1,21 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1"%>
+<html>
+<head>
+ <title>Pull Page</title>
+</head>
+<body>
+<h2>Pulled Product Listing</h2>
+<%
+java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
+for (String prod : list)
+{
+ out.print("<p>");
+ out.print(prod);
+ out.println("</p>");
+
+}
+%>
+<br><br>
+</body>
+</html>
\ No newline at end of file
diff --git a/examples/wildfly-demo/third-party/src/main/webapp/redirect.jsp b/examples/wildfly-demo/third-party/src/main/webapp/redirect.jsp
new file mode 100644
index 0000000..35ff870
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/webapp/redirect.jsp
@@ -0,0 +1,3 @@
+<%
+ org.jboss.resteasy.example.oauth.ProductDatabaseClient.redirect(request, response);
+%>
\ No newline at end of file
diff --git a/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
new file mode 100755
index 0000000..74f5dff
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -0,0 +1,9 @@
+<jboss-deployment-structure>
+ <deployment>
+ <!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
+ <dependencies>
+ <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
+ <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
+ </dependencies>
+ </deployment>
+</jboss-deployment-structure>
\ No newline at end of file
diff --git a/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/web.xml b/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..501b203
--- /dev/null
+++ b/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>oauth-client</module-name>
+
+ <listener>
+ <listener-class>org.jboss.resteasy.example.oauth.Bootstrap</listener-class>
+ </listener>
+ <!--
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint>
+ </security-constraint>
+ -->
+
+</web-app>
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
index 5d2d7d4..531aa0c 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/AuthenticatedActionsValve.java
@@ -8,7 +8,7 @@ import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.jboss.logging.Logger;
import org.keycloak.SkeletonKeySession;
-import org.keycloak.adapters.as7.config.ManagedResourceConfig;
+import org.keycloak.adapters.config.ManagedResourceConfig;
import org.keycloak.representations.SkeletonKeyToken;
import javax.management.ObjectName;
@@ -66,6 +66,7 @@ public class AuthenticatedActionsValve extends ValveBase {
protected void queryBearerToken(Request request, Response response, SkeletonKeySession session) throws IOException, ServletException {
log.debugv("queryBearerToken {0}",request.getRequestURI());
if (abortTokenResponse(request, response, session)) return;
+ response.setStatus(200);
response.setContentType("text/plain");
response.getOutputStream().write(session.getTokenString().getBytes());
response.getOutputStream().flush();
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
index 88e05b6..3c314e1 100755
--- 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
@@ -12,8 +12,9 @@ import org.apache.catalina.deploy.LoginConfig;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.ResourceMetadata;
-import org.keycloak.adapters.as7.config.ManagedResourceConfig;
-import org.keycloak.adapters.as7.config.ManagedResourceConfigLoader;
+import org.keycloak.adapters.as7.config.CatalinaManagedResourceConfigLoader;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+import org.keycloak.adapters.config.ManagedResourceConfigLoader;
import javax.security.auth.login.LoginException;
import javax.servlet.ServletException;
@@ -45,7 +46,7 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements
}
protected void init() {
- ManagedResourceConfigLoader managedResourceConfigLoader = new ManagedResourceConfigLoader(context);
+ ManagedResourceConfigLoader managedResourceConfigLoader = new CatalinaManagedResourceConfigLoader(context);
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
managedResourceConfigLoader.init(false);
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaManagedResourceConfigLoader.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaManagedResourceConfigLoader.java
new file mode 100755
index 0000000..ebdec9b
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaManagedResourceConfigLoader.java
@@ -0,0 +1,27 @@
+package org.keycloak.adapters.as7.config;
+
+import org.apache.catalina.Context;
+import org.keycloak.adapters.config.ManagedResourceConfigLoader;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+public class CatalinaManagedResourceConfigLoader extends ManagedResourceConfigLoader {
+
+ public CatalinaManagedResourceConfigLoader(Context context) {
+ InputStream is = null;
+ String path = context.getServletContext().getInitParameter("keycloak.config.file");
+ if (path == null) {
+ is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json");
+ } else {
+ try {
+ is = new FileInputStream(path);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ loadConfig(is);
+ }
+
+}
\ No newline at end of file
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java
index f1ecfd8..2e926c8 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CorsPreflightChecker.java
@@ -3,7 +3,7 @@ package org.keycloak.adapters.as7;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.jboss.logging.Logger;
-import org.keycloak.adapters.as7.config.ManagedResourceConfig;
+import org.keycloak.adapters.config.ManagedResourceConfig;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
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
index a25a12d..3d67ed4 100755
--- 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
@@ -22,8 +22,9 @@ 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.adapters.as7.config.CatalinaManagedResourceConfigLoader;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+import org.keycloak.adapters.config.ManagedResourceConfigLoader;
import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.representations.idm.admin.LogoutAction;
@@ -66,32 +67,12 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
}
protected void init() {
- ManagedResourceConfigLoader managedResourceConfigLoader = new ManagedResourceConfigLoader(context);
+ ManagedResourceConfigLoader managedResourceConfigLoader = new CatalinaManagedResourceConfigLoader(context);
managedResourceConfigLoader.init(true);
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
- 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.setSslRequired(!remoteSkeletonKeyConfig.isSslNotRequired());
-
- for (Map.Entry<String, String> entry : managedResourceConfigLoader.getRemoteSkeletonKeyConfig().getCredentials().entrySet()) {
- realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue());
- }
-
- ResteasyClient client = managedResourceConfigLoader.getClient();
-
- realmConfiguration.setClient(client);
- realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
- realmConfiguration.setCodeUrl(client.target(tokenUrl));
+ realmConfiguration = managedResourceConfigLoader.getRealmConfiguration();
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(remoteSkeletonKeyConfig, getNext(), getContainer(), getController());
setNext(actions);
}
integration/pom.xml 1(+1 -0)
diff --git a/integration/pom.xml b/integration/pom.xml
index 5028c1d..74f4c15 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -16,6 +16,7 @@
<modules>
<module>as7-eap6/adapter</module>
+ <module>undertow</module>
<!-- <module>as7-eap6/jboss-modules</module> -->
</modules>
</project>
integration/undertow/pom.xml 78(+78 -0)
diff --git a/integration/undertow/pom.xml b/integration/undertow/pom.xml
new file mode 100755
index 0000000..4fed19b
--- /dev/null
+++ b/integration/undertow/pom.xml
@@ -0,0 +1,78 @@
+<?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-undertow-adapter</artifactId>
+ <name>Keycloak Undertow 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>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </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>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java
new file mode 100755
index 0000000..15b445e
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AuthenticatedActionsHandler.java
@@ -0,0 +1,116 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+import org.jboss.logging.Logger;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+import org.keycloak.representations.SkeletonKeyToken;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Pre-installed actions that must be authenticated
+ *
+ * Actions include:
+ *
+ * CORS Origin Check and Response headers
+ * K_QUERY_BEARER_TOKEN: Get bearer token from server for Javascripts CORS requests
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AuthenticatedActionsHandler implements HttpHandler {
+ private static final Logger log = Logger.getLogger(AuthenticatedActionsHandler.class);
+ protected ManagedResourceConfig config;
+ protected HttpHandler next;
+
+ protected AuthenticatedActionsHandler(ManagedResourceConfig config, HttpHandler next) {
+ this.config = config;
+ this.next = next;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ log.debugv("AuthenticatedActionsValve.invoke {0}", exchange.getRequestURI());
+ SkeletonKeySession session = getSkeletonKeySession(exchange);
+ if (corsRequest(exchange, session)) return;
+ String requestUri = exchange.getRequestURI();
+ if (requestUri.endsWith("K_QUERY_BEARER_TOKEN")) {
+ queryBearerToken(exchange, session);
+ return;
+ }
+ next.handleRequest(exchange);
+ }
+
+ public SkeletonKeySession getSkeletonKeySession(HttpServerExchange exchange) {
+ SkeletonKeySession skSession = exchange.getAttachment(KeycloakAuthenticationMechanism.SKELETON_KEY_SESSION_ATTACHMENT_KEY);
+ if (skSession != null) return skSession;
+ return null;
+ }
+
+ protected void queryBearerToken(HttpServerExchange exchange, SkeletonKeySession session) throws IOException, ServletException {
+ log.debugv("queryBearerToken {0}",exchange.getRequestURI());
+ if (abortTokenResponse(exchange, session)) return;
+ exchange.setResponseCode(200);
+ exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
+ exchange.getResponseSender().send(session.getTokenString());
+ exchange.endExchange();
+ }
+
+ protected boolean abortTokenResponse(HttpServerExchange exchange, SkeletonKeySession session) throws IOException {
+ if (session == null) {
+ log.debugv("session was null, sending back 401: {0}",exchange.getRequestURI());
+ exchange.setResponseCode(200);
+ exchange.endExchange();
+ return true;
+ }
+ if (!config.isExposeToken()) {
+ exchange.setResponseCode(200);
+ exchange.endExchange();
+ return true;
+ }
+ if (!config.isCors() && exchange.getRequestHeaders().getFirst(Headers.ORIGIN) != null) {
+ exchange.setResponseCode(200);
+ exchange.endExchange();
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean corsRequest(HttpServerExchange exchange, SkeletonKeySession session) throws IOException {
+ if (!config.isCors()) return false;
+ log.debugv("CORS enabled + request.getRequestURI()");
+ String origin = exchange.getRequestHeaders().getFirst("Origin");
+ log.debugv("Origin: {0} uri: {1}", origin, exchange.getRequestURI());
+ if (session != null && origin != null) {
+ SkeletonKeyToken token = session.getToken();
+ Set<String> allowedOrigins = token.getAllowedOrigins();
+ if (log.isDebugEnabled()) {
+ for (String a : allowedOrigins) log.debug(" " + a);
+ }
+ if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
+ if (allowedOrigins == null) {
+ log.debugv("allowedOrigins was null in token");
+ }
+ if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) {
+ log.debugv("allowedOrigins did not contain origin");
+
+ }
+ exchange.setResponseCode(403);
+ exchange.endExchange();
+ return true;
+ }
+ log.debugv("returning origin: {0}", origin);
+ exchange.setResponseCode(200);
+ exchange.getResponseHeaders().put(PreflightCorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+ exchange.getResponseHeaders().put(PreflightCorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ } else {
+ log.debugv("session or origin was null: {0}", exchange.getRequestURI());
+ }
+ return false;
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/BearerTokenAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/BearerTokenAuthenticator.java
new file mode 100755
index 0000000..a76604b
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/BearerTokenAuthenticator.java
@@ -0,0 +1,149 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.jboss.logging.Logger;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.VerificationException;
+import org.keycloak.representations.SkeletonKeyToken;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static io.undertow.util.Headers.AUTHORIZATION;
+import static io.undertow.util.Headers.WWW_AUTHENTICATE;
+import static io.undertow.util.StatusCodes.UNAUTHORIZED;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class BearerTokenAuthenticator {
+ protected ResourceMetadata resourceMetadata;
+ protected Logger log = Logger.getLogger(BearerTokenAuthenticator.class);
+ protected String tokenString;
+ protected SkeletonKeyToken token;
+ protected boolean useResourceRoleMappings;
+ protected String surrogate;
+ protected KeycloakChallenge challenge;
+
+ public BearerTokenAuthenticator(ResourceMetadata resourceMetadata, boolean useResourceRoleMappings) {
+ this.resourceMetadata = resourceMetadata;
+ this.useResourceRoleMappings = useResourceRoleMappings;
+ }
+
+ public KeycloakChallenge getChallenge() {
+ return challenge;
+ }
+
+ public ResourceMetadata getResourceMetadata() {
+ return resourceMetadata;
+ }
+
+ public String getTokenString() {
+ return tokenString;
+ }
+
+ public SkeletonKeyToken getToken() {
+ return token;
+ }
+
+ public String getSurrogate() {
+ return surrogate;
+ }
+
+ public AuthenticationMechanism.AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange) {
+ List<String> authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION);
+ if (authHeaders == null || authHeaders.size() == 0) {
+ challenge = challengeResponse(exchange, null, null);
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ tokenString = null;
+ for (String authHeader : authHeaders) {
+ String[] split = authHeader.trim().split("\\s+");
+ if (split == null || split.length != 2) continue;
+ if (!split[0].equalsIgnoreCase("Bearer")) continue;
+ tokenString = split[1];
+ }
+
+ if (tokenString == null) {
+ challenge = challengeResponse(exchange, null, null);
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ try {
+ token = RSATokenVerifier.verifyToken(tokenString, resourceMetadata);
+ } catch (VerificationException e) {
+ log.error("Failed to verify token", e);
+ challenge = challengeResponse(exchange, "invalid_token", e.getMessage());
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ boolean verifyCaller = false;
+ Set<String> roles = new HashSet<String>();
+ if (useResourceRoleMappings) {
+ verifyCaller = token.isVerifyCaller(resourceMetadata.getResourceName());
+ } else {
+ verifyCaller = token.isVerifyCaller();
+ }
+ surrogate = null;
+ if (verifyCaller) {
+ if (token.getTrustedCertificates() == null || token.getTrustedCertificates().size() == 0) {
+ log.warn("No trusted certificates in token");
+ challenge = clientCertChallenge();
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+
+ // for now, we just make sure Undertow did two-way SSL
+ // assume JBoss Web verifies the client cert
+ X509Certificate[] chain = new X509Certificate[0];
+ try {
+ chain = exchange.getConnection().getSslSessionInfo().getPeerCertificateChain();
+ } catch (SSLPeerUnverifiedException ignore) {
+
+ }
+ if (chain == null || chain.length == 0) {
+ log.warn("No certificates provided by undertow to verify the caller");
+ challenge = clientCertChallenge();
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ surrogate = chain[0].getSubjectDN().getName();
+ }
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+
+ protected KeycloakChallenge clientCertChallenge() {
+ return new KeycloakChallenge() {
+ @Override
+ public AuthenticationMechanism.ChallengeResult sendChallenge(HttpServerExchange httpServerExchange, SecurityContext securityContext) {
+ // do the same thing as client cert auth
+ return new AuthenticationMechanism.ChallengeResult(false);
+ }
+ };
+ }
+
+
+ protected KeycloakChallenge challengeResponse(HttpServerExchange exchange, String error, String description) {
+ 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("\"");
+ }
+ String challenge = header.toString();
+ exchange.getResponseHeaders().add(WWW_AUTHENTICATE, challenge);
+ return new KeycloakChallenge() {
+ @Override
+ public AuthenticationMechanism.ChallengeResult sendChallenge(HttpServerExchange httpServerExchange, SecurityContext securityContext) {
+ return new AuthenticationMechanism.ChallengeResult(true, UNAUTHORIZED);
+ }
+ };
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
new file mode 100755
index 0000000..75b3df5
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakAuthenticationMechanism.java
@@ -0,0 +1,142 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import org.jboss.logging.Logger;
+import org.keycloak.RealmConfiguration;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.SkeletonKeyPrincipal;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+import org.keycloak.representations.SkeletonKeyToken;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Set;
+
+import static io.undertow.util.Headers.WWW_AUTHENTICATE;
+import static io.undertow.util.StatusCodes.UNAUTHORIZED;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakAuthenticationMechanism implements AuthenticationMechanism {
+ protected Logger log = Logger.getLogger(KeycloakAuthenticationMechanism.class);
+
+ public static final AttachmentKey<KeycloakChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(KeycloakChallenge.class);
+ public static final AttachmentKey<SkeletonKeySession> SKELETON_KEY_SESSION_ATTACHMENT_KEY = AttachmentKey.create(SkeletonKeySession.class);
+
+ protected ResourceMetadata resourceMetadata;
+ protected ManagedResourceConfig config;
+ protected RealmConfiguration realmConfig;
+ protected int sslRedirectPort;
+
+ public KeycloakAuthenticationMechanism(ResourceMetadata resourceMetadata, ManagedResourceConfig config, RealmConfiguration realmConfig, int sslRedirectPort) {
+ this.resourceMetadata = resourceMetadata;
+ this.config = config;
+ this.realmConfig = realmConfig;
+ this.sslRedirectPort = sslRedirectPort;
+ }
+
+ public KeycloakAuthenticationMechanism(ResourceMetadata resourceMetadata, ManagedResourceConfig config, RealmConfiguration realmConfig) {
+ this.resourceMetadata = resourceMetadata;
+ this.config = config;
+ this.realmConfig = realmConfig;
+ }
+
+ @Override
+ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+ BearerTokenAuthenticator bearer = createBearerTokenAuthenticator();
+ AuthenticationMechanismOutcome outcome = bearer.authenticate(exchange);
+ if (outcome == AuthenticationMechanismOutcome.NOT_AUTHENTICATED) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, bearer.getChallenge());
+ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ else if (outcome == AuthenticationMechanismOutcome.AUTHENTICATED) {
+ final SkeletonKeyToken token = bearer.getToken();
+ String surrogate = bearer.getSurrogate();
+ SkeletonKeySession session = new SkeletonKeySession(bearer.getTokenString(), token, resourceMetadata);
+ propagateBearer(exchange, session);
+ completeAuthentication(exchange, securityContext, token, surrogate);
+ return AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+ else if (config.isBearerOnly()) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, bearer.getChallenge());
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ OAuthAuthenticator oauth = createOAuthAuthenticator(exchange);
+ outcome = oauth.authenticate();
+ if (outcome == AuthenticationMechanismOutcome.NOT_AUTHENTICATED) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, oauth.getChallenge());
+ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ else if (outcome == AuthenticationMechanismOutcome.NOT_ATTEMPTED) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, oauth.getChallenge());
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+
+ }
+ SkeletonKeySession session = new SkeletonKeySession(oauth.getTokenString(), oauth.getToken(), resourceMetadata);
+ propagateOauth(exchange, session);
+ completeAuthentication(exchange, securityContext, oauth.getToken(), null);
+ log.info("AUTHENTICATED");
+ return AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+
+ protected OAuthAuthenticator createOAuthAuthenticator(HttpServerExchange exchange) {
+ return new OAuthAuthenticator(exchange, realmConfig, sslRedirectPort);
+ }
+
+ protected BearerTokenAuthenticator createBearerTokenAuthenticator() {
+ return new BearerTokenAuthenticator(resourceMetadata, config.isUseResourceRoleMappings());
+ }
+
+ protected void completeAuthentication(HttpServerExchange exchange, SecurityContext securityContext, SkeletonKeyToken token, String surrogate) {
+ final SkeletonKeyPrincipal skeletonKeyPrincipal = new SkeletonKeyPrincipal(token.getPrincipal(), surrogate);
+ Set<String> roles = null;
+ if (config.isUseResourceRoleMappings()) {
+ 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();
+ }
+ if (roles == null) roles = Collections.emptySet();
+ final Set<String> accountRoles = roles;
+ Account account = new Account() {
+ @Override
+ public Principal getPrincipal() {
+ return skeletonKeyPrincipal;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return accountRoles;
+ }
+ };
+ securityContext.authenticationComplete(account, "FORM");
+ }
+
+ protected void propagateBearer(HttpServerExchange exchange, SkeletonKeySession session) {
+ exchange.putAttachment(SKELETON_KEY_SESSION_ATTACHMENT_KEY, session);
+
+ }
+
+ protected void propagateOauth(HttpServerExchange exchange, SkeletonKeySession session) {
+ exchange.putAttachment(SKELETON_KEY_SESSION_ATTACHMENT_KEY, session);
+ }
+
+
+ @Override
+ public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+ KeycloakChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
+ if (challenge != null) {
+ return challenge.sendChallenge(exchange, securityContext);
+ }
+ return new ChallengeResult(false);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java
new file mode 100755
index 0000000..6e77c89
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface KeycloakChallenge {
+ public AuthenticationMechanism.ChallengeResult sendChallenge(HttpServerExchange httpServerExchange, SecurityContext securityContext);
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
new file mode 100755
index 0000000..5ff6e1e
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java
@@ -0,0 +1,75 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.servlet.ServletExtension;
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.ServletSessionConfig;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+import org.keycloak.adapters.config.ManagedResourceConfigLoader;
+
+import javax.servlet.ServletContext;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakServletExtension implements ServletExtension {
+ protected Logger log = Logger.getLogger(KeycloakServletExtension.class);
+
+ @Override
+ public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) {
+ if (deploymentInfo.getLoginConfig() == null || !deploymentInfo.getLoginConfig().getAuthMethod().equalsIgnoreCase("keycloak")) {
+ log.info("auth-method is not keycloak!");
+ return;
+ }
+ log.info("KeycloakServletException initialization");
+ deploymentInfo.setIgnoreStandardAuthenticationMechanism(true);
+ InputStream is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json");
+ if (is == null) throw new RuntimeException("Unable to find /WEB-INF/keycloak.json configuration file");
+ ManagedResourceConfigLoader loader = new ManagedResourceConfigLoader(is);
+ loader.init(true);
+ ManagedResourceConfig keycloakConfig = loader.getRemoteSkeletonKeyConfig();
+ PreflightCorsHandler.Wrapper preflight = new PreflightCorsHandler.Wrapper(keycloakConfig);
+ ServletKeycloakAuthenticationMechanism auth = new ServletKeycloakAuthenticationMechanism(loader.getResourceMetadata(),
+ keycloakConfig,
+ loader.getRealmConfiguration(),
+ deploymentInfo.getConfidentialPortManager());
+ ServletAuthenticatedActionsHandler.Wrapper actions = new ServletAuthenticatedActionsHandler.Wrapper(keycloakConfig);
+
+ // setup handlers
+
+ deploymentInfo.addInitialHandlerChainWrapper(preflight); // cors preflight
+ deploymentInfo.addAuthenticationMechanism(auth); // authentication
+ deploymentInfo.addInnerHandlerChainWrapper(ServletPropagateSessionHandler.WRAPPER); // propagates SkeletonKeySession
+ deploymentInfo.addInnerHandlerChainWrapper(actions); // handles authenticated actions and cors.
+
+ deploymentInfo.setIdentityManager(new IdentityManager() {
+ @Override
+ public Account verify(Account account) {
+ log.info("Verifying account in IdentityManager");
+ return account;
+ }
+
+ @Override
+ public Account verify(String id, Credential credential) {
+ log.warn("Shouldn't call verify!!!");
+ throw new IllegalStateException("Not allowed");
+ }
+
+ @Override
+ public Account verify(Credential credential) {
+ log.warn("Shouldn't call verify!!!");
+ throw new IllegalStateException("Not allowed");
+ }
+ });
+
+ log.info("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
+ ServletSessionConfig cookieConfig = new ServletSessionConfig();
+ cookieConfig.setPath(deploymentInfo.getContextPath());
+ deploymentInfo.setServletSessionConfig(cookieConfig);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java
new file mode 100755
index 0000000..23fd318
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/OAuthAuthenticator.java
@@ -0,0 +1,294 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.handlers.Cookie;
+import io.undertow.server.handlers.CookieImpl;
+import io.undertow.util.Headers;
+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.keycloak.representations.idm.CredentialRepresentation;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.util.Deque;
+import java.util.Map;
+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 OAuthAuthenticator {
+ private static final Logger log = Logger.getLogger(OAuthAuthenticator.class);
+ protected RealmConfiguration realmInfo;
+ protected int sslRedirectPort;
+ protected String tokenString;
+ protected SkeletonKeyToken token;
+ protected HttpServerExchange exchange;
+ protected String redirectUri;
+ protected KeycloakChallenge challenge;
+
+ public OAuthAuthenticator(HttpServerExchange exchange, RealmConfiguration realmInfo, int sslRedirectPort) {
+ this.exchange = exchange;
+ this.realmInfo = realmInfo;
+ this.sslRedirectPort = sslRedirectPort;
+ }
+
+ public KeycloakChallenge getChallenge() {
+ return challenge;
+ }
+
+ public String getTokenString() {
+ return tokenString;
+ }
+
+ public SkeletonKeyToken getToken() {
+ return token;
+ }
+
+ public String getRedirectUri() {
+ return redirectUri;
+ }
+
+ protected String getRequestUrl() {
+ UriBuilder uriBuilder = UriBuilder.fromUri(exchange.getRequestURI())
+ .replaceQuery(exchange.getQueryString());
+ if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
+ return uriBuilder.build().toString();
+ }
+
+ protected boolean isRequestSecure() {
+ return exchange.getProtocol().toString().equalsIgnoreCase("https");
+ }
+
+ protected Cookie getCookie(String cookieName) {
+ Map<String, Cookie> requestCookies = exchange.getRequestCookies();
+ if (requestCookies == null) return null;
+ return requestCookies.get(cookieName);
+ }
+
+ protected String getCookieValue(String cookieName) {
+ Cookie cookie = getCookie(cookieName);
+ if (cookie == null) return null;
+ return cookie.getValue();
+ }
+
+ protected String getQueryParamValue(String paramName) {
+ Map<String,Deque<String>> queryParameters = exchange.getQueryParameters();
+ if (queryParameters == null) return null;
+ Deque<String> strings = queryParameters.get(paramName);
+ if (strings == null) return null;
+ return strings.getFirst();
+ }
+
+ protected String getError() {
+ return getQueryParamValue("error");
+ }
+
+ protected String getCode() {
+ return getQueryParamValue("code");
+ }
+
+ protected String getRedirectUri(String state) {
+ String url = getRequestUrl();
+ log.info("sending redirect uri: " + url);
+ if (!isRequestSecure() && realmInfo.isSslRequired()) {
+ int port = sslRedirectPort();
+ 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.getMetadata().getResourceName())
+ .queryParam("redirect_uri", url)
+ .queryParam("state", state)
+ .queryParam("login", "true")
+ .build().toString();
+ }
+
+ protected int sslRedirectPort() {
+ return sslRedirectPort;
+ }
+
+ protected static final AtomicLong counter = new AtomicLong();
+
+ protected String getStateCode() {
+ return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
+ }
+
+ protected KeycloakChallenge loginRedirect() {
+ final String state = getStateCode();
+ final String redirect = getRedirectUri(state);
+ return new KeycloakChallenge() {
+ @Override
+ public AuthenticationMechanism.ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+ if (redirect == null) {
+ return new AuthenticationMechanism.ChallengeResult(true, 403);
+ }
+ CookieImpl cookie = new CookieImpl(realmInfo.getStateCookieName(), state);
+ //cookie.setPath(getDefaultCookiePath()); todo I don't think we need to set state cookie path as it will be the same redirect
+ cookie.setSecure(realmInfo.isSslRequired());
+ exchange.setResponseCookie(cookie);
+ exchange.getResponseHeaders().put(Headers.LOCATION, redirect);
+ return new AuthenticationMechanism.ChallengeResult(true, 302);
+ }
+ };
+ }
+
+ protected KeycloakChallenge checkStateCookie() {
+ Cookie stateCookie = getCookie(realmInfo.getStateCookieName());
+
+ if (stateCookie == null) {
+ log.warn("No state cookie");
+ return challenge(400);
+ }
+ // reset the cookie
+ log.info("** reseting application state cookie");
+ Cookie reset = new CookieImpl(realmInfo.getStateCookieName(), "");
+ reset.setPath(stateCookie.getPath());
+ reset.setMaxAge(0);
+ exchange.setResponseCookie(reset);
+
+ String stateCookieValue = getCookieValue(realmInfo.getStateCookieName());
+
+ String state = getQueryParamValue("state");
+ if (state == null) {
+ log.warn("state parameter was null");
+ return challenge(400);
+ }
+ if (!state.equals(stateCookieValue)) {
+ log.warn("state parameter invalid");
+ log.warn("cookie: " + stateCookieValue);
+ log.warn("queryParam: " + state);
+ return challenge(400);
+ }
+ return null;
+
+ }
+
+ public AuthenticationMechanism.AuthenticationMechanismOutcome authenticate() {
+ String code = getCode();
+ if (code == null) {
+ log.info("there was no code");
+ String error = getError();
+ if (error != null) {
+ // todo how do we send a response?
+ log.warn("There was an error: " + error);
+ challenge = challenge(400);
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ } else {
+ log.info("redirecting to auth server");
+ challenge = loginRedirect();
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+ } else {
+ log.info("there was a code, resolving");
+ challenge = resolveCode(code);
+ if (challenge != null) {
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ return AuthenticationMechanism.AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+
+ }
+
+ protected KeycloakChallenge challenge(final int code) {
+ return new KeycloakChallenge() {
+ @Override
+ public AuthenticationMechanism.ChallengeResult sendChallenge(HttpServerExchange httpServerExchange, SecurityContext securityContext) {
+ return new AuthenticationMechanism.ChallengeResult(true, code);
+ }
+ };
+ }
+
+ /**
+ * Start or continue the oauth login process.
+ * <p/>
+ * if code query parameter is not present, then browser is redirected to authUrl. The redirect URL will be
+ * the URL of the current request.
+ * <p/>
+ * 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 null if an access token was obtained, otherwise a challenge is returned
+ */
+ protected KeycloakChallenge resolveCode(String code) {
+ // abort if not HTTPS
+ if (realmInfo.isSslRequired() && !isRequestSecure()) {
+ log.error("SSL is required");
+ return challenge(403);
+ }
+
+ log.info("checking state cookie for after code");
+ KeycloakChallenge challenge = checkStateCookie();
+ if (challenge != null) return challenge;
+
+ String client_id = realmInfo.getMetadata().getResourceName();
+ String password = realmInfo.getResourceCredentials().asMap().getFirst("password");
+ //String authHeader = BasicAuthHelper.createHeader(client_id, password);
+ redirectUri = stripOauthParametersFromRedirect();
+ Form form = new Form();
+ form.param("grant_type", "authorization_code")
+ .param("code", code)
+ .param("client_id", client_id)
+ .param(CredentialRepresentation.PASSWORD, password)
+ .param("redirect_uri", redirectUri);
+
+ Response res = realmInfo.getCodeUrl().request()
+ .post(Entity.form(form));
+ AccessTokenResponse tokenResponse;
+ try {
+ if (res.getStatus() != 200) {
+ log.error("failed to turn code into token");
+ log.error("status from server: " + res.getStatus());
+ if (res.getStatus() == 400 && res.getMediaType() != null) {
+ log.error(" " + res.readEntity(String.class));
+ }
+ return challenge(403);
+ }
+ 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("Token Verification succeeded!");
+ } catch (VerificationException e) {
+ log.error("failed verification of token");
+ return challenge(403);
+ }
+ log.info("successful authenticated");
+ return null;
+ }
+
+ /**
+ * strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
+ */
+ protected String stripOauthParametersFromRedirect() {
+ UriBuilder builder = UriBuilder.fromUri(exchange.getRequestURI())
+ .replaceQuery(exchange.getQueryString())
+ .replaceQueryParam("code", null)
+ .replaceQueryParam("state", null);
+ return builder.build().toString();
+ }
+
+
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/PreflightCorsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/PreflightCorsHandler.java
new file mode 100755
index 0000000..0096b2d
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/PreflightCorsHandler.java
@@ -0,0 +1,80 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.HttpString;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PreflightCorsHandler implements HttpHandler {
+ private static final Logger log = Logger.getLogger(PreflightCorsHandler.class);
+ protected ManagedResourceConfig config;
+ protected HttpHandler next;
+
+ public static final HttpString ACCESS_CONTROL_ALLOW_ORIGIN = new HttpString("Access-Control-Allow-Origin");
+ public static final HttpString ACCESS_CONTROL_ALLOW_CREDENTIALS = new HttpString("Access-Control-Allow-Credentials");
+ public static final HttpString ACCESS_CONTROL_ALLOW_METHODS = new HttpString("Access-Control-Allow-Methods");
+ public static final HttpString ACCESS_CONTROL_ALLOW_HEADERS = new HttpString("Access-Control-Allow-Headers");
+ public static final HttpString ACCESS_CONTROL_MAX_AGE = new HttpString("Access-Control-Max-Age");
+
+ public static class Wrapper implements HandlerWrapper {
+ protected ManagedResourceConfig config;
+
+ public Wrapper(ManagedResourceConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new PreflightCorsHandler(config, handler);
+ }
+ }
+
+ protected PreflightCorsHandler(ManagedResourceConfig config, HttpHandler next) {
+ this.config = config;
+ this.next = next;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ log.debugv("checkCorsPreflight {0}", exchange.getRequestURI());
+ if (!exchange.getRequestMethod().toString().equalsIgnoreCase("OPTIONS")) {
+ log.debug("checkCorsPreflight: not options ");
+ next.handleRequest(exchange);
+ return;
+ }
+ if (exchange.getRequestHeaders().getFirst("Origin") == null) {
+ log.debug("checkCorsPreflight: no origin header");
+ next.handleRequest(exchange);
+ return;
+ }
+ log.debug("Preflight request returning");
+ exchange.setResponseCode(200);
+ String origin = exchange.getRequestHeaders().getFirst("Origin");
+ exchange.getResponseHeaders().put(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+ exchange.getResponseHeaders().put(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+ String requestMethods = exchange.getRequestHeaders().getFirst("Access-Control-Request-Method");
+ if (requestMethods != null) {
+ if (config.getCorsAllowedMethods() != null) {
+ requestMethods = config.getCorsAllowedMethods();
+ }
+ exchange.getResponseHeaders().put(ACCESS_CONTROL_ALLOW_METHODS, requestMethods);
+ }
+ String allowHeaders = exchange.getRequestHeaders().getFirst("Access-Control-Request-Headers");
+ if (allowHeaders != null) {
+ if (config.getCorsAllowedHeaders() != null) {
+ allowHeaders = config.getCorsAllowedHeaders();
+ }
+ exchange.getResponseHeaders().put(ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders);
+ }
+ if (config.getCorsMaxAge() > -1) {
+ exchange.getResponseHeaders().put(ACCESS_CONTROL_MAX_AGE, Integer.toString(config.getCorsMaxAge()));
+ }
+ exchange.endExchange();
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAuthenticatedActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAuthenticatedActionsHandler.java
new file mode 100755
index 0000000..fb771e6
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletAuthenticatedActionsHandler.java
@@ -0,0 +1,48 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletAuthenticatedActionsHandler extends AuthenticatedActionsHandler {
+
+ protected ServletAuthenticatedActionsHandler(ManagedResourceConfig config, HttpHandler next) {
+ super(config, next);
+ }
+
+ public static class Wrapper implements HandlerWrapper {
+ protected ManagedResourceConfig config;
+
+ public Wrapper(ManagedResourceConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new ServletAuthenticatedActionsHandler(config, handler);
+ }
+ }
+
+ @Override
+ public SkeletonKeySession getSkeletonKeySession(HttpServerExchange exchange) {
+ SkeletonKeySession skSession = super.getSkeletonKeySession(exchange);
+ if (skSession != null) return skSession;
+
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ HttpSession session = req.getSession(false);
+ if (session == null) return null;
+ return (SkeletonKeySession)session.getAttribute(SkeletonKeySession.class.getName());
+
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
new file mode 100755
index 0000000..9ac90aa
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthenticationMechanism.java
@@ -0,0 +1,48 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.api.ConfidentialPortManager;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.RealmConfiguration;
+import org.keycloak.ResourceMetadata;
+import org.keycloak.SkeletonKeySession;
+import org.keycloak.adapters.config.ManagedResourceConfig;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletKeycloakAuthenticationMechanism extends KeycloakAuthenticationMechanism {
+ protected ConfidentialPortManager portManager;
+
+ public ServletKeycloakAuthenticationMechanism(ResourceMetadata resourceMetadata, ManagedResourceConfig config, RealmConfiguration realmConfig, ConfidentialPortManager portManager) {
+ super(resourceMetadata, config, realmConfig);
+ this.portManager = portManager;
+ }
+
+ @Override
+ protected OAuthAuthenticator createOAuthAuthenticator(HttpServerExchange exchange) {
+ return new ServletOAuthAuthenticator(exchange, realmConfig, portManager);
+ }
+
+ @Override
+ protected void propagateBearer(HttpServerExchange exchange, SkeletonKeySession session) {
+ super.propagateBearer(exchange, session);
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ req.setAttribute(SkeletonKeySession.class.getName(), session);
+ }
+
+ @Override
+ protected void propagateOauth(HttpServerExchange exchange, SkeletonKeySession skSession) {
+ super.propagateOauth(exchange, skSession);
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ req.setAttribute(SkeletonKeySession.class.getName(), skSession);
+ HttpSession session = req.getSession(true);
+ session.setAttribute(SkeletonKeySession.class.getName(), skSession);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletOAuthAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletOAuthAuthenticator.java
new file mode 100755
index 0000000..754c7b4
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletOAuthAuthenticator.java
@@ -0,0 +1,23 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.api.ConfidentialPortManager;
+import org.keycloak.RealmConfiguration;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletOAuthAuthenticator extends OAuthAuthenticator {
+ protected ConfidentialPortManager portManager;
+
+ public ServletOAuthAuthenticator(HttpServerExchange exchange, RealmConfiguration realmInfo, ConfidentialPortManager portManager) {
+ super(exchange, realmInfo, -1);
+ this.portManager = portManager;
+ }
+
+ @Override
+ protected int sslRedirectPort() {
+ return portManager.getConfidentialPort(exchange);
+ }
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java
new file mode 100755
index 0000000..b8a25d3
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPropagateSessionHandler.java
@@ -0,0 +1,63 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.SkeletonKeySession;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletPropagateSessionHandler implements HttpHandler {
+
+ private static final Logger log = Logger.getLogger(ServletPropagateSessionHandler.class);
+
+ protected HttpHandler next;
+
+ protected ServletPropagateSessionHandler(HttpHandler next) {
+ this.next = next;
+ }
+
+ public static final HandlerWrapper WRAPPER = new HandlerWrapper() {
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new ServletPropagateSessionHandler(handler);
+ }
+ };
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ log.info("handleRequest");
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ SkeletonKeySession skSession = (SkeletonKeySession)req.getAttribute(SkeletonKeySession.class.getName());
+ if (skSession != null) {
+ log.info("skSession is in request");
+ next.handleRequest(exchange);
+ return;
+ }
+
+ HttpSession session = req.getSession(false);
+ if (session == null) {
+ log.info("http session was null, nothing to propagate");
+ next.handleRequest(exchange);
+ return;
+ }
+ skSession = (SkeletonKeySession)session.getAttribute(SkeletonKeySession.class.getName());
+ if (skSession == null) {
+ log.info("skSession not in http session, nothing to propagate");
+ next.handleRequest(exchange);
+ return;
+ }
+ log.info("propagating");
+ req.setAttribute(SkeletonKeySession.class.getName(), skSession);
+ exchange.putAttachment(KeycloakAuthenticationMechanism.SKELETON_KEY_SESSION_ATTACHMENT_KEY, skSession);
+ next.handleRequest(exchange);
+ }
+}
diff --git a/integration/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension b/integration/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
new file mode 100755
index 0000000..fc0939d
--- /dev/null
+++ b/integration/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
@@ -0,0 +1 @@
+org.keycloak.adapters.undertow.KeycloakServletExtension