Keycloak indlejret i en Spring Boot-applikation

1. Oversigt

Keycloak er en open source-identitets- og adgangsstyringsløsning administreret af RedHat og udviklet i Java af JBoss.

I denne vejledning lærer vi hvordan man opretter en Keycloak-server indlejret i et Spring Boot-program. Dette gør det let at starte en forudkonfigureret Keycloak-server.

Keycloak kan også køres som en enkeltstående server, men så indebærer det at downloade den og opsætte den via Admin Console.

2. Forudkonfiguration af nøglelak

Til at begynde med skal vi forstå, hvordan vi kan forudkonfigurere en Keycloak-server.

Serveren indeholder et sæt verdener, hvor hvert område fungerer som en isoleret enhed til brugeradministration. For at forudkonfigurere det skal vi specificere en realm definitionsfil i et JSON-format.

Alt der kan konfigureres ved hjælp af Keycloak Admin Console er vedvarende i denne JSON.

Vores autorisationsserver vil være forudkonfigureret med baeldung-realm.json. Lad os se et par relevante konfigurationer i filen:

  • brugere: vores standardbrugere ville være [e-mail beskyttet] og [e-mail beskyttet]; de har også deres legitimationsoplysninger her
  • kunder: vi definerer en klient med id'et newClient
  • standardFlowEnabled: indstillet til sand for at aktivere autorisationskodeflow for newClient
  • redirectUris: newClientHer vises de URL'er, som serveren omdirigerer til efter vellykket godkendelse
  • webOrigins: indstillet til “+” for at tillade CORS-understøttelse af alle webadresser, der er angivet som redirectUris

Keycloak-serveren udsteder JWT-tokens som standard, så der kræves ingen separat konfiguration til det. Lad os se på Maven-konfigurationerne næste.

3. Maven-konfiguration

Da vi integrerer Keycloak inde i en Spring Boot-applikation, er det ikke nødvendigt at downloade det separat.

I stedet, vi opretter følgende sæt afhængigheder:

 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 

Bemærk, at vi bruger Spring Boot's 2.2.6.RELEASE-version her. Afhængighederne spring-boot-starter-data-jpa og H2 er tilsat for persistens. Den anden springframework.boot afhængigheder er til websupport, da vi også skal kunne køre Keycloak autorisationsserver såvel som admin konsol som webtjenester.

Vi har også brug for et par afhængigheder til Keycloak og RESTEasy:

 org.jboss.resteasy resteasy-jackson2-provider 3.12.1.Final org.keycloak keycloak-afhængigheder-server-alle 11.0.2 pom 

Se Maven-webstedet for de nyeste versioner af Keycloak og RESTEasy.

Og endelig er vi nødt til at tilsidesætte for at bruge den version, der er erklæret af Keycloak i stedet for den, der er defineret af Spring Boot:

 10.1.8 Afsluttende 

4. Indbygget nøglekonfiguration

Lad os nu definere Spring-konfigurationen for vores autorisationsserver:

@Configuration public class EmbeddedKeycloakConfig {@Bean ServletRegistrationBean keycloakJaxRsApplication (KeycloakServerProperties keycloakServerProperties, DataSource dataSource) kaster Exception {mockJndiEnvironment (dataSource); EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties; ServletRegistrationBean servlet = ny ServletRegistrationBean (ny 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); retur servlet; } @Bean FilterRegistrationBean keycloakSessionManagement (KeycloakServerProperties keycloakServerProperties) {FilterRegistrationBean filter = nyt FilterRegistrationBean (); filter.setName ("Management af Keycloak-session"); filter.setFilter (nyt EmbeddedKeycloakRequestFilter ()); filter.addUrlPatterns (keycloakServerProperties.getContextPath () + "/ *"); returfilter; } privat ugyldigt mockJndiEnvironment (DataSource dataSource) kaster NamingException {NamingManager.setInitialContextFactoryBuilder ((env) -> (miljø) -> ny InitialContext () {@Override offentlig objektopslag (Navn navn) {return lookup (name.toString ()) } @Override offentlig objektopslag (strengnavn) {if ("spring / datasource" .equals (name)) {return dataSource;} return null;} @Override public NameParser getNameParser (strengnavn) {return CompositeName :: new;} @Override offentlig ugyldig luk () {}}); }} 

Bemærk: skal du ikke bekymre dig om kompileringsfejlen, vi definerer EmbeddedKeycloakRequestFilter klasse senere.

Som vi kan se her, konfigurerede vi først Keycloak som en JAX-RS-applikation med KeycloakServerProperties til vedvarende lagring af Keycloak-egenskaber som specificeret i vores realm-definitionsfil. Vi tilføjede derefter et sessionsstyringsfilter og spottede et JNDI-miljø for at bruge et forår / datakilde, som er vores H2-database i hukommelsen.

5. KeycloakServerProperties

Lad os nu se på KeycloakServerProperties vi nævnte lige:

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

Som vi kan se, dette er en simpel POJO at indstille kontekststi, admin Bruger og realm-definitionsfil.

6. EmbeddedKeycloakApplication

Lad os derefter se klassen, der bruger de konfigurationer, vi har angivet før, til at skabe riger:

offentlig klasse EmbeddedKeycloakApplication udvider KeycloakApplication {privat statisk endelig Logger LOG = LoggerFactory.getLogger (EmbeddedKeycloakApplication.class); statisk KeycloakServerProperties keycloakServerProperties; beskyttet ugyldig loadConfig () {JsonConfigProviderFactory fabrik = ny RegularJsonConfigProviderFactory (); Config.init (factory.create () .orElseThrow (() -> ny NoSuchElementException ("Ingen værdi til stede"))); } offentlig EmbeddedKeycloakApplication () {createMasterRealmAdminUser (); createBaeldungRealm (); } privat ugyldigt createMasterRealmAdminUser () {KeycloakSession session = getSessionFactory (). create (); ApplianceBootstrap applianceBootstrap = ny ApplianceBootstrap (session); AdminUser admin = keycloakServerProperties.getAdminUser (); prøv {session.getTransactionManager (). start (); applianceBootstrap.createMasterRealmUser (admin.getUsername (), admin.getPassword ()); session.getTransactionManager (). commit (); } catch (Exception ex) {LOG.warn ("Kunne ikke oprette keycloak master admin-bruger: {}", ex.getMessage ()); session.getTransactionManager (). tilbagevenden (); } session.close (); } privat tomrum createBaeldungRealm () {KeycloakSession session = getSessionFactory (). create (); prøv {session.getTransactionManager (). start (); RealmManager manager = ny RealmManager (session); Ressource lessonRealmImportFile = ny ClassPathResource (keycloakServerProperties.getRealmImportFile ()); manager.importRealm (JsonSerialization.readValue (lessonRealmImportFile.getInputStream (), RealmRepresentation.class)); session.getTransactionManager (). commit (); } fange (Undtagelse ex) {LOG.warn ("Kunne ikke importere Realm json-fil: {}", ex.getMessage ()); session.getTransactionManager (). tilbagevenden (); } session.close (); }} 

7. Brugerdefinerede platformimplementeringer

Som vi sagde, er Keycloak udviklet af RedHat / JBoss. Derfor giver den funktionalitets- og udvidelsesbiblioteker til at implementere applikationen på en Wildfly-server eller som en Quarkus-løsning.

I dette tilfælde bevæger vi os væk fra disse alternativer, og som følge heraf er vi nødt til at levere tilpassede implementeringer til nogle platformsspecifikke grænseflader og klasser.

For eksempel i EmbeddedKeycloakApplication vi konfigurerede lige, vi indlæste først Keycloaks serverkonfiguration keycloak-server.jsonved hjælp af en tom underklasse af det abstrakte JsonConfigProviderFactory:

offentlig klasse RegularJsonConfigProviderFactory udvider JsonConfigProviderFactory {}

Så forlængede vi KeycloakApplication at skabe to riger: mestre og baeldung. Disse oprettes i henhold til de egenskaber, der er angivet i vores realm-definitionsfil, baeldung-realm.json.

Som du kan se, bruger vi en KeycloakSession for at udføre alle transaktionerne, og for at dette kunne fungere korrekt, måtte vi oprette en brugerdefineret AbstractRequestFilter (EmbeddedKeycloakRequestFilter) og indstil en bønne til dette ved hjælp af en KeycloakSessionServletFilter i EmbeddedKeycloakConfig fil.

Derudover har vi brug for et par tilpassede udbydere, så vi har vores egne implementeringer af org.keycloak.common.util.ResteasyProvider og org.keycloak.platform.PlatformProvider og ikke stole på eksterne afhængigheder.

Det er vigtigt, at oplysninger om disse brugerdefinerede udbydere inkluderes i projektets META-INF / tjenester mappe, så de afhentes ved kørsel.

8. At bringe det hele sammen

Som vi så, Keycloak har meget forenklet de krævede konfigurationer fra applikationssiden. Der er ikke behov for at programmatisk definere datakilden eller nogen sikkerhedskonfigurationer.

For at bringe det hele sammen er vi nødt til at definere konfigurationen til Spring og en Spring Boot Application.

8.1. ansøgning.yml

Vi bruger en simpel YAML til forårskonfigurationerne:

server: port: 8083 fjeder: datakilde: brugernavn: sa url: jdbc: h2: mem: testdb keycloak: server: contextPath: / auth admin Bruger: brugernavn: bael-admin adgangskode: ******** realmImportFile: baeldung- realm.json

8.2. Spring Boot ansøgning

Endelig er her Spring Boot-applikationen:

@SpringBootApplication (ekskluder = LiquibaseAutoConfiguration.class) @EnableConfigurationProperties (KeycloakServerProperties.class) public class AuthorizationServerApp {private static final Logger LOG = LoggerFactory.getLogger (AuthorizationServerApp.class); offentlig statisk ugyldig hoved (String [] args) kaster Undtagelse {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); }; }}

Især her har vi aktiveret KeycloakServerProperties konfiguration for at injicere den i ApplicationListener bønne.

Efter at have kørt denne klasse, vi kan få adgang til autorisationsservers velkomstside på // localhost: 8083 / auth /.

9. Konklusion

I denne hurtige vejledning så vi, hvordan man opsætter en Keycloak-server indlejret i en Spring Boot-applikation. Kildekoden til denne applikation er tilgængelig på GitHub.

Den originale idé til denne implementering blev udviklet af Thomas Darimont og kan findes i projektet embedded-spring-boot-keycloak-server.