Search code examples
javaspring-bootoauth-2.0spring-webflux

Insufficient scope in resource server with spring webflux


I'm having trouble setting up a ResourceServer that uses Webflux in spring-boot 2.1.3.RELEASE. The same token used to authenticate is working fine with a resource server that doesn't use Webflux, and if I set .permitAll() it works too (obviously). Here's my resource server config

The authorization server uses jwt token store.

@EnableWebFluxSecurity
public class ResourceServerConfig {

  @Value("${security.oauth2.resource.jwt.key-value}")
  private String publicKey;
  @Autowired Environment env;
  @Bean
  SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
    http
        .authorizeExchange()
        .pathMatchers("/availableInspections/**").hasRole("READ_PRIVILEGE")
        .anyExchange().authenticated()
        .and()
        .oauth2ResourceServer()
        .jwt()
        .jwtDecoder(reactiveJwtDecoder())
    ;
    return http.build();
  }

@Bean
public ReactiveJwtDecoder reactiveJwtDecoder() throws Exception{
    return new NimbusReactiveJwtDecoder(getPublicKeyFromString(publicKey));
  }

public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
    String publicKeyPEM = key;
    publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
    publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
    byte[] encoded = Base64.getMimeDecoder().decode(publicKeyPEM);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
    return pubKey;
  }
}

The error I'm getting is

WWW-Authenticate: Bearer error="insufficient_scope", error_description="The token provided has insufficient scope [read] for this request", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1", scope="read"

I've verified that the token has the required scope...

What do I need to change/add in order to get my token read correctly?


Solution

  • Spring seems to add only what is under scope in the jwt token, ignoring everything from authorities - so they can't be used in a webflux resource server unless we extend JwtAuthenticationConverter to also add from authorities (or other claims) in the token. In the security config, I've added jwtAuthenticationConverter

    http
    // path config like above
    .oauth2ResourceServer()
    .jwt()
    .jwtDecoder(reactiveJwtDecoder())
    .jwtAuthenticationConverter(converter())
    

    and have overriden JwtAuthenticationConverter to include authorities granted authorities:

    public class MyJwtAuthenticationConverter extends JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
        private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
    
        private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
                Arrays.asList("scope", "scp", "authorities"); // added authorities
    
    
        protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
            return this.getScopes(jwt)
                            .stream()
                            .map(authority -> SCOPE_AUTHORITY_PREFIX + authority)
                            .map(SimpleGrantedAuthority::new)
                            .collect(Collectors.toList());
        }
    
        private Collection<String> getScopes(Jwt jwt) {
          Collection<String> authorities = new ArrayList<>();
            // add to collection instead of returning early
            for ( String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES ) {
                Object scopes = jwt.getClaims().get(attributeName);
                if (scopes instanceof String) {
                    if (StringUtils.hasText((String) scopes)) {
                        authorities.addAll(Arrays.asList(((String) scopes).split(" ")));
                    }
                } else if (scopes instanceof Collection) {
                    authorities.addAll((Collection<String>) scopes);
                }
            }
    
            return authorities;
        }
    }