19
19
import java .io .IOException ;
20
20
import java .util .HashMap ;
21
21
import java .util .Map ;
22
- import java .util .function .Consumer ;
23
22
24
23
import jakarta .servlet .http .HttpServletRequest ;
25
24
import jakarta .servlet .http .HttpServletResponse ;
31
30
import org .springframework .http .client .ClientHttpRequestExecution ;
32
31
import org .springframework .http .client .ClientHttpRequestInterceptor ;
33
32
import org .springframework .http .client .ClientHttpResponse ;
34
- import org .springframework .security . access . AccessDeniedException ;
33
+ import org .springframework .lang . Nullable ;
35
34
import org .springframework .security .authentication .AnonymousAuthenticationToken ;
36
35
import org .springframework .security .core .Authentication ;
37
36
import org .springframework .security .core .authority .AuthorityUtils ;
45
44
import org .springframework .security .oauth2 .client .OAuth2AuthorizedClientProvider ;
46
45
import org .springframework .security .oauth2 .client .OAuth2AuthorizedClientService ;
47
46
import org .springframework .security .oauth2 .client .RemoveAuthorizedClientOAuth2AuthorizationFailureHandler ;
48
- import org .springframework .security .oauth2 .client .authentication .OAuth2AuthenticationToken ;
49
- import org .springframework .security .oauth2 .client .registration .ClientRegistration ;
50
47
import org .springframework .security .oauth2 .client .web .OAuth2AuthorizedClientRepository ;
51
48
import org .springframework .security .oauth2 .core .OAuth2AuthorizationException ;
52
49
import org .springframework .security .oauth2 .core .OAuth2Error ;
53
50
import org .springframework .security .oauth2 .core .OAuth2ErrorCodes ;
54
51
import org .springframework .security .oauth2 .core .endpoint .OAuth2ParameterNames ;
55
52
import org .springframework .util .Assert ;
56
53
import org .springframework .util .StringUtils ;
57
- import org .springframework .web .client .RestClient ;
58
54
import org .springframework .web .client .RestClientResponseException ;
59
55
import org .springframework .web .context .request .RequestContextHolder ;
60
56
import org .springframework .web .context .request .ServletRequestAttributes ;
@@ -114,14 +110,9 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
114
110
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken ("anonymous" ,
115
111
"anonymousUser" , AuthorityUtils .createAuthorityList ("ROLE_ANONYMOUS" ));
116
112
117
- private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2ClientHttpRequestInterceptor .class .getName ()
118
- .concat (".clientRegistrationId" );
119
-
120
113
private final OAuth2AuthorizedClientManager authorizedClientManager ;
121
114
122
- private String defaultClientRegistrationId ;
123
-
124
- private boolean useAuthenticatedClientRegistrationId ;
115
+ private final ClientRegistrationIdResolver clientRegistrationIdResolver ;
125
116
126
117
// @formatter:off
127
118
private OAuth2AuthorizationFailureHandler authorizationFailureHandler =
@@ -138,41 +129,23 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
138
129
* manages the authorized client(s)
139
130
*/
140
131
public OAuth2ClientHttpRequestInterceptor (OAuth2AuthorizedClientManager authorizedClientManager ) {
141
- Assert .notNull (authorizedClientManager , "authorizedClientManager cannot be null" );
142
- this .authorizedClientManager = authorizedClientManager ;
143
- }
144
-
145
- /**
146
- * Sets the default {@code clientRegistrationId} to be used for resolving an
147
- * {@link OAuth2AuthorizedClient}.
148
- *
149
- * <p>
150
- * By default, the {@code clientRegistrationId} is obtained from the current
151
- * {@link Authentication principal}. Using this setter overrides the default, but can
152
- * be overridden by providing an
153
- * {@link RestClient.RequestHeadersSpec#attributes(Consumer) attribute} via
154
- * {@link #clientRegistrationId(String)}.
155
- * @param clientRegistrationId the default {@code clientRegistrationId}
156
- */
157
- public void setDefaultClientRegistrationId (String clientRegistrationId ) {
158
- Assert .hasText (clientRegistrationId , "clientRegistrationId cannot be empty" );
159
- this .defaultClientRegistrationId = clientRegistrationId ;
132
+ this (authorizedClientManager , new RequestAttributeClientRegistrationIdResolver ());
160
133
}
161
134
162
135
/**
163
- * Enables or disables discovering the {@code clientRegistrationId} from the current
164
- * {@link Authentication principal}. It is recommended to be cautious with this
165
- * feature since all HTTP requests will receive the access token if it can be resolved
166
- * from the current Authentication.
167
- *
168
- * <p>
169
- * This feature requires the user to be logged in via OAuth2 or OpenID Connect Login.
170
- * @param useAuthenticatedClientRegistrationId true if the
171
- * {@code clientRegistrationId} should be discovered from the current
172
- * {@link Authentication principal}. The default is false.
136
+ * Constructs a {@code OAuth2ClientHttpRequestInterceptor} using the provided
137
+ * parameters.
138
+ * @param authorizedClientManager the {@link OAuth2AuthorizedClientManager} which
139
+ * manages the authorized client(s)
140
+ * @param clientRegistrationIdResolver the strategy for resolving a
141
+ * {@code clientRegistrationId} from the intercepted request
173
142
*/
174
- public void setUseAuthenticatedClientRegistrationId (boolean useAuthenticatedClientRegistrationId ) {
175
- this .useAuthenticatedClientRegistrationId = useAuthenticatedClientRegistrationId ;
143
+ public OAuth2ClientHttpRequestInterceptor (OAuth2AuthorizedClientManager authorizedClientManager ,
144
+ ClientRegistrationIdResolver clientRegistrationIdResolver ) {
145
+ Assert .notNull (authorizedClientManager , "authorizedClientManager cannot be null" );
146
+ Assert .notNull (clientRegistrationIdResolver , "clientRegistrationIdResolver cannot be null" );
147
+ this .authorizedClientManager = authorizedClientManager ;
148
+ this .clientRegistrationIdResolver = clientRegistrationIdResolver ;
176
149
}
177
150
178
151
/**
@@ -268,19 +241,6 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur
268
241
this .securityContextHolderStrategy = securityContextHolderStrategy ;
269
242
}
270
243
271
- /**
272
- * Modifies the {@link RestClient.RequestHeadersSpec#attributes(Consumer) attributes}
273
- * to include the {@link ClientRegistration#getRegistrationId() clientRegistrationId}
274
- * to be used to look up the {@link OAuth2AuthorizedClient}.
275
- * @param clientRegistrationId the {@link ClientRegistration#getRegistrationId()
276
- * clientRegistrationId} to be used to look up the {@link OAuth2AuthorizedClient}
277
- * @return the {@link Consumer} to populate the attributes
278
- */
279
- public static Consumer <Map <String , Object >> clientRegistrationId (String clientRegistrationId ) {
280
- Assert .hasText (clientRegistrationId , "clientRegistrationId cannot be empty" );
281
- return (attributes ) -> attributes .put (CLIENT_REGISTRATION_ID_ATTR_NAME , clientRegistrationId );
282
- }
283
-
284
244
@ Override
285
245
public ClientHttpResponse intercept (HttpRequest request , byte [] body , ClientHttpRequestExecution execution )
286
246
throws IOException {
@@ -306,7 +266,11 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp
306
266
}
307
267
308
268
private void authorizeClient (HttpRequest request , Authentication principal ) {
309
- String clientRegistrationId = clientRegistrationId (request , principal );
269
+ String clientRegistrationId = this .clientRegistrationIdResolver .resolve (request );
270
+ if (clientRegistrationId == null ) {
271
+ return ;
272
+ }
273
+
310
274
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId (clientRegistrationId )
311
275
.principal (principal )
312
276
.build ();
@@ -323,7 +287,11 @@ private void handleAuthorizationFailure(HttpRequest request, Authentication prin
323
287
return ;
324
288
}
325
289
326
- String clientRegistrationId = clientRegistrationId (request , principal );
290
+ String clientRegistrationId = this .clientRegistrationIdResolver .resolve (request );
291
+ if (clientRegistrationId == null ) {
292
+ return ;
293
+ }
294
+
327
295
ClientAuthorizationException authorizationException = new ClientAuthorizationException (error ,
328
296
clientRegistrationId );
329
297
handleAuthorizationFailure (authorizationException , principal );
@@ -368,35 +336,6 @@ private static Map<String, String> parseWwwAuthenticateHeader(String wwwAuthenti
368
336
return parameters ;
369
337
}
370
338
371
- private String clientRegistrationId (HttpRequest request , Authentication principal ) {
372
- String clientRegistrationId = (String ) request .getAttributes ().get (CLIENT_REGISTRATION_ID_ATTR_NAME );
373
- if (clientRegistrationId == null ) {
374
- clientRegistrationId = this .defaultClientRegistrationId ;
375
- }
376
- if (clientRegistrationId == null && this .useAuthenticatedClientRegistrationId ) {
377
- if (principal instanceof OAuth2AuthenticationToken ) {
378
- clientRegistrationId = ((OAuth2AuthenticationToken ) principal ).getAuthorizedClientRegistrationId ();
379
- }
380
- else if (principal instanceof AnonymousAuthenticationToken ) {
381
- throw new AccessDeniedException ("Authentication is required" );
382
- }
383
- else {
384
- throw new IllegalStateException ("Unable to discover clientRegistrationId."
385
- + " When useAuthenticatedClientRegistrationId=true, the current principal must be of type OAuth2AuthenticationToken"
386
- + " (OAuth2 or OpenID Connect Login is required in order to use this feature)." );
387
- }
388
- }
389
- if (clientRegistrationId == null ) {
390
- throw new IllegalStateException ("No clientRegistrationId was provided."
391
- + " Please consider using OAuth2ClientHttpRequestInterceptor.clientRegistrationId(String) to provide one per request via RestClient.RequestHeadersSpec#attributes(Consumer),"
392
- + " OAuth2ClientHttpRequestInterceptor#setDefaultClientRegistrationId(String) to provide a default for all requests,"
393
- + " or OAuth2ClientHttpRequestInterceptor#setUseAuthenticatedClientRegistrationId(true) to configure resolving one from the current principal"
394
- + " (OAuth2 or OpenID Connect Login is required in order to use this feature)." );
395
- }
396
-
397
- return clientRegistrationId ;
398
- }
399
-
400
339
private void handleAuthorizationFailure (OAuth2AuthorizationException authorizationException ,
401
340
Authentication principal ) {
402
341
ServletRequestAttributes requestAttributes = (ServletRequestAttributes ) RequestContextHolder
@@ -412,4 +351,24 @@ private void handleAuthorizationFailure(OAuth2AuthorizationException authorizati
412
351
this .authorizationFailureHandler .onAuthorizationFailure (authorizationException , principal , attributes );
413
352
}
414
353
354
+ /**
355
+ * A strategy for resolving a {@code clientRegistrationId} from an intercepted
356
+ * request.
357
+ */
358
+ @ FunctionalInterface
359
+ public interface ClientRegistrationIdResolver {
360
+
361
+ /**
362
+ * Resolve the {@code clientRegistrationId} from the current request, which is
363
+ * used to obtain an {@link OAuth2AuthorizedClient}.
364
+ * @param request the intercepted request, containing HTTP method, URI, headers,
365
+ * and request attributes
366
+ * @return the {@code clientRegistrationId} to be used for resolving an
367
+ * {@link OAuth2AuthorizedClient}.
368
+ */
369
+ @ Nullable
370
+ String resolve (HttpRequest request );
371
+
372
+ }
373
+
415
374
}
0 commit comments