Просмотр исходного кода

WebClient OAuth2 Support for defaultClientRegistrationId

Fixes: gh-5872
Rob Winch 7 лет назад
Родитель
Сommit
dcbf762a0b

+ 5 - 2
docs/manual/src/docs/asciidoc/_includes/reactive/webclient.adoc

@@ -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]

+ 5 - 2
docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/webclient.adoc

@@ -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]

+ 12 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/OAuth2AuthorizedClientResolver.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)

+ 9 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java

@@ -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.

+ 15 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java

@@ -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) {

+ 12 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientResolver.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))
 				.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("The clientRegistrationId could not be resolved. Please provide one")));
 

+ 23 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java

@@ -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);

+ 22 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java

@@ -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();