Fullstack-IAM Teil 2: Spring Boot als Resource Server mit Keycloak Integration

28.10.2022

In diesem zweiten Teil der Blogreihe, wird aufgezeigt, wie sich die Integration eines Resource Servers (Spring Boot) mit Keycloak realisieren lässt.

Notwendiges Wissen

Technische Voraussetzungen

Spring Boot (Resource Server)

Bootstrapping Spring Boot

Zu Beginn soll ein Spring Boot Projekt mit allen erforderlichen Abhängigkeiten erstellt werden. Das Artefakt dient nachher als Basis für die Entwicklung und Konfiguration.

  • Erstellung eines initialen Spring Boot Projektes
  • Hinzufügen der Dependencies
    • Spring Web
    • Spring Security
    • Spring OAuth Resource Server
  • Klick auf Generate
    • Projekt wird heruntergeladen

Security Konfiguration

In Spring Boot lässt sich die Security Konfiguration anpassen, indem die eigene Klasse um den WebSecurityConfigureAdapter erweitert wird. Zusätzlich benötigen wir noch eine Annotation über unserer Klasse @EnableWebSecurity

  • Klasse namens SecurityConfiguration erstellen
  • Klasse um WebSecurityConfigureAdapter erweitern
  • Annotation @EnableWebSecurity über die SecurityConfiguration setzen
  • Überschreiben der configure() Methode
    • cors().configurationSource(request -> setupCorsConfiguration())
      • Damit unser Client auf diesen Resource Server zugreifen darf.
    • authorizeRequest().mvcMatchers(„/api/secured/**).hasAuthority(„SCOPE_default-user-scope“)
      • Damit alle Anfragen, welche auf Schnittstelle gehen die mit „/api/secured/“ beginnen eine Authentifizierung benötigen und im SCOPE: default-user-scope von Keycloak enthalten sein müssen.
    • anyRequest().permitAll()
      • Damit alle Anfragen die nicht den obigen Regeln entsprechen keine Authentifizierung benötigen
    • oauth2ResourceServer().jwt()
      • Damit Spring Boot weiß, dass es die Authentifizierung mit einem Json Web Token (JWT) als Resource Server durchführen soll.
package com.maxxrl.resourceserver;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;

import java.util.Collections;
import java.util.List;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().configurationSource(request -> setupCorsConfiguration()).and()
                .authorizeRequests()
                .mvcMatchers("/api/secured/**").hasAuthority("SCOPE_default-user-scope")

                .anyRequest().permitAll()
                .and()
                .oauth2ResourceServer()
                .jwt();
    }

    private CorsConfiguration setupCorsConfiguration() {
        List frontendOrigins = Collections.singletonList("http://localhost:4200");
        CorsConfiguration cors = new CorsConfiguration();
        cors.setAllowedOrigins(frontendOrigins);
        cors.setAllowedHeaders(List.of("*"));
        return cors;
    }
}

Hinzufügen der notwendigen Parameter für OAuth2.0 in der application.properties (oder application.yml) Datei.

Somit sind bereits alle notwendigen Konfigurationen gesetzt, damit der Resource Server über Keycloak geschützt werden kann.

Schnittstelle entwickeln

Wir möchten zwei Schnittstellen entwickeln. Eine die über Keycloak abgesichert ist sowie eine andere welche immer aufgerufen werden kann.

package com.maxxrl.resourceserver;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api")
@RestController
public class SecuredController {

    @GetMapping(path = "/secured")
    public DoubleSlash securedHelloWorld() {
        return new DoubleSlash("Hello from doubleSlash secured");
    }

    @GetMapping(path = "/unsecured")
    public DoubleSlash unsecuredHelloWorld() {
        return new DoubleSlash("Hello from doubleSlash unsecured");
    }

    class DoubleSlash {
        private final String content;

        public DoubleSlash(String content) {
            this.content = content;
        }

        public String getContent() {
            return content;
        }

    }
}

Da die Entwicklung des Resources Servers mit seinen zwei Schnittstellen soweit abgeschlossen ist. Kann der Resource Server via Spring gestartet werden.

Schnittstelle testen

Für die Tests soll zuerst die abgesicherte Schnittstelle ohne und dann mit Token angefragt werden. Ohne Token erwarten wir, dass wir eine 401 Unauthorized erhalten. Weil wir nicht berechtigt sind auf die Resource zuzugreifen. Wenn das Token an die Anfrage angehängt wird erwarten wir die gesicherte Antwort mit einem 200 Statuscode.

Anfrage an abgesicherte Schnittstelle ohne Token:

curl --request GET \
--url http://localhost:9090/api/secured

Response

HTTP: 401 – Unauthorized

Anfrage an Keycloak um ein Token zu erhalten

curl --request POST \
--url http://localhost:8080/auth/realms/secure-realm/protocol/openid-connect/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data client_id=secure-client \
--data username=client-user \
--data password=test \
--data grant_type=password

Response

HTTP: 200

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjaUtjaHlTdXEzNzdHTHdqemVmcDFhNnN1QjlOSEpPdFdBMFZFRndfV0tRIn0.eyJleHAiOjE2NjYxNjcyNzIsImlhdCI6MTY2NjE2Njk3MiwianRpIjoiMzIyOWZlN2ItNzAyOS00ODM5LWI1MGYtNzZkZWFhNzgwMGMwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NlY3VyZS1yZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI3YmE2MmFlZC1iODg0LTQwODAtYTYxYy0yMzU1MWExYjBlNmIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzZWN1cmUtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjdmOTM5MzQ1LTgyOTQtNDZmNi1hZTAyLWE3NmI3MDA1NTU3MSIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLXNlY3VyZS1yZWFsbSIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIGRlZmF1bHQtdXNlci1zY29wZSIsInNpZCI6IjdmOTM5MzQ1LTgyOTQtNDZmNi1hZTAyLWE3NmI3MDA1NTU3MSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJjbGllbnQtdXNlciJ9.SPQgs7GRZ9CNdveQys1PPHhg-3TUl2LIjowFGg9fW6PB-M94VOI7PJc0KNLneU4s5u09ypjHp9Dc-fShcRZeqgpiqjj9XZZpipeYtCw8kBCzxdEQvp6rOej-IjF2EMvraUTp00HfEXkM0eht7ETLRBcOUYPT4NWsrMrgTHbvbeaEyfpW4NkqxlAixjgj4AgYg7qWh7L430Bvl_1US19KWH7Ch4TpX4gRSmQ8hEzOe0U9Q5hKHBgWA2hD-rfdwAeHaOMIXI9SPnyZxfJBl-7oENyPnpSqE4QJkTacbs0kRTgtlQ5nL0FsjRxK6_KmRzOD9WCx_sZ3mQpYWowUCw_1xA",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkNGFmNzNjMC0yNjdjLTQxMGQtYTQ3ZS1hNDM1MTc2YTNiZGQifQ.eyJleHAiOjE2NjYxNjg3NzIsImlhdCI6MTY2NjE2Njk3MiwianRpIjoiZjQxMDFmZjMtNjQwMy00MGU4LWI5OGQtM2ExNTI5N2E4OWNiIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NlY3VyZS1yZWFsbSIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9zZWN1cmUtcmVhbG0iLCJzdWIiOiI3YmE2MmFlZC1iODg0LTQwODAtYTYxYy0yMzU1MWExYjBlNmIiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoic2VjdXJlLWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiI3ZjkzOTM0NS04Mjk0LTQ2ZjYtYWUwMi1hNzZiNzAwNTU1NzEiLCJzY29wZSI6InByb2ZpbGUgZW1haWwgZGVmYXVsdC11c2VyLXNjb3BlIiwic2lkIjoiN2Y5MzkzNDUtODI5NC00NmY2LWFlMDItYTc2YjcwMDU1NTcxIn0._61qe4R_b4zB2b0xWj_XG4n3mbTIAonNtZjQLqzAIlM",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "7f939345-8294-46f6-ae02-a76b70055571",
"scope": "profile email default-user-scope"
}

Nutzung des Tokens an der abgesicherten Schnittstelle:

curl --request GET \
--url http://localhost:9090/api/secured \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjaUtjaHlTdXEzNzdHTHdqemVmcDFhNnN1QjlOSEpPdFdBMFZFRndfV0tRIn0.eyJleHAiOjE2NjYxNjcyNzIsImlhdCI6MTY2NjE2Njk3MiwianRpIjoiMzIyOWZlN2ItNzAyOS00ODM5LWI1MGYtNzZkZWFhNzgwMGMwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3NlY3VyZS1yZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI3YmE2MmFlZC1iODg0LTQwODAtYTYxYy0yMzU1MWExYjBlNmIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzZWN1cmUtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjdmOTM5MzQ1LTgyOTQtNDZmNi1hZTAyLWE3NmI3MDA1NTU3MSIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLXNlY3VyZS1yZWFsbSIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIGRlZmF1bHQtdXNlci1zY29wZSIsInNpZCI6IjdmOTM5MzQ1LTgyOTQtNDZmNi1hZTAyLWE3NmI3MDA1NTU3MSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJjbGllbnQtdXNlciJ9.SPQgs7GRZ9CNdveQys1PPHhg-3TUl2LIjowFGg9fW6PB-M94VOI7PJc0KNLneU4s5u09ypjHp9Dc-fShcRZeqgpiqjj9XZZpipeYtCw8kBCzxdEQvp6rOej-IjF2EMvraUTp00HfEXkM0eht7ETLRBcOUYPT4NWsrMrgTHbvbeaEyfpW4NkqxlAixjgj4AgYg7qWh7L430Bvl_1US19KWH7Ch4TpX4gRSmQ8hEzOe0U9Q5hKHBgWA2hD-rfdwAeHaOMIXI9SPnyZxfJBl-7oENyPnpSqE4QJkTacbs0kRTgtlQ5nL0FsjRxK6_KmRzOD9WCx_sZ3mQpYWowUCw_1xA'

Response:

HTTP: 200

{
"content": "Hello from doubleSlash secured"
}

Zusammenfassung

Es hat sich gezeigt, dass der Aufwand für die Integration von Keycloak in Spring Boot anhand eines Basisbeispiels sehr gering ist. Wir mussten lediglich in den application.properties die öffentliche CERT API von Keycloak eintragen und die SpringSecurity überschreiben, um zu definieren, welche Schnittstellen über OAuth2.0 abgesichert werden sollen. Die entwickelte Schnittstelle ermöglichte uns zuletzt einen Test in vorm von CURL durchzuführen, um zu beweisen, dass die Schnittstelle über Keycloak abgesichert ist. Im nächsten Teil der Blogserie soll die Anbindung eines Frontend mit Angular in die bestehende Architektur Keycloak + Resource Server integriert werden.

GitHub

Zum Angular Client Projekt

Zum Keycloak Server & Spring Boot Resource Server

Weitere Interessante Links und Quellen zum Thema

Die Rollverteilung im OAuth 2.0 Standard verstehen und Vorstellung des Implizit Flow

OAuth 2.0: Der Authorization Code Flow im Detail

https://ordina-jworks.github.io/security/2019/08/22/Securing-Web-Applications-With-Keycloak.html#/

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*