5 votes

Obtention de 403 Forbidden pour WebFluxTest dans une application Resource Server sécurisée Oauth2 (Client Credentials)

J'ai une application web réactive (Spring WebFlux) où j'ai quelques API REST qui sont des ressources protégées (Oauth2). Pour y accéder manuellement, j'ai besoin d'obtenir un jeton d'autorisation avec les informations d'identification du client et d'utiliser ce jeton dans la requête.

Je dois maintenant écrire des tests dans lesquels je peux invoquer les API en passant par le WebTestClient de Spring. J'obtiens 403 forbidden en essayant d'accéder à l'API. Où est-ce que je me trompe en écrivant le cas de test ?

Voici ma configuration de sécurité :

@EnableWebFluxSecurity
public class WebSecurityConfiguration {

  @Bean
  SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeExchange()
        .pathMatchers(ACTUATOR_ENDPOINT_PATTERN)
        .permitAll()
        .pathMatchers("/my/api/*")
        .hasAuthority("SCOPE_myApi")
        .anyExchange().authenticated()
        .and()
        .oauth2ResourceServer()
        .jwt();
    http.addFilterAfter(new SomeFilter(), SecurityWebFiltersOrder.AUTHORIZATION);

    return http.build();
  }

  @Bean
  public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
      ReactiveClientRegistrationRepository clientRegistrationRepository,
      ReactiveOAuth2AuthorizedClientService authorizedClientService) {

    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
        ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build();

    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager =
        new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
  }

  @Bean
  public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    return WebClient.builder().filter(oauth).build();
  }

}

Note:- J'ai besoin de ce bean webclient parce qu'à l'intérieur de ce filtre (que j'ai ajouté à la SecurityWebFilterChain), j'appelle une autre ressource/API protégée et la réponse de cette API est définie dans le contexte réactif.

Mon application yaml :

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${oidc-issuer-uri}
      client:
        provider:
          myProvider:
            issuer-uri: ${oidc-issuer-uri}
        registration:
          myProvider:
            client-id: another-service-client
            client-secret: ${another-service-clientSecret}
            scope: anotherServiceScope
            authorization-grant-type: client_credentials

Mon contrôleur :

@RestController
public class MyController {
 @GetMapping(value = "/my/api/greet")
  public Mono<String> greet() {
return Mono.subscriberContext()
        .flatMap(context -> {
String someVal = context.get("MY_CONTEXT"); //This context is being set inside the filter 'SomeFilter'
//Use this someVal
return Mono.just("Hello World");
});

  }
}

Mon cas de test :

@RunWith(SpringRunner.class)
@WebFluxTest(controllers = {MyController.class})
@Import({WebSecurityConfiguration.class})
@WithMockUser
public class MyControllerTest {

  @Autowired
  private WebTestClient webTestClient;

  @Test
  public void test_greet() throws Exception {

    webTestClient.mutateWith(csrf()).get()
        .uri("/my/api/greet")
        .exchange()
        .expectStatus().isOk();
  }

}

Note:- Je ne peux pas contourner le problème en n'utilisant pas ma classe WebSecurityConfiguration. Parce que le contexte réactif est défini dans le filtre qui est ajouté dans la websecurityconfiguration.

1voto

Abhinaba Chakraborty Points 2766

2 choses sont nécessaires ici :

  1. Tout d'abord, pour accéder à /my/api/greet, le webTestClient a besoin de SCOPE_myApi et comme aucun "utilisateur" n'est impliqué ici, nous n'avons pas besoin de @WithMockUser.

    @Test public void test_greet() {

    webTestClient
        .mutateWith(mockOidcLogin().authorities(new SimpleGrantedAuthority("SCOPE_myApi")))
        .get()
        .uri("/my/api/greet")
        .exchange()
        .expectStatus().isOk()
        .expectBody(String.class).isEqualTo("mockSasToken");

    }

  2. Ensuite, nous avons besoin d'un serveur wiremock pour simuler la réponse d'un "autre service"

Pour cela, une option est d'utiliser spring boot @AutoConfigureWireMock(port = 0) pour démarrer automatiquement un serveur wiremock et l'arrêter pour nous sur un port aléatoire.

Ensuite, nous bloquons la réponse pour "un autre service" et le point de terminaison du jeton Oauth2 dans la méthode de test.

Enfin, nous avons besoin d'un profil Spring "test" et d'une application-test.yaml correspondante dans laquelle nous indiquons à Spring d'utiliser les points d'extrémité wiremock pour récupérer les jetons :

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:${wiremock.server.port}/.well-known/jwks_uri
      client:
        provider:
          myProvider:
            token-uri: http://localhost:${wiremock.server.port}/.well-known/token
        registration:
          myProvider:
            client-id: mockClient
            client-secret: mockSecret

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X