Merge pull request #916 from jimmidyson/spring-boot-integration

Spring boot adapter
This commit is contained in:
Bill Burke 2015-01-16 19:01:04 -05:00
commit 42bdb7731d
8 changed files with 395 additions and 0 deletions

View file

@ -15,6 +15,7 @@
<!ENTITY Jetty9Adapter SYSTEM "modules/jetty9-adapter.xml">
<!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml">
<!ENTITY FuseAdapter SYSTEM "modules/fuse-adapter.xml">
<!ENTITY SpringBootAdapter SYSTEM "modules/spring-boot-adapter.xml">
<!ENTITY InstalledApplications SYSTEM "modules/installed-applications.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml">
<!ENTITY SAML SYSTEM "modules/saml.xml">
@ -95,6 +96,7 @@ This one is short
&Jetty8Adapter;
&FuseAdapter;
&JavascriptAdapter;
&SpringBootAdapter;
&InstalledApplications;
&Logout;
&MultiTenancy;

View file

@ -0,0 +1,72 @@
<section id="spring-boot-adapter">
<title>Spring Boot Adapter</title>
<para>
To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter
JAR to your app. You then have to provide some extra configuration via normal Spring
Boot configuration (<literal>application.properties</literal>). Let's go over these steps.
</para>
<section id="spring-boot-adapter-installation">
<title>Adapter Installation</title>
<para>
The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all
you need to do is add the Keycloak Spring Boot adapter JAR to your project. Depending on
what container you are using with Spring Boot, you also need to add the appropriate
Keycloak container adapter. If you are using Maven, add the following to your pom.xml (using
Tomcat as an example):
</para>
<para>
<programlisting>
<![CDATA[
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-adapter</artifactId>
<version>1.2.0.Beta1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<version>${keycloak.version}</version>
</dependency>
]]>
</programlisting>
</para>
</section>
<section id="spring-boot-adapter-configuration">
<title>Required Spring Boot Adapter Configuration</title>
<para>
This section describes how to configure your Spring Boot app to use Keycloak.
</para>
<para>
Instead of a <literal>keycloak.json</literal> file, you configure the realm for the Spring
Boot Keycloak adapter via the normal Spring Boot configuration. For example:
</para>
<programlisting>
<![CDATA[
keycloak.realm = demorealm
keycloak.realmKey = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCWYuxXmsmfV+Xc9Ik8QET8lD4wuHrJAXbbutS2O/eMjQQLNK7QDX/k/XhOkhxP0YBEypqeXeGaeQJjCxDhFjJXQuewUEMlmSja3IpoJ9/hFn4Cns4m7NGO+rtvnfnwgVfsEOS5EmZhRddp+40KBPPJfTH6Vgu6KjQwuFPj6DTwIDAQAB
keycloak.auth-server-url = http://127.0.0.1:8080/auth
keycloak.ssl-required = external
keycloak.resource = demoapp
keycloak.credentials.secret = 11111111-1111-1111-1111-111111111111
keycloak.use-resource-role-mappings = true
]]>
</programlisting>
<para>
You also need to specify the J2EE security config that would normally go in the <literal>web.xml</literal>.
Here's an example configuration:
</para>
<programlisting>
<![CDATA[
keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff
keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = admin
keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = user
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure
keycloak.securityConstraints[0].securityCollections[1].name = admin stuff
keycloak.securityConstraints[0].securityCollections[1].authRoles[0] = admin
keycloak.securityConstraints[0].securityCollections[1].patterns[0] = /admin
]]>
</programlisting>
</section>
</section>

View file

@ -29,5 +29,6 @@
<module>installed</module>
<module>admin-client</module>
<module>osgi-adapter</module>
<module>spring-boot</module>
</modules>
</project>

77
integration/spring-boot/pom.xml Executable file
View file

@ -0,0 +1,77 @@
<?xml version="1.0"?>
<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.2.0.Beta1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-spring-boot-adapter</artifactId>
<name>Keycloak Spring Boot Integration</name>
<description/>
<properties>
<spring-boot.version>1.2.1.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty92-adapter</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<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>

View file

@ -0,0 +1,28 @@
package org.keycloak.adapters.springboot;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.representations.adapters.config.AdapterConfig;
public class KeycloakSpringBootConfigResolver implements org.keycloak.adapters.KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private static AdapterConfig adapterConfig;
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(KeycloakSpringBootConfigResolver.adapterConfig);
return keycloakDeployment;
}
static void setAdapterConfig(AdapterConfig adapterConfig) {
KeycloakSpringBootConfigResolver.adapterConfig = adapterConfig;
}
}

View file

@ -0,0 +1,123 @@
package org.keycloak.adapters.springboot;
import org.apache.catalina.Context;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashSet;
import java.util.Set;
/**
* Keycloak authentication integration for Spring Boot
*
* @author <a href="mailto:jimmidyson@gmail.com">Jimmi Dyson</a>
* @version $Revision: 1 $
*/
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
public class KeycloakSpringBootConfiguration {
private KeycloakSpringBootProperties keycloakProperties;
@Autowired
public void setKeycloakSpringBootProperties(KeycloakSpringBootProperties keycloakProperties) {
this.keycloakProperties = keycloakProperties;
KeycloakSpringBootConfigResolver.setAdapterConfig(keycloakProperties);
}
@Bean
public EmbeddedServletContainerCustomizer getKeycloakContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
container.addContextValves(new KeycloakAuthenticatorValve());
container.addContextCustomizers(getTomcatKeycloakContextCustomizer());
} else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented");
} else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
throw new IllegalArgumentException("Jetty Keycloak integration is not yet implemented");
}
}
};
}
@Bean
public TomcatContextCustomizer getTomcatKeycloakContextCustomizer() {
return new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
LoginConfig loginConfig = new LoginConfig();
loginConfig.setAuthMethod("KEYCLOAK");
context.setLoginConfig(loginConfig);
Set<String> authRoles = new HashSet<String>();
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
for (String authRole : collection.getAuthRoles()) {
if (!authRoles.contains(authRole)) {
context.addSecurityRole(authRole);
authRoles.add(authRole);
}
}
}
}
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
SecurityConstraint tomcatConstraint = new SecurityConstraint();
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
SecurityCollection tomcatSecCollection = new SecurityCollection();
if (collection.getName() != null) {
tomcatSecCollection.setName(collection.getName());
}
if (collection.getDescription() != null) {
tomcatSecCollection.setDescription(collection.getDescription());
}
for (String authRole : collection.getAuthRoles()) {
tomcatConstraint.addAuthRole(authRole);
}
for (String pattern : collection.getPatterns()) {
tomcatSecCollection.addPattern(pattern);
}
for (String method : collection.getMethods()) {
tomcatSecCollection.addMethod(method);
}
for (String method : collection.getOmittedMethods()) {
tomcatSecCollection.addOmittedMethod(method);
}
tomcatConstraint.addCollection(tomcatSecCollection);
}
context.addConstraint(tomcatConstraint);
}
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
}
};
}
}

View file

@ -0,0 +1,90 @@
package org.keycloak.adapters.springboot;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
@ConfigurationProperties(prefix = "keycloak", ignoreUnknownFields = false)
public class KeycloakSpringBootProperties extends AdapterConfig {
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
public static class SecurityConstraint {
private List<SecurityCollection> securityCollections = new ArrayList<SecurityCollection>();
public List<SecurityCollection> getSecurityCollections() {
return securityCollections;
}
public void setSecurityCollections(List<SecurityCollection> securityCollections) {
this.securityCollections = securityCollections;
}
}
public static class SecurityCollection {
private String name;
private String description;
private List<String> authRoles = new ArrayList<String>();
private List<String> patterns = new ArrayList<String>();
private List<String> methods = new ArrayList<String>();
private List<String> omittedMethods = new ArrayList<String>();
public List<String> getAuthRoles() {
return authRoles;
}
public List<String> getPatterns() {
return patterns;
}
public List<String> getMethods() {
return methods;
}
public String getDescription() {
return description;
}
public String getName() {
return name;
}
public List<String> getOmittedMethods() {
return omittedMethods;
}
public void setName(String name) {
this.name = name;
}
public void setDescription(String description) {
this.description = description;
}
public void setAuthRoles(List<String> authRoles) {
this.authRoles = authRoles;
}
public void setPatterns(List<String> patterns) {
this.patterns = patterns;
}
public void setMethods(List<String> methods) {
this.methods = methods;
}
public void setOmittedMethods(List<String> omittedMethods) {
this.omittedMethods = omittedMethods;
}
}
public List<SecurityConstraint> getSecurityConstraints() {
return securityConstraints;
}
public void setSecurityConstraints(List<SecurityConstraint> securityConstraints) {
this.securityConstraints = securityConstraints;
}
}

View file

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration