2

I have a JWT where the roles can be found under a specific claim. This claim is in a nested structure. How can I tell the JwtAuthenticationConverter to find the roles under a certain path ?

As Authorization-Server I use Keycloak. It would be a possibility to add a mapper for the roles. But I want to exclude this possibility for now, because the goal is to find the roles under the specific claim.

Here is my decoded JWT. The role "user-role" should be located under the claim: "resource_access" -> "user" -> "roles":

  "resource_access": {
    "admin": {
      "roles": [
        "admin-role"
      ]
    },
    "user": {
      "roles": [
        "user-role"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },

And here is my configuration for the JwtAuthenticationConverter:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
    @Override
    public void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
    }

    private static JwtAuthenticationConverter jwtAuthenticationConverter()
    {
        var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("resource_access.user.roles");
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        var jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

I hope someone can help me. Thanks :)

npriebe
  • 111
  • 3
  • 10
  • I would try to make a custom JwtGrantedAuthoritiesConverter. Take a look at org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter implementation. Since it is final you cannot just extend it, so you will need to copy-paste from JwtGrantedAuthoritiesConverter and reimplement a getAuthorities method – Andrey Grigoriev May 13 '22 at 11:23
  • Spring uses the Nimbus library to parse the JOSE header and the JWT claims section https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-claimsetmapping i would look through the nimbus documentation and try to see if you can find anything there https://bitbucket.org/connect2id/nimbus-jose-jwt/wiki/Home – Toerktumlare May 13 '22 at 15:41
  • Okay thanks, i have found my solution with this post. https://stackoverflow.com/questions/58205510/spring-security-mapping-oauth2-claims-with-roles-to-secure-resource-server-endp – npriebe May 18 '22 at 10:26

1 Answers1

4

thanks for your help. The solution was to implement a custom converter.

Here is my solution:

My custom converter:

@AllArgsConstructor
public class KeycloakJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
{
    @NotEmpty
    private List<String> clientIds;

    @Override
    public AbstractAuthenticationToken convert(Jwt source)
    {
        return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source)
                .stream(), extractResourceRoles(source).stream())
                .collect(toSet()));
    }

    private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt)
    {
        var resourceAccess = new HashMap<>(jwt.getClaim("resource_access"));
        var resourceRoles = new ArrayList<>();

        clientIds.stream().forEach(id ->
        {
            if (resourceAccess.containsKey(id))
            {
                var resource = (Map<String, List<String>>) resourceAccess.get(id);
                resource.get("roles").forEach(role -> resourceRoles.add(id + "_" + role));
            }
        });
        return resourceRoles.isEmpty() ? emptySet() : resourceRoles.stream().map(r -> new SimpleGrantedAuthority("ROLE_" + r)).collect(toSet());
    }
}

My security config:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
    @Value("${spring.security.jwt.role.locations}")
    private List<String> rolesLocation;

    @Override
    public void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(new KeycloakJwtAuthenticationConverter(rolesLocation));
    }
}
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
npriebe
  • 111
  • 3
  • 10