spring-projects / spring-projects/spring-security

Extendable JWT authority mapping

Open
#8,844 6 comments 0 reactions 1 assignee View on GitHub

@jzheaux is already working on this.

Since Jul 17, 2020.

in: oauth2 status: feedback-provided type: enhancement
Dominant language
Java
Stars
9.6k
Forks
6.3k
Avg merge
2d 11h
Merged PRs (30d)
52

Description

Expected Behavior
There seems to be no standard on what JWT contains to communicate client authorities. At the moment Spring Security just maps everything within scope or scp to SCOPE_. I think, it would be nice to have the possibility to configure further mappings, to be used in conjunction with pathMatchers.

Current Behavior

As far as I could see, there is no way to extend the default in an easy way.

Context

I personally use the following:

public final class ExtendableReactiveJwtAuthenticationConverterAdapter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {

    private HashMap<String, String> authoritiesMap = new HashMap<>() {{
        put("scope", "SCOPE_");
        put("scp", "SCOPE_");
    }};

    @Override
    public Mono<AbstractAuthenticationToken> convert(Jwt jwt) {
        return Flux
                .fromIterable(authoritiesMap.entrySet())
                .filter(claimPrefixEntry -> jwt.containsClaim(claimPrefixEntry.getKey()))
                .flatMap(entry -> extractAuthorities(jwt, entry.getKey(), entry.getValue()))
                .collectList()
                .map(authorities -> new JwtAuthenticationToken(jwt, authorities));
    }

    public ExtendableReactiveJwtAuthenticationConverterAdapter addMapping(String claimName, String prefix) {
        authoritiesMap.put(claimName, prefix);
        return this;
    }

    public ExtendableReactiveJwtAuthenticationConverterAdapter setMappings(HashMap<String, String> authoritiesMap) {
        this.authoritiesMap = authoritiesMap;
        return this;
    }

    @SuppressWarnings("unchecked")
    private Flux<GrantedAuthority> extractAuthorities(Jwt jwt, String claimName, String prefix) {
        Object claim = jwt.getClaim(claimName);

        Flux<String> authorities;
        if (claim instanceof String)
            authorities = Flux.fromArray(((String) claim).split(" "));
        else if (claim instanceof Collection)
            authorities = Flux.fromIterable((Collection<String>) claim);
        else
            authorities = Flux.empty();

        return authorities.map(authority -> new SimpleGrantedAuthority(prefix + authority));
    }

}

This allows me to configure my OAUTH resource server like:

.jwt(jwtSpec -> jwtSpec.jwtAuthenticationConverter(
        new ExtendableReactiveJwtAuthenticationConverterAdapter().addMapping("groups", "GROUP_")
))

Like so, defaults are preserved and extensions are easy to configure. Plus, I think it is a bit more readable than JwtGrantedAuthoritiesConverter.

At the moment the default uses ReactiveJwtAuthenticationConverterAdapter with a hard coded dependency on JwtAuthenticationConverter which again hard coded depends on JwtGrantedAuthoritiesConverter. This chain is a little inflexible. I understand, it is due to reusing the same code for blocking and reactive implementation. However, it would (probably not just for me) be nice to have the same functionality as within ExtendableReactiveJwtAuthenticationConverterAdapter.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.