Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting 403 Forbidden for WebFluxTest in Oauth2 Secured (Client Credentials) Resource Server Application

I have a reactive(Spring WebFlux) web-application where I am having few REST APIs which are protected resources.(Oauth2) . To access them manually, I need to get an authorization token with client credentials grant type and use that token in the request.

Now, I need to write tests where I can invoke the APIs by making a call through Spring's WebTestClient. I am getting 403 forbidden on trying to access the API. Where am I doing wrong when writing the test case.

Below is my security configuration:

@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:- I need this webclient bean because inside that filter (which I added to the SecurityWebFilterChain) I am calling another protected resource/API and the response of that API is being set in the reactive context

My 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

My Controller:

@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");
});
    
  }
}

My Test Case:

@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:- I cannot bypass by not using my WebSecurityConfiguration class. Because the reactive context is being set in the filter which is added in the websecurityconfiguration.

like image 881
Abhinaba Chakraborty Avatar asked Nov 07 '22 06:11

Abhinaba Chakraborty


1 Answers

2 things are required here:

  1. First to access the /my/api/greet, the webTestClient needs SCOPE_myApi and since no "user" is involved here so we dont need @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");
  }
  1. Next we need a wiremock server to mock the response of the "another service"

For this one option is to use spring boot @AutoConfigureWireMock(port = 0) to automatically boot up a wiremock server and shutdown for us at a random port.

Next we stub the response for the "another service" and the Oauth2 token endpoint in the test method.

Lastly, we need a "test" spring profile and a corresponding application-test.yaml where we tell spring to use the wiremock endpoints to fetch token:

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
like image 199
Abhinaba Chakraborty Avatar answered Nov 12 '22 12:11

Abhinaba Chakraborty