Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2014-01-13 13:57:36 -05:00
commit 355d4bdf91
54 changed files with 1436 additions and 105 deletions

View file

@ -17,6 +17,7 @@ public class AbstractOAuthClient {
protected KeyStore truststore; protected KeyStore truststore;
protected String authUrl; protected String authUrl;
protected String codeUrl; protected String codeUrl;
protected String scope;
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE; protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
protected String stateCookiePath; protected String stateCookiePath;
protected boolean isSecure; protected boolean isSecure;
@ -68,6 +69,14 @@ public class AbstractOAuthClient {
this.codeUrl = codeUrl; this.codeUrl = codeUrl;
} }
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getStateCookieName() { public String getStateCookieName() {
return stateCookieName; return stateCookieName;
} }

View file

@ -14,6 +14,7 @@ public class ResourceMetadata {
protected String clientKeyPassword; protected String clientKeyPassword;
protected KeyStore truststore; protected KeyStore truststore;
protected PublicKey realmKey; protected PublicKey realmKey;
protected String scope;
public String getResourceName() { public String getResourceName() {
return resourceName; return resourceName;
@ -78,4 +79,12 @@ public class ResourceMetadata {
public void setRealmKey(PublicKey realmKey) { public void setRealmKey(PublicKey realmKey) {
this.realmKey = realmKey; this.realmKey = realmKey;
} }
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
} }

View file

@ -13,7 +13,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
"resource", "credentials", "resource", "credentials",
"use-resource-role-mappings", "use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods", "enable-cors", "cors-max-age", "cors-allowed-methods",
"expose-token", "bearer-only", "expose-token", "bearer-only", "scope",
"connection-pool-size", "connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password" "client-keystore", "client-keystore-password", "client-key-password"

View file

@ -2,6 +2,7 @@ package org.keycloak.representations.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;
import org.keycloak.representations.SkeletonKeyScope;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -16,7 +17,7 @@ import java.util.Map;
"resource", "credentials", "resource", "credentials",
"use-resource-role-mappings", "use-resource-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods", "enable-cors", "cors-max-age", "cors-allowed-methods",
"expose-token", "bearer-only"}) "expose-token", "bearer-only", "scope"})
public class BaseAdapterConfig extends BaseRealmConfig { public class BaseAdapterConfig extends BaseRealmConfig {
@JsonProperty("resource") @JsonProperty("resource")
protected String resource; protected String resource;
@ -36,6 +37,9 @@ public class BaseAdapterConfig extends BaseRealmConfig {
protected boolean bearerOnly; protected boolean bearerOnly;
@JsonProperty("credentials") @JsonProperty("credentials")
protected Map<String, String> credentials = new HashMap<String, String>(); protected Map<String, String> credentials = new HashMap<String, String>();
@JsonProperty("scope")
protected SkeletonKeyScope scope;
public boolean isUseResourceRoleMappings() { public boolean isUseResourceRoleMappings() {
return useResourceRoleMappings; return useResourceRoleMappings;
@ -108,4 +112,12 @@ public class BaseAdapterConfig extends BaseRealmConfig {
public void setCredentials(Map<String, String> credentials) { public void setCredentials(Map<String, String> credentials) {
this.credentials = credentials; this.credentials = credentials;
} }
public SkeletonKeyScope getScope() {
return scope;
}
public void setScope(SkeletonKeyScope scope) {
this.scope = scope;
}
} }

View file

@ -1,4 +1,4 @@
package org.jboss.resteasy.example.oauth; package org.keycloak.example.oauth;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;

View file

@ -1,4 +1,4 @@
package org.jboss.resteasy.example.oauth; package org.keycloak.example.oauth;
import javax.ws.rs.ApplicationPath; import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;

View file

@ -1,4 +1,4 @@
package org.jboss.resteasy.example.oauth; package org.keycloak.example.oauth;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;

View file

@ -38,5 +38,6 @@
<module>product-app</module> <module>product-app</module>
<module>database-service</module> <module>database-service</module>
<module>third-party</module> <module>third-party</module>
<module>third-party-cdi</module>
</modules> </modules>
</project> </project>

View file

@ -0,0 +1,74 @@
<?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-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.keycloak.example.as7.demo</groupId>
<artifactId>oauth-client-cdi-example</artifactId>
<packaging>war</packaging>
<name>Simple OAuth Client Using CDI and JSF</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>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.0-SP4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.faces</groupId>
<artifactId>jboss-jsf-api_2.1_spec</artifactId>
<version>2.0.1.Final</version>
<scope>provided</scope>
</dependency>
<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-servlet-oauth-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<finalName>oauth-client-cdi</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>

View file

@ -0,0 +1,57 @@
package org.keycloak.example.oauth;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.jboss.logging.Logger;
import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.servlet.ServletOAuthClientConfigLoader;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@WebListener
public class AppContextListener implements ServletContextListener {
private static final Logger logger = Logger.getLogger(AppContextListener.class);
@Inject
private ServletOAuthClient oauthClient;
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
InputStream is = null;
String path = context.getInitParameter("keycloak.config.file");
if (path == null) {
is = context.getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is);
loader.initOAuthClientConfiguration(true);
loader.configureServletOAuthClient(oauthClient);
oauthClient.start();
logger.info("OAuth client configured and started");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
oauthClient.stop();
logger.info("OAuth client stopped");
}
}

View file

@ -0,0 +1,41 @@
package org.keycloak.example.oauth;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.servlet.ServletOAuthClient;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CDIResourcesProducer {
@Produces
@RequestScoped
public FacesContext produceFacesContext() {
return FacesContext.getCurrentInstance();
}
@Produces
@RequestScoped
@ServletRequestQualifier
public HttpServletRequest produceServletRequest() {
return (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
}
@Produces
@RequestScoped
public HttpServletResponse produceServletResponse() {
return (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
}
@Produces
@ApplicationScoped
public ServletOAuthClient produceOAuthClient() {
return new ServletOAuthClient();
}
}

View file

@ -0,0 +1,104 @@
package org.keycloak.example.oauth;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.util.JsonSerialization;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @version $Revision: 1 $
*/
@ApplicationScoped
@Named("databaseClient")
public class DatabaseClient {
@Inject
@ServletRequestQualifier
private HttpServletRequest request;
@Inject
private HttpServletResponse response;
@Inject
private FacesContext facesContext;
@Inject
private ServletOAuthClient oauthClient;
@Inject
private UserData userData;
private static final Logger logger = Logger.getLogger(DatabaseClient.class);
public void retrieveAccessToken() {
try {
oauthClient.redirectRelative("client.jsf", request, response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static class TypedList extends ArrayList<String> {}
public void sendCustomersRequest() {
List<String> customers = sendRequestToDBApplication("http://localhost:8080/database/customers");
userData.setCustomers(customers);
}
public void sendProductsRequest() {
List<String> products = sendRequestToDBApplication("http://localhost:8080/database/products");
userData.setProducts(products);
}
protected List<String> sendRequestToDBApplication(String dbUri) {
HttpClient client = oauthClient.getClient();
HttpGet get = new HttpGet(dbUri);
try {
if (userData.isHasAccessToken()) {
get.addHeader("Authorization", "Bearer " + userData.getAccessToken());
}
HttpResponse response = client.execute(get);
switch (response.getStatusLine().getStatusCode()) {
case 200: HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, TypedList.class);
} finally {
is.close();
}
case 401: facesContext.addMessage(null, new FacesMessage("Status: 401. Request not authenticated! You need to retrieve access token first."));
break;
case 403: facesContext.addMessage(null, new FacesMessage("Status: 403. Access token has insufficient privileges"));
break;
default: facesContext.addMessage(null, new FacesMessage("Status: " + response.getStatusLine() + ". Not able to retrieve data. See log for details"));
logger.warn("Error occured. Status: " + response.getStatusLine());
}
return null;
} catch (IOException e) {
e.printStackTrace();
facesContext.addMessage(null, new FacesMessage("Unknown error. See log for details"));
return null;
}
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.example.oauth;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
/**
* This is needed because Faces context is not available in HTTP filters
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@RequestScoped
@Named("messagesChecker")
public class MessagesChecker {
@Inject
@ServletRequestQualifier
private HttpServletRequest request;
@Inject
private FacesContext facesContext;
public String getCheckMessage() {
String oauthError = (String)request.getAttribute(RefreshTokenFilter.OAUTH_ERROR_ATTR);
if (oauthError != null) {
facesContext.addMessage(null, new FacesMessage("OAuth error occured: " + oauthError));
}
return null;
}
}

View file

@ -0,0 +1,62 @@
package org.keycloak.example.oauth;
import java.io.IOException;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.servlet.ServletOAuthClient;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@WebFilter(value = "/client.jsf")
public class RefreshTokenFilter implements Filter {
public static final String OAUTH_ERROR_ATTR = "oauthErrorAttr";
@Inject
private ServletOAuthClient oauthClient;
@Inject
private UserData userData;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
Map<String, String[]> reqParams = request.getParameterMap();
if (reqParams.containsKey("code")) {
try {
String accessToken = oauthClient.getBearerToken(request);
userData.setAccessToken(accessToken);
} catch (TokenGrantRequest.HttpFailure e) {
throw new ServletException(e);
}
} else if (reqParams.containsKey("error")) {
String oauthError = reqParams.get("error")[0];
request.setAttribute(OAUTH_ERROR_ATTR, oauthError);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}

View file

@ -0,0 +1,19 @@
package org.keycloak.example.oauth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* This is needed to have same code working in AS7 and Wildfly. In Wildfly is HttpServletRequest injected automatically, in AS7 it's not
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface ServletRequestQualifier {
}

View file

@ -0,0 +1,63 @@
package org.keycloak.example.oauth;
import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@SessionScoped
@Named("userData")
public class UserData implements Serializable {
private String accessToken;
private List<String> products;
private List<String> customers;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public boolean isHasAccessToken() {
return accessToken != null;
}
public String getAccessTokenAvailabilityMessage() {
StringBuilder builder = new StringBuilder("Access token ");
if (!isHasAccessToken()) {
builder.append("not ");
}
return builder.append("available!").toString();
}
public List<String> getProducts() {
return products;
}
public void setProducts(List<String> products) {
this.products = products;
}
public boolean isHasProducts() {
return products != null;
}
public List<String> getCustomers() {
return customers;
}
public void setCustomers(List<String> customers) {
this.customers = customers;
}
public boolean isHasCustomers() {
return customers != null;
}
}

View file

@ -0,0 +1,23 @@
<!--
JBoss, Home of Professional Open Source
Copyright 2013, Red Hat, Inc. and/or its affiliates, and individual
contributors by the @authors tag. See the copyright.txt in the
distribution for a full listing of individual contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Marker file indicating CDI should be enabled -->
<beans 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/beans_1_0.xsd">
</beans>

View file

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!--
JBoss, Home of Professional Open Source
Copyright 2013, Red Hat, Inc. and/or its affiliates, and individual
contributors by the @authors tag. See the copyright.txt in the
distribution for a full listing of individual contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Marker file indicating JSF should be enabled -->
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xi="http://www.w3.org/2001/XInclude"
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-facesconfig_2_0.xsd">
</faces-config>

View file

@ -0,0 +1,7 @@
<jboss-deployment-structure>
<deployment>
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
<dependencies>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -0,0 +1,12 @@
{
"resource" : "third-party",
"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"
},
"scope": {
"realm": [ "user" ]
}
}

View file

@ -0,0 +1,20 @@
<?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-cdi</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>
</security-constraint>
-->
</web-app>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<body>
<h1>Third Party App That Pulls Data Using OAuth</h1>
<h:form>
#{userData.accessTokenAvailabilityMessage}
<br />
<h:commandButton id="retrieve_token" value="Retrieve/refresh access token" action="#{databaseClient.retrieveAccessToken}"/>
<h:commandButton id="products_request" value="Load products list" action="#{databaseClient.sendProductsRequest}"/>
<h:commandButton id="customers_request" value="Load customers list" action="#{databaseClient.sendCustomersRequest}"/>
</h:form>
<ui:fragment rendered="#{userData.hasProducts}">
<hr />
<h3>Products data available</h3>
<ui:repeat value="#{userData.products}" var="product">
#{product}<br/>
</ui:repeat>
</ui:fragment>
<ui:fragment rendered="#{userData.hasCustomers}">
<hr />
<h3>Customers data available</h3>
<ui:repeat value="#{userData.customers}" var="customer">
#{customer}<br/>
</ui:repeat>
</ui:fragment>
<div style="color: red">
#{messagesChecker.checkMessage}
<h:messages globalOnly="true"/>
</div>
</body>
</html>

View file

@ -0,0 +1,5 @@
<html>
<head>
<meta http-equiv="Refresh" content="0; URL=client.jsf">
</head>
</html>

View file

@ -1,15 +1,19 @@
package org.keycloak.example.oauth; package org.keycloak.example.oauth;
import org.keycloak.servlet.ServletOAuthClient; import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.servlet.ServletOAuthClientConfigLoader;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
/** /**
* Stupid init code to load up the truststore so we can make appropriate SSL connections * 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. * You really should use a better way of initializing this stuff.
* *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -32,36 +36,34 @@ public class Bootstrap implements ServletContextListener {
@Override @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
client = new ServletOAuthClient(); client = new ServletOAuthClient();
/* ServletContext context = sce.getServletContext();
// hardcoded, WARNING, you should really have a better way of doing this
// configuration. Either use something like Spring or CDI, or even pull configureClient(context);
// 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.start(); client.start();
sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client); context.setAttribute(ServletOAuthClient.class.getName(), client);
} }
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
client.stop(); client.stop();
} }
private void configureClient(ServletContext context) {
InputStream is = null;
String path = context.getInitParameter("keycloak.config.file");
if (path == null) {
is = context.getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is);
loader.initOAuthClientConfiguration(true);
loader.configureServletOAuthClient(client);
}
} }

View file

@ -21,11 +21,11 @@ import java.util.List;
*/ */
public class ProductDatabaseClient { public class ProductDatabaseClient {
public static void redirect(HttpServletRequest request, HttpServletResponse response) { public static void redirect(HttpServletRequest request, HttpServletResponse response) {
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute // The ServletOAuthClient is obtained by getting a context attribute
// that is set in the Bootstrap context listenr in this project. // that is set in the Bootstrap context listener in this project.
// You really should come up with a better way to initialize // You really should come up with a better way to initialize
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
// and take a look how it works. // and take a look how it works. You can also take a look at third-party-cdi example
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
try { try {
oAuthClient.redirectRelative("pull_data.jsp", request, response); oAuthClient.redirectRelative("pull_data.jsp", request, response);
@ -37,11 +37,11 @@ public class ProductDatabaseClient {
static class TypedList extends ArrayList<String> {} static class TypedList extends ArrayList<String> {}
public static List<String> getProducts(HttpServletRequest request) { public static List<String> getProducts(HttpServletRequest request) {
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute // The ServletOAuthClient is obtained by getting a context attribute
// that is set in the Bootstrap context listenr in this project. // that is set in the Bootstrap context listener in this project.
// You really should come up with a better way to initialize // You really should come up with a better way to initialize
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
// and take a look how it works. // and take a look how it works. You can also take a look at third-party-cdi example
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
String token = null; String token = null;
try { try {

View file

@ -0,0 +1,12 @@
{
"resource" : "third-party",
"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"
},
"scope": {
"realm": [ "user" ]
}
}

View file

@ -1,4 +1,4 @@
package org.jboss.resteasy.example.oauth; package org.keycloak.example.oauth;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;

View file

@ -1,4 +1,4 @@
package org.jboss.resteasy.example.oauth; package org.keycloak.example.oauth;
import javax.ws.rs.ApplicationPath; import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;

View file

@ -1,4 +1,4 @@
package org.jboss.resteasy.example.oauth; package org.keycloak.example.oauth;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;

View file

@ -39,5 +39,6 @@
<module>product-app</module> <module>product-app</module>
<module>database-service</module> <module>database-service</module>
<module>third-party</module> <module>third-party</module>
<module>third-party-cdi</module>
</modules> </modules>
</project> </project>

View file

@ -0,0 +1,74 @@
<?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-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.keycloak.example.wildfly.demo</groupId>
<artifactId>oauth-client-cdi-example</artifactId>
<packaging>war</packaging>
<name>Simple OAuth Client Using CDI and JSF</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>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.0-SP4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.faces</groupId>
<artifactId>jboss-jsf-api_2.1_spec</artifactId>
<version>2.0.1.Final</version>
<scope>provided</scope>
</dependency>
<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-servlet-oauth-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<finalName>oauth-client-cdi</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>

View file

@ -0,0 +1,57 @@
package org.keycloak.example.oauth;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.jboss.logging.Logger;
import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.servlet.ServletOAuthClientConfigLoader;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@WebListener
public class AppContextListener implements ServletContextListener {
private static final Logger logger = Logger.getLogger(AppContextListener.class);
@Inject
private ServletOAuthClient oauthClient;
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
InputStream is = null;
String path = context.getInitParameter("keycloak.config.file");
if (path == null) {
is = context.getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is);
loader.initOAuthClientConfiguration(true);
loader.configureServletOAuthClient(oauthClient);
oauthClient.start();
logger.info("OAuth client configured and started");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
oauthClient.stop();
logger.info("OAuth client stopped");
}
}

View file

@ -0,0 +1,41 @@
package org.keycloak.example.oauth;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.servlet.ServletOAuthClient;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CDIResourcesProducer {
@Produces
@RequestScoped
public FacesContext produceFacesContext() {
return FacesContext.getCurrentInstance();
}
@Produces
@RequestScoped
@ServletRequestQualifier
public HttpServletRequest produceServletRequest() {
return (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
}
@Produces
@RequestScoped
public HttpServletResponse produceServletResponse() {
return (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
}
@Produces
@ApplicationScoped
public ServletOAuthClient produceOAuthClient() {
return new ServletOAuthClient();
}
}

View file

@ -0,0 +1,104 @@
package org.keycloak.example.oauth;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.util.JsonSerialization;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @version $Revision: 1 $
*/
@ApplicationScoped
@Named("databaseClient")
public class DatabaseClient {
@Inject
@ServletRequestQualifier
private HttpServletRequest request;
@Inject
private HttpServletResponse response;
@Inject
private FacesContext facesContext;
@Inject
private ServletOAuthClient oauthClient;
@Inject
private UserData userData;
private static final Logger logger = Logger.getLogger(DatabaseClient.class);
public void retrieveAccessToken() {
try {
oauthClient.redirectRelative("client.jsf", request, response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static class TypedList extends ArrayList<String> {}
public void sendCustomersRequest() {
List<String> customers = sendRequestToDBApplication("http://localhost:8080/database/customers");
userData.setCustomers(customers);
}
public void sendProductsRequest() {
List<String> products = sendRequestToDBApplication("http://localhost:8080/database/products");
userData.setProducts(products);
}
protected List<String> sendRequestToDBApplication(String dbUri) {
HttpClient client = oauthClient.getClient();
HttpGet get = new HttpGet(dbUri);
try {
if (userData.isHasAccessToken()) {
get.addHeader("Authorization", "Bearer " + userData.getAccessToken());
}
HttpResponse response = client.execute(get);
switch (response.getStatusLine().getStatusCode()) {
case 200: HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, TypedList.class);
} finally {
is.close();
}
case 401: facesContext.addMessage(null, new FacesMessage("Status: 401. Request not authenticated! You need to retrieve access token first."));
break;
case 403: facesContext.addMessage(null, new FacesMessage("Status: 403. Access token has insufficient privileges"));
break;
default: facesContext.addMessage(null, new FacesMessage("Status: " + response.getStatusLine() + ". Not able to retrieve data. See log for details"));
logger.warn("Error occured. Status: " + response.getStatusLine());
}
return null;
} catch (IOException e) {
e.printStackTrace();
facesContext.addMessage(null, new FacesMessage("Unknown error. See log for details"));
return null;
}
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.example.oauth;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
/**
* This is needed because Faces context is not available in HTTP filters
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@RequestScoped
@Named("messagesChecker")
public class MessagesChecker {
@Inject
@ServletRequestQualifier
private HttpServletRequest request;
@Inject
private FacesContext facesContext;
public String getCheckMessage() {
String oauthError = (String)request.getAttribute(RefreshTokenFilter.OAUTH_ERROR_ATTR);
if (oauthError != null) {
facesContext.addMessage(null, new FacesMessage("OAuth error occured: " + oauthError));
}
return null;
}
}

View file

@ -0,0 +1,62 @@
package org.keycloak.example.oauth;
import java.io.IOException;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.servlet.ServletOAuthClient;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@WebFilter(value = "/client.jsf")
public class RefreshTokenFilter implements Filter {
public static final String OAUTH_ERROR_ATTR = "oauthErrorAttr";
@Inject
private ServletOAuthClient oauthClient;
@Inject
private UserData userData;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
Map<String, String[]> reqParams = request.getParameterMap();
if (reqParams.containsKey("code")) {
try {
String accessToken = oauthClient.getBearerToken(request);
userData.setAccessToken(accessToken);
} catch (TokenGrantRequest.HttpFailure e) {
throw new ServletException(e);
}
} else if (reqParams.containsKey("error")) {
String oauthError = reqParams.get("error")[0];
request.setAttribute(OAUTH_ERROR_ATTR, oauthError);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}

View file

@ -0,0 +1,19 @@
package org.keycloak.example.oauth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* This is needed to have same code working in AS7 and Wildfly. In Wildfly is HttpServletRequest injected automatically, in AS7 it's not
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface ServletRequestQualifier {
}

View file

@ -0,0 +1,63 @@
package org.keycloak.example.oauth;
import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@SessionScoped
@Named("userData")
public class UserData implements Serializable {
private String accessToken;
private List<String> products;
private List<String> customers;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public boolean isHasAccessToken() {
return accessToken != null;
}
public String getAccessTokenAvailabilityMessage() {
StringBuilder builder = new StringBuilder("Access token ");
if (!isHasAccessToken()) {
builder.append("not ");
}
return builder.append("available!").toString();
}
public List<String> getProducts() {
return products;
}
public void setProducts(List<String> products) {
this.products = products;
}
public boolean isHasProducts() {
return products != null;
}
public List<String> getCustomers() {
return customers;
}
public void setCustomers(List<String> customers) {
this.customers = customers;
}
public boolean isHasCustomers() {
return customers != null;
}
}

View file

@ -0,0 +1,23 @@
<!--
JBoss, Home of Professional Open Source
Copyright 2013, Red Hat, Inc. and/or its affiliates, and individual
contributors by the @authors tag. See the copyright.txt in the
distribution for a full listing of individual contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Marker file indicating CDI should be enabled -->
<beans 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/beans_1_0.xsd">
</beans>

View file

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!--
JBoss, Home of Professional Open Source
Copyright 2013, Red Hat, Inc. and/or its affiliates, and individual
contributors by the @authors tag. See the copyright.txt in the
distribution for a full listing of individual contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Marker file indicating JSF should be enabled -->
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xi="http://www.w3.org/2001/XInclude"
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-facesconfig_2_0.xsd">
</faces-config>

View file

@ -0,0 +1,7 @@
<jboss-deployment-structure>
<deployment>
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
<dependencies>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -0,0 +1,12 @@
{
"resource" : "third-party",
"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"
},
"scope": {
"realm": [ "user" ]
}
}

View file

@ -0,0 +1,20 @@
<?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-cdi</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>
</security-constraint>
-->
</web-app>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<body>
<h1>Third Party App That Pulls Data Using OAuth</h1>
<h:form>
#{userData.accessTokenAvailabilityMessage}
<br />
<h:commandButton id="retrieve_token" value="Retrieve/refresh access token" action="#{databaseClient.retrieveAccessToken}"/>
<h:commandButton id="products_request" value="Load products list" action="#{databaseClient.sendProductsRequest}"/>
<h:commandButton id="customers_request" value="Load customers list" action="#{databaseClient.sendCustomersRequest}"/>
</h:form>
<ui:fragment rendered="#{userData.hasProducts}">
<hr />
<h3>Products data available</h3>
<ui:repeat value="#{userData.products}" var="product">
#{product}<br/>
</ui:repeat>
</ui:fragment>
<ui:fragment rendered="#{userData.hasCustomers}">
<hr />
<h3>Customers data available</h3>
<ui:repeat value="#{userData.customers}" var="customer">
#{customer}<br/>
</ui:repeat>
</ui:fragment>
<div style="color: red">
#{messagesChecker.checkMessage}
<h:messages globalOnly="true"/>
</div>
</body>
</html>

View file

@ -0,0 +1,5 @@
<html>
<head>
<meta http-equiv="Refresh" content="0; URL=client.jsf">
</head>
</html>

View file

@ -1,15 +1,19 @@
package org.keycloak.example.oauth; package org.keycloak.example.oauth;
import org.keycloak.servlet.ServletOAuthClient; import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.servlet.ServletOAuthClientConfigLoader;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
/** /**
* Stupid init code to load up the truststore so we can make appropriate SSL connections * 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. * You really should use a better way of initializing this stuff.
* *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -32,36 +36,34 @@ public class Bootstrap implements ServletContextListener {
@Override @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
client = new ServletOAuthClient(); client = new ServletOAuthClient();
/* ServletContext context = sce.getServletContext();
// hardcoded, WARNING, you should really have a better way of doing this
// configuration. Either use something like Spring or CDI, or even pull configureClient(context);
// 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.start(); client.start();
sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client); context.setAttribute(ServletOAuthClient.class.getName(), client);
} }
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
client.stop(); client.stop();
} }
private void configureClient(ServletContext context) {
InputStream is = null;
String path = context.getInitParameter("keycloak.config.file");
if (path == null) {
is = context.getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is);
loader.initOAuthClientConfiguration(true);
loader.configureServletOAuthClient(client);
}
} }

View file

@ -21,11 +21,11 @@ import java.util.List;
*/ */
public class ProductDatabaseClient { public class ProductDatabaseClient {
public static void redirect(HttpServletRequest request, HttpServletResponse response) { public static void redirect(HttpServletRequest request, HttpServletResponse response) {
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute // The ServletOAuthClient is obtained by getting a context attribute
// that is set in the Bootstrap context listenr in this project. // that is set in the Bootstrap context listener in this project.
// You really should come up with a better way to initialize // You really should come up with a better way to initialize
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
// and take a look how it works. // and take a look how it works. You can also take a look at third-party-cdi example
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
try { try {
oAuthClient.redirectRelative("pull_data.jsp", request, response); oAuthClient.redirectRelative("pull_data.jsp", request, response);
@ -37,11 +37,11 @@ public class ProductDatabaseClient {
static class TypedList extends ArrayList<String> {} static class TypedList extends ArrayList<String> {}
public static List<String> getProducts(HttpServletRequest request) { public static List<String> getProducts(HttpServletRequest request) {
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute // The ServletOAuthClient is obtained by getting a context attribute
// that is set in the Bootstrap context listenr in this project. // that is set in the Bootstrap context listener in this project.
// You really should come up with a better way to initialize // You really should come up with a better way to initialize
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
// and take a look how it works. // and take a look how it works. You can also take a look at third-party-cdi example
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
String token = null; String token = null;
try { try {

View file

@ -0,0 +1,12 @@
{
"resource" : "third-party",
"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"
},
"scope": {
"realm": [ "user" ]
}
}

View file

@ -2,7 +2,10 @@ package org.keycloak.adapters.config;
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.keycloak.representations.SkeletonKeyScope;
import org.keycloak.util.Base64Url;
import org.keycloak.util.EnvUtil; import org.keycloak.util.EnvUtil;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.PemUtils; import org.keycloak.util.PemUtils;
import org.keycloak.adapters.ResourceMetadata; import org.keycloak.adapters.ResourceMetadata;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;
@ -35,29 +38,8 @@ public class AdapterConfigLoader {
} }
public void init() { public void init() {
String truststorePath = adapterConfig.getTruststore(); initTruststore();
if (truststorePath != null) { initClientKeystore();
truststorePath = EnvUtil.replace(truststorePath);
String truststorePassword = adapterConfig.getTruststorePassword();
truststorePath = null;
try {
this.truststore = loadKeyStore(truststorePath, truststorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore", e);
}
}
String clientKeystore = adapterConfig.getClientKeystore();
String clientKeyPassword = null;
if (clientKeystore != null) {
clientKeystore = EnvUtil.replace(clientKeystore);
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
clientCertKeystore = null;
try {
clientCertKeystore = loadKeyStore(clientKeystore, clientKeystorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load keystore", e);
}
}
String realm = adapterConfig.getRealm(); String realm = adapterConfig.getRealm();
if (realm == null) throw new RuntimeException("Must set 'realm' in config"); if (realm == null) throw new RuntimeException("Must set 'realm' in config");
@ -81,10 +63,15 @@ public class AdapterConfigLoader {
resourceMetadata.setResourceName(resource); resourceMetadata.setResourceName(resource);
resourceMetadata.setRealmKey(realmKey); resourceMetadata.setRealmKey(realmKey);
resourceMetadata.setClientKeystore(clientCertKeystore); resourceMetadata.setClientKeystore(clientCertKeystore);
clientKeyPassword = adapterConfig.getClientKeyPassword(); String clientKeyPassword = adapterConfig.getClientKeyPassword();
resourceMetadata.setClientKeyPassword(clientKeyPassword); resourceMetadata.setClientKeyPassword(clientKeyPassword);
resourceMetadata.setTruststore(this.truststore); resourceMetadata.setTruststore(this.truststore);
if (adapterConfig.getScope() != null) {
String scope = encodeScope(adapterConfig.getScope());
resourceMetadata.setScope(scope);
}
} }
public AdapterConfig getAdapterConfig() { public AdapterConfig getAdapterConfig() {
@ -113,4 +100,40 @@ public class AdapterConfigLoader {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
protected void initTruststore() {
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {
truststorePath = EnvUtil.replace(truststorePath);
String truststorePassword = adapterConfig.getTruststorePassword();
try {
this.truststore = loadKeyStore(truststorePath, truststorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore", e);
}
}
}
protected void initClientKeystore() {
String clientKeystore = adapterConfig.getClientKeystore();
if (clientKeystore != null) {
clientKeystore = EnvUtil.replace(clientKeystore);
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
clientCertKeystore = null;
try {
clientCertKeystore = loadKeyStore(clientKeystore, clientKeystorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load keystore", e);
}
}
}
protected String encodeScope(SkeletonKeyScope scope) {
try {
byte[] scopeBytes = JsonSerialization.writeValueAsBytes(scope);
return Base64Url.encode(scopeBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View file

@ -0,0 +1,38 @@
package org.keycloak.adapters.config;
import java.io.InputStream;
import org.keycloak.AbstractOAuthClient;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class OAuthClientConfigLoader extends RealmConfigurationLoader {
public OAuthClientConfigLoader() {
}
public OAuthClientConfigLoader(InputStream is) {
super(is);
}
/**
* For now, configure just things supported by AbstractOAuthClient
*/
public void initOAuthClientConfiguration() {
initTruststore();
initClientKeystore();
}
public void configureOAuthClient(AbstractOAuthClient oauthClient) {
oauthClient.setClientId(adapterConfig.getResource());
oauthClient.setPassword(adapterConfig.getCredentials().get("password"));
oauthClient.setAuthUrl(adapterConfig.getAuthUrl());
oauthClient.setCodeUrl(adapterConfig.getCodeUrl());
oauthClient.setTruststore(truststore);
if (adapterConfig.getScope() != null) {
String scope = encodeScope(adapterConfig.getScope());
oauthClient.setScope(scope);
}
}
}

View file

@ -138,12 +138,15 @@ public class ServletOAuthLogin {
if (port != 443) secureUrl.port(port); if (port != 443) secureUrl.port(port);
url = secureUrl.build().toString(); url = secureUrl.build().toString();
} }
return realmInfo.getAuthUrl().clone() KeycloakUriBuilder uriBuilder = realmInfo.getAuthUrl().clone()
.queryParam("client_id", realmInfo.getMetadata().getResourceName()) .queryParam("client_id", realmInfo.getMetadata().getResourceName())
.queryParam("redirect_uri", url) .queryParam("redirect_uri", url)
.queryParam("state", state) .queryParam("state", state)
.queryParam("login", "true") .queryParam("login", "true");
.build().toString(); if (realmInfo.getMetadata().getScope() != null) {
uriBuilder.queryParam("scope", realmInfo.getMetadata().getScope());
}
return uriBuilder.build().toString();
} }
protected static final AtomicLong counter = new AtomicLong(); protected static final AtomicLong counter = new AtomicLong();

View file

@ -18,6 +18,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.net.URI; import java.net.URI;
import java.net.URL;
/** /**
* Helper code to obtain oauth access tokens via browser redirects * Helper code to obtain oauth access tokens via browser redirects
@ -87,11 +88,15 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
state += "#" + path; state += "#" + path;
} }
URI url = UriBuilder.fromUri(authUrl) UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
.queryParam("client_id", clientId) .queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri) .queryParam("redirect_uri", redirectUri)
.queryParam("state", state) .queryParam("state", state);
.build(); if (scope != null) {
uriBuilder.queryParam("scope", scope);
}
URI url = uriBuilder.build();
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true); NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
logger.debug("NewCookie: " + cookie.toString()); logger.debug("NewCookie: " + cookie.toString());
logger.debug("Oauth Redirect to: " + url); logger.debug("Oauth Redirect to: " + url);

View file

@ -84,11 +84,15 @@ public class ServletOAuthClient extends AbstractOAuthClient {
public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException { public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
String state = getStateCode(); String state = getStateCode();
URI url = KeycloakUriBuilder.fromUri(authUrl) KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
.queryParam("client_id", clientId) .queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri) .queryParam("redirect_uri", redirectUri)
.queryParam("state", state) .queryParam("state", state);
.build(); if (scope != null) {
uriBuilder.queryParam("scope", scope);
}
URI url = uriBuilder.build();
String stateCookiePath = this.stateCookiePath; String stateCookiePath = this.stateCookiePath;
if (stateCookiePath == null) stateCookiePath = request.getContextPath(); if (stateCookiePath == null) stateCookiePath = request.getContextPath();
if (stateCookiePath.equals("")) stateCookiePath = "/"; if (stateCookiePath.equals("")) stateCookiePath = "/";

View file

@ -0,0 +1,36 @@
package org.keycloak.servlet;
import java.io.InputStream;
import org.keycloak.adapters.config.OAuthClientConfigLoader;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ServletOAuthClientConfigLoader extends OAuthClientConfigLoader {
public ServletOAuthClientConfigLoader() {
}
public ServletOAuthClientConfigLoader(InputStream is) {
super(is);
}
/**
* For now, configure just things supported by ServletOAuthClient
* @param setupClient
*/
public void initOAuthClientConfiguration(boolean setupClient) {
initOAuthClientConfiguration();
if (setupClient) {
initClient();
}
}
public void configureServletOAuthClient(ServletOAuthClient oauthClient) {
configureOAuthClient(oauthClient);
if (client != null) {
oauthClient.setClient(client);
}
}
}