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 =
|
||||
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
|
||||
// (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()
|
||||
.filter(oauth)
|
||||
.build();
|
||||
|
@ -48,7 +50,8 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
|||
[[webclient-implicit]]
|
||||
== 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).
|
||||
|
||||
[source,java]
|
||||
|
|
|
@ -39,9 +39,11 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
|||
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
|
||||
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
|
||||
// (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()
|
||||
.filter(oauth)
|
||||
.apply(oauth2.oauth2Configuration())
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
@ -50,6 +52,7 @@ WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
|
|||
== 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.
|
||||
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).
|
||||
|
||||
[source,java]
|
||||
|
|
|
@ -55,6 +55,8 @@ class OAuth2AuthorizedClientResolver {
|
|||
|
||||
private boolean defaultOAuth2AuthorizedClient;
|
||||
|
||||
private String defaultClientRegistrationId;
|
||||
|
||||
public OAuth2AuthorizedClientResolver(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
@ -75,6 +77,15 @@ class OAuth2AuthorizedClientResolver {
|
|||
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
|
||||
* client_credentials grant.
|
||||
|
@ -92,6 +103,7 @@ class OAuth2AuthorizedClientResolver {
|
|||
.switchIfEmpty(currentAuthentication());
|
||||
|
||||
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
|
||||
.switchIfEmpty(Mono.justOrEmpty(this.defaultClientRegistrationId))
|
||||
.switchIfEmpty(clientRegistrationId(defaultedAuthentication));
|
||||
|
||||
Mono<Optional<ServerWebExchange>> defaultedExchange = Mono.justOrEmpty(exchange)
|
||||
|
|
|
@ -188,6 +188,15 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
|
|||
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
|
||||
* client_credentials grant.
|
||||
|
|
|
@ -121,6 +121,8 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
|||
|
||||
private boolean defaultOAuth2AuthorizedClient;
|
||||
|
||||
private String defaultClientRegistrationId;
|
||||
|
||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {}
|
||||
|
||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
||||
|
@ -152,6 +154,16 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
|||
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}
|
||||
* @return the {@link Consumer} to configure the builder
|
||||
|
@ -295,6 +307,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
|||
|
||||
Authentication authentication = getAuthentication(attrs);
|
||||
String clientRegistrationId = getClientRegistrationId(attrs);
|
||||
if (clientRegistrationId == null) {
|
||||
clientRegistrationId = this.defaultClientRegistrationId;
|
||||
}
|
||||
if (clientRegistrationId == null
|
||||
&& this.defaultOAuth2AuthorizedClient
|
||||
&& authentication instanceof OAuth2AuthenticationToken) {
|
||||
|
|
|
@ -55,6 +55,8 @@ class OAuth2AuthorizedClientResolver {
|
|||
|
||||
private boolean defaultOAuth2AuthorizedClient;
|
||||
|
||||
private String defaultClientRegistrationId;
|
||||
|
||||
public OAuth2AuthorizedClientResolver(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
@ -75,6 +77,15 @@ class OAuth2AuthorizedClientResolver {
|
|||
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
|
||||
* client_credentials grant.
|
||||
|
@ -92,6 +103,7 @@ class OAuth2AuthorizedClientResolver {
|
|||
.switchIfEmpty(currentAuthentication());
|
||||
|
||||
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
|
||||
.switchIfEmpty(Mono.justOrEmpty(this.defaultClientRegistrationId))
|
||||
.switchIfEmpty(clientRegistrationId(defaultedAuthentication))
|
||||
.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();
|
||||
}
|
||||
|
||||
@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
|
||||
public void filterWhenClientRegistrationIdFromAuthenticationThenAuthorizedClientResolved() {
|
||||
this.function.setDefaultOAuth2AuthorizedClient(true);
|
||||
|
|
|
@ -296,6 +296,28 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
|||
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
|
||||
public void defaultRequestWhenClientIdNotFoundThenIllegalArgumentException() {
|
||||
this.registration = TestClientRegistrations.clientCredentials().build();
|
||||
|
|
Loading…
Reference in New Issue