OAuth 2.0 Resource Server: Keycloak Rollen auf Spring Security Authorities mappen
In vielen modernen Anwendungen nutzen wir Keycloak zur Authentifizierung für den Zugriff auf Spring Boot Anwendungen. Dabei werden Benutzer und ihre Rollen in Keycloak gespeichert bzw. von Keycloak bereitgestellt. In einer Spring-Anwendung möchten wir diese Rollen nun nutzen, um den Zugriff auf Ressourcen zu autorisieren. In einem weiteren Blogpost betrachten wir Fall in dem unsere Anwendung als OAuth 2.0 Client agiert und einen OAuth 2.0 Login implementiert.
Standardmäßig extrahiert Spring Security automatisch Authorities aus dem OAuth2 Scope Claim. Die vergebenen Rollen werden durch Keycloak in einem separaten Claim bereitgestellt und stehen damit nicht automatisch für die Nutzung zur Autorisierung zur Verfügung. Nachfolgend ist kurz beschrieben, wie wir die Keycloak-Rollen als Spring Security Authorities verfügbar machen.
Für diesen Blog-Post nehmen wir an, dass user JWT folgende Struktur aufweist:
{
"resource_access": {
"client-id": {
"roles": [
"ADMIN"
]
}
}
// more claims ...
}
Die Rollen, für die wir uns interessieren, liegen also im Claim resource_access
.
Wie in vielen Bereich von Spring Security können wir auch für diesen Fall mit einer eigenen Implementierung den Standard überschreiben uns auch aus diesem Claim Granted Authorities machen.
private JwtAuthenticationConverter jwtAuthenticationConverter() {
Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter =
new KeycloakRolesJwtGrantedAuthoritiesConverter();
JwtAuthenticationConverter jwtAuthenticationConverter =
new JwtAuthenticationConverter();
jwtAuthenticationConverter
.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
Bei der Verarbeitung eines Request wird ein JwtAuthenticationConverter
aufgerufen.
Dieser wandelt das im Request enthaltene JWT in ein Spring Security Authentication
-Objekt um.
Um diese Aufgabe zu erfüllen, greift dieser Converter selbst auf einen weiteren Konverter zu, den JwtGrantedAuthoritiesConverter
.
Dieser übernimmt schließlich das Auslesen der Authorities aus dem JWT.
Standardmäßig werden dafür die Einträge des Scope-Claims im Token herangezogen und mit dem Präfix SCOPE_
versehen.
Um hier unsere Logik nutzen zu können, stellen wir eine eigene Implementierung bereit, den KeycloakRolesJwtGrantedAuthoritiesConverter
.
Dazu muss das Interface Converter<Jwt, Collection<GrantedAuthority>>
aus dem Spring-Security-Framework implementiert werden.
Das Interface schreibt lediglich eine einzelne Methode vor, die ein JWT entgegennimmt und es in eine Sammlung von GrantedAuthority
-Objekten umwandelt.
public class KeycloakRolesJwtGrantedAuthoritiesConverter
implements Converter<Jwt, Collection<GrantedAuthority>> {
private final JwtGrantedAuthoritiesConverter defaultConverter
= new JwtGrantedAuthoritiesConverter();
@Override
public Collection<GrantedAuthority> convert(final Jwt jwt) {
return Stream.concat(
defaultConverter.convert(jwt).stream(),
extractRoles(jwt).stream()
)
.collect(Collectors.toSet());
}
private Collection<? extends GrantedAuthority> extractRoles(final Jwt jwt) {
return Optional.ofNullable(
jwt.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());
}
}
Die Methode convert
kombiniert die Authorities aus dem Standard-Converter mit den extrahierten Keycloak-Rollen.
Die Methode extractRoles
navigiert durch die verschachtelte Struktur des JWTs, um die Rollen zu finden und in Granted Authorities mit dem Präfix ROLE_
umzuwandeln.
In diesem Fall werden die Rollen im Token als Array unter resource_access.client-id.roles
erwartet.
Bei Realm-Rollen muss der Code entsprechend der Struktur des Tokens angepasst werden.
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) {
return httpSecurity
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt ->
jwt.jwtAuthenticationConverter(
jwtAuthenticationConverter()
)
)
)
.authorizeHttpRequests(matcher -> matcher
.requestMatchers("/api/private").hasRole("ADMIN")
.anyRequest().authenticated()
)
.build();
}
Schließlich muss der angepasste JwtAuthenticationConverter
noch in der SecurityFilterChain
registriert 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.