Keycloak Eingebettet in eine Spring Boot-Anwendung

1. Übersicht

Keycloak ist eine Open-Source-Lösung für das Identitäts- und Zugriffsmanagement, die von RedHat verwaltet und von JBoss in Java entwickelt wird.

In diesem Tutorial erfahren Sie, wie Sie einen in eine Spring Boot-Anwendung eingebetteten Keycloak-Server einrichten . Dies erleichtert das Starten eines vorkonfigurierten Keycloak-Servers.

Keycloak kann auch als eigenständiger Server ausgeführt werden. Anschließend muss es heruntergeladen und über die Admin-Konsole eingerichtet werden.

2. Keycloak-Vorkonfiguration

Lassen Sie uns zunächst verstehen, wie wir einen Keycloak-Server vorkonfigurieren können.

Der Server enthält eine Reihe von Bereichen, wobei jeder Bereich als isolierte Einheit für die Benutzerverwaltung fungiert. Um es vorkonfigurieren zu können, müssen wir eine Realm-Definitionsdatei in einem JSON-Format angeben.

Alles, was mit der Keycloak Admin Console konfiguriert werden kann, bleibt in diesem JSON erhalten.

Unser Autorisierungsserver wird mit baeldung-Realm.json vorkonfiguriert . Sehen wir uns einige relevante Konfigurationen in der Datei an:

  • Benutzer : Unsere Standardbenutzer wären [E-Mail-geschützt] und [E-Mail-geschützt] . Hier haben sie auch ihre Anmeldeinformationen
  • clients : Wir definieren einen Client mit der ID newClient
  • standardFlowEnabled : Auf true setzen, um den Autorisierungscode-Fluss für newClient zu aktivieren
  • redirectUris : Hier werden die URLs von newClient aufgelistet , zu denen der Server nach erfolgreicher Authentifizierung umleitet
  • webOrigins : Auf "+" setzen , um die CORS-Unterstützung für alle als redirectUris aufgelisteten URLs zu ermöglichen

Der Keycloak-Server stellt standardmäßig JWT-Token aus, daher ist hierfür keine separate Konfiguration erforderlich. Schauen wir uns als nächstes die Maven-Konfigurationen an.

3. Maven-Konfiguration

Da wir Keycloak in eine Spring Boot-Anwendung einbetten, muss es nicht separat heruntergeladen werden.

Stattdessen richten wir die folgenden Abhängigkeiten ein :

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-actuator   org.springframework.boot spring-boot-starter-data-jpa   com.h2database h2 runtime  

Beachten Sie, dass wir hier die Version 2.2.6.RELEASE von Spring Boot verwenden. Die Abhängigkeiten Spring-Boot-Starter-Data-JPA und H2 wurden aus Gründen der Persistenz hinzugefügt. Die anderen Abhängigkeiten von springframework.boot dienen der Webunterstützung, da wir auch den Keycloak-Autorisierungsserver sowie die Administrationskonsole als Webdienste ausführen können müssen.

Wir brauchen auch ein paar Abhängigkeiten für Keycloak und RESTEasy :

 org.jboss.resteasy resteasy-jackson2-provider 3.12.1.Final   org.keycloak keycloak-dependencies-server-all 11.0.2 pom  

Auf der Maven-Website finden Sie die neuesten Versionen von Keycloak und RESTEasy.

Und schließlich müssen wir das außer Kraft setzen Eigenschaft, um die von Keycloak deklarierte Version anstelle der von Spring Boot definierten zu verwenden:

 10.1.8.Final 

4. Embedded Keycloak-Konfiguration

Definieren wir nun die Spring-Konfiguration für unseren Autorisierungsserver:

@Configuration public class EmbeddedKeycloakConfig { @Bean ServletRegistrationBean keycloakJaxRsApplication( KeycloakServerProperties keycloakServerProperties, DataSource dataSource) throws Exception { mockJndiEnvironment(dataSource); EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties; ServletRegistrationBean servlet = new ServletRegistrationBean( new HttpServlet30Dispatcher()); servlet.addInitParameter("javax.ws.rs.Application", EmbeddedKeycloakApplication.class.getName()); servlet.addInitParameter(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, keycloakServerProperties.getContextPath()); servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, "true"); servlet.addUrlMappings(keycloakServerProperties.getContextPath() + "/*"); servlet.setLoadOnStartup(1); servlet.setAsyncSupported(true); return servlet; } @Bean FilterRegistrationBean keycloakSessionManagement( KeycloakServerProperties keycloakServerProperties) { FilterRegistrationBean filter = new FilterRegistrationBean(); filter.setName("Keycloak Session Management"); filter.setFilter(new EmbeddedKeycloakRequestFilter()); filter.addUrlPatterns(keycloakServerProperties.getContextPath() + "/*"); return filter; } private void mockJndiEnvironment(DataSource dataSource) throws NamingException { NamingManager.setInitialContextFactoryBuilder( (env) -> (environment) -> new InitialContext() { @Override public Object lookup(Name name) { return lookup(name.toString()); } @Override public Object lookup(String name) { if ("spring/datasource".equals(name)) { return dataSource; } return null; } @Override public NameParser getNameParser(String name) { return CompositeName::new; } @Override public void close() { } }); } } 

Hinweis: Machen Sie sich keine Sorgen über den Kompilierungsfehler. Wir werden die EmbeddedKeycloakRequestFilter- Klasse später definieren .

Wie wir hier sehen können, haben wir Keycloak zuerst als JAX-RS-Anwendung mit KeycloakServerProperties für die dauerhafte Speicherung von Keycloak-Eigenschaften konfiguriert, wie in unserer Realm-Definitionsdatei angegeben. Anschließend haben wir einen Sitzungsverwaltungsfilter hinzugefügt und eine JNDI-Umgebung verspottet, um eine Spring- / Datenquelle zu verwenden , bei der es sich um unsere speicherinterne H2-Datenbank handelt.

5. KeycloakServerProperties

Lassen Sie uns jetzt einen Blick auf die KeycloakServerProperties wir gerade erwähnt:

@ConfigurationProperties(prefix = "keycloak.server") public class KeycloakServerProperties { String contextPath = "/auth"; String realmImportFile = "baeldung-realm.json"; AdminUser adminUser = new AdminUser(); // getters and setters public static class AdminUser { String username = "admin"; String password = "admin"; // getters and setters } } 

Wie wir sehen können, ist dies ein einfaches POJO, um die Definitionsdatei contextPath , adminUser und Realm festzulegen .

6. EmbeddedKeycloakApplication

Als nächstes sehen wir uns die Klasse an, die die zuvor festgelegten Konfigurationen verwendet, um Bereiche zu erstellen:

public class EmbeddedKeycloakApplication extends KeycloakApplication { private static final Logger LOG = LoggerFactory.getLogger(EmbeddedKeycloakApplication.class); static KeycloakServerProperties keycloakServerProperties; protected void loadConfig() { JsonConfigProviderFactory factory = new RegularJsonConfigProviderFactory(); Config.init(factory.create() .orElseThrow(() -> new NoSuchElementException("No value present"))); } public EmbeddedKeycloakApplication() { createMasterRealmAdminUser(); createBaeldungRealm(); } private void createMasterRealmAdminUser() { KeycloakSession session = getSessionFactory().create(); ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); AdminUser admin = keycloakServerProperties.getAdminUser(); try { session.getTransactionManager().begin(); applianceBootstrap.createMasterRealmUser(admin.getUsername(), admin.getPassword()); session.getTransactionManager().commit(); } catch (Exception ex) { LOG.warn("Couldn't create keycloak master admin user: {}", ex.getMessage()); session.getTransactionManager().rollback(); } session.close(); } private void createBaeldungRealm() { KeycloakSession session = getSessionFactory().create(); try { session.getTransactionManager().begin(); RealmManager manager = new RealmManager(session); Resource lessonRealmImportFile = new ClassPathResource( keycloakServerProperties.getRealmImportFile()); manager.importRealm(JsonSerialization.readValue(lessonRealmImportFile.getInputStream(), RealmRepresentation.class)); session.getTransactionManager().commit(); } catch (Exception ex) { LOG.warn("Failed to import Realm json file: {}", ex.getMessage()); session.getTransactionManager().rollback(); } session.close(); } } 

7. Benutzerdefinierte Plattformimplementierungen

Wie gesagt, Keycloak wird von RedHat / JBoss entwickelt. Daher bietet es Funktionen und Erweiterungsbibliotheken zum Bereitstellen der Anwendung auf einem Wildfly-Server oder als Quarkus-Lösung.

In diesem Fall entfernen wir uns von diesen Alternativen und müssen daher benutzerdefinierte Implementierungen für einige plattformspezifische Schnittstellen und Klassen bereitstellen.

In der soeben konfigurierten EmbeddedKeycloakApplication haben wir beispielsweise zuerst die Serverkonfiguration keycloak-server.json von Keycloak geladen und dabei eine leere Unterklasse der Zusammenfassung JsonConfigProviderFactory verwendet :

public class RegularJsonConfigProviderFactory extends JsonConfigProviderFactory { }

Anschließend haben wir KeycloakApplication erweitert , um zwei Bereiche zu erstellen: Master und Baeldung . Diese werden gemäß den Eigenschaften erstellt, die in unserer Realm-Definitionsdatei baeldung-Realm.json angegeben sind .

As you can see, we use a KeycloakSession to perform all the transactions, and for this to work properly, we had to create a custom AbstractRequestFilter (EmbeddedKeycloakRequestFilter) and set up a bean for this using a KeycloakSessionServletFilter in the EmbeddedKeycloakConfig file.

Additionally, we need a couple of custom providers so that we have our own implementations of org.keycloak.common.util.ResteasyProvider and org.keycloak.platform.PlatformProvider and do not rely on external dependencies.

Importantly, information about these custom providers should be included in the project's META-INF/services folder so that they are picked up at runtime.

8. Bringing It All Together

As we saw, Keycloak has much simplified the required configurations from the application side. There is no need to programmatically define the datasource or any security configurations.

To bring it all together, we need to define the configuration for Spring and a Spring Boot Application.

8.1. application.yml

We'll be using a simple YAML for the Spring configurations:

server: port: 8083 spring: datasource: username: sa url: jdbc:h2:mem:testdb keycloak: server: contextPath: /auth adminUser: username: bael-admin password: ******** realmImportFile: baeldung-realm.json

8.2. Spring Boot Application

Lastly, here's the Spring Boot Application:

@SpringBootApplication(exclude = LiquibaseAutoConfiguration.class) @EnableConfigurationProperties(KeycloakServerProperties.class) public class AuthorizationServerApp { private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerApp.class); public static void main(String[] args) throws Exception { SpringApplication.run(AuthorizationServerApp.class, args); } @Bean ApplicationListener onApplicationReadyEventListener( ServerProperties serverProperties, KeycloakServerProperties keycloakServerProperties) { return (evt) -> { Integer port = serverProperties.getPort(); String keycloakContextPath = keycloakServerProperties.getContextPath(); LOG.info("Embedded Keycloak started: //localhost:{}{} to use keycloak", port, keycloakContextPath); }; } }

Insbesondere haben wir hier die KeycloakServerProperties- Konfiguration aktiviert , um sie in die ApplicationListener- Bean einzufügen .

Nach dem Ausführen dieser Klasse können wir auf die Begrüßungsseite des Autorisierungsservers unter // localhost: 8083 / auth / zugreifen .

9. Fazit

In diesem kurzen Tutorial haben wir gesehen, wie ein in eine Spring Boot-Anwendung eingebetteter Keycloak-Server eingerichtet wird. Der Quellcode für diese Anwendung ist auf GitHub verfügbar.

Die ursprüngliche Idee für diese Implementierung wurde von Thomas Darimont entwickelt und ist im Projekt embedded-spring-boot-keycloak-server zu finden.