Search code examples
javaspring-security-oauth2spring-webfluxspring-security-test

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.


Solution

  • 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