Keycloak intégré dans une application Spring Boot

1. Vue d'ensemble

Keycloak est une solution open source de gestion des identités et des accès administrée par RedHat et développée en Java par JBoss.

Dans ce didacticiel, nous allons apprendre à configurer un serveur Keycloak intégré dans une application Spring Boot . Cela facilite le démarrage d'un serveur Keycloak préconfiguré.

Keycloak peut également être exécuté en tant que serveur autonome, mais cela implique ensuite de le télécharger et de le configurer via la console d'administration.

2. Pré-configuration de Keycloak

Pour commencer, comprenons comment nous pouvons préconfigurer un serveur Keycloak.

Le serveur contient un ensemble de domaines, chaque domaine agissant comme une unité isolée pour la gestion des utilisateurs. Pour le préconfigurer, nous devons spécifier un fichier de définition de domaine au format JSON.

Tout ce qui peut être configuré à l'aide de la console d'administration Keycloak est conservé dans ce JSON.

Notre serveur d'autorisation sera pré-configuré avec baeldung-realm.json . Voyons quelques configurations pertinentes dans le fichier:

  • utilisateurs : nos utilisateurs par défaut seraient [email protected] et [email protected] ; ils auront également leurs identifiants ici
  • clients : nous définirons un client avec l'id newClient
  • standardFlowEnabled : défini sur true pour activer le flux de code d'autorisation pour newClient
  • redirectUris : les URL de newClient vers lesquelles le serveur redirigera après une authentification réussie sont répertoriées ici
  • webOrigins : défini sur «+» pour autoriser le support CORS pour toutes les URL répertoriées comme redirectUris

Le serveur Keycloak émet des jetons JWT par défaut, il n'y a donc pas de configuration distincte requise pour cela. Regardons ensuite les configurations Maven.

3. Configuration Maven

Puisque nous allons intégrer Keycloak dans une application Spring Boot, il n'est pas nécessaire de le télécharger séparément.

Au lieu de cela, nous allons configurer l'ensemble de dépendances suivant :

 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  

Notez que nous utilisons la version 2.2.6.RELEASE de Spring Boot ici. Les dépendances spring-boot-starter-data-jpa et H2 ont été ajoutées pour la persistance. Les autres dépendances de springframework.boot concernent le support Web, car nous devons également pouvoir exécuter le serveur d'autorisation Keycloak ainsi que la console d'administration en tant que services Web.

Nous aurons également besoin de quelques dépendances pour Keycloak et RESTEasy :

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

Consultez le site Maven pour les dernières versions de Keycloak et RESTEasy.

Et enfin, nous devons remplacer le property, pour utiliser la version déclarée par Keycloak au lieu de celle définie par Spring Boot:

 10.1.8.Final 

4. Configuration de Keycloak intégrée

Définissons maintenant la configuration Spring pour notre serveur d'autorisation:

@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() { } }); } } 

Remarque: ne vous inquiétez pas de l'erreur de compilation, nous définirons la classe EmbeddedKeycloakRequestFilter plus tard.

Comme nous pouvons le voir ici, nous avons d'abord configuré Keycloak en tant qu'application JAX-RS avec KeycloakServerProperties pour le stockage persistant des propriétés Keycloak comme spécifié dans notre fichier de définition de domaine. Nous avons ensuite ajouté un filtre de gestion de session et simulé un environnement JNDI pour utiliser un spring / datasource , qui est notre base de données H2 en mémoire.

5. KeycloakServerProperties

Jetons maintenant un coup d'œil aux KeycloakServerProperties que nous venons de mentionner:

@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 } } 

Comme nous pouvons le voir, il s'agit d'un simple POJO pour définir le fichier contextPath , adminUser et de définition de domaine .

6. Application EmbeddedKeycloak

Ensuite, voyons la classe, qui utilise les configurations que nous avons définies auparavant, pour créer des royaumes:

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. Implémentations de plates-formes personnalisées

Comme nous l'avons dit, Keycloak est développé par RedHat / JBoss. Par conséquent, il fournit des fonctionnalités et des bibliothèques d'extensions pour déployer l'application sur un serveur Wildfly ou en tant que solution Quarkus.

Dans ce cas, nous nous éloignons de ces alternatives et, par conséquent, nous devons fournir des implémentations personnalisées pour certaines interfaces et classes spécifiques à la plate-forme.

Par exemple, dans EmbeddedKeycloakApplication que nous venons de configurer, nous avons d'abord chargé la configuration de serveur de Keycloak keycloak-server.json , en utilisant une sous-classe vide de l'abstrait JsonConfigProviderFactory :

public class RegularJsonConfigProviderFactory extends JsonConfigProviderFactory { }

Ensuite, nous avons étendu KeycloakApplication pour créer deux royaumes: master et baeldung . Ceux-ci sont créés selon les propriétés spécifiées dans notre fichier de définition de domaine, baeldung-realm.json .

Comme vous pouvez le voir, nous utilisons un KeycloakSession pour effectuer toutes les transactions, et pour que cela fonctionne correctement, nous avons dû créer un AbstractRequestFilter personnalisé ( EmbeddedKeycloakRequestFilter ) et configurer un bean pour cela en utilisant un KeycloakSessionServletFilter dans le fichier EmbeddedKeycloakConfig .

De plus, nous avons besoin de quelques fournisseurs personnalisés pour avoir nos propres implémentations de org.keycloak.common.util.ResteasyProvider et org.keycloak.platform.PlatformProvider et ne pas dépendre de dépendances externes.

Il est important de noter que les informations sur ces fournisseurs personnalisés doivent être incluses dans le dossier META-INF / services du projet afin qu'elles soient récupérées lors de l'exécution.

8. Rassembler tout cela

Comme nous l'avons vu, Keycloak a beaucoup simplifié les configurations requises du côté application . Il n'est pas nécessaire de définir par programme la source de données ou toute configuration de sécurité.

Pour réunir tout cela, nous devons définir la configuration de Spring et d'une application Spring Boot.

8.1. application.yml

Nous utiliserons un simple YAML pour les configurations Spring:

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. Application Spring Boot

Enfin, voici l'application Spring Boot:

@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); }; } }

Notamment, ici nous avons activé la configuration KeycloakServerProperties pour l'injecter dans le bean ApplicationListener .

Après avoir exécuté cette classe, nous pouvons accéder à la page d'accueil du serveur d'autorisation à l'adresse // localhost: 8083 / auth / .

9. Conclusion

Dans ce tutoriel rapide, nous avons vu comment configurer un serveur Keycloak intégré dans une application Spring Boot. Le code source de cette application est disponible à l'adresse over sur GitHub.

L'idée originale de cette implémentation a été développée par Thomas Darimont et peut être trouvée dans le projet embedded-spring-boot-keycloak-server.