wildfly adapter
This commit is contained in:
parent
2861ea0e96
commit
965bc6dccb
73 changed files with 2738 additions and 307 deletions
|
@ -2,6 +2,7 @@ package org.keycloak;
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
||||||
|
import org.keycloak.adapters.config.ManagedResourceConfig;
|
||||||
|
|
||||||
import javax.ws.rs.core.Form;
|
import javax.ws.rs.core.Form;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -19,6 +20,12 @@ public class RealmConfiguration {
|
||||||
protected boolean sslRequired = true;
|
protected boolean sslRequired = true;
|
||||||
protected String stateCookieName = "OAuth_Token_Request_State";
|
protected String stateCookieName = "OAuth_Token_Request_State";
|
||||||
|
|
||||||
|
public RealmConfiguration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmConfiguration(ManagedResourceConfig config) {
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceMetadata getMetadata() {
|
public ResourceMetadata getMetadata() {
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.keycloak.adapters.as7.config;
|
package org.keycloak.adapters.config;
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonProperty;
|
import org.codehaus.jackson.annotate.JsonProperty;
|
||||||
import org.codehaus.jackson.annotate.JsonPropertyOrder;
|
import org.codehaus.jackson.annotate.JsonPropertyOrder;
|
||||||
|
@ -59,6 +59,8 @@ public class ManagedResourceConfig {
|
||||||
protected String corsAllowedMethods;
|
protected String corsAllowedMethods;
|
||||||
@JsonProperty("expose-token")
|
@JsonProperty("expose-token")
|
||||||
protected boolean exposeToken;
|
protected boolean exposeToken;
|
||||||
|
@JsonProperty("bearer-only")
|
||||||
|
protected boolean bearerOnly;
|
||||||
|
|
||||||
public boolean isUseResourceRoleMappings() {
|
public boolean isUseResourceRoleMappings() {
|
||||||
return useResourceRoleMappings;
|
return useResourceRoleMappings;
|
||||||
|
@ -239,4 +241,12 @@ public class ManagedResourceConfig {
|
||||||
public void setExposeToken(boolean exposeToken) {
|
public void setExposeToken(boolean exposeToken) {
|
||||||
this.exposeToken = exposeToken;
|
this.exposeToken = exposeToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBearerOnly() {
|
||||||
|
return bearerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBearerOnly(boolean bearerOnly) {
|
||||||
|
this.bearerOnly = bearerOnly;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,60 +1,57 @@
|
||||||
package org.keycloak.adapters.as7.config;
|
package org.keycloak.adapters.config;
|
||||||
|
|
||||||
import org.apache.catalina.Context;
|
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.EnvUtil;
|
import org.keycloak.EnvUtil;
|
||||||
import org.keycloak.PemUtils;
|
import org.keycloak.PemUtils;
|
||||||
|
import org.keycloak.RealmConfiguration;
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
import org.keycloak.representations.idm.PublishedRealmRepresentation;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
public class ManagedResourceConfigLoader {
|
public class ManagedResourceConfigLoader {
|
||||||
static final Logger log = Logger.getLogger(ManagedResourceConfigLoader.class);
|
|
||||||
protected ManagedResourceConfig remoteSkeletonKeyConfig;
|
protected ManagedResourceConfig remoteSkeletonKeyConfig;
|
||||||
protected ResourceMetadata resourceMetadata;
|
protected ResourceMetadata resourceMetadata;
|
||||||
protected KeyStore clientCertKeystore;
|
protected KeyStore clientCertKeystore;
|
||||||
protected KeyStore truststore;
|
protected KeyStore truststore;
|
||||||
protected ResteasyClient client;
|
protected ResteasyClient client;
|
||||||
|
protected RealmConfiguration realmConfiguration;
|
||||||
|
|
||||||
public ManagedResourceConfigLoader(Context context) {
|
public ManagedResourceConfigLoader() {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
}
|
||||||
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
|
|
||||||
InputStream is = null;
|
public ManagedResourceConfigLoader(InputStream is) {
|
||||||
String path = context.getServletContext().getInitParameter("keycloak.config.file");
|
loadConfig(is);
|
||||||
if (path == null) {
|
}
|
||||||
is = context.getServletContext().getResourceAsStream("/WEB-INF/resteasy-oauth.json");
|
|
||||||
} else {
|
|
||||||
try {
|
public static KeyStore loadKeyStore(String filename, String password) throws Exception {
|
||||||
is = new FileInputStream(path);
|
KeyStore trustStore = KeyStore.getInstance(KeyStore
|
||||||
} catch (FileNotFoundException e) {
|
.getDefaultType());
|
||||||
throw new RuntimeException(e);
|
File truststoreFile = new File(filename);
|
||||||
}
|
FileInputStream trustStream = new FileInputStream(truststoreFile);
|
||||||
}
|
trustStore.load(trustStream, password.toCharArray());
|
||||||
remoteSkeletonKeyConfig = null;
|
trustStream.close();
|
||||||
try {
|
return trustStore;
|
||||||
remoteSkeletonKeyConfig = mapper.readValue(is, ManagedResourceConfig.class);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(boolean setupClient) {
|
public void init(boolean setupClient) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
String truststorePath = remoteSkeletonKeyConfig.getTruststore();
|
String truststorePath = remoteSkeletonKeyConfig.getTruststore();
|
||||||
if (truststorePath != null) {
|
if (truststorePath != null) {
|
||||||
truststorePath = EnvUtil.replace(truststorePath);
|
truststorePath = EnvUtil.replace(truststorePath);
|
||||||
|
@ -127,6 +124,31 @@ public class ManagedResourceConfigLoader {
|
||||||
resourceMetadata.setClientKeyPassword(clientKeyPassword);
|
resourceMetadata.setClientKeyPassword(clientKeyPassword);
|
||||||
resourceMetadata.setTruststore(this.truststore);
|
resourceMetadata.setTruststore(this.truststore);
|
||||||
|
|
||||||
|
if (!setupClient || remoteSkeletonKeyConfig.isBearerOnly()) return;
|
||||||
|
|
||||||
|
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 : getRemoteSkeletonKeyConfig().getCredentials().entrySet()) {
|
||||||
|
realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResteasyClient client = getClient();
|
||||||
|
|
||||||
|
realmConfiguration.setClient(client);
|
||||||
|
realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
||||||
|
realmConfiguration.setCodeUrl(client.target(tokenUrl));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initClient() {
|
protected void initClient() {
|
||||||
|
@ -158,16 +180,6 @@ public class ManagedResourceConfigLoader {
|
||||||
client = builder.build();
|
client = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyStore loadKeyStore(String filename, String password) throws Exception {
|
|
||||||
KeyStore trustStore = KeyStore.getInstance(KeyStore
|
|
||||||
.getDefaultType());
|
|
||||||
File truststoreFile = new File(filename);
|
|
||||||
FileInputStream trustStream = new FileInputStream(truststoreFile);
|
|
||||||
trustStore.load(trustStream, password.toCharArray());
|
|
||||||
trustStream.close();
|
|
||||||
return trustStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ManagedResourceConfig getRemoteSkeletonKeyConfig() {
|
public ManagedResourceConfig getRemoteSkeletonKeyConfig() {
|
||||||
return remoteSkeletonKeyConfig;
|
return remoteSkeletonKeyConfig;
|
||||||
}
|
}
|
||||||
|
@ -187,4 +199,19 @@ public class ManagedResourceConfigLoader {
|
||||||
public KeyStore getTruststore() {
|
public KeyStore getTruststore() {
|
||||||
return truststore;
|
return truststore;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public RealmConfiguration getRealmConfiguration() {
|
||||||
|
return realmConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadConfig(InputStream is) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
|
||||||
|
remoteSkeletonKeyConfig = null;
|
||||||
|
try {
|
||||||
|
remoteSkeletonKeyConfig = mapper.readValue(is, ManagedResourceConfig.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
examples/wildfly-demo/README.md
Executable file
79
examples/wildfly-demo/README.md
Executable file
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
73
examples/wildfly-demo/customer-app/pom.xml
Executable file
73
examples/wildfly-demo/customer-app/pom.xml
Executable file
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
5
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-web.xml
Executable file
5
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/jboss-web.xml
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
<jboss-web>
|
||||||
|
<valve>
|
||||||
|
<class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
|
||||||
|
</valve>
|
||||||
|
</jboss-web>
|
12
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/keycloak.json
Executable file
12
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/keycloak.json
Executable file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
49
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/web.xml
Executable file
49
examples/wildfly-demo/customer-app/src/main/webapp/WEB-INF/web.xml
Executable file
|
@ -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>
|
|
@ -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>
|
38
examples/wildfly-demo/customer-app/src/main/webapp/customers/cors-test.html
Executable file
38
examples/wildfly-demo/customer-app/src/main/webapp/customers/cors-test.html
Executable file
|
@ -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>
|
28
examples/wildfly-demo/customer-app/src/main/webapp/customers/view.jsp
Executable file
28
examples/wildfly-demo/customer-app/src/main/webapp/customers/view.jsp
Executable file
|
@ -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>
|
|
@ -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>
|
62
examples/wildfly-demo/database-service/pom.xml
Executable file
62
examples/wildfly-demo/database-service/pom.xml
Executable file
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
{
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<jboss-web>
|
||||||
|
<valve>
|
||||||
|
<class-name>org.keycloak.adapters.as7.BearerTokenAuthenticatorValve</class-name>
|
||||||
|
</valve>
|
||||||
|
</jboss-web>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"realm" : "demo",
|
||||||
|
"resource" : "database-service",
|
||||||
|
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
|
"enable-cors" : true,
|
||||||
|
"bearer-only" : true
|
||||||
|
|
||||||
|
}
|
29
examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/web.xml
Executable file
29
examples/wildfly-demo/database-service/src/main/webapp/WEB-INF/web.xml
Executable file
|
@ -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>
|
43
examples/wildfly-demo/pom.xml
Executable file
43
examples/wildfly-demo/pom.xml
Executable file
|
@ -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>
|
73
examples/wildfly-demo/product-app/pom.xml
Executable file
73
examples/wildfly-demo/product-app/pom.xml
Executable file
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
5
examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-web.xml
Executable file
5
examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/jboss-web.xml
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
<jboss-web>
|
||||||
|
<valve>
|
||||||
|
<class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
|
||||||
|
</valve>
|
||||||
|
</jboss-web>
|
11
examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/keycloak.json
Executable file
11
examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/keycloak.json
Executable file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
49
examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/web.xml
Executable file
49
examples/wildfly-demo/product-app/src/main/webapp/WEB-INF/web.xml
Executable file
|
@ -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>
|
|
@ -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>
|
14
examples/wildfly-demo/product-app/src/main/webapp/index.html
Normal file
14
examples/wildfly-demo/product-app/src/main/webapp/index.html
Normal file
|
@ -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>
|
28
examples/wildfly-demo/product-app/src/main/webapp/products/view.jsp
Executable file
28
examples/wildfly-demo/product-app/src/main/webapp/products/view.jsp
Executable file
|
@ -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>
|
142
examples/wildfly-demo/server/pom.xml
Executable file
142
examples/wildfly-demo/server/pom.xml
Executable file
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
31
examples/wildfly-demo/server/src/main/resources/META-INF/persistence.xml
Executable file
31
examples/wildfly-demo/server/src/main/resources/META-INF/persistence.xml
Executable file
|
@ -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>
|
91
examples/wildfly-demo/server/src/main/resources/META-INF/testrealm.json
Executable file
91
examples/wildfly-demo/server/src/main/resources/META-INF/testrealm.json
Executable file
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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>
|
53
examples/wildfly-demo/server/src/main/webapp/WEB-INF/web.xml
Executable file
53
examples/wildfly-demo/server/src/main/webapp/WEB-INF/web.xml
Executable file
|
@ -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>
|
61
examples/wildfly-demo/third-party/pom.xml
vendored
Executable file
61
examples/wildfly-demo/third-party/pom.xml
vendored
Executable file
|
@ -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>
|
69
examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java
vendored
Executable file
69
examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/Bootstrap.java
vendored
Executable file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
69
examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
vendored
Executable file
69
examples/wildfly-demo/third-party/src/main/java/org/jboss/resteasy/example/oauth/ProductDatabaseClient.java
vendored
Executable file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
vendored
Executable file
9
examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
vendored
Executable file
|
@ -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>
|
23
examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/web.xml
vendored
Executable file
23
examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/web.xml
vendored
Executable file
|
@ -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>
|
6
examples/wildfly-demo/third-party/src/main/webapp/index.html
vendored
Normal file
6
examples/wildfly-demo/third-party/src/main/webapp/index.html
vendored
Normal file
|
@ -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>
|
21
examples/wildfly-demo/third-party/src/main/webapp/pull_data.jsp
vendored
Normal file
21
examples/wildfly-demo/third-party/src/main/webapp/pull_data.jsp
vendored
Normal file
|
@ -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>
|
3
examples/wildfly-demo/third-party/src/main/webapp/redirect.jsp
vendored
Normal file
3
examples/wildfly-demo/third-party/src/main/webapp/redirect.jsp
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<%
|
||||||
|
org.jboss.resteasy.example.oauth.ProductDatabaseClient.redirect(request, response);
|
||||||
|
%>
|
|
@ -8,7 +8,7 @@ import org.apache.catalina.connector.Response;
|
||||||
import org.apache.catalina.valves.ValveBase;
|
import org.apache.catalina.valves.ValveBase;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.SkeletonKeySession;
|
import org.keycloak.SkeletonKeySession;
|
||||||
import org.keycloak.adapters.as7.config.ManagedResourceConfig;
|
import org.keycloak.adapters.config.ManagedResourceConfig;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
|
|
||||||
import javax.management.ObjectName;
|
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 {
|
protected void queryBearerToken(Request request, Response response, SkeletonKeySession session) throws IOException, ServletException {
|
||||||
log.debugv("queryBearerToken {0}",request.getRequestURI());
|
log.debugv("queryBearerToken {0}",request.getRequestURI());
|
||||||
if (abortTokenResponse(request, response, session)) return;
|
if (abortTokenResponse(request, response, session)) return;
|
||||||
|
response.setStatus(200);
|
||||||
response.setContentType("text/plain");
|
response.setContentType("text/plain");
|
||||||
response.getOutputStream().write(session.getTokenString().getBytes());
|
response.getOutputStream().write(session.getTokenString().getBytes());
|
||||||
response.getOutputStream().flush();
|
response.getOutputStream().flush();
|
||||||
|
|
|
@ -12,8 +12,9 @@ import org.apache.catalina.deploy.LoginConfig;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.adapters.as7.config.ManagedResourceConfig;
|
import org.keycloak.adapters.as7.config.CatalinaManagedResourceConfigLoader;
|
||||||
import org.keycloak.adapters.as7.config.ManagedResourceConfigLoader;
|
import org.keycloak.adapters.config.ManagedResourceConfig;
|
||||||
|
import org.keycloak.adapters.config.ManagedResourceConfigLoader;
|
||||||
|
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
@ -45,7 +46,7 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init() {
|
protected void init() {
|
||||||
ManagedResourceConfigLoader managedResourceConfigLoader = new ManagedResourceConfigLoader(context);
|
ManagedResourceConfigLoader managedResourceConfigLoader = new CatalinaManagedResourceConfigLoader(context);
|
||||||
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
|
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
|
||||||
managedResourceConfigLoader.init(false);
|
managedResourceConfigLoader.init(false);
|
||||||
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
|
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.keycloak.adapters.as7;
|
||||||
import org.apache.catalina.connector.Request;
|
import org.apache.catalina.connector.Request;
|
||||||
import org.apache.catalina.connector.Response;
|
import org.apache.catalina.connector.Response;
|
||||||
import org.jboss.logging.Logger;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
|
|
@ -22,8 +22,9 @@ import org.keycloak.RealmConfiguration;
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.SkeletonKeyPrincipal;
|
import org.keycloak.SkeletonKeyPrincipal;
|
||||||
import org.keycloak.SkeletonKeySession;
|
import org.keycloak.SkeletonKeySession;
|
||||||
import org.keycloak.adapters.as7.config.ManagedResourceConfig;
|
import org.keycloak.adapters.as7.config.CatalinaManagedResourceConfigLoader;
|
||||||
import org.keycloak.adapters.as7.config.ManagedResourceConfigLoader;
|
import org.keycloak.adapters.config.ManagedResourceConfig;
|
||||||
|
import org.keycloak.adapters.config.ManagedResourceConfigLoader;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
import org.keycloak.representations.idm.admin.LogoutAction;
|
import org.keycloak.representations.idm.admin.LogoutAction;
|
||||||
|
|
||||||
|
@ -66,32 +67,12 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init() {
|
protected void init() {
|
||||||
ManagedResourceConfigLoader managedResourceConfigLoader = new ManagedResourceConfigLoader(context);
|
ManagedResourceConfigLoader managedResourceConfigLoader = new CatalinaManagedResourceConfigLoader(context);
|
||||||
managedResourceConfigLoader.init(true);
|
managedResourceConfigLoader.init(true);
|
||||||
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
|
resourceMetadata = managedResourceConfigLoader.getResourceMetadata();
|
||||||
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
|
remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig();
|
||||||
|
|
||||||
realmConfiguration = new RealmConfiguration();
|
realmConfiguration = managedResourceConfigLoader.getRealmConfiguration();
|
||||||
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));
|
|
||||||
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(remoteSkeletonKeyConfig, getNext(), getContainer(), getController());
|
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(remoteSkeletonKeyConfig, getNext(), getContainer(), getController());
|
||||||
setNext(actions);
|
setNext(actions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
package org.keycloak.adapters.as7.config;
|
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonProperty;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class AuthServerConfig {
|
|
||||||
@JsonProperty("realm")
|
|
||||||
protected String realm;
|
|
||||||
|
|
||||||
@JsonProperty("realm-private-key")
|
|
||||||
protected String realmPrivateKey;
|
|
||||||
|
|
||||||
@JsonProperty("realm-public-key")
|
|
||||||
protected String realmPublicKey;
|
|
||||||
|
|
||||||
@JsonProperty("realm-keystore")
|
|
||||||
protected String realmKeyStore;
|
|
||||||
|
|
||||||
@JsonProperty("realm-keystore-password")
|
|
||||||
protected String realmKeystorePassword;
|
|
||||||
|
|
||||||
@JsonProperty("realm-key-alias")
|
|
||||||
protected String realmKeyAlias;
|
|
||||||
|
|
||||||
@JsonProperty("realm-private-key-password")
|
|
||||||
protected String realmPrivateKeyPassword;
|
|
||||||
|
|
||||||
@JsonProperty("access-code-lifetime")
|
|
||||||
protected int accessCodeLifetime;
|
|
||||||
|
|
||||||
@JsonProperty("token-lifetime")
|
|
||||||
protected int tokenLifetime;
|
|
||||||
|
|
||||||
@JsonProperty("admin-role")
|
|
||||||
protected String adminRole;
|
|
||||||
|
|
||||||
@JsonProperty("login-role")
|
|
||||||
protected String loginRole;
|
|
||||||
|
|
||||||
@JsonProperty("oauth-client-role")
|
|
||||||
protected String clientRole;
|
|
||||||
|
|
||||||
@JsonProperty("wildcard-role")
|
|
||||||
protected String wildcardRole;
|
|
||||||
|
|
||||||
@JsonProperty("cancel-propagation")
|
|
||||||
protected boolean cancelPropagation;
|
|
||||||
|
|
||||||
@JsonProperty("sso-disabled")
|
|
||||||
protected boolean ssoDisabled;
|
|
||||||
|
|
||||||
// these properties are optional and used to provide connection metadata when the server wants to make
|
|
||||||
// remote SSL connections
|
|
||||||
|
|
||||||
protected String truststore;
|
|
||||||
@JsonProperty("truststore-password")
|
|
||||||
protected String truststorePassword;
|
|
||||||
@JsonProperty("client-keystore")
|
|
||||||
protected String clientKeystore;
|
|
||||||
@JsonProperty("client-keystore-password")
|
|
||||||
protected String clientKeystorePassword;
|
|
||||||
@JsonProperty("client-key-password")
|
|
||||||
protected String clientKeyPassword;
|
|
||||||
|
|
||||||
protected List<String> resources = new ArrayList<String>();
|
|
||||||
|
|
||||||
|
|
||||||
public String getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealm(String realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmPrivateKey() {
|
|
||||||
return realmPrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealmPrivateKey(String realmPrivateKey) {
|
|
||||||
this.realmPrivateKey = realmPrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmPublicKey() {
|
|
||||||
return realmPublicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealmPublicKey(String realmPublicKey) {
|
|
||||||
this.realmPublicKey = realmPublicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAccessCodeLifetime() {
|
|
||||||
return accessCodeLifetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccessCodeLifetime(int accessCodeLifetime) {
|
|
||||||
this.accessCodeLifetime = accessCodeLifetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTruststore() {
|
|
||||||
return truststore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTruststore(String truststore) {
|
|
||||||
this.truststore = truststore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTruststorePassword() {
|
|
||||||
return truststorePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTruststorePassword(String truststorePassword) {
|
|
||||||
this.truststorePassword = truststorePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientKeystore() {
|
|
||||||
return clientKeystore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientKeystore(String clientKeystore) {
|
|
||||||
this.clientKeystore = clientKeystore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientKeystorePassword() {
|
|
||||||
return clientKeystorePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientKeystorePassword(String clientKeystorePassword) {
|
|
||||||
this.clientKeystorePassword = clientKeystorePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientKeyPassword() {
|
|
||||||
return clientKeyPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientKeyPassword(String clientKeyPassword) {
|
|
||||||
this.clientKeyPassword = clientKeyPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCancelPropagation() {
|
|
||||||
return cancelPropagation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCancelPropagation(boolean cancelPropagation) {
|
|
||||||
this.cancelPropagation = cancelPropagation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSsoDisabled() {
|
|
||||||
return ssoDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSsoDisabled(boolean ssoDisabled) {
|
|
||||||
this.ssoDisabled = ssoDisabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getResources() {
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAdminRole() {
|
|
||||||
return adminRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdminRole(String adminRole) {
|
|
||||||
this.adminRole = adminRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLoginRole() {
|
|
||||||
return loginRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoginRole(String loginRole) {
|
|
||||||
this.loginRole = loginRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientRole() {
|
|
||||||
return clientRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientRole(String clientRole) {
|
|
||||||
this.clientRole = clientRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWildcardRole() {
|
|
||||||
return wildcardRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWildcardRole(String wildcardRole) {
|
|
||||||
this.wildcardRole = wildcardRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmKeyStore() {
|
|
||||||
return realmKeyStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealmKeyStore(String realmKeyStore) {
|
|
||||||
this.realmKeyStore = realmKeyStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmKeystorePassword() {
|
|
||||||
return realmKeystorePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealmKeystorePassword(String realmKeystorePassword) {
|
|
||||||
this.realmKeystorePassword = realmKeystorePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmKeyAlias() {
|
|
||||||
return realmKeyAlias;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealmKeyAlias(String realmKeyAlias) {
|
|
||||||
this.realmKeyAlias = realmKeyAlias;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealmPrivateKeyPassword() {
|
|
||||||
return realmPrivateKeyPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealmPrivateKeyPassword(String realmPrivateKeyPassword) {
|
|
||||||
this.realmPrivateKeyPassword = realmPrivateKeyPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTokenLifetime() {
|
|
||||||
return tokenLifetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTokenLifetime(int tokenLifetime) {
|
|
||||||
this.tokenLifetime = tokenLifetime;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>as7-eap6/adapter</module>
|
<module>as7-eap6/adapter</module>
|
||||||
|
<module>undertow</module>
|
||||||
<!-- <module>as7-eap6/jboss-modules</module> -->
|
<!-- <module>as7-eap6/jboss-modules</module> -->
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
78
integration/undertow/pom.xml
Executable file
78
integration/undertow/pom.xml
Executable file
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.adapters.undertow.KeycloakServletExtension
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<resteasy.version>3.0.5.Final</resteasy.version>
|
<resteasy.version>3.0.5.Final</resteasy.version>
|
||||||
<undertow.version>1.0.0.Beta12</undertow.version>
|
<undertow.version>1.0.0.Beta21</undertow.version>
|
||||||
<picketlink.version>2.5.0.Beta6</picketlink.version>
|
<picketlink.version>2.5.0.Beta6</picketlink.version>
|
||||||
<mongo.driver.version>2.11.2</mongo.driver.version>
|
<mongo.driver.version>2.11.2</mongo.driver.version>
|
||||||
<jboss.logging.version>3.1.1.GA</jboss.logging.version>
|
<jboss.logging.version>3.1.1.GA</jboss.logging.version>
|
||||||
|
|
|
@ -26,6 +26,7 @@ import io.undertow.Undertow.Builder;
|
||||||
import io.undertow.server.handlers.resource.FileResource;
|
import io.undertow.server.handlers.resource.FileResource;
|
||||||
import io.undertow.server.handlers.resource.FileResourceManager;
|
import io.undertow.server.handlers.resource.FileResourceManager;
|
||||||
import io.undertow.server.handlers.resource.Resource;
|
import io.undertow.server.handlers.resource.Resource;
|
||||||
|
import io.undertow.server.handlers.resource.ResourceChangeListener;
|
||||||
import io.undertow.server.handlers.resource.ResourceManager;
|
import io.undertow.server.handlers.resource.ResourceManager;
|
||||||
import io.undertow.server.handlers.resource.URLResource;
|
import io.undertow.server.handlers.resource.URLResource;
|
||||||
import io.undertow.servlet.Servlets;
|
import io.undertow.servlet.Servlets;
|
||||||
|
@ -326,6 +327,26 @@ public class KeycloakServer {
|
||||||
return new FileResource(file, new FileResourceManager(file.getParentFile(), 1), path);
|
return new FileResource(file, new FileResourceManager(file.getParentFile(), 1), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResourceChangeListenerSupported() {
|
||||||
|
return false; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerResourceChangeListener(ResourceChangeListener resourceChangeListener) {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeResourceChangeListener(ResourceChangeListener resourceChangeListener) {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File file(String... path) {
|
private static File file(String... path) {
|
||||||
|
|
Loading…
Reference in a new issue