CustomJwtAuthenticationConverter.java

package com.tdmconsult.ete.configuration;

import com.google.common.collect.ImmutableList;
import com.tdmconsult.ete.configuration.CustomJwtAuthenticationToken.CustomUserPrincipal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;

public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    // use framework-default converter as starting point
    private final JwtGrantedAuthoritiesConverter defaultAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();

    @Override
    public AbstractAuthenticationToken convert(final Jwt jwt) {

        Collection<GrantedAuthority> authorities = Objects.requireNonNull(
                defaultAuthoritiesConverter.convert(jwt), "unexpected behavior");

        authorities.addAll(extractRealmRoles(jwt));
        authorities.addAll(extractClientRoles(jwt));

        CustomUserPrincipal principal = new CustomUserPrincipal(
                jwt.getSubject(), // "sub"
                jwt.getClaimAsString("preferred_username"),
                jwt.getClaimAsString("email"),
                jwt.getClaimAsString("given_name"),
                jwt.getClaimAsString("family_name"),
                ImmutableList.copyOf(authorities)
        );

        return new CustomJwtAuthenticationToken(jwt, authorities, principal);
    }

    private Collection<GrantedAuthority> extractRealmRoles(final Jwt jwt) {

        Map<String, Object> realmAccess = jwt.getClaim("realm_access");

        if (realmAccess == null || !realmAccess.containsKey("roles")) {
            return List.of(); // Keine Rollen vorhanden
        }

        // Extrahiert die Liste der Rollen-Strings
        @SuppressWarnings("unchecked")
        List<String> roles = (List<String>) realmAccess.get("roles");

        return roles.stream()
                .filter(roleName -> roleName.startsWith("ROLE_"))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    private Collection<GrantedAuthority> extractClientRoles(final Jwt jwt) {
        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
        if (resourceAccess == null) {
            return List.of();
        }

        List<GrantedAuthority> clientAuthorities = new ArrayList<>();

        // Wir iterieren über alle Clients (z.B. "mein-vue-client", "account", etc.)
        for (Map.Entry<String, Object> entry : resourceAccess.entrySet()) {
            if (entry.getValue() instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, Object> clientConfig = (Map<String, Object>) entry.getValue();

                if (clientConfig.containsKey("roles") && clientConfig.get("roles") instanceof List) {
                    @SuppressWarnings("unchecked")
                    List<String> clientRoles = (List<String>) clientConfig.get("roles");

                    // Jede Client-Rolle wird flach als "ROLE_rollenname" hinzugefügt
                    clientRoles.stream()
                            .map(role -> new SimpleGrantedAuthority(entry.getKey() + ":" + role))
                            .forEach(clientAuthorities::add);
                }
            }
        }

        return clientAuthorities;
    }
}