Neuigkeiten von trion.
Immer gut informiert.

OAuth 2.0 Login: Keycloak Rollen auf Spring Security Authorities mappen

Spring Boot

In einem vorherigen Artikel haben wir uns damit befasst, wie man Keycloak Rollen als Spring Security Authorities verfügbar macht, wenn Spring Boot als Resource Server agiert. In diesem Folgebeitrag betrachten wir nun den Fall, in dem unsere Spring Boot Anwendung die Rolle eines OAuth 2.0 Client einnimmt und einen OAuth 2.0 Login mit Keycloak implementiert. Auch hier wollen wir die Rollen aus Keycloak korrekt mappen.

Für diesen Artikel wird angenommen, dass Keycloak bereits konfiguriert ist, um einen OIDC Login zu realisieren. Getestet wurde der Code mit Spring Boot 3.3.4.

Zum Bereitstellen der OAuth 2.0 Login Funktionalität nimmt unsere Spring Boot Anwendung die Rolle eines Confidential Clients ein, der sich per Client ID und Client Secret ausweist. Für den Login selbst wird der Authorization Code Flow verwendet.

Analog zum Resource Server extrahiert Spring Security auch mit diesem Setup automatisch Authorities aus dem Scope Claim des Access Tokens. Rollen, die wir über Keycloak vergebenen, stehen nicht direkt zur Verfügung.

Wurde Keycloak entsprechend konfiguriert können diese direkt aus dem Access Token ausgelesen werden. In der Spring Security Dokumentation wird gezeigt, wie dazu ein OAuth2UserService bereitgestellt werden kann.

Alternativ ist es aber auch möglich die Rollen über den Userinfo Endpunkt abzurufen. Um aus den Userinfos Authorities zu machen, kann man eine Bean von Typ GrantedAuthoritiesMapper bereitstellen. Nachfolgend schauen wir uns diese etwas einfachere Möglichkeit genauer an.

Aber fangen wir zunächst vorne an. Für diesen Blogpost nehmen wir an, dass unsere Userinfos folgende Struktur aufweisen:

Userinfos mit Keycloak Rolle
{
  "resource_access": {
    "client-id": {
      "roles": [
        "ADMIN"
      ]
    }
  }
  // ...
}

Die Rolle ADMIN, für die wir uns interessieren, liegen also im Claim resource_access.

Zunächst Konfigurieren wir unsere Spring Boot Anwendung für den OAuth 2.0 Login. Dazu werden die Starter Spring Security und OAuth2 Client benötigt.

Mit folgender Konfiguration wird Keycloak als OAuth 2.0 Provider registriert.

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            provider: 'keycloak-provider'
            client-id: 'client-id'
            client-secret: 'Cl1entS3cret'
            scope:
              - openid
        provider:
          keycloak-provider:
            issuer-uri: 'http://my.keycloak.com/realms/myRealm'

Ist bisher keine SecurityFilterChain bereitgestellt wird diese automatisch wie folgt konfiguriert und damit der OAuth 2.0 Login aktiviert:

@Bean
SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) {
    http.authorizeHttpRequests((requests) -> requests
        .anyRequest().authenticated()
    );
    http.oauth2Login(withDefaults());
    http.oauth2Client(withDefaults());
    return http.build();
}

An diesem Punkt werden Aufrufer unsere Endpunkte direkt zum Login bei Keycloak weitergeleitet. Als Letztes müssen wir nun noch einen GrantedAuthoritiesMapper als Bean bereitstellen, um die Keycloak Rollen in Authorities umzuwandeln.

@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {

  return (Collection<? extends GrantedAuthority> authorities) -> {

    Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

    for (GrantedAuthority grantedAuthority : authorities) {

      if (grantedAuthority instanceof OidcUserAuthority oidcUserAuthority) {

        Set<SimpleGrantedAuthority> mappedOidcAuthorities =
          Optional.ofNullable(oidcUserAuthority.getUserInfo())
            .map(oidcUserInfo -> oidcUserInfo.getClaimAsMap("resource_access"))
            .map(resourceAccess -> resourceAccess.get("client-id"))
            .map(Map.class::cast)
            .map(client -> client.get("roles"))
            .map(Collection.class::cast)
            .map(roles -> ((Collection<String>) roles).stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toSet())
            ).orElse(Collections.emptySet());

        mappedAuthorities.addAll(mappedOidcAuthorities);

      } else {

        mappedAuthorities.add(grantedAuthority);

      }

    }

    return mappedAuthorities;

  };

}

Über die von Spring bereits extrahierte OidcUserAuthority erhalten wir Zugriff auf die Userinfos. Durch mehrere map() Aufrufe navigieren wir durch die verschachtelte Struktur zum Rollen-Array unter resource_access.client-id.roles. Die gefundenen Rollen wandel wir in Granted Authorities mit dem Präfix ROLE_ um. Andere Authorities werden unverändert übernommen. Bei Realmrollen muss der Code entsprechend der Struktur der Userinfos angepasst werden.

Nun können die hinterlegten Rollen komfortabel für die Zugriffskontrolle auf Request- oder Methodenebene verwendet werden.




Zu den Themen Keycloak und Spring Security bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Vor der Inbetriebnahme einer Keycloak-Installation, sollte sichergestellt sein, dass das System tatsächlich bereit für den produktiven Einsatz ist und, dass alle wesentlichen Punkte bei Konfiguration, Integration und Betriebsprozessen berücksichtigt wurden. Hierbei hilft unser Production Readiness Review für Keycloak.

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.