Skip to content

Commit

Permalink
[THORN-2039] Adding a multitenancy demo (thorntail#183)
Browse files Browse the repository at this point in the history
* [THORN-2039] Adding a multitenancy demo

* [THORN-2039] Improving the multitenancy demo

* [THORN-2039] Adding another realm file

* Updating the security pom
  • Loading branch information
sberyozkin authored and kenfinnigan committed Jun 1, 2018
1 parent 5e6b95d commit c5a2e31
Show file tree
Hide file tree
Showing 12 changed files with 492 additions and 1 deletion.
112 changes: 112 additions & 0 deletions security/keycloak-multitenancy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Keycloak Multitenancy Example

This example shows how the application and health endpoints requiring different Keycloak adapter configurations can be secured.

## Start Keycloak

### Keycloak Swarm Server

Download the latest Swarm Keycloak standalone server jar, for example, 2018.5.0 version:

``` sh
wget http://repo1.maven.org/maven2/org/wildfly/swarm/servers/keycloak/2018.5.0/keycloak-2018.5.0-swarm.jar .
```
and start it:

``` sh
THIS_EXAMPLE=/path/to/this/example
java -Dswarm.http.port=8180 \
-Dkeycloak.migration.action=import \
-Dkeycloak.migration.provider=dir \
-Dkeycloak.migration.dir=${THIS_EXAMPLE}/realm \
-jar keycloak-2018.5.0-swarm.jar
```


### Local installed

``` sh
THIS_EXAMPLE=/path/to/this/example
cd $KEYCLOAK_HOME
bin/standalone.sh \
-Djboss.socket.binding.port-offset=100 \
-Dkeycloak.migration.action=import \
-Dkeycloak.migration.provider=dir \
-Dkeycloak.migration.dir=${THIS_EXAMPLE}/realm
```

### Docker

``` sh
docker run -it -d \
-p 8180:8080 \
-v `pwd`/realm:/tmp/realm \
jboss/keycloak:3.4.0.Final \
-b 0.0.0.0 \
-Dkeycloak.migration.action=import \
-Dkeycloak.migration.provider=dir \
-Dkeycloak.migration.dir=/tmp/realm
```

## Build Example

``` sh
mvn clean package
```

## Start the example server

``` sh
java -jar target/example-keycloak-multitenancy-swarm.jar
```

### Access the secured application endpoint

``` sh
curl localhost:8080/app/secured -v
```

You'll get the response with `401 Unauthorized`. Let's get a Token to access it.

### Obtain Token from Keycloak Server

``` sh
USER=user1
PASS=password1
RESULT=`curl -s --data "grant_type=password&client_id=wildfly-swarm-app-client&username=${USER}&password=${PASS}" http://localhost:8180/auth/realms/wildfly-swarm-app-client/protocol/openid-connect/token`
TOKEN=`echo $RESULT | sed 's/.*access_token":"//g' | sed 's/".*//g'`
```

### Access the secured resource with the Token

``` sh
curl -H "Authorization: bearer $TOKEN" localhost:8080/app/secured
```

You'll get the response which contains `Hi user1, this is Secured Resource accessed from the wildfly-swarm-app-client`.

### Access the secured health endpoints

``` sh
curl localhost:8080/app/health -v
```

You'll get the response with `401 Unauthorized`. Let's get a Token to access it.

### Obtain Token from Keycloak Server

``` sh
USER=user1
PASS=password1
RESULT=`curl -s --data "grant_type=password&client_id=wildfly-swarm-health-client&username=${USER}&password=${PASS}" http://localhost:8180/auth/realms/wildfly-swarm-health-client/protocol/openid-connect/token`
TOKEN=`echo $RESULT | sed 's/.*access_token":"//g' | sed 's/".*//g'`
```

### Access the secured resource with the Token

``` sh
curl -H "Authorization: bearer $TOKEN" localhost:8080/app/health
```

You'll get the information about the realm (wildfly-swarm-health-client) and the service health.

62 changes: 62 additions & 0 deletions security/keycloak-multitenancy/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2017 Red Hat, Inc. and/or its affiliates.
~
~ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.wildfly.swarm.examples</groupId>
<artifactId>examples-security</artifactId>
<version>2018.6.0-SNAPSHOT</version>
</parent>

<artifactId>example-keycloak-multitenancy</artifactId>

<name>WildFly Swarm Examples: Keycloak Multitenancy</name>
<description>WildFly Swarm Examples: Multitenancy</description>

<packaging>war</packaging>

<properties>
<version.wildfly-controller-client>2.2.1.Final</version.wildfly-controller-client>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.wildfly.swarm</groupId>
<artifactId>wildfly-swarm-plugin</artifactId>
<executions>
<execution>
<id>package</id>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.wildfly.swarm</groupId>
<artifactId>jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.swarm</groupId>
<artifactId>keycloak</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.swarm</groupId>
<artifactId>microprofile-health</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-controller-client</artifactId>
<version>${version.wildfly-controller-client}</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"id" : "wildfly-swarm-app-client",
"realm" : "wildfly-swarm-app-client",
"enabled" : true,
"sslRequired" : "external",
"roles" : {
"realm" : [ {
"name" : "app-admin",
"description" : "Admin priviliges"
} ]
},
"requiredCredentials" : [ "password" ],
"users" : [ {
"username" : "user1",
"enabled" : true,
"credentials" : [ {
"type" : "password",
"hashedSaltedValue" : "gjPMPgJ42FC2u/erwTWG3jgBlDSgI97iv50kLvHJJBlXSQ7snHzLrUojPtjkQTdswIv4eGYt7k3mrZBewc5nIg==",
"salt" : "g1OYhdfFGct8WcvSEg+FRg==",
"hashIterations" : 20000,
"algorithm" : "pbkdf2",
"createdDate" : 1484340574721
} ],
"realmRoles" : [ "app-admin" ],
"clientRoles" : {
"account" : [ "view-profile", "manage-account" ]
}
} ],
"clientScopeMappings" : {
"realm-management" : [ {
"client" : "wildfly-swarm-app-client",
"roles" : [ "app-admin" ]
} ]
},
"clients" : [ {
"clientId" : "wildfly-swarm-app-client",
"enabled" : true,
"directAccessGrantsEnabled" : true,
"publicClient" : true
} ],

"keycloakVersion" : "3.4.0.Final"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"id" : "wildfly-swarm-health-client",
"realm" : "wildfly-swarm-health-client",
"enabled" : true,
"sslRequired" : "external",
"roles" : {
"realm" : [ {
"name" : "health-admin",
"description" : "Admin priviliges"
} ]
},
"requiredCredentials" : [ "password" ],
"users" : [ {
"username" : "user1",
"enabled" : true,
"credentials" : [ {
"type" : "password",
"hashedSaltedValue" : "gjPMPgJ42FC2u/erwTWG3jgBlDSgI97iv50kLvHJJBlXSQ7snHzLrUojPtjkQTdswIv4eGYt7k3mrZBewc5nIg==",
"salt" : "g1OYhdfFGct8WcvSEg+FRg==",
"hashIterations" : 20000,
"algorithm" : "pbkdf2",
"createdDate" : 1484340574721
} ],
"realmRoles" : [ "health-admin" ],
"clientRoles" : {
"account" : [ "view-profile", "manage-account" ]
}
} ],
"clientScopeMappings" : {
"realm-management" : [ {
"client" : "wildfly-swarm-health-client",
"roles" : [ "health-admin" ]
} ]
},
"clients" : [ {
"clientId" : "wildfly-swarm-health-client",
"enabled" : true,
"directAccessGrantsEnabled" : true,
"publicClient" : true
} ],

"keycloakVersion" : "3.4.0.Final"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.wildfly.swarm.examples.keycloak;

import java.io.IOException;
import java.net.InetAddress;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;

import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.dmr.ModelNode;
import org.wildfly.swarm.SwarmInfo;

@Path("health")
@Produces("application/json")
public class HealthResource {

@Context
private HttpHeaders httpHeaders;

@GET
public String getAll() {
ModelNode node = new ModelNode();
addRealmProperty(node);
node.add("node", getNodeInfo());
node.add("threads", getThreadsInfo());
node.add("heap", getHeapInfo());

return node.toJSONString(false);
}

@GET
@Path("node")
public String getNode() {
return addRealmProperty(getNodeInfo()).toJSONString(false);
}
@GET
@Path("heap")
public String getHeap() {
return addRealmProperty(getHeapInfo()).toJSONString(false);
}
@GET
@Path("threads")
public String getThreads() {
return addRealmProperty(getThreadsInfo()).toJSONString(false);
}

private ModelNode getNodeInfo() {

ModelNode op = new ModelNode();
op.get("address").setEmptyList();
op.get("operation").set("query");
op.get("select").add("name");
op.get("select").add("server-state");
op.get("select").add("suspend-state");
op.get("select").add("running-mode");
op.get("select").add("uuid");

return getModelNodeResponse(op);

}

private ModelNode getHeapInfo() {
ModelNode op = new ModelNode();
op.get("address").add("core-service", "platform-mbean");
op.get("address").add("type", "memory");
op.get("operation").set("query");
op.get("select").add("heap-memory-usage");
op.get("select").add("non-heap-memory-usage");

return getModelNodeResponse(op);
}

private ModelNode getThreadsInfo() {
ModelNode op = new ModelNode();
op.get("address").add("core-service", "platform-mbean");
op.get("address").add("type", "threading");
op.get("operation").set("query");
op.get("select").add("thread-count");
op.get("select").add("peak-thread-count");
op.get("select").add("total-started-thread-count");
op.get("select").add("current-thread-cpu-time");
op.get("select").add("current-thread-user-time");

return getModelNodeResponse(op);
}

private static ModelNode unwrap(ModelNode response) {
if (response.get("outcome").asString().equals("success")) {
return response.get("result");
} else {
return response;
}
}

private ModelNode addRealmProperty(ModelNode node) {
return node.add("realm", httpHeaders.getHeaderString("Realm"));
}

private ModelNode getModelNodeResponse(ModelNode op) {
try (ModelControllerClient client = ModelControllerClient.Factory.create(
InetAddress.getByName("localhost"), 9990)) {
ModelNode response = client.execute(op);
ModelNode unwrapped = unwrap(response);
unwrapped.get("swarm-version").set(SwarmInfo.VERSION);
return unwrapped;
} catch (IOException e) {
return new ModelNode().get("failure-description").set(e.getMessage());
}
}
}
Loading

0 comments on commit c5a2e31

Please sign in to comment.