Keycloak ingebed in een Spring Boot-applicatie

1. Overzicht

Keycloak is een open-source oplossing voor identiteits- en toegangsbeheer beheerd door RedHat, en ontwikkeld in Java door JBoss.

In deze tutorial leren we hoe u een Keycloak-server instelt die is ingebed in een Spring Boot-applicatie. Dit maakt het eenvoudig om een ​​voorgeconfigureerde Keycloak server op te starten.

Keycloak kan ook als een standalone server worden uitgevoerd, maar dan moet je het downloaden en instellen via de Admin Console.

2. Keycloak Pre-configuratie

Laten we om te beginnen eens kijken hoe we een Keycloak-server kunnen voorconfigureren.

De server bevat een reeks realms, waarbij elke realm fungeert als een geïsoleerde eenheid voor gebruikersbeheer. Om het vooraf te configureren, moeten we een realm-definitiebestand specificeren in een JSON-indeling.

Alles wat kan worden geconfigureerd met de Keycloak Admin Console, wordt bewaard in deze JSON.

Onze autorisatieserver wordt vooraf geconfigureerd met baeldung-realm.json. Laten we een paar relevante configuraties in het bestand bekijken:

  • gebruikers: onze standaardgebruikers zouden zijn [e-mail beveiligd] en [e-mail beveiligd]; ze hebben hier ook hun inloggegevens
  • klanten: we definiëren een klant met de id nieuwe klant
  • standardFlowEnabled: ingesteld op true om autorisatiecodestroom voor te activeren nieuwe klant
  • redirectUris: nieuwe klantDe URL's waarnaar de server verwijst na succesvolle authenticatie, worden hier vermeld
  • webOrigins: ingesteld op “+” om CORS-ondersteuning toe te staan ​​voor alle URL's die worden vermeld als redirectUris

De Keycloak-server geeft standaard JWT-tokens uit, dus daar is geen aparte configuratie voor nodig. Laten we nu eens kijken naar de Maven-configuraties.

3. Maven-configuratie

Omdat we Keycloak in een Spring Boot-applicatie integreren, is het niet nodig om het apart te downloaden.

In plaats daarvan, we zullen de volgende reeks afhankelijkheden opzetten:

 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 

Merk op dat we hier de 2.2.6.RELEASE-versie van Spring Boot gebruiken. De afhankelijkheden spring-boot-starter-data-jpa en H2 zijn toegevoegd voor persistentie. De andere springframework.boot afhankelijkheden zijn voor webondersteuning, omdat we ook de Keycloak-autorisatieserver en de admin-console als webservices moeten kunnen draaien.

We hebben ook een aantal afhankelijkheden nodig voor Keycloak en RESTEasy:

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

Kijk op de Maven-site voor de nieuwste versies van Keycloak en RESTEasy.

En tot slot moeten we de eigenschap, om de versie te gebruiken die is gedeclareerd door Keycloak in plaats van degene die is gedefinieerd door Spring Boot:

 10.1.8.Finale 

4. Ingebouwde Keycloak-configuratie

Laten we nu de Spring-configuratie voor onze autorisatieserver definiëren:

@Configuration openbare klasse EmbeddedKeycloakConfig {@Bean ServletRegistrationBean keycloakJaxRsApplication (KeycloakServerProperties keycloakServerProperties, DataSource dataSource) gooit Uitzondering {mockJndiEnvironment (dataSource); EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties; ServletRegistrationBean servlet = nieuwe ServletRegistrationBean (nieuwe 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); retourneer servlet; } @Bean FilterRegistrationBean keycloakSessionManagement (KeycloakServerProperties keycloakServerProperties) {FilterRegistrationBean filter = nieuwe FilterRegistrationBean (); filter.setName ("Keycloak Session Management"); filter.setFilter (nieuwe EmbeddedKeycloakRequestFilter ()); filter.addUrlPatterns (keycloakServerProperties.getContextPath () + "/ *"); retourfilter; } private void mockJndiEnvironment (DataSource dataSource) gooit NamingException {NamingManager.setInitialContextFactoryBuilder ((env) -> (omgeving) -> nieuwe InitialContext () {@Override publieke object lookup (naam naam) {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 () {}}); }} 

Opmerking: maak je geen zorgen over de compilatiefout, we zullen de EmbeddedKeycloakRequestFilter les later.

Zoals we hier kunnen zien, hebben we Keycloak eerst geconfigureerd als een JAX-RS-applicatie met KeycloakServerProperties voor permanente opslag van Keycloak-eigenschappen zoals gespecificeerd in ons realm-definitiebestand. Vervolgens hebben we een sessiebeheerfilter toegevoegd en een JNDI-omgeving bespot om een spring / datasource, dat is onze H2-database in het geheugen.

5. KeycloakServerProperties

Laten we nu eens kijken naar het KeycloakServerProperties we noemden zojuist:

@ConfigurationProperties (prefix = "keycloak.server") openbare klasse KeycloakServerProperties {String contextPath = "/ auth"; String realmImportFile = "baeldung-realm.json"; AdminUser adminUser = nieuwe AdminUser (); // getters en setters openbare statische klasse AdminUser {String gebruikersnaam = "admin"; String wachtwoord = "admin"; // getters en setters}} 

Zoals we kunnen zien, dit is een eenvoudige POJO om de contextPath, adminGebruiker en realm-definitiebestand.

6. EmbeddedKeycloakApplication

Laten we vervolgens de klasse bekijken, die de configuraties gebruikt die we eerder hebben ingesteld, om rijken te creëren:

openbare klasse EmbeddedKeycloakApplication breidt KeycloakApplication uit {privé statische laatste Logger LOG = LoggerFactory.getLogger (EmbeddedKeycloakApplication.class); statische KeycloakServerProperties keycloakServerProperties; beschermde ongeldige loadConfig () {JsonConfigProviderFactory factory = nieuwe RegularJsonConfigProviderFactory (); Config.init (factory.create () .orElseThrow (() -> nieuwe NoSuchElementException ("Geen waarde aanwezig"))); } openbare EmbeddedKeycloakApplication () {createMasterRealmAdminUser (); createBaeldungRealm (); } private void createMasterRealmAdminUser () {KeycloakSession sessie = getSessionFactory (). create (); ApplianceBootstrap applianceBootstrap = nieuwe ApplianceBootstrap (sessie); AdminUser admin = keycloakServerProperties.getAdminUser (); probeer {session.getTransactionManager (). begin (); applianceBootstrap.createMasterRealmUser (admin.getUsername (), admin.getPassword ()); session.getTransactionManager (). commit (); } catch (Exception ex) {LOG.warn ("Kan keycloak master admin gebruiker niet aanmaken: {}", ex.getMessage ()); session.getTransactionManager (). rollback (); } session.close (); } private void createBaeldungRealm () {KeycloakSession-sessie = getSessionFactory (). create (); probeer {session.getTransactionManager (). begin (); RealmManager manager = nieuwe RealmManager (sessie); Resource lesRealmImportFile = nieuwe ClassPathResource (keycloakServerProperties.getRealmImportFile ()); manager.importRealm (JsonSerialization.readValue (lesRealmImportFile.getInputStream (), RealmRepresentation.class)); session.getTransactionManager (). commit (); } catch (Exception ex) {LOG.warn ("Importeren van Realm json-bestand is mislukt: {}", ex.getMessage ()); session.getTransactionManager (). rollback (); } session.close (); }} 

7. Implementaties van aangepaste platforms

Zoals we al zeiden, is Keycloak ontwikkeld door RedHat / JBoss. Daarom biedt het functionaliteit en extensiebibliotheken om de applicatie op een Wildfly-server of als een Quarkus-oplossing te implementeren.

In dit geval stappen we af van die alternatieven, en als gevolg daarvan moeten we aangepaste implementaties bieden voor sommige platformspecifieke interfaces en klassen.

Bijvoorbeeld in de EmbeddedKeycloakApplication we hebben zojuist geconfigureerd we hebben eerst de serverconfiguratie van Keycloak geladen keycloak-server.json, met behulp van een lege subklasse van de samenvatting JsonConfigProviderFactory:

openbare klasse RegularJsonConfigProviderFactory breidt JsonConfigProviderFactory {} uit

Daarna hebben we verlengd KeycloakApplication om twee rijken te creëren: meester en baeldung. Deze worden gemaakt volgens de eigenschappen die zijn gespecificeerd in ons realm-definitiebestand, baeldung-realm.json.

Zoals u kunt zien, gebruiken we een KeycloakSession om alle transacties uit te voeren, en om dit goed te laten werken, moesten we een custom AbstractRequestFilter (EmbeddedKeycloakRequestFilter) en zet hiervoor een boon met a KeycloakSessionServletFilter in de EmbeddedKeycloakConfig het dossier.

Bovendien hebben we er een paar nodig aangepaste providers, zodat we onze eigen implementaties van hebben org.keycloak.common.util.ResteasyProvider en org.keycloak.platform.PlatformProvider en vertrouw niet op externe afhankelijkheden.

Belangrijk is dat informatie over deze aangepaste providers moet worden opgenomen in de projectgegevens META-INF / diensten map zodat ze tijdens runtime worden opgehaald.

8. Alles samenbrengen

Zoals we zagen, Keycloak heeft de benodigde configuraties vanuit de applicatiezijde sterk vereenvoudigd. Het is niet nodig om de gegevensbron of beveiligingsconfiguraties programmatisch te definiëren.

Om alles bij elkaar te brengen, moeten we de configuratie voor Spring en een Spring Boot-applicatie definiëren.

8.1. application.yml

We zullen een eenvoudige YAML gebruiken voor de Spring-configuraties:

server: poort: 8083 spring: datasource: gebruikersnaam: sa url: jdbc: h2: mem: testdb keycloak: server: contextPath: / auth admin User: gebruikersnaam: bael-admin wachtwoord: ******** realmImportFile: baeldung- realm.json

8.2. Spring Boot-applicatie

Ten slotte is hier de Spring Boot-applicatie:

@SpringBootApplication (exclusief = LiquibaseAutoConfiguration.class) @EnableConfigurationProperties (KeycloakServerProperties.class) openbare klasse AuthorizationServerApp {privé statisch eindlogboek LOG = LoggerFactory.getLogger (AuthorizationServerApp.class); public static void main (String [] args) gooit Uitzondering {SpringApplication.run (AuthorizationServerApp.class, args); } @Bean ApplicationListener onApplicationReadyEventListener (ServerProperties serverProperties, KeycloakServerProperties keycloakServerProperties) {return (evt) -> {Integer port = serverProperties.getPort (); Tekenreeks keycloakContextPath = keycloakServerProperties.getContextPath (); LOG.info ("Embedded Keycloak gestart: // localhost: {} {} om keycloak te gebruiken", port, keycloakContextPath); }; }}

Met name hebben we hier het KeycloakServerProperties configuratie om het in de ApplicationListener Boon.

Na het runnen van deze les, we hebben toegang tot de welkomstpagina van de autorisatieserver op // localhost: 8083 / auth /.

9. Conclusie

In deze korte tutorial hebben we gezien hoe je een Keycloak-server instelt die is ingebed in een Spring Boot-applicatie. De broncode voor deze applicatie is beschikbaar op GitHub.

Het oorspronkelijke idee voor deze implementatie is ontwikkeld door Thomas Darimont en is te vinden in het project embedded-spring-boot-keycloak-server.