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 16400dc922c..fdbe3f828ab 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,10 @@
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.core.OAuth2AuthorizationException;
import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
@@ -26,10 +29,33 @@
/**
* An implementation of an {@link ReactiveOAuth2AuthorizedClientManager}
- * that is capable of operating outside of a {@code ServerHttpRequest} context,
+ * 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 #setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)}.
+ *
+ * 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
@@ -45,6 +71,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 +87,8 @@ public 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 +96,7 @@ public Mono authorize(OAuth2AuthorizeRequest authorizeRe
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,12 +120,33 @@ private Mono createAuthorizationContext(OAuth2Author
}));
}
- private Mono authorizeAndSave(OAuth2AuthorizationContext authorizationContext) {
+ /**
+ * Performs authorization, and notifies 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(
+ // Notify the authorizationSuccessHandler of the successful authorization
+ .flatMap(authorizedClient -> authorizationSuccessHandler.onAuthorizationSuccess(
authorizedClient,
- authorizationContext.getPrincipal())
+ principal,
+ Collections.emptyMap())
.thenReturn(authorizedClient))
+ // Notify the authorizationFailureHandler of the failed authorization
+ .onErrorResume(OAuth2AuthorizationException.class, authorizationException -> authorizationFailureHandler.onAuthorizationFailure(
+ authorizationException,
+ principal,
+ Collections.emptyMap())
+ .then(Mono.error(authorizationException)))
.switchIfEmpty(Mono.defer(()-> Mono.justOrEmpty(authorizationContext.getAuthorizedClient())));
}
@@ -121,6 +172,36 @@ public void setContextAttributesMapper(FunctionA {@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}.
*/
@@ -134,4 +215,5 @@ public Mono