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