Skip to content

Add OAuth + WebClient Support #4921

Closed
Closed
@rwinch

Description

@rwinch

WebClient Support

This is now resolved. Demo usage can be found in oauth2webclient and oauth2webclient-webflux samples. A quick tour of the support:

Built In WebClient Support

WebClient has built in support for easily adding a Bearer token. For example:

webClient.get()
    .headers(h -> h.setBearerAuth(token))
    ...

Why use Spring Security Extensions?

Spring Security provides first class support for OAuth2. A few advantages of using this support are:

  • If an access token is requested and not present, Spring Security will automatically request the access token.
    • For authorization_code this involves performing the redirect and then replaying the original request
    • For client_credentials the token is simply requested and saved
  • Spring Security will automatically refresh expired tokens (if a refresh token is present)
  • Builds on other Spring Security OAuth2 support making things like using discovery of endpoints very simple
  • Users can choose to transparently include the current OAuth token or explicitly select which token should be used.

Setup

The first step is ensuring to setup the WebClient correctly.

For a Servlet environment this looks like:

@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
	ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
	// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
	oauth2.setDefaultOAuth2AuthorizedClient(true);
	return WebClient.builder()
			.apply(oauth2.oauth2Configuration())
			.build();
}

For other (i.e. WebFlux) environments it looks like:

@Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrationRepository,
		ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
	ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
			new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
	// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
	oauth.setDefaultOAuth2AuthorizedClient(true);
	return WebClient.builder()
			.filter(oauth)
			.build();
}

Implicit OAuth2AuthorizedClient

If we set defaultOAuth2AuthorizedClient to true in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token. This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint).

Mono<String> body = this.webClient
		.get()
		.uri(this.uri)
		.retrieve()
		.bodyToMono(String.class);

Explicit OAuth2AuthorizedClient

You can also explicitly provide an OAuth2AuthorizedClient by setting it on the requests attributes. In the example below we resolve the OAuth2AuthorizedClient using Spring WebFlux or Spring MVC argument resolver support. However, the user can choose to resolve the OAuth2AuthorizedClient however they wish.

@GetMapping("/explicit")
Mono<String> explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) {
	return this.webClient
			.get()
			.uri(this.uri)
			.attributes(oauth2AuthorizedClient(authorizedClient))
			.retrieve()
			.bodyToMono(String.class);
}

clientRegistrationId

Alternatively, it is possible to specify the clientRegistrationId on the request attributes and the WebClient will attempt to lookup the OAuth2AuthorizedClient. If it is not found, one will automatically be acquired.

Mono<String> body = this.webClient
		.get()
		.uri(this.uri)
		.attributes(clientRegistrationId("client-id"))
		.retrieve()
		.bodyToMono(String.class);

Summary (original)

We should add OAuth + WebClient support. The support for using WebClient in a Servlet environment and WebFlux must be separate because:

  • The default values will be obtained from different types of contexts. For example, Spring Security's Authentication will be obtained from the SecurityContextHolder in a Servlet environment but from the ReactiveSecurityContextHolder in WebFlux.
  • In the Servlet world we must be able to save to the HttpSession which is a blocking API. The rest of the OAuth support in the Servlet world is also using a blocking API

This is going to be broken up into multiple issues:

Metadata

Metadata

Assignees

Labels

in: oauth2An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions