Keycloak incorporato in un'applicazione Spring Boot

1. Panoramica

Keycloak è una soluzione open source per la gestione delle identità e degli accessi amministrata da RedHat e sviluppata in Java da JBoss.

In questo tutorial impareremo come configurare un server Keycloak incorporato in un'applicazione Spring Boot . Ciò semplifica l'avvio di un server Keycloak preconfigurato.

Keycloak può essere eseguito anche come server autonomo, ma in seguito richiede il download e la configurazione tramite la Console di amministrazione.

2. Pre-configurazione di Keycloak

Per cominciare, capiamo come possiamo preconfigurare un server Keycloak.

Il server contiene una serie di aree di autenticazione, ciascuna delle quali funge da unità isolata per la gestione dell'utente. Per preconfigurarlo, è necessario specificare un file di definizione dell'area di autenticazione in un formato JSON.

Tutto ciò che può essere configurato utilizzando la Keycloak Admin Console viene mantenuto in questo JSON.

Il nostro server di autorizzazione sarà preconfigurato con baeldung-realm.json . Vediamo alcune configurazioni rilevanti nel file:

  • utenti : i nostri utenti predefiniti sarebbero [email protected] e [email protected] ; avranno anche le loro credenziali qui
  • client : definiremo un client con l'id newClient
  • standardFlowEnabled : impostato su true per attivare il flusso del codice di autorizzazione per newClient
  • redirectUris : gli URL di newClient a cui il server reindirizzerà dopo un'autenticazione riuscita sono elencati qui
  • webOrigins : impostare su "+" per consentire il supporto CORS per tutti gli URL elencati come redirectUris

Il server Keycloak emette i token JWT per impostazione predefinita, quindi non è richiesta alcuna configurazione separata per questo. Diamo un'occhiata alle configurazioni di Maven dopo.

3. Configurazione Maven

Poiché incorporeremo Keycloak all'interno di un'applicazione Spring Boot, non è necessario scaricarlo separatamente.

Invece, configureremo il seguente set di dipendenze :

 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  

Nota che stiamo usando la versione 2.2.6.RELEASE di Spring Boot qui. Le dipendenze spring-boot-starter-data-jpa e H2 sono state aggiunte per la persistenza. Le altre dipendenze springframework.boot sono per il supporto web, poiché dobbiamo anche essere in grado di eseguire il server di autorizzazione Keycloak e la console di amministrazione come servizi web.

Avremo anche bisogno di un paio di dipendenze per Keycloak e RESTEasy :

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

Controllare il sito Maven per le ultime versioni di Keycloak e RESTEasy.

Infine, dobbiamo sovrascrivere il file , per utilizzare la versione dichiarata da Keycloak invece di quella definita da Spring Boot:

 10.1.8.Final 

4. Configurazione Keycloak incorporata

Ora definiamo la configurazione Spring per il nostro server di autorizzazione:

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

Nota: non preoccuparti dell'errore di compilazione, definiremo la classe EmbeddedKeycloakRequestFilter in seguito.

Come possiamo vedere qui, abbiamo prima configurato Keycloak come applicazione JAX-RS con KeycloakServerProperties per l'archiviazione persistente delle proprietà Keycloak come specificato nel nostro file di definizione realm. Abbiamo quindi aggiunto un filtro di gestione della sessione e abbiamo deriso un ambiente JNDI per utilizzare una sorgente / origine dati , che è il nostro database H2 in memoria.

5. KeycloakServerProperties

Ora diamo uno sguardo alle KeycloakServerProperties che abbiamo appena menzionato:

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

Come possiamo vedere, questo è un semplice POJO per impostare il file di definizione contextPath , adminUser e realm .

6. EmbeddedKeycloakApplication

Successivamente, vediamo la classe, che utilizza le configurazioni che abbiamo impostato prima, per creare i reami:

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. Implementazioni di piattaforme personalizzate

Come abbiamo detto, Keycloak è sviluppato da RedHat / JBoss. Pertanto, fornisce funzionalità e librerie di estensioni per distribuire l'applicazione su un server Wildfly o come soluzione Quarkus.

In questo caso, ci stiamo allontanando da queste alternative e, di conseguenza, dobbiamo fornire implementazioni personalizzate per alcune interfacce e classi specifiche della piattaforma.

Ad esempio, in EmbeddedKeycloakApplication che abbiamo appena configurato, abbiamo prima caricato la configurazione del server Keycloak keycloak-server.json , utilizzando una sottoclasse vuota dell'astratto JsonConfigProviderFactory :

public class RegularJsonConfigProviderFactory extends JsonConfigProviderFactory { }

Quindi, abbiamo esteso KeycloakApplication per creare due regni: master e baeldung . Questi vengono creati secondo le proprietà specificate nel nostro file di definizione del dominio, baeldung-realm.json .

Come puoi vedere, utilizziamo una KeycloakSession per eseguire tutte le transazioni e, affinché funzioni correttamente, abbiamo dovuto creare un AbstractRequestFilter personalizzato ( EmbeddedKeycloakRequestFilter ) e impostare un bean per questo utilizzando un KeycloakSessionServletFilter nel file EmbeddedKeycloakConfig .

Inoltre, abbiamo bisogno di un paio di provider personalizzati in modo da avere le nostre implementazioni di org.keycloak.common.util.ResteasyProvider e org.keycloak.platform.PlatformProvider e non fare affidamento su dipendenze esterne.

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

In particolare, qui abbiamo abilitato la configurazione KeycloakServerProperties per iniettarlo nel bean ApplicationListener .

Dopo aver eseguito questa classe, possiamo accedere alla pagina di benvenuto del server di autorizzazione su // localhost: 8083 / auth / .

9. Conclusione

In questo breve tutorial, abbiamo visto come configurare un server Keycloak incorporato in un'applicazione Spring Boot. Il codice sorgente di questa applicazione è disponibile su GitHub.

L'idea originale per questa implementazione è stata sviluppata da Thomas Darimont e può essere trovata nel progetto embedded-spring-boot-keycloak-server.