baao
baao

Reputation: 73281

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?

Upvotes: 3

Views: 5863

Answers (3)

David KELLER
David KELLER

Reputation: 656

with springboot+webflux in version 2.7.1 and Keycloak as IdP, I have the same issue

 SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
    http
        .authorizeExchange()
        .pathMatchers("/availableInspections/**").hasRole("READ_PRIVILEGE")
        .and()
        .oauth2ResourceServer()
        .jwt()
        // .jwtDecoder(reactiveJwtDecoder()) not used
    ;
    return http.build();
  }

=> Gives the following error : www-authenticate: Bearer error="insufficient_scope",error_description="The request requires higher privileges than provided by the access token.",error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"

Then I see that the line ".anyExchange().authenticated()" was missing on my project :

 SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
    http
        .authorizeExchange()
        .pathMatchers("/availableInspections/**").hasRole("READ_PRIVILEGE")
        .anyExchange().authenticated()
        .and()
        .oauth2ResourceServer()
        .jwt()
        // .jwtDecoder(reactiveJwtDecoder()) not used
    ;
    return http.build();
  }

=> no more error :) !

Upvotes: 0

lcs_godoy
lcs_godoy

Reputation: 103

A little bit late, but a simpler solution could be (in kotlin, but easy to translate to java):

class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {

  private val jwtGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()

  override fun convert(source: Jwt): AbstractAuthenticationToken {
    val scopes = jwtGrantedAuthoritiesConverter.convert(source)
    val authorities = source.getClaimAsStringList("authorities")?.map { SimpleGrantedAuthority(it) }
    return JwtAuthenticationToken(source, scopes.orEmpty() + authorities.orEmpty())
  }
}

and then supply implementation to WebSecurityConfigurerAdapter, like:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
class ResourceServerConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http {
            oauth2ResourceServer { 
                jwt { 
                    jwtDecoder = reactiveJwtDecoder()
                    jwtAuthenticationConverter = CustomJwtAuthenticationConverter() 
                } 
            }
        }
    }
}

Upvotes: 1

baao
baao

Reputation: 73281

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;
    }
}

Upvotes: 7

Related Questions