Vous faites de la programmation réactive avec Spring webflux et vous souhaitez utiliser Keycloak pour protéger vos apis ? Je vous explique dans cet article comment effectuer cela en quelques étapes. En effet depuis sa version 5.1 , spring security vient avec le module spring-security-oauth2-resource-server pour les cas d’utilisation d’IAM (Identity Access Management) comme Keycloak ou Okta … et c’est plutôt simple …
NB: Je considère que vous utilisez déjà Keycloak et que vous savez comment configurer un domaine et y ajouter des clients. Si ce n’est pas le cas, nos articles précédents vous montrent la voie.
Fiche Technique
Keycloak : 4.8.1.Final
Spring boot : 2.1.1.RELEASE
Spring webflux : 2.1.1.RELEASE
Spring Oauth2 : 5.1.2.RELEASE
Etape 1 :
Un petit tour sur https://start.spring.io/ pour initialiser un projet avec les dépendances citées ci-dessus.
Rajoutez ensuite ces deux dépendances très importantes qui permettront à Spring Boot de configurer automatiquement une sécurité oauth2
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> <version>5.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> <version>5.1.2.RELEASE</version> </dependency>
Ces dépendances viennent évidemment accompagner spring-boot-starter-security déjà présent dans votre pom.xml.
Etape 2 :
Ouvrez votre fichier application.properties et ajouter la ligne suivante :
spring.security.oauth2.resourceserver.jwt.issuer-uri=issuer_url
issuer_url est bien sûr l’URL qui permet d’accéder à votre realm keycloak (ex: http://localhost:8080/auth/realms/demo-realm). Spring récupère cet url puis la concatène avec /.well_known/openid-configuration pour initialiser la configuration du serveur de ressources. Assurez-vous donc que l’URL est bel et bien accessible.
Vous trouverez un exemple de configuration de realm ici.
Etape 3 :
Il ne nous reste plus qu’à créer une classe de configuration et y coller le code suivant :
@EnableWebFluxSecurity class SecurityConfig { @Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange() .anyExchange().authenticated() .and() .oauth2ResourceServer() .jwt(); // @formatter:on return http.build(); } }
Et voilà le tour est joué !!!
L’intérêt d’utiliser cette approche (générique) est de pouvoir changer d’IAM sans pour autant changer les dépendances (c’est aussi valable pour les numéros de version).
Si vous souhaitez ajouter une gestion des rôles, le code ci-dessous pourrait vous être utile :
@Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { // @formatter:off http .csrf().disable() .httpBasic().disable() .formLogin().disable() .authorizeExchange() .pathMatchers(HttpMethod.OPTIONS).permitAll() .pathMatchers("/user").hasAuthority("USER") .pathMatchers("/admin").hasAuthority("ADMIN") .anyExchange().denyAll() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(grantedAuthoritiesExtractor()); // @formatter:on return http.build(); } private Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() { GrantedAuthoritiesExtractor extractor = new GrantedAuthoritiesExtractor(); return new ReactiveJwtAuthenticationConverterAdapter(extractor); } static class GrantedAuthoritiesExtractor extends JwtAuthenticationConverter { @Override protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) { Map<String, Object> claims = jwt.getClaims(); JSONObject realmAccess = (JSONObject) claims.get("realm_access"); JSONArray roles = (JSONArray) realmAccess.get("roles"); return roles.stream() .map(Object::toString) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } }
Liens utiles
https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/
https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html
Notre série d’articles Keycloak :
- Partie 1 : Installation de Keycloak
- Partie 2 : Configuration d’un domaine (realm)
- Partie 3 : Utilisation des connecteurs Spring de Keycloak
- Partie 4 : Utilisation du connecteur Keycloak.js avec Angular6+
- Partie 5 : Sécuriser une API multi-tenant avec Keycloak