spring-projects / spring-projects/spring-security

Add test support for WebClient using ServletOAuth2AuthorizedClientExchangeFilterFunction#setDefaultOAuth2AuthorizedClient(true)

Open
#11,330 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

in: oauth2 type: enhancement
Dominant language
Java
Stars
9.6k
Forks
6.3k
Avg merge
2d 11h
Merged PRs (30d)
52

Description

Expected Behavior

Have an easy mechanism to use .with(oauth2Login()) on a service that uses the ServletOAuth2AuthorizedClientExchangeFilterFunction#setDefaultOAuth2AuthorizedClient(true) feature.

Current Behavior

When I use .with(oauth2Login()) on such service, the call isn't processed correctly, it fails as it can't find a valid AuthorizedClient in the corresponding repositories.

Context

I have this WebClient setup:

@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
    oauth2.setDefaultOAuth2AuthorizedClient(true);
    return WebClient.builder()
        .apply(oauth2.oauth2Configuration())
        .build();
}

Note: I'm aware I should be cautious when using setDefaultOAuth2AuthorizedClient(true), but I guess it's still a valid scenario, correct?

And this simple endpoint:

@GetMapping("/projects")
public String getProjects(Model model) {
    List<ProjectModel> projects = this.webClient.get()
        .uri(projectApiUrl)
        .retrieve()
        .bodyToMono(new ParameterizedTypeReference<List<ProjectModel>>() {
        })
        .block();
    model.addAttribute("projects", projects);
    return "projects";
}

Note: I'm using block() just because I'm using WebClient in a Servlet stack.

And finally, I have this test:

@Test
void givenMockedUser_whenRequestResources_thenOK() throws Exception {
    String mockedResources = "[{\"id\":1,\"name\":\"Project 1\",\"dateCreated\":\"2015-06-01\"}]";

    resourceServer.enqueue(new MockResponse().setBody(mockedResources)
        .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));

    mvc.perform(get("/projects")
        .with(oauth2Login())
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(content().string(containsString("Project 1")));

    RecordedRequest request = resourceServer.takeRequest();
    assertThat(request.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Bearer");
}

This test fails with the following error message:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Could not find ClientRegistration with id 'test'
...

Fair enough, I configure the OAuth2LoginRequestPostProcessor to use my custom ClientRegistration configured in my service:

.with(oauth2Login().clientRegistration(clientRegistrationRepository.findByRegistrationId("custom")))

Now the test fails because the service is returning a 302 response to trigger the OAuth authorization code flow, because it can't find an authorizedClient instance.

For what I understand from this note in the docs:

Further, it also links that OAuth2User to a simple instance of OAuth2AuthorizedClient that it deposits in a mock OAuth2AuthorizedClientRepository

This method does deposit the AuthorizedClient in a repository, but if understand how this works correctly, this will be stored in a HttpSessionOAuth2AuthorizedClientRepository that is not used when the WebClient resolves the AuthorizedClient using the setDefaultOAuth2AuthorizedClient feature; the OAuth2AuthorizeRequestconfigured in the ServletOAuth2AuthorizedClientExchangeFilterFunction simply has a null authorizedClient attribute.

Naturally, if I run this same test but using the @RegisteredOAuth2AuthorizedClient in the Controller endpoint, the test passes ok.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ServletOAuth2AuthorizedClientExchangeFilterFunction#setDefaultOAuth2AuthorizedClient(true), OAuth2LoginRequestPostProcessor, and the authorized-client repositories described in the issue. Reproduce the shown MockMvc test with oauth2Login() and a custom ClientRegistration. Done means the test can use the default authorized-client behavior and reaches the mocked resource with a Bearer authorization header.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
security, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.