WebClient OAuth2 Support for defaultClientRegistrationId
Fixes: gh-5872
This commit is contained in:
parent
f48055ce47
commit
dcbf762a0b
|
@ -38,7 +38,9 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
||||||
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
|
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
|
||||||
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
|
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
|
||||||
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
|
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
|
||||||
oauth.setDefaultOAuth2AuthorizedClient(true);
|
// oauth.setDefaultOAuth2AuthorizedClient(true);
|
||||||
|
// (optional) set a default ClientRegistration.registrationId
|
||||||
|
// oauth.setDefaultClientRegistrationId("client-registration-id");
|
||||||
return WebClient.builder()
|
return WebClient.builder()
|
||||||
.filter(oauth)
|
.filter(oauth)
|
||||||
.build();
|
.build();
|
||||||
|
@ -48,7 +50,8 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
||||||
[[webclient-implicit]]
|
[[webclient-implicit]]
|
||||||
== Implicit OAuth2AuthorizedClient
|
== 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.
|
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.
|
||||||
|
Alternatively, if we set `defaultClientRegistrationId` to a valid `ClientRegistration` id, that registration is used to 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).
|
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).
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
|
|
|
@ -39,9 +39,11 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
||||||
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
|
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
|
||||||
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
|
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
|
||||||
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
|
// (optional) explicitly opt into using the oauth2Login to provide an access token implicitly
|
||||||
oauth.setDefaultOAuth2AuthorizedClient(true);
|
// oauth.setDefaultOAuth2AuthorizedClient(true);
|
||||||
|
// (optional) set a default ClientRegistration.registrationId
|
||||||
|
// oauth.setDefaultClientRegistrationId("client-registration-id");
|
||||||
return WebClient.builder()
|
return WebClient.builder()
|
||||||
.filter(oauth)
|
.apply(oauth2.oauth2Configuration())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -50,6 +52,7 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
||||||
== Implicit OAuth2AuthorizedClient
|
== 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.
|
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.
|
||||||
|
Alternatively, if we set `defaultClientRegistrationId` to a valid `ClientRegistration` id, that registration is used to 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).
|
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).
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
|
|
|
@ -55,6 +55,8 @@ class OAuth2AuthorizedClientResolver {
|
||||||
|
|
||||||
private boolean defaultOAuth2AuthorizedClient;
|
private boolean defaultOAuth2AuthorizedClient;
|
||||||
|
|
||||||
|
private String defaultClientRegistrationId;
|
||||||
|
|
||||||
public OAuth2AuthorizedClientResolver(
|
public OAuth2AuthorizedClientResolver(
|
||||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||||
|
@ -75,6 +77,15 @@ class OAuth2AuthorizedClientResolver {
|
||||||
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
|
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, will be used as the default {@link ClientRegistration#getRegistrationId()}. It is
|
||||||
|
* recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||||
|
* @param clientRegistrationId the id to use
|
||||||
|
*/
|
||||||
|
public void setDefaultClientRegistrationId(String clientRegistrationId) {
|
||||||
|
this.defaultClientRegistrationId = clientRegistrationId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
|
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
|
||||||
* client_credentials grant.
|
* client_credentials grant.
|
||||||
|
@ -92,6 +103,7 @@ class OAuth2AuthorizedClientResolver {
|
||||||
.switchIfEmpty(currentAuthentication());
|
.switchIfEmpty(currentAuthentication());
|
||||||
|
|
||||||
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
|
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
|
||||||
|
.switchIfEmpty(Mono.justOrEmpty(this.defaultClientRegistrationId))
|
||||||
.switchIfEmpty(clientRegistrationId(defaultedAuthentication));
|
.switchIfEmpty(clientRegistrationId(defaultedAuthentication));
|
||||||
|
|
||||||
Mono<Optional<ServerWebExchange>> defaultedExchange = Mono.justOrEmpty(exchange)
|
Mono<Optional<ServerWebExchange>> defaultedExchange = Mono.justOrEmpty(exchange)
|
||||||
|
|
|
@ -188,6 +188,15 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
||||||
this.authorizedClientResolver.setDefaultOAuth2AuthorizedClient(defaultOAuth2AuthorizedClient);
|
this.authorizedClientResolver.setDefaultOAuth2AuthorizedClient(defaultOAuth2AuthorizedClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, will be used as the default {@link ClientRegistration#getRegistrationId()}. It is
|
||||||
|
* recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||||
|
* @param clientRegistrationId the id to use
|
||||||
|
*/
|
||||||
|
public void setDefaultClientRegistrationId(String clientRegistrationId) {
|
||||||
|
this.authorizedClientResolver.setDefaultClientRegistrationId(clientRegistrationId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
|
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
|
||||||
* client_credentials grant.
|
* client_credentials grant.
|
||||||
|
|
|
@ -121,6 +121,8 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
|
|
||||||
private boolean defaultOAuth2AuthorizedClient;
|
private boolean defaultOAuth2AuthorizedClient;
|
||||||
|
|
||||||
|
private String defaultClientRegistrationId;
|
||||||
|
|
||||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {}
|
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {}
|
||||||
|
|
||||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
||||||
|
@ -152,6 +154,16 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
|
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, will be used as the default {@link ClientRegistration#getRegistrationId()}. It is
|
||||||
|
* recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||||
|
* @param clientRegistrationId the id to use
|
||||||
|
*/
|
||||||
|
public void setDefaultClientRegistrationId(String clientRegistrationId) {
|
||||||
|
this.defaultClientRegistrationId = clientRegistrationId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the builder with {@link #defaultRequest()} and adds this as a {@link ExchangeFilterFunction}
|
* Configures the builder with {@link #defaultRequest()} and adds this as a {@link ExchangeFilterFunction}
|
||||||
* @return the {@link Consumer} to configure the builder
|
* @return the {@link Consumer} to configure the builder
|
||||||
|
@ -295,6 +307,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
|
|
||||||
Authentication authentication = getAuthentication(attrs);
|
Authentication authentication = getAuthentication(attrs);
|
||||||
String clientRegistrationId = getClientRegistrationId(attrs);
|
String clientRegistrationId = getClientRegistrationId(attrs);
|
||||||
|
if (clientRegistrationId == null) {
|
||||||
|
clientRegistrationId = this.defaultClientRegistrationId;
|
||||||
|
}
|
||||||
if (clientRegistrationId == null
|
if (clientRegistrationId == null
|
||||||
&& this.defaultOAuth2AuthorizedClient
|
&& this.defaultOAuth2AuthorizedClient
|
||||||
&& authentication instanceof OAuth2AuthenticationToken) {
|
&& authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
|
|
@ -55,6 +55,8 @@ class OAuth2AuthorizedClientResolver {
|
||||||
|
|
||||||
private boolean defaultOAuth2AuthorizedClient;
|
private boolean defaultOAuth2AuthorizedClient;
|
||||||
|
|
||||||
|
private String defaultClientRegistrationId;
|
||||||
|
|
||||||
public OAuth2AuthorizedClientResolver(
|
public OAuth2AuthorizedClientResolver(
|
||||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||||
|
@ -75,6 +77,15 @@ class OAuth2AuthorizedClientResolver {
|
||||||
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
|
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, will be used as the default {@link ClientRegistration#getRegistrationId()}. It is
|
||||||
|
* recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||||
|
* @param clientRegistrationId the id to use
|
||||||
|
*/
|
||||||
|
public void setDefaultClientRegistrationId(String clientRegistrationId) {
|
||||||
|
this.defaultClientRegistrationId = clientRegistrationId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
|
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
|
||||||
* client_credentials grant.
|
* client_credentials grant.
|
||||||
|
@ -92,6 +103,7 @@ class OAuth2AuthorizedClientResolver {
|
||||||
.switchIfEmpty(currentAuthentication());
|
.switchIfEmpty(currentAuthentication());
|
||||||
|
|
||||||
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
|
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
|
||||||
|
.switchIfEmpty(Mono.justOrEmpty(this.defaultClientRegistrationId))
|
||||||
.switchIfEmpty(clientRegistrationId(defaultedAuthentication))
|
.switchIfEmpty(clientRegistrationId(defaultedAuthentication))
|
||||||
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("The clientRegistrationId could not be resolved. Please provide one")));
|
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("The clientRegistrationId could not be resolved. Please provide one")));
|
||||||
|
|
||||||
|
|
|
@ -300,6 +300,29 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
assertThat(getBody(request0)).isEmpty();
|
assertThat(getBody(request0)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenDefaultClientRegistrationIdThenAuthorizedClientResolved() {
|
||||||
|
this.function.setDefaultClientRegistrationId(this.registration.getRegistrationId());
|
||||||
|
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", this.accessToken.getIssuedAt(), this.accessToken.getExpiresAt());
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.registration,
|
||||||
|
"principalName", this.accessToken, refreshToken);
|
||||||
|
when(this.authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).thenReturn(Mono.just(authorizedClient));
|
||||||
|
when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(Mono.just(this.registration));
|
||||||
|
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.function.filter(request, this.exchange).block();
|
||||||
|
|
||||||
|
List<ClientRequest> requests = this.exchange.getRequests();
|
||||||
|
assertThat(requests).hasSize(1);
|
||||||
|
|
||||||
|
ClientRequest request0 = requests.get(0);
|
||||||
|
assertThat(request0.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer token-0");
|
||||||
|
assertThat(request0.url().toASCIIString()).isEqualTo("https://example.com");
|
||||||
|
assertThat(request0.method()).isEqualTo(HttpMethod.GET);
|
||||||
|
assertThat(getBody(request0)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filterWhenClientRegistrationIdFromAuthenticationThenAuthorizedClientResolved() {
|
public void filterWhenClientRegistrationIdFromAuthenticationThenAuthorizedClientResolved() {
|
||||||
this.function.setDefaultOAuth2AuthorizedClient(true);
|
this.function.setDefaultOAuth2AuthorizedClient(true);
|
||||||
|
|
|
@ -296,6 +296,28 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
assertThat(authorizedClient.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken());
|
assertThat(authorizedClient.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultRequestWhenDefaultClientRegistrationIdThenAuthorizedClient() {
|
||||||
|
this.registration = TestClientRegistrations.clientCredentials().build();
|
||||||
|
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(this.clientRegistrationRepository,
|
||||||
|
this.authorizedClientRepository);
|
||||||
|
this.function.setDefaultClientRegistrationId(this.registration.getRegistrationId());
|
||||||
|
this.function.setClientCredentialsTokenResponseClient(this.clientCredentialsTokenResponseClient);
|
||||||
|
when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(this.registration);
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses
|
||||||
|
.accessTokenResponse().build();
|
||||||
|
when(this.clientCredentialsTokenResponseClient.getTokenResponse(any())).thenReturn(
|
||||||
|
accessTokenResponse);
|
||||||
|
|
||||||
|
Map<String, Object> attrs = getDefaultRequestAttributes();
|
||||||
|
OAuth2AuthorizedClient authorizedClient = getOAuth2AuthorizedClient(attrs);
|
||||||
|
|
||||||
|
assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||||
|
assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration);
|
||||||
|
assertThat(authorizedClient.getPrincipalName()).isEqualTo("anonymousUser");
|
||||||
|
assertThat(authorizedClient.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultRequestWhenClientIdNotFoundThenIllegalArgumentException() {
|
public void defaultRequestWhenClientIdNotFoundThenIllegalArgumentException() {
|
||||||
this.registration = TestClientRegistrations.clientCredentials().build();
|
this.registration = TestClientRegistrations.clientCredentials().build();
|
||||||
|
|
Loading…
Reference in New Issue