Merge pull request #337 from patriot1burke/master

app/keycloak self bootstrapping bundle
This commit is contained in:
Bill Burke 2014-04-11 13:26:22 -04:00
commit 1aa58ec580
25 changed files with 965 additions and 17 deletions

16
bundled-war-example/README.md Executable file
View file

@ -0,0 +1,16 @@
Self Bootstrapping Keycloak Server and Bundled Application
==========================================================
This is an example of bundling the Keycloak server with an app within the same WAR in an EAP 6.x environment.
* On bootup, a default realm is imported from WEB-INF/testrealm.json if it doesn't exist yet.
* On bootup, the adapter config is created on the fly and configured with the testrealm imported.
* The application is secured with keycloak (see jboss-web.xml)
* web.xml security constraints are set for the secured URLs that are secured by keycloak
* Because of weirdness with Resteasy 2.3.x, any secured JAX-RS urls from the application must have a security
constraint that denies all as they will be reachable in two places. Under the Keycloak REST url "/rest" and under the
application's REST url "/database".
* Adapter config can be modified on the fly by getting the AdapterDeploymentContext from a servlet context attribute.
* You must specify a host-port context param so that the auth url for AdapterConfig can be set correctly.
* Run this demo by going to http://localhost:8080/app-bundle. Then click on the url.

285
bundled-war-example/pom.xml Executable file
View file

@ -0,0 +1,285 @@
<?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-beta-1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>app-bundle</artifactId>
<packaging>war</packaging>
<name>Keycloak Server and App Bundle EAP 6.x</name>
<description/>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jboss-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.iharder</groupId>
<artifactId>base64</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core-jaxrs</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-jboss-logging</artifactId>
<version>${project.version}</version>
</dependency>
<!-- social -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-github</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.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-facebook</artifactId>
<version>${project.version}</version>
</dependency>
<!-- forms -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms-common-freemarker</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms-common-themes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-account-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-account-freemarker</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-login-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-login-freemarker</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-js-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<!-- authentication api -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authentication-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authentication-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authentication-picketlink</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-impl</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId>
</dependency>
<!-- timer -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-timer-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-timer-basic</artifactId>
<version>${project.version}</version>
</dependency>
<!-- picketlink -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-picketlink-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-picketlink-realm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<!-- resteasy -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>app-bundle</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>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,72 @@
package org.keycloak.example;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CustomerDatabaseClient {
static class TypedList extends ArrayList<String> {
}
public static class Failure extends Exception {
private int status;
public Failure(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
}
public static IDToken getIDToken(HttpServletRequest req) {
KeycloakSecurityContext session = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
return session.getIdToken();
}
public static List<String> getCustomers(HttpServletRequest req) throws Failure {
KeycloakSecurityContext session = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
HttpClient client = new HttpClientBuilder()
.disableTrustManager().build();
try {
HttpGet get = new HttpGet("http://localhost:8080/app-bundle/database/customers");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
try {
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != 200) {
throw new Failure(response.getStatusLine().getStatusCode());
}
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, TypedList.class);
} finally {
is.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} finally {
client.getConnectionManager().shutdown();
}
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.example.oauth;
import org.jboss.resteasy.annotations.cache.NoCache;
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")
@NoCache
public List<String> getCustomers() {
ArrayList<String> rtn = new ArrayList<String>();
rtn.add("Bill Burke");
rtn.add("Stian Thorgersen");
rtn.add("Stan Silvert");
rtn.add("Gabriel Cardoso");
rtn.add("Viliam Rockai");
rtn.add("Marek Posolda");
rtn.add("Boleslaw Dawidowicz");
return rtn;
}
}

View file

@ -0,0 +1,25 @@
package org.keycloak.example.oauth;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DataApplication extends Application
{
@Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>();
set.add(CustomerService.class);
set.add(ProductService.class);
return set;
}
@Override
public Set<Object> getSingletons() {
return super.getSingletons(); //To change body of overridden methods use File | Settings | File Templates.
}
}

View file

@ -0,0 +1,27 @@
package org.keycloak.example.oauth;
import org.jboss.resteasy.annotations.cache.NoCache;
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")
@NoCache
public List<String> getProducts() {
ArrayList<String> rtn = new ArrayList<String>();
rtn.add("iphone");
rtn.add("ipad");
rtn.add("ipod");
return rtn;
}
}

View file

@ -0,0 +1,84 @@
package org.keycloak.server;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.models.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class KeycloakServerApplication extends KeycloakApplication {
private static final Logger log = Logger.getLogger(KeycloakServerApplication.class);
public KeycloakServerApplication(@Context ServletContext servletContext,@Context Dispatcher dispatcher) throws FileNotFoundException {
super(servletContext, dispatcher);
KeycloakSession session = factory.createSession();
session.getTransaction().begin();
try {
InputStream is = servletContext.getResourceAsStream("/WEB-INF/testrealm.json");
RealmRepresentation rep = loadJson(is, RealmRepresentation.class);
RealmModel realm = importRealm(session, rep);
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext)servletContext.getAttribute(AdapterDeploymentContext.class.getName());
AdapterConfig adapterConfig = new AdapterConfig();
String host = (String)servletContext.getInitParameter("host-port");
String uri = KeycloakUriBuilder.fromUri("http://" + host).path(servletContext.getContextPath()).build().toString();
log.info("**** auth server url: " + uri);
adapterConfig.setRealm("demo");
adapterConfig.setResource("customer-portal");
adapterConfig.setRealmKey(realm.getPublicKeyPem());
Map<String, String> creds = new HashMap<String, String>();
creds.put(CredentialRepresentation.SECRET, "password");
adapterConfig.setCredentials(creds);
adapterConfig.setAuthServerUrl(uri);
adapterConfig.setSslNotRequired(true);
deploymentContext.updateDeployment(adapterConfig);
session.getTransaction().commit();
} finally {
session.close();
}
}
public RealmModel importRealm(KeycloakSession session, RealmRepresentation rep) {
RealmManager manager = new RealmManager(session);
RealmModel realm = manager.getRealmByName(rep.getRealm());
if (realm != null) {
log.info("Not importing realm " + rep.getRealm() + " realm already exists");
return realm;
}
realm = manager.createRealm(rep.getId(), rep.getRealm());
manager.importRealm(rep, realm);
log.info("Imported realm " + realm.getName());
return realm;
}
private static <T> T loadJson(InputStream is, Class<T> type) {
try {
return JsonSerialization.readValue(is, type);
} catch (IOException e) {
throw new RuntimeException("Failed to parse json", e);
}
}
}

View file

@ -0,0 +1,39 @@
<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.CredentialEntity</class>
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
<class>org.keycloak.models.jpa.entities.UserEntity</class>
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
<persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.keycloak.audit.jpa.EventEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>

View file

@ -0,0 +1,36 @@
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.apache.httpcomponents"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
</dependencies>
<exclusions>
<!-- Exclude keycloak modules -->
<module name="org.keycloak.keycloak-core" />
<module name="org.keycloak.keycloak-adapter-core" />
<module name="org.keycloak.keycloak-undertow-adapter" />
<module name="org.keycloak.keycloak-as7-adapter" />
<!--
<module name="org.apache.cxf" />
<module name="javaee.api" />
<module name="javax.ws.rs.api"/>
<module name="org.codehaus.jackson.jackson-core-asl" />
<module name="org.jboss.resteasy.resteasy-atom-provider" />
<module name="org.jboss.resteasy.resteasy-cdi" />
<module name="org.jboss.resteasy.resteasy-crypto" />
<module name="org.jboss.resteasy.resteasy-jackson-provider" />
<module name="org.jboss.resteasy.resteasy-jaxb-provider" />
<module name="org.jboss.resteasy.resteasy-jaxrs" />
<module name="org.jboss.resteasy.resteasy-jettison-provider" />
<module name="org.jboss.resteasy.resteasy-jsapi" />
<module name="org.jboss.resteasy.resteasy-json-p-provider" />
<module name="org.jboss.resteasy.resteasy-multipart-provider" />
<module name="org.jboss.resteasy.resteasy-validator-provider-11" />
<module name="org.jboss.resteasy.resteasy-yaml-provider" />
-->
</exclusions>
</deployment>
</jboss-deployment-structure>

View file

@ -0,0 +1,5 @@
<jboss-web>
<valve>
<class-name>org.keycloak.adapters.as7.KeycloakAuthenticatorValve</class-name>
</valve>
</jboss-web>

View file

@ -0,0 +1,70 @@
{
"realm": "demo",
"enabled": true,
"accessTokenLifespan": 3000,
"accessCodeLifespan": 10,
"accessCodeLifespanUserAction": 6000,
"sslNotRequired": true,
"registrationAllowed": false,
"social": false,
"updateProfileOnInitialSocialLogin": false,
"requiredCredentials": [ "password" ],
"users" : [
{
"username" : "bburke@redhat.com",
"enabled": true,
"email" : "bburke@redhat.com",
"firstName": "Bill",
"lastName": "Burke",
"credentials" : [
{ "type" : "password",
"value" : "password" }
]
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"roleMappings": [
{
"username": "bburke@redhat.com",
"roles": ["user"]
}
],
"scopeMappings": [
{
"client": "customer-portal",
"roles": ["user"]
}
],
"applications": [
{
"name": "customer-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/app-bundle",
"baseUrl": "http://localhost:8080/app-bundle",
"redirectUris": [
"http://localhost:8080/app-bundle/*"
],
"secret": "password"
}
],
"applicationRoleMappings": {
"account": [
{
"username": "bburke@redhat.com",
"roles": ["manage-account"]
}
]
}
}

View file

@ -0,0 +1,144 @@
<?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>app-bundle</module-name>
<context-param>
<param-name>host-port</param-name>
<param-value>localhost:8080</param-value>
</context-param>
<servlet>
<servlet-name>Keycloak REST Interface</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.server.KeycloakServerApplication</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>
<servlet>
<servlet-name>Customer REST Interface</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.oauth.DataApplication</param-value>
</init-param>
<init-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/database</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet>
<servlet-name>TmpAdminRedirectServlet</servlet-name>
<servlet-class>org.keycloak.services.tmp.TmpAdminRedirectServlet</servlet-class>
</servlet>
<listener>
<listener-class>org.keycloak.services.listeners.KeycloakSessionDestroyListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<filter>
<filter-name>Keycloak Client Connection Filter</filter-name>
<filter-class>org.keycloak.services.filters.ClientConnectionFilter</filter-class>
</filter>
<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>
<filter-mapping>
<filter-name>Keycloak Client Connection Filter</filter-name>
<url-pattern>/rest/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>Keycloak REST Interface</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Customer REST Interface</servlet-name>
<url-pattern>/database/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>TmpAdminRedirectServlet</servlet-name>
<url-pattern>/admin</url-pattern>
<url-pattern>/admin/</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>
-->
<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>
<web-resource-name>Database</web-resource-name>
<url-pattern>/database/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>deny</web-resource-name>
<url-pattern>/rest/customers/*</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>demo</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</web-app>

View file

@ -0,0 +1,47 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" %>
<%@ page import="org.keycloak.example.CustomerDatabaseClient" %>
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
<%@ page import="org.keycloak.representations.IDToken" %>
<html>
<head>
<title>Customer View Page</title>
</head>
<body bgcolor="#E3F6CE">
<%
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/app-bundle/rest/realms/demo/tokens/logout")
.queryParam("redirect_uri", "http://localhost:8080/app-bundle").build().toString();
String acctUri = "http://localhost:8080/app-bundle/rest/realms/demo/account?referrer=customer-portal";
IDToken idToken = CustomerDatabaseClient.getIDToken(request);
%>
<p><a href="<%=logoutUri%>">logout</a> | <a
href="<%=acctUri%>">manage acct</a></p>
Servlet User Principal <b><%=request.getUserPrincipal().getName()%>
</b> made this request.
<p><b>Caller IDToken values</b> (<i>You can specify what is returned in IDToken in the customer-portal claims page in the admin console</i>:</p>
<p>Username: <%=idToken.getPreferredUsername()%></p>
<p>Email: <%=idToken.getEmail()%></p>
<p>Full Name: <%=idToken.getName()%></p>
<p>First: <%=idToken.getGivenName()%></p>
<p>Last: <%=idToken.getFamilyName()%></p>
<h2>Customer Listing</h2>
<%
java.util.List<String> list = null;
try {
list = CustomerDatabaseClient.getCustomers(request);
} catch (CustomerDatabaseClient.Failure failure) {
out.println("There was a failure processing request. You either didn't configure Keycloak properly, or maybe" +
"you just forgot to secure the database service?");
out.println("Status from database service invocation was: " + failure.getStatus());
return;
}
for (String cust : list) {
out.print("<p>");
out.print(cust);
out.println("</p>");
}
%>
<br><br>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!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>
</body>
</html>

View file

@ -0,0 +1,26 @@
package org.keycloak.adapters;
import org.keycloak.representations.adapters.config.AdapterConfig;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AdapterDeploymentContext {
protected KeycloakDeployment deployment;
public AdapterDeploymentContext() {
}
public AdapterDeploymentContext(KeycloakDeployment deployment) {
this.deployment = deployment;
}
public KeycloakDeployment getDeployment() {
return deployment;
}
public void updateDeployment(AdapterConfig config) {
deployment = KeycloakDeploymentBuilder.build(config);
}
}

View file

@ -12,6 +12,7 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class KeycloakDeployment {
protected boolean configured;
protected String realm;
protected PublicKey realmKey;
protected KeycloakUriBuilder authUrl;
@ -37,6 +38,14 @@ public class KeycloakDeployment {
protected boolean exposeToken;
protected volatile int notBefore;
public boolean isConfigured() {
return configured;
}
public void setConfigured(boolean configured) {
this.configured = configured;
}
public String getResourceName() {
return resourceName;
}

View file

@ -27,7 +27,7 @@ public class KeycloakDeploymentBuilder {
protected KeycloakDeployment internalBuild(AdapterConfig adapterConfig) {
deployment.setConfigured(true);
if (adapterConfig.getRealm() == null) throw new RuntimeException("Must set 'realm' in config");
deployment.setRealm(adapterConfig.getRealm());
String resource = adapterConfig.getResource();

View file

@ -35,6 +35,7 @@ public class PreAuthActionsHandler {
}
public boolean handleRequest() {
if (!deployment.isConfigured()) return false;
String requestUri = facade.getRequest().getURI();
log.debugv("adminRequest {0}", requestUri);
if (preflightCors()) {

View file

@ -9,6 +9,7 @@ import org.apache.catalina.valves.ValveBase;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.representations.AccessToken;
@ -33,10 +34,10 @@ import java.util.Set;
*/
public class AuthenticatedActionsValve extends ValveBase {
private static final Logger log = Logger.getLogger(AuthenticatedActionsValve.class);
protected KeycloakDeployment deployment;
protected AdapterDeploymentContext deploymentContext;
public AuthenticatedActionsValve(KeycloakDeployment deployment, Valve next, Container container, ObjectName controller) {
this.deployment = deployment;
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container, ObjectName controller) {
this.deploymentContext = deploymentContext;
if (next == null) throw new RuntimeException("WTF is next null?!");
setNext(next);
setContainer(container);
@ -47,7 +48,7 @@ public class AuthenticatedActionsValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new CatalinaHttpFacade(request, response));
AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deploymentContext.getDeployment(), new CatalinaHttpFacade(request, response));
if (handler.handledRequest()) {
return;
}

View file

@ -13,6 +13,7 @@ import org.apache.catalina.deploy.LoginConfig;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.KeycloakDeployment;
@ -55,7 +56,7 @@ import java.util.Map;
public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
private static final Logger log = Logger.getLogger(KeycloakAuthenticatorValve.class);
protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected KeycloakDeployment deployment;
protected AdapterDeploymentContext deploymentContext;
@Override
@ -100,15 +101,26 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
protected void init() {
this.deployment = KeycloakDeploymentBuilder.build(getConfigInputStream(context));
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deployment, getNext(), getContainer(), getController());
InputStream configInputStream = getConfigInputStream(context);
KeycloakDeployment kd = null;
if (configInputStream == null) {
log.warn("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
kd = new KeycloakDeployment();
kd.setConfigured(false);
} else {
kd = KeycloakDeploymentBuilder.build(configInputStream);
}
deploymentContext = new AdapterDeploymentContext(kd);
context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer(), getController());
setNext(actions);
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment, new CatalinaHttpFacade(request, response));
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext.getDeployment(), new CatalinaHttpFacade(request, response));
if (handler.handleRequest()) {
return;
}
@ -120,8 +132,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
@Override
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
if (!deploymentContext.getDeployment().isConfigured()) return false;
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response);
CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deploymentContext.getDeployment(), this, userSessionManagement, facade, request);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
if (facade.isEnded()) {
@ -146,7 +159,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext)request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
if (session == null) return;
// just in case session got serialized
session.setDeployment(deployment);
session.setDeployment(deploymentContext.getDeployment());
if (session.isActive()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will

View file

@ -102,6 +102,7 @@
<module>testsuite</module>
<module>server</module>
<module>timer</module>
<module>bundled-war-example</module>
</modules>
<dependencyManagement>

View file

@ -1,5 +1,6 @@
package org.keycloak.server;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.Config;
import org.keycloak.models.KeycloakSession;
@ -21,8 +22,8 @@ public class KeycloakServerApplication extends KeycloakApplication {
private static final Logger log = Logger.getLogger(KeycloakServerApplication.class);
public KeycloakServerApplication(@Context ServletContext servletContext) throws FileNotFoundException {
super(servletContext);
public KeycloakServerApplication(@Context ServletContext servletContext, @Context Dispatcher dispatcher) throws FileNotFoundException {
super(servletContext, dispatcher);
String importRealm = System.getProperty("keycloak.import");
if (importRealm != null) {

View file

@ -1,5 +1,6 @@
package org.keycloak.services.resources;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.SkeletonKeyContextResolver;
import org.keycloak.audit.AuditListener;
@ -52,9 +53,10 @@ public class KeycloakApplication extends Application {
protected ProviderSessionFactory providerSessionFactory;
protected String contextPath;
public KeycloakApplication(@Context ServletContext context) {
this.factory = createSessionFactory();
public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this);
this.contextPath = context.getContextPath();
this.factory = createSessionFactory();
this.providerSessionFactory = createProviderSessionFactory();
context.setAttribute(KeycloakSessionFactory.class.getName(), factory);
//classes.add(KeycloakSessionCleanupFilter.class);

View file

@ -52,7 +52,7 @@ public class ApplicationResource {
protected UriInfo uriInfo;
@Context
protected Application keycloak;
protected KeycloakApplication keycloak;
protected KeycloakApplication getKeycloakApplication() {
return (KeycloakApplication)keycloak;

View file

@ -41,7 +41,7 @@ public class OAuthClientResource {
protected UriInfo uriInfo;
@Context
protected Application application;
protected KeycloakApplication application;
protected KeycloakApplication getApplication() {
return (KeycloakApplication)application;