diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java
index 16400dc922..d78d8a556e 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,12 @@ package org.springframework.security.oauth2.client;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler;
+import org.springframework.security.oauth2.client.web.SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
@@ -25,17 +30,42 @@ import java.util.Map;
import java.util.function.Function;
/**
- * An implementation of an {@link ReactiveOAuth2AuthorizedClientManager}
- * that is capable of operating outside of a {@code ServerHttpRequest} context,
+ * An implementation of a {@link ReactiveOAuth2AuthorizedClientManager}
+ * that is capable of operating outside of the context of a {@link ServerWebExchange},
* e.g. in a scheduled/background thread and/or in the service-tier.
*
- *
This is a reactive equivalent of {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}
+ * (When operating within the context of a {@link ServerWebExchange},
+ * use {@link DefaultReactiveOAuth2AuthorizedClientManager} instead.)
+ *
+ * This is a reactive equivalent of {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}.
+ *
+ * Authorized Client Persistence
+ *
+ * This client manager utilizes a {@link ReactiveOAuth2AuthorizedClientService}
+ * to persist {@link OAuth2AuthorizedClient}s.
+ *
+ * By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
+ * will be saved in the authorized client service.
+ * This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationSuccessHandler}
+ * via {@link #setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)}.
+ *
+ * By default, when an authorization attempt fails due to an
+ * {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT} error,
+ * the previously saved {@link OAuth2AuthorizedClient}
+ * will be removed from the authorized client service.
+ * (The {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT}
+ * error generally occurs when a refresh token that is no longer valid
+ * is used to retrieve a new access token.)
+ * This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationFailureHandler}
+ * via {@link #setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)}.
*
* @author Ankur Pathak
* @author Phil Clay
* @see ReactiveOAuth2AuthorizedClientManager
* @see ReactiveOAuth2AuthorizedClientProvider
* @see ReactiveOAuth2AuthorizedClientService
+ * @see ReactiveOAuth2AuthorizationSuccessHandler
+ * @see ReactiveOAuth2AuthorizationFailureHandler
* @since 5.2.2
*/
public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
@@ -45,6 +75,8 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
private final ReactiveOAuth2AuthorizedClientService authorizedClientService;
private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty();
private Function>> contextAttributesMapper = new DefaultContextAttributesMapper();
+ private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
+ private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler;
/**
* Constructs an {@code AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters.
@@ -59,6 +91,8 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientService = authorizedClientService;
+ this.authorizationSuccessHandler = new SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler(authorizedClientService);
+ this.authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(authorizedClientService);
}
@Override
@@ -66,7 +100,7 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
return createAuthorizationContext(authorizeRequest)
- .flatMap(this::authorizeAndSave);
+ .flatMap(authorizationContext -> authorize(authorizationContext, authorizeRequest.getPrincipal()));
}
private Mono createAuthorizationContext(OAuth2AuthorizeRequest authorizeRequest) {
@@ -90,13 +124,34 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
}));
}
- private Mono authorizeAndSave(OAuth2AuthorizationContext authorizationContext) {
+ /**
+ * Performs authorization and then delegates to either the {@link #authorizationSuccessHandler}
+ * or {@link #authorizationFailureHandler}, depending on the authorization result.
+ *
+ * @param authorizationContext the context to authorize
+ * @param principal the principle to authorize
+ * @return a {@link Mono} that emits the authorized client after the authorization attempt succeeds
+ * and the {@link #authorizationSuccessHandler} has completed,
+ * or completes with an exception after the authorization attempt fails
+ * and the {@link #authorizationFailureHandler} has completed
+ */
+ private Mono authorize(
+ OAuth2AuthorizationContext authorizationContext,
+ Authentication principal) {
return this.authorizedClientProvider.authorize(authorizationContext)
- .flatMap(authorizedClient -> this.authorizedClientService.saveAuthorizedClient(
+ // Delegate to the authorizationSuccessHandler of the successful authorization
+ .flatMap(authorizedClient -> this.authorizationSuccessHandler.onAuthorizationSuccess(
authorizedClient,
- authorizationContext.getPrincipal())
+ principal,
+ Collections.emptyMap())
.thenReturn(authorizedClient))
- .switchIfEmpty(Mono.defer(()-> Mono.justOrEmpty(authorizationContext.getAuthorizedClient())));
+ // Delegate to the authorizationFailureHandler of the failed authorization
+ .onErrorResume(OAuth2AuthorizationException.class, authorizationException -> this.authorizationFailureHandler.onAuthorizationFailure(
+ authorizationException,
+ principal,
+ Collections.emptyMap())
+ .then(Mono.error(authorizationException)))
+ .switchIfEmpty(Mono.defer(() -> Mono.justOrEmpty(authorizationContext.getAuthorizedClient())));
}
/**
@@ -121,6 +176,36 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
this.contextAttributesMapper = contextAttributesMapper;
}
+ /**
+ * Sets the handler that handles successful authorizations.
+ *
+ * A {@link SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler}
+ * is used by default.
+ *
+ * @param authorizationSuccessHandler the handler that handles successful authorizations.
+ * @see SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler
+ * @since 5.3
+ */
+ public void setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
+ Assert.notNull(authorizationSuccessHandler, "authorizationSuccessHandler cannot be null");
+ this.authorizationSuccessHandler = authorizationSuccessHandler;
+ }
+
+ /**
+ * Sets the handler that handles authorization failures.
+ *
+ * A {@link RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler}
+ * is used by default.
+ *
+ * @param authorizationFailureHandler the handler that handles authorization failures.
+ * @see RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
+ * @since 5.3
+ */
+ public void setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) {
+ Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
+ this.authorizationFailureHandler = authorizationFailureHandler;
+ }
+
/**
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
*/
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java
new file mode 100644
index 0000000000..0cbd6ee2c8
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationException.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.client;
+
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.util.Assert;
+
+/**
+ * This exception is thrown on the client side when an attempt to authenticate
+ * or authorize an OAuth 2.0 client fails.
+ *
+ * @author Phil Clay
+ * @since 5.3
+ * @see OAuth2AuthorizedClient
+ */
+public class ClientAuthorizationException extends OAuth2AuthorizationException {
+
+ private final String clientRegistrationId;
+
+ /**
+ * Constructs a {@code ClientAuthorizationException} using the provided parameters.
+ *
+ * @param error the {@link OAuth2Error OAuth 2.0 Error}
+ * @param clientRegistrationId the identifier for the client's registration
+ */
+ public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId) {
+ this(error, clientRegistrationId, error.toString());
+ }
+ /**
+ * Constructs a {@code ClientAuthorizationException} using the provided parameters.
+ *
+ * @param error the {@link OAuth2Error OAuth 2.0 Error}
+ * @param clientRegistrationId the identifier for the client's registration
+ * @param message the exception message
+ */
+ public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId, String message) {
+ super(error, message);
+ Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
+ this.clientRegistrationId = clientRegistrationId;
+ }
+
+ /**
+ * Constructs a {@code ClientAuthorizationException} using the provided parameters.
+ *
+ * @param error the {@link OAuth2Error OAuth 2.0 Error}
+ * @param clientRegistrationId the identifier for the client's registration
+ * @param cause the root cause
+ */
+ public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId, Throwable cause) {
+ this(error, clientRegistrationId, error.toString(), cause);
+ }
+
+ /**
+ * Constructs a {@code ClientAuthorizationException} using the provided parameters.
+ *
+ * @param error the {@link OAuth2Error OAuth 2.0 Error}
+ * @param clientRegistrationId the identifier for the client's registration
+ * @param message the exception message
+ * @param cause the root cause
+ */
+ public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId, String message, Throwable cause) {
+ super(error, message, cause);
+ Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
+ this.clientRegistrationId = clientRegistrationId;
+ }
+
+ /**
+ * Returns the identifier for the client's registration.
+ *
+ * @return the identifier for the client's registration
+ */
+ public String getClientRegistrationId() {
+ return this.clientRegistrationId;
+ }
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java
index 0a0c81ea98..d9b9e7a6a7 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientAuthorizationRequiredException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,7 @@
*/
package org.springframework.security.oauth2.client;
-import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.util.Assert;
/**
* This exception is thrown when an OAuth 2.0 Client is required
@@ -27,9 +25,8 @@ import org.springframework.util.Assert;
* @since 5.1
* @see OAuth2AuthorizedClient
*/
-public class ClientAuthorizationRequiredException extends OAuth2AuthorizationException {
+public class ClientAuthorizationRequiredException extends ClientAuthorizationException {
private static final String CLIENT_AUTHORIZATION_REQUIRED_ERROR_CODE = "client_authorization_required";
- private final String clientRegistrationId;
/**
* Constructs a {@code ClientAuthorizationRequiredException} using the provided parameters.
@@ -38,17 +35,7 @@ public class ClientAuthorizationRequiredException extends OAuth2AuthorizationExc
*/
public ClientAuthorizationRequiredException(String clientRegistrationId) {
super(new OAuth2Error(CLIENT_AUTHORIZATION_REQUIRED_ERROR_CODE,
- "Authorization required for Client Registration Id: " + clientRegistrationId, null));
- Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
- this.clientRegistrationId = clientRegistrationId;
- }
-
- /**
- * Returns the identifier for the client's registration.
- *
- * @return the identifier for the client's registration
- */
- public String getClientRegistrationId() {
- return this.clientRegistrationId;
+ "Authorization required for Client Registration Id: " + clientRegistrationId, null),
+ clientRegistrationId);
}
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizationFailureHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizationFailureHandler.java
new file mode 100644
index 0000000000..ed93e6cbf4
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizationFailureHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.client;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+/**
+ * Handles when an OAuth 2.0 Client
+ * fails to authorize (or re-authorize)
+ * via the authorization server or resource server.
+ *
+ * @author Phil Clay
+ * @since 5.3
+ */
+@FunctionalInterface
+public interface ReactiveOAuth2AuthorizationFailureHandler {
+
+ /**
+ * Called when an OAuth 2.0 Client
+ * fails to authorize (or re-authorize)
+ * via the authorization server or resource server.
+ *
+ * @param authorizationException the exception that contains details about what failed
+ * @param principal the {@code Principal} that was attempted to be authorized
+ * @param attributes an immutable {@code Map} of extra optional attributes present under certain conditions.
+ * For example, this might contain a {@link org.springframework.web.server.ServerWebExchange ServerWebExchange}
+ * if the authorization was performed within the context of a {@code ServerWebExchange}.
+ * @return an empty {@link Mono} that completes after this handler has finished handling the event.
+ */
+ Mono onAuthorizationFailure(
+ OAuth2AuthorizationException authorizationException,
+ Authentication principal,
+ Map attributes);
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizationSuccessHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizationSuccessHandler.java
new file mode 100644
index 0000000000..1df3258f63
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizationSuccessHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.client;
+
+import org.springframework.security.core.Authentication;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+/**
+ * Handles when an OAuth 2.0 Client
+ * has been successfully authorized (or re-authorized)
+ * via the authorization server.
+ *
+ * @author Phil Clay
+ * @since 5.3
+ */
+@FunctionalInterface
+public interface ReactiveOAuth2AuthorizationSuccessHandler {
+
+ /**
+ * Called when an OAuth 2.0 Client
+ * has been successfully authorized (or re-authorized)
+ * via the authorization server.
+ *
+ * @param authorizedClient the client that was successfully authorized
+ * @param principal the {@code Principal} associated with the authorized client
+ * @param attributes an immutable {@code Map} of extra optional attributes present under certain conditions.
+ * For example, this might contain a {@link org.springframework.web.server.ServerWebExchange ServerWebExchange}
+ * if the authorization was performed within the context of a {@code ServerWebExchange}.
+ * @return an empty {@link Mono} that completes after this handler has finished handling the event.
+ */
+ Mono onAuthorizationSuccess(
+ OAuth2AuthorizedClient authorizedClient,
+ Authentication principal,
+ Map attributes);
+
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java
new file mode 100644
index 0000000000..36819178bc
--- /dev/null
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.client.endpoint;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.oauth2.client.ClientAuthorizationException;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+import java.util.Set;
+
+import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
+
+/**
+ * Abstract base class for all of the {@code WebClientReactive*TokenResponseClient}s
+ * that communicate to the Authorization Server's Token Endpoint.
+ *
+ * Submits a form request body specific to the type of grant request.
+ *
+ * Accepts a JSON response body containing an OAuth 2.0 Access token or error.
+ *
+ * @author Phil Clay
+ * @since 5.3
+ * @param type of grant request
+ * @see RFC-6749 Token Endpoint
+ * @see WebClientReactiveAuthorizationCodeTokenResponseClient
+ * @see WebClientReactiveClientCredentialsTokenResponseClient
+ * @see WebClientReactivePasswordTokenResponseClient
+ * @see WebClientReactiveRefreshTokenTokenResponseClient
+ */
+abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient
+ implements ReactiveOAuth2AccessTokenResponseClient {
+
+ private WebClient webClient = WebClient.builder().build();
+
+ @Override
+ public Mono getTokenResponse(T grantRequest) {
+ Assert.notNull(grantRequest, "grantRequest cannot be null");
+ return Mono.defer(() -> this.webClient.post()
+ .uri(clientRegistration(grantRequest).getProviderDetails().getTokenUri())
+ .headers(headers -> populateTokenRequestHeaders(grantRequest, headers))
+ .body(createTokenRequestBody(grantRequest))
+ .exchange()
+ .flatMap(response -> readTokenResponse(grantRequest, response)));
+ }
+
+ /**
+ * Returns the {@link ClientRegistration} for the given {@code grantRequest}.
+ *
+ * @param grantRequest the grant request
+ * @return the {@link ClientRegistration} for the given {@code grantRequest}.
+ */
+ abstract ClientRegistration clientRegistration(T grantRequest);
+
+ /**
+ * Populates the headers for the token request.
+ *
+ * @param grantRequest the grant request
+ * @param headers the headers to populate
+ */
+ private void populateTokenRequestHeaders(T grantRequest, HttpHeaders headers) {
+ ClientRegistration clientRegistration = clientRegistration(grantRequest);
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+ if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
+ headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
+ }
+ }
+
+ /**
+ * Creates and returns the body for the token request.
+ *
+ * This method pre-populates the body with some standard properties,
+ * and then delegates to {@link #populateTokenRequestBody(AbstractOAuth2AuthorizationGrantRequest, BodyInserters.FormInserter)}
+ * for subclasses to further populate the body before returning.
+ *
+ * @param grantRequest the grant request
+ * @return the body for the token request.
+ */
+ private BodyInserters.FormInserter createTokenRequestBody(T grantRequest) {
+ BodyInserters.FormInserter body = BodyInserters
+ .fromFormData(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue());
+ return populateTokenRequestBody(grantRequest, body);
+ }
+
+ /**
+ * Populates the body of the token request.
+ *
+ * By default, populates properties that are common to all grant types.
+ * Subclasses can extend this method to populate grant type specific properties.
+ *
+ * @param grantRequest the grant request
+ * @param body the body to populate
+ * @return the populated body
+ */
+ BodyInserters.FormInserter populateTokenRequestBody(T grantRequest, BodyInserters.FormInserter body) {
+ ClientRegistration clientRegistration = clientRegistration(grantRequest);
+ if (!ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
+ body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
+ }
+ if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
+ body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
+ }
+ Set scopes = scopes(grantRequest);
+ if (!CollectionUtils.isEmpty(scopes)) {
+ body.with(OAuth2ParameterNames.SCOPE,
+ StringUtils.collectionToDelimitedString(scopes, " "));
+ }
+ return body;
+ }
+
+ /**
+ * Returns the scopes to include as a property in the token request.
+ *
+ * @param grantRequest the grant request
+ * @return the scopes to include as a property in the token request.
+ */
+ abstract Set scopes(T grantRequest);
+
+ /**
+ * Returns the scopes to include in the response if the authorization
+ * server returned no scopes in the response.
+ *
+ * As per RFC-6749 Section 5.1 Successful Access Token Response,
+ * if AccessTokenResponse.scope is empty, then default to the scope
+ * originally requested by the client in the Token Request.
+ *
+ * @param grantRequest the grant request
+ * @return the scopes to include in the response if the authorization
+ * server returned no scopes.
+ */
+ Set defaultScopes(T grantRequest) {
+ return scopes(grantRequest);
+ }
+
+ /**
+ * Reads the token response from the response body.
+ *
+ * @param grantRequest the request for which the response was received.
+ * @param response the client response from which to read
+ * @return the token response from the response body.
+ */
+ private Mono readTokenResponse(T grantRequest, ClientResponse response) {
+ return response.body(oauth2AccessTokenResponse())
+ .onErrorMap(OAuth2AuthorizationException.class, e -> createClientAuthorizationException(
+ response,
+ clientRegistration(grantRequest).getRegistrationId(),
+ e))
+ .map(tokenResponse -> populateTokenResponse(grantRequest, tokenResponse));
+ }
+
+ /**
+ * Wraps the given {@link OAuth2AuthorizationException} in a {@link ClientAuthorizationException}
+ * that provides response details, and a more descriptive exception message.
+ *
+ * @param response the token response
+ * @param clientRegistrationId the id of the {@link ClientRegistration} for which a token is being requested
+ * @param authorizationException the {@link OAuth2AuthorizationException} to wrap
+ * @return the {@link ClientAuthorizationException} that wraps the given {@link OAuth2AuthorizationException}
+ */
+ private OAuth2AuthorizationException createClientAuthorizationException(
+ ClientResponse response,
+ String clientRegistrationId,
+ OAuth2AuthorizationException authorizationException) {
+
+ String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
+ response.rawStatusCode(),
+ authorizationException.getError());
+
+ return new ClientAuthorizationException(
+ authorizationException.getError(),
+ clientRegistrationId,
+ message,
+ authorizationException);
+ }
+
+ /**
+ * Populates the given {@link OAuth2AccessTokenResponse} with additional details
+ * from the grant request.
+ *
+ * @param grantRequest the request for which the response was received.
+ * @param tokenResponse the original token response
+ * @return a token response optionally populated with additional details from the request.
+ */
+ OAuth2AccessTokenResponse populateTokenResponse(T grantRequest, OAuth2AccessTokenResponse tokenResponse) {
+ if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
+ Set defaultScopes = defaultScopes(grantRequest);
+ tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
+ .scopes(defaultScopes)
+ .build();
+ }
+ return tokenResponse;
+ }
+
+ /**
+ * Sets the {@link WebClient} used when requesting the OAuth 2.0 Access Token Response.
+ *
+ * @param webClient the {@link WebClient} used when requesting the Access Token Response
+ */
+ public void setWebClient(WebClient webClient) {
+ Assert.notNull(webClient, "webClient cannot be null");
+ this.webClient = webClient;
+ }
+}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java
index 76d49cc367..80cbe4cafd 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,24 +15,19 @@
*/
package org.springframework.security.oauth2.client.endpoint;
-import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.web.reactive.function.BodyInserters;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.util.Assert;
-import reactor.core.publisher.Mono;
-import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
+import java.util.Collections;
+import java.util.Set;
/**
- * An implementation of an {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges"
+ * An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges"
* an authorization code credential for an access token credential
* at the Authorization Server's Token Endpoint.
*
@@ -41,7 +36,7 @@ import static org.springframework.security.oauth2.core.web.reactive.function.OAu
*
* @author Rob Winch
* @since 5.1
- * @see OAuth2AccessTokenResponseClient
+ * @see ReactiveOAuth2AccessTokenResponseClient
* @see OAuth2AuthorizationCodeGrantRequest
* @see OAuth2AccessTokenResponse
* @see Nimbus OAuth 2.0 SDK
@@ -49,64 +44,37 @@ import static org.springframework.security.oauth2.core.web.reactive.function.OAu
* @see Section 4.1.4 Access Token Response (Authorization Code Grant)
* @see Section 4.2 Client Creates the Code Challenge
*/
-public class WebClientReactiveAuthorizationCodeTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient {
- private WebClient webClient = WebClient.builder()
- .build();
+public class WebClientReactiveAuthorizationCodeTokenResponseClient extends
+ AbstractWebClientReactiveOAuth2AccessTokenResponseClient {
- /**
- * @param webClient the webClient to set
- */
- public void setWebClient(WebClient webClient) {
- Assert.notNull(webClient, "webClient cannot be null");
- this.webClient = webClient;
+ @Override
+ ClientRegistration clientRegistration(OAuth2AuthorizationCodeGrantRequest grantRequest) {
+ return grantRequest.getClientRegistration();
}
@Override
- public Mono getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
- return Mono.defer(() -> {
- ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
- OAuth2AuthorizationExchange authorizationExchange = authorizationGrantRequest.getAuthorizationExchange();
- String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
- BodyInserters.FormInserter body = body(authorizationExchange, clientRegistration);
-
- return this.webClient.post()
- .uri(tokenUri)
- .accept(MediaType.APPLICATION_JSON)
- .headers(headers -> {
- if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
- headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
- }
- })
- .body(body)
- .exchange()
- .flatMap(response -> response.body(oauth2AccessTokenResponse()))
- .map(response -> {
- if (response.getAccessToken().getScopes().isEmpty()) {
- response = OAuth2AccessTokenResponse.withResponse(response)
- .scopes(authorizationExchange.getAuthorizationRequest().getScopes())
- .build();
- }
- return response;
- });
- });
+ Set scopes(OAuth2AuthorizationCodeGrantRequest grantRequest) {
+ return Collections.emptySet();
}
- private static BodyInserters.FormInserter body(OAuth2AuthorizationExchange authorizationExchange, ClientRegistration clientRegistration) {
+ @Override
+ Set defaultScopes(OAuth2AuthorizationCodeGrantRequest grantRequest) {
+ return grantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes();
+ }
+
+ @Override
+ BodyInserters.FormInserter populateTokenRequestBody(
+ OAuth2AuthorizationCodeGrantRequest grantRequest,
+ BodyInserters.FormInserter body) {
+ super.populateTokenRequestBody(grantRequest, body);
+ OAuth2AuthorizationExchange authorizationExchange = grantRequest.getAuthorizationExchange();
OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
- BodyInserters.FormInserter body = BodyInserters
- .fromFormData(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
- .with(OAuth2ParameterNames.CODE, authorizationResponse.getCode());
+ body.with(OAuth2ParameterNames.CODE, authorizationResponse.getCode());
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
- String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
if (redirectUri != null) {
body.with(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
}
- if (!ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
- body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
- }
- if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
- body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
- }
+ String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
if (codeVerifier != null) {
body.with(PkceParameterNames.CODE_VERIFIER, codeVerifier);
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java
index 6acfd38547..b5f4142ef6 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,112 +15,36 @@
*/
package org.springframework.security.oauth2.client.endpoint;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
-import org.springframework.web.reactive.function.BodyInserters;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.reactive.function.client.WebClientResponseException;
-import reactor.core.publisher.Mono;
import java.util.Set;
-import java.util.function.Consumer;
-
-import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
/**
- * An implementation of an {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges"
- * an authorization code credential for an access token credential
+ * An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges"
+ * a client credential for an access token credential
* at the Authorization Server's Token Endpoint.
*
* @author Rob Winch
* @since 5.1
- * @see OAuth2AccessTokenResponseClient
+ * @see ReactiveOAuth2AccessTokenResponseClient
* @see OAuth2AuthorizationCodeGrantRequest
* @see OAuth2AccessTokenResponse
* @see Nimbus OAuth 2.0 SDK
* @see Section 4.1.3 Access Token Request (Authorization Code Grant)
* @see Section 4.1.4 Access Token Response (Authorization Code Grant)
*/
-public class WebClientReactiveClientCredentialsTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient {
- private WebClient webClient = WebClient.builder()
- .build();
+public class WebClientReactiveClientCredentialsTokenResponseClient extends
+ AbstractWebClientReactiveOAuth2AccessTokenResponseClient {
@Override
- public Mono getTokenResponse(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
- return Mono.defer(() -> {
- ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
-
- String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
- BodyInserters.FormInserter body = body(authorizationGrantRequest);
-
- return this.webClient.post()
- .uri(tokenUri)
- .accept(MediaType.APPLICATION_JSON)
- .headers(headers(clientRegistration))
- .body(body)
- .exchange()
- .flatMap(response -> {
- HttpStatus status = HttpStatus.resolve(response.rawStatusCode());
- if (status == null || !status.is2xxSuccessful()) {
- // extract the contents of this into a method named oauth2AccessTokenResponse but has an argument for the response
- return response.bodyToFlux(DataBuffer.class)
- .map(DataBufferUtils::release)
- .then(Mono.error(WebClientResponseException.create(response.rawStatusCode(),
- "Cannot get token, expected 2xx HTTP Status code",
- null,
- null,
- null
- )));
- }
- return response.body(oauth2AccessTokenResponse()); })
- .map(response -> {
- if (response.getAccessToken().getScopes().isEmpty()) {
- response = OAuth2AccessTokenResponse.withResponse(response)
- .scopes(authorizationGrantRequest.getClientRegistration().getScopes())
- .build();
- }
- return response;
- });
- });
+ ClientRegistration clientRegistration(OAuth2ClientCredentialsGrantRequest grantRequest) {
+ return grantRequest.getClientRegistration();
}
- private Consumer headers(ClientRegistration clientRegistration) {
- return headers -> {
- headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
- if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
- headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
- }
- };
+ @Override
+ Set scopes(OAuth2ClientCredentialsGrantRequest grantRequest) {
+ return grantRequest.getClientRegistration().getScopes();
}
- private static BodyInserters.FormInserter body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
- ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
- BodyInserters.FormInserter body = BodyInserters
- .fromFormData(OAuth2ParameterNames.GRANT_TYPE, authorizationGrantRequest.getGrantType().getValue());
- Set scopes = clientRegistration.getScopes();
- if (!CollectionUtils.isEmpty(scopes)) {
- String scope = StringUtils.collectionToDelimitedString(scopes, " ");
- body.with(OAuth2ParameterNames.SCOPE, scope);
- }
- if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
- body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
- body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
- }
- return body;
- }
-
- public void setWebClient(WebClient webClient) {
- Assert.notNull(webClient, "webClient cannot be null");
- this.webClient = webClient;
- }
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java
index 41fe121694..442e2543fc 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,29 +15,14 @@
*/
package org.springframework.security.oauth2.client.endpoint;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
-import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
-import java.util.Collections;
-import java.util.function.Consumer;
-
-import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
+import java.util.Set;
/**
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient}
@@ -53,82 +38,26 @@ import static org.springframework.security.oauth2.core.web.reactive.function.OAu
* @see Section 4.3.2 Access Token Request (Resource Owner Password Credentials Grant)
* @see Section 4.3.3 Access Token Response (Resource Owner Password Credentials Grant)
*/
-public final class WebClientReactivePasswordTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient {
- private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
- private WebClient webClient = WebClient.builder().build();
+public final class WebClientReactivePasswordTokenResponseClient extends
+ AbstractWebClientReactiveOAuth2AccessTokenResponseClient {
@Override
- public Mono getTokenResponse(OAuth2PasswordGrantRequest passwordGrantRequest) {
- Assert.notNull(passwordGrantRequest, "passwordGrantRequest cannot be null");
- return Mono.defer(() -> {
- ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
- return this.webClient.post()
- .uri(clientRegistration.getProviderDetails().getTokenUri())
- .headers(tokenRequestHeaders(clientRegistration))
- .body(tokenRequestBody(passwordGrantRequest))
- .exchange()
- .flatMap(response -> {
- HttpStatus status = HttpStatus.resolve(response.rawStatusCode());
- if (status == null || !status.is2xxSuccessful()) {
- OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
- "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " +
- "HTTP Status Code " + response.rawStatusCode(), null);
- return response
- .bodyToMono(DataBuffer.class)
- .map(DataBufferUtils::release)
- .then(Mono.error(new OAuth2AuthorizationException(oauth2Error)));
- }
- return response.body(oauth2AccessTokenResponse());
- })
- .map(tokenResponse -> {
- if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
- // As per spec, in Section 5.1 Successful Access Token Response
- // https://tools.ietf.org/html/rfc6749#section-5.1
- // If AccessTokenResponse.scope is empty, then default to the scope
- // originally requested by the client in the Token Request
- tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
- .scopes(passwordGrantRequest.getClientRegistration().getScopes())
- .build();
- }
- return tokenResponse;
- });
- });
+ ClientRegistration clientRegistration(OAuth2PasswordGrantRequest grantRequest) {
+ return grantRequest.getClientRegistration();
}
- private static Consumer tokenRequestHeaders(ClientRegistration clientRegistration) {
- return headers -> {
- headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
- headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
- if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
- headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
- }
- };
+ @Override
+ Set scopes(OAuth2PasswordGrantRequest grantRequest) {
+ return grantRequest.getClientRegistration().getScopes();
}
- private static BodyInserters.FormInserter tokenRequestBody(OAuth2PasswordGrantRequest passwordGrantRequest) {
- ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
- BodyInserters.FormInserter body = BodyInserters.fromFormData(
- OAuth2ParameterNames.GRANT_TYPE, passwordGrantRequest.getGrantType().getValue());
- body.with(OAuth2ParameterNames.USERNAME, passwordGrantRequest.getUsername());
- body.with(OAuth2ParameterNames.PASSWORD, passwordGrantRequest.getPassword());
- if (!CollectionUtils.isEmpty(passwordGrantRequest.getClientRegistration().getScopes())) {
- body.with(OAuth2ParameterNames.SCOPE,
- StringUtils.collectionToDelimitedString(passwordGrantRequest.getClientRegistration().getScopes(), " "));
- }
- if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
- body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
- body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
- }
- return body;
+ @Override
+ BodyInserters.FormInserter populateTokenRequestBody(
+ OAuth2PasswordGrantRequest grantRequest,
+ BodyInserters.FormInserter body) {
+ return super.populateTokenRequestBody(grantRequest, body)
+ .with(OAuth2ParameterNames.USERNAME, grantRequest.getUsername())
+ .with(OAuth2ParameterNames.PASSWORD, grantRequest.getPassword());
}
- /**
- * Sets the {@link WebClient} used when requesting the OAuth 2.0 Access Token Response.
- *
- * @param webClient the {@link WebClient} used when requesting the Access Token Response
- */
- public void setWebClient(WebClient webClient) {
- Assert.notNull(webClient, "webClient cannot be null");
- this.webClient = webClient;
- }
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java
index 6d6daa83d5..9ad787af7f 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,29 +15,15 @@
*/
package org.springframework.security.oauth2.client.endpoint;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
-import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
-import java.util.Collections;
-import java.util.function.Consumer;
-
-import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
+import java.util.Set;
/**
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient}
@@ -52,66 +38,37 @@ import static org.springframework.security.oauth2.core.web.reactive.function.OAu
* @see OAuth2AccessTokenResponse
* @see Section 6 Refreshing an Access Token
*/
-public final class WebClientReactiveRefreshTokenTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient {
- private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
- private WebClient webClient = WebClient.builder().build();
+public final class WebClientReactiveRefreshTokenTokenResponseClient extends
+ AbstractWebClientReactiveOAuth2AccessTokenResponseClient {
@Override
- public Mono getTokenResponse(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
- Assert.notNull(refreshTokenGrantRequest, "refreshTokenGrantRequest cannot be null");
- return Mono.defer(() -> {
- ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration();
- return this.webClient.post()
- .uri(clientRegistration.getProviderDetails().getTokenUri())
- .headers(tokenRequestHeaders(clientRegistration))
- .body(tokenRequestBody(refreshTokenGrantRequest))
- .exchange()
- .flatMap(response -> {
- HttpStatus status = HttpStatus.resolve(response.rawStatusCode());
- if (status == null || !status.is2xxSuccessful()) {
- OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
- "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " +
- "HTTP Status Code " + response.rawStatusCode(), null);
- return response
- .bodyToMono(DataBuffer.class)
- .map(DataBufferUtils::release)
- .then(Mono.error(new OAuth2AuthorizationException(oauth2Error)));
- }
- return response.body(oauth2AccessTokenResponse());
- })
- .map(tokenResponse -> tokenResponse(refreshTokenGrantRequest, tokenResponse));
- });
+ ClientRegistration clientRegistration(OAuth2RefreshTokenGrantRequest grantRequest) {
+ return grantRequest.getClientRegistration();
}
- private static Consumer tokenRequestHeaders(ClientRegistration clientRegistration) {
- return headers -> {
- headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
- headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
- if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
- headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
- }
- };
+ @Override
+ Set scopes(OAuth2RefreshTokenGrantRequest grantRequest) {
+ return grantRequest.getScopes();
}
- private static BodyInserters.FormInserter tokenRequestBody(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
- ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration();
- BodyInserters.FormInserter body = BodyInserters.fromFormData(
- OAuth2ParameterNames.GRANT_TYPE, refreshTokenGrantRequest.getGrantType().getValue());
- body.with(OAuth2ParameterNames.REFRESH_TOKEN,
- refreshTokenGrantRequest.getRefreshToken().getTokenValue());
- if (!CollectionUtils.isEmpty(refreshTokenGrantRequest.getScopes())) {
- body.with(OAuth2ParameterNames.SCOPE,
- StringUtils.collectionToDelimitedString(refreshTokenGrantRequest.getScopes(), " "));
- }
- if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
- body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
- body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
- }
- return body;
+ @Override
+ Set defaultScopes(OAuth2RefreshTokenGrantRequest grantRequest) {
+ return grantRequest.getAccessToken().getScopes();
}
- private static OAuth2AccessTokenResponse tokenResponse(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest,
- OAuth2AccessTokenResponse accessTokenResponse) {
+ @Override
+ BodyInserters.FormInserter populateTokenRequestBody(
+ OAuth2RefreshTokenGrantRequest grantRequest,
+ BodyInserters.FormInserter body) {
+ return super.populateTokenRequestBody(grantRequest, body)
+ .with(OAuth2ParameterNames.REFRESH_TOKEN, grantRequest.getRefreshToken().getTokenValue());
+ }
+
+ @Override
+ OAuth2AccessTokenResponse populateTokenResponse(
+ OAuth2RefreshTokenGrantRequest grantRequest,
+ OAuth2AccessTokenResponse accessTokenResponse) {
+
if (!CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes()) &&
accessTokenResponse.getRefreshToken() != null) {
return accessTokenResponse;
@@ -119,26 +76,13 @@ public final class WebClientReactiveRefreshTokenTokenResponseClient implements R
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse.withResponse(accessTokenResponse);
if (CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes())) {
- // As per spec, in Section 5.1 Successful Access Token Response
- // https://tools.ietf.org/html/rfc6749#section-5.1
- // If AccessTokenResponse.scope is empty, then default to the scope
- // originally requested by the client in the Token Request
- tokenResponseBuilder.scopes(refreshTokenGrantRequest.getAccessToken().getScopes());
+ tokenResponseBuilder.scopes(defaultScopes(grantRequest));
}
if (accessTokenResponse.getRefreshToken() == null) {
// Reuse existing refresh token
- tokenResponseBuilder.refreshToken(refreshTokenGrantRequest.getRefreshToken().getTokenValue());
+ tokenResponseBuilder.refreshToken(grantRequest.getRefreshToken().getTokenValue());
}
return tokenResponseBuilder.build();
}
- /**
- * Sets the {@link WebClient} used when requesting the OAuth 2.0 Access Token Response.
- *
- * @param webClient the {@link WebClient} used when requesting the Access Token Response
- */
- public void setWebClient(WebClient webClient) {
- Assert.notNull(webClient, "webClient cannot be null");
- this.webClient = webClient;
- }
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
index 852e910e6f..eead89d565 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizationFailureHandler;
+import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizationSuccessHandler;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -37,18 +40,54 @@ import java.util.Map;
import java.util.function.Function;
/**
- * The default implementation of a {@link ReactiveOAuth2AuthorizedClientManager}.
+ * The default implementation of a {@link ReactiveOAuth2AuthorizedClientManager}
+ * for use within the context of a {@link ServerWebExchange}.
+ *
+ * (When operating outside of the context of a {@link ServerWebExchange},
+ * use {@link org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} instead.)
+ *
+ * This is a reactive equivalent of {@link DefaultOAuth2AuthorizedClientManager}.
+ *
+ * Authorized Client Persistence
+ *
+ * This client manager utilizes a {@link ServerOAuth2AuthorizedClientRepository}
+ * to persist {@link OAuth2AuthorizedClient}s.
+ *
+ * By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
+ * will be saved in the authorized client repository.
+ * This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationSuccessHandler}
+ * via {@link #setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)}.
+ *
+ * By default, when an authorization attempt fails due to an
+ * {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT} error,
+ * the previously saved {@link OAuth2AuthorizedClient}
+ * will be removed from the authorized client repository.
+ * (The {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT}
+ * error generally occurs when a refresh token that is no longer valid
+ * is used to retrieve a new access token.)
+ * This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationFailureHandler}
+ * via {@link #setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)}.
*
* @author Joe Grandja
+ * @author Phil Clay
* @since 5.2
* @see ReactiveOAuth2AuthorizedClientManager
* @see ReactiveOAuth2AuthorizedClientProvider
+ * @see ReactiveOAuth2AuthorizationSuccessHandler
+ * @see ReactiveOAuth2AuthorizationFailureHandler
*/
public final class DefaultReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager {
+
+ private static final Mono currentServerWebExchangeMono = Mono.subscriberContext()
+ .filter(c -> c.hasKey(ServerWebExchange.class))
+ .map(c -> c.get(ServerWebExchange.class));
+
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty();
private Function>> contextAttributesMapper = new DefaultContextAttributesMapper();
+ private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
+ private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler;
/**
* Constructs a {@code DefaultReactiveOAuth2AuthorizedClientManager} using the provided parameters.
@@ -62,6 +101,8 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
+ this.authorizationSuccessHandler = new SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler(authorizedClientRepository);
+ this.authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(authorizedClientRepository);
}
@Override
@@ -70,57 +111,76 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
Authentication principal = authorizeRequest.getPrincipal();
- ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
- return Mono.justOrEmpty(authorizeRequest.getAuthorizedClient())
- .switchIfEmpty(Mono.defer(() -> loadAuthorizedClient(clientRegistrationId, principal, serverWebExchange)))
- .flatMap(authorizedClient -> {
- // Re-authorize
- return authorizationContext(authorizeRequest, authorizedClient)
- .flatMap(this.authorizedClientProvider::authorize)
- .flatMap(reauthorizedClient -> saveAuthorizedClient(reauthorizedClient, principal, serverWebExchange))
- // Default to the existing authorizedClient if the client was not re-authorized
- .defaultIfEmpty(authorizeRequest.getAuthorizedClient() != null ?
- authorizeRequest.getAuthorizedClient() : authorizedClient);
- })
- .switchIfEmpty(Mono.deferWithContext(context ->
- // Authorize
- this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
- .switchIfEmpty(Mono.error(() -> new IllegalArgumentException(
- "Could not find ClientRegistration with id '" + clientRegistrationId + "'")))
- .flatMap(clientRegistration -> authorizationContext(authorizeRequest, clientRegistration))
- .flatMap(this.authorizedClientProvider::authorize)
- .flatMap(authorizedClient -> saveAuthorizedClient(authorizedClient, principal, serverWebExchange))
- .subscriberContext(context)
- )
- );
+ return Mono.justOrEmpty(authorizeRequest.getAttribute(ServerWebExchange.class.getName()))
+ .switchIfEmpty(currentServerWebExchangeMono)
+ .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("serverWebExchange cannot be null")))
+ .flatMap(serverWebExchange -> Mono.justOrEmpty(authorizeRequest.getAuthorizedClient())
+ .switchIfEmpty(Mono.defer(() -> loadAuthorizedClient(clientRegistrationId, principal, serverWebExchange)))
+ .flatMap(authorizedClient -> {
+ // Re-authorize
+ return authorizationContext(authorizeRequest, authorizedClient)
+ .flatMap(authorizationContext -> authorize(authorizationContext, principal, serverWebExchange))
+ // Default to the existing authorizedClient if the client was not re-authorized
+ .defaultIfEmpty(authorizeRequest.getAuthorizedClient() != null ?
+ authorizeRequest.getAuthorizedClient() : authorizedClient);
+ })
+ .switchIfEmpty(Mono.deferWithContext(context ->
+ // Authorize
+ this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
+ .switchIfEmpty(Mono.error(() -> new IllegalArgumentException(
+ "Could not find ClientRegistration with id '" + clientRegistrationId + "'")))
+ .flatMap(clientRegistration -> authorizationContext(authorizeRequest, clientRegistration))
+ .flatMap(authorizationContext -> authorize(authorizationContext, principal, serverWebExchange))
+ .subscriberContext(context)
+ )
+ ));
}
private Mono loadAuthorizedClient(String clientRegistrationId, Authentication principal, ServerWebExchange serverWebExchange) {
- return Mono.justOrEmpty(serverWebExchange)
- .switchIfEmpty(Mono.defer(() -> currentServerWebExchange()))
- .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("serverWebExchange cannot be null")))
- .flatMap(exchange -> this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, exchange));
+ return this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, serverWebExchange);
}
- private Mono saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal, ServerWebExchange serverWebExchange) {
- return Mono.justOrEmpty(serverWebExchange)
- .switchIfEmpty(Mono.defer(() -> currentServerWebExchange()))
- .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("serverWebExchange cannot be null")))
- .flatMap(exchange -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, exchange)
- .thenReturn(authorizedClient));
+ /**
+ * Performs authorization and then delegates to either the {@link #authorizationSuccessHandler}
+ * or {@link #authorizationFailureHandler}, depending on the authorization result.
+ *
+ * @param authorizationContext the context to authorize
+ * @param principal the principle to authorize
+ * @param serverWebExchange the currently active exchange
+ * @return a {@link Mono} that emits the authorized client after the authorization attempt succeeds
+ * and the {@link #authorizationSuccessHandler} has completed,
+ * or completes with an exception after the authorization attempt fails
+ * and the {@link #authorizationFailureHandler} has completed
+ */
+ private Mono authorize(
+ OAuth2AuthorizationContext authorizationContext,
+ Authentication principal,
+ ServerWebExchange serverWebExchange) {
+
+ return this.authorizedClientProvider.authorize(authorizationContext)
+ // Delegate to the authorizationSuccessHandler of the successful authorization
+ .flatMap(authorizedClient -> this.authorizationSuccessHandler.onAuthorizationSuccess(
+ authorizedClient,
+ principal,
+ createAttributes(serverWebExchange))
+ .thenReturn(authorizedClient))
+ // Delegate to the authorizationFailureHandler of the failed authorization
+ .onErrorResume(OAuth2AuthorizationException.class, authorizationException -> this.authorizationFailureHandler.onAuthorizationFailure(
+ authorizationException,
+ principal,
+ createAttributes(serverWebExchange))
+ .then(Mono.error(authorizationException)));
}
- private static Mono currentServerWebExchange() {
- return Mono.subscriberContext()
- .filter(c -> c.hasKey(ServerWebExchange.class))
- .map(c -> c.get(ServerWebExchange.class));
+ private Map createAttributes(ServerWebExchange serverWebExchange) {
+ return Collections.singletonMap(ServerWebExchange.class.getName(), serverWebExchange);
}
private Mono authorizationContext(OAuth2AuthorizeRequest authorizeRequest,
OAuth2AuthorizedClient authorizedClient) {
return Mono.just(authorizeRequest)
- .flatMap(this.contextAttributesMapper::apply)
+ .flatMap(this.contextAttributesMapper)
.map(attrs -> OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient)
.principal(authorizeRequest.getPrincipal())
.attributes(attributes -> {
@@ -134,7 +194,7 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
private Mono authorizationContext(OAuth2AuthorizeRequest authorizeRequest,
ClientRegistration clientRegistration) {
return Mono.just(authorizeRequest)
- .flatMap(this.contextAttributesMapper::apply)
+ .flatMap(this.contextAttributesMapper)
.map(attrs -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration)
.principal(authorizeRequest.getPrincipal())
.attributes(attributes -> {
@@ -167,6 +227,36 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
this.contextAttributesMapper = contextAttributesMapper;
}
+ /**
+ * Sets the handler that handles successful authorizations.
+ *
+ * A {@link SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler}
+ * is used by default.
+ *
+ * @param authorizationSuccessHandler the handler that handles successful authorizations.
+ * @see SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler
+ * @since 5.3
+ */
+ public void setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
+ Assert.notNull(authorizationSuccessHandler, "authorizationSuccessHandler cannot be null");
+ this.authorizationSuccessHandler = authorizationSuccessHandler;
+ }
+
+ /**
+ * Sets the handler that handles authorization failures.
+ *
+ * A {@link RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler}
+ * is used by default.
+ *
+ * @param authorizationFailureHandler the handler that handles authorization failures.
+ * @see RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
+ * @since 5.3
+ */
+ public void setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) {
+ Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
+ this.authorizationFailureHandler = authorizationFailureHandler;
+ }
+
/**
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
*/
@@ -176,7 +266,7 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
public Mono