1
1
/*
2
- * Copyright 2002-2019 the original author or authors.
2
+ * Copyright 2002-2020 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
18
18
import org .springframework .security .core .Authentication ;
19
19
import org .springframework .security .oauth2 .client .registration .ReactiveClientRegistrationRepository ;
20
+ import org .springframework .security .oauth2 .client .web .DefaultReactiveOAuth2AuthorizedClientManager ;
21
+ import org .springframework .security .oauth2 .client .web .RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler ;
22
+ import org .springframework .security .oauth2 .client .web .SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler ;
23
+ import org .springframework .security .oauth2 .core .OAuth2AuthorizationException ;
20
24
import org .springframework .util .Assert ;
25
+ import org .springframework .web .server .ServerWebExchange ;
21
26
import reactor .core .publisher .Mono ;
22
27
23
28
import java .util .Collections ;
24
29
import java .util .Map ;
25
30
import java .util .function .Function ;
26
31
27
32
/**
28
- * An implementation of an {@link ReactiveOAuth2AuthorizedClientManager}
29
- * that is capable of operating outside of a {@code ServerHttpRequest} context ,
33
+ * An implementation of a {@link ReactiveOAuth2AuthorizedClientManager}
34
+ * that is capable of operating outside of the context of a {@link ServerWebExchange} ,
30
35
* e.g. in a scheduled/background thread and/or in the service-tier.
31
36
*
32
- * <p>This is a reactive equivalent of {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}</p>
37
+ * <p>(When operating <em>within</em> the context of a {@link ServerWebExchange},
38
+ * use {@link DefaultReactiveOAuth2AuthorizedClientManager} instead.)</p>
39
+ *
40
+ * <p>This is a reactive equivalent of {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}.</p>
41
+ *
42
+ * <h2>Authorized Client Persistence</h2>
43
+ *
44
+ * <p>This client manager utilizes a {@link ReactiveOAuth2AuthorizedClientService}
45
+ * to persist {@link OAuth2AuthorizedClient}s.</p>
46
+ *
47
+ * <p>By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
48
+ * will be saved in the authorized client service.
49
+ * This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationSuccessHandler}
50
+ * via {@link #setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)}.</p>
51
+ *
52
+ * <p>By default, when an authorization attempt fails due to an
53
+ * {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT} error,
54
+ * the previously saved {@link OAuth2AuthorizedClient}
55
+ * will be removed from the authorized client service.
56
+ * (The {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT}
57
+ * error generally occurs when a refresh token that is no longer valid
58
+ * is used to retrieve a new access token.)
59
+ * This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationFailureHandler}
60
+ * via {@link #setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)}.</p>
33
61
*
34
62
* @author Ankur Pathak
35
63
* @author Phil Clay
36
64
* @see ReactiveOAuth2AuthorizedClientManager
37
65
* @see ReactiveOAuth2AuthorizedClientProvider
38
66
* @see ReactiveOAuth2AuthorizedClientService
67
+ * @see ReactiveOAuth2AuthorizationSuccessHandler
68
+ * @see ReactiveOAuth2AuthorizationFailureHandler
39
69
* @since 5.2.2
40
70
*/
41
71
public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
@@ -45,6 +75,8 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
45
75
private final ReactiveOAuth2AuthorizedClientService authorizedClientService ;
46
76
private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono .empty ();
47
77
private Function <OAuth2AuthorizeRequest , Mono <Map <String , Object >>> contextAttributesMapper = new DefaultContextAttributesMapper ();
78
+ private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler ;
79
+ private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler ;
48
80
49
81
/**
50
82
* Constructs an {@code AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters.
@@ -59,14 +91,16 @@ public AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
59
91
Assert .notNull (authorizedClientService , "authorizedClientService cannot be null" );
60
92
this .clientRegistrationRepository = clientRegistrationRepository ;
61
93
this .authorizedClientService = authorizedClientService ;
94
+ this .authorizationSuccessHandler = new SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler (authorizedClientService );
95
+ this .authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler (authorizedClientService );
62
96
}
63
97
64
98
@ Override
65
99
public Mono <OAuth2AuthorizedClient > authorize (OAuth2AuthorizeRequest authorizeRequest ) {
66
100
Assert .notNull (authorizeRequest , "authorizeRequest cannot be null" );
67
101
68
102
return createAuthorizationContext (authorizeRequest )
69
- .flatMap (this :: authorizeAndSave );
103
+ .flatMap (authorizationContext -> authorize ( authorizationContext , authorizeRequest . getPrincipal ()) );
70
104
}
71
105
72
106
private Mono <OAuth2AuthorizationContext > createAuthorizationContext (OAuth2AuthorizeRequest authorizeRequest ) {
@@ -90,13 +124,34 @@ private Mono<OAuth2AuthorizationContext> createAuthorizationContext(OAuth2Author
90
124
}));
91
125
}
92
126
93
- private Mono <OAuth2AuthorizedClient > authorizeAndSave (OAuth2AuthorizationContext authorizationContext ) {
127
+ /**
128
+ * Performs authorization and then delegates to either the {@link #authorizationSuccessHandler}
129
+ * or {@link #authorizationFailureHandler}, depending on the authorization result.
130
+ *
131
+ * @param authorizationContext the context to authorize
132
+ * @param principal the principle to authorize
133
+ * @return a {@link Mono} that emits the authorized client after the authorization attempt succeeds
134
+ * and the {@link #authorizationSuccessHandler} has completed,
135
+ * or completes with an exception after the authorization attempt fails
136
+ * and the {@link #authorizationFailureHandler} has completed
137
+ */
138
+ private Mono <OAuth2AuthorizedClient > authorize (
139
+ OAuth2AuthorizationContext authorizationContext ,
140
+ Authentication principal ) {
94
141
return this .authorizedClientProvider .authorize (authorizationContext )
95
- .flatMap (authorizedClient -> this .authorizedClientService .saveAuthorizedClient (
142
+ // Delegate to the authorizationSuccessHandler of the successful authorization
143
+ .flatMap (authorizedClient -> this .authorizationSuccessHandler .onAuthorizationSuccess (
96
144
authorizedClient ,
97
- authorizationContext .getPrincipal ())
145
+ principal ,
146
+ Collections .emptyMap ())
98
147
.thenReturn (authorizedClient ))
99
- .switchIfEmpty (Mono .defer (()-> Mono .justOrEmpty (authorizationContext .getAuthorizedClient ())));
148
+ // Delegate to the authorizationFailureHandler of the failed authorization
149
+ .onErrorResume (OAuth2AuthorizationException .class , authorizationException -> this .authorizationFailureHandler .onAuthorizationFailure (
150
+ authorizationException ,
151
+ principal ,
152
+ Collections .emptyMap ())
153
+ .then (Mono .error (authorizationException )))
154
+ .switchIfEmpty (Mono .defer (() -> Mono .justOrEmpty (authorizationContext .getAuthorizedClient ())));
100
155
}
101
156
102
157
/**
@@ -121,6 +176,36 @@ public void setContextAttributesMapper(Function<OAuth2AuthorizeRequest, Mono<Map
121
176
this .contextAttributesMapper = contextAttributesMapper ;
122
177
}
123
178
179
+ /**
180
+ * Sets the handler that handles successful authorizations.
181
+ *
182
+ * <p>A {@link SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler}
183
+ * is used by default.</p>
184
+ *
185
+ * @param authorizationSuccessHandler the handler that handles successful authorizations.
186
+ * @see SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler
187
+ * @since 5.3
188
+ */
189
+ public void setAuthorizationSuccessHandler (ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler ) {
190
+ Assert .notNull (authorizationSuccessHandler , "authorizationSuccessHandler cannot be null" );
191
+ this .authorizationSuccessHandler = authorizationSuccessHandler ;
192
+ }
193
+
194
+ /**
195
+ * Sets the handler that handles authorization failures.
196
+ *
197
+ * <p>A {@link RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler}
198
+ * is used by default.</p>
199
+ *
200
+ * @param authorizationFailureHandler the handler that handles authorization failures.
201
+ * @see RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
202
+ * @since 5.3
203
+ */
204
+ public void setAuthorizationFailureHandler (ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler ) {
205
+ Assert .notNull (authorizationFailureHandler , "authorizationFailureHandler cannot be null" );
206
+ this .authorizationFailureHandler = authorizationFailureHandler ;
207
+ }
208
+
124
209
/**
125
210
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
126
211
*/
0 commit comments