16
16
17
17
package org .springframework .security .oauth2 .client .web .reactive .function .client ;
18
18
19
+ import org .springframework .http .HttpHeaders ;
19
20
import org .springframework .http .HttpStatus ;
20
- import org .springframework .lang .Nullable ;
21
21
import org .springframework .security .authentication .AnonymousAuthenticationToken ;
22
22
import org .springframework .security .core .Authentication ;
23
23
import org .springframework .security .core .authority .AuthorityUtils ;
34
34
import org .springframework .security .oauth2 .client .ReactiveOAuth2AuthorizedClientProvider ;
35
35
import org .springframework .security .oauth2 .client .ReactiveOAuth2AuthorizedClientProviderBuilder ;
36
36
import org .springframework .security .oauth2 .client .RefreshTokenReactiveOAuth2AuthorizedClientProvider ;
37
- import org .springframework .security .oauth2 .client .web .RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler ;
38
- import org .springframework .security .oauth2 .client .web .SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler ;
39
37
import org .springframework .security .oauth2 .client .authentication .OAuth2AuthenticationToken ;
40
38
import org .springframework .security .oauth2 .client .endpoint .OAuth2ClientCredentialsGrantRequest ;
41
39
import org .springframework .security .oauth2 .client .endpoint .ReactiveOAuth2AccessTokenResponseClient ;
42
40
import org .springframework .security .oauth2 .client .registration .ClientRegistration ;
43
41
import org .springframework .security .oauth2 .client .registration .ReactiveClientRegistrationRepository ;
44
42
import org .springframework .security .oauth2 .client .web .DefaultReactiveOAuth2AuthorizedClientManager ;
43
+ import org .springframework .security .oauth2 .client .web .RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler ;
44
+ import org .springframework .security .oauth2 .client .web .SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler ;
45
45
import org .springframework .security .oauth2 .client .web .server .ServerOAuth2AuthorizedClientRepository ;
46
46
import org .springframework .security .oauth2 .client .web .server .UnAuthenticatedServerOAuth2AuthorizedClientRepository ;
47
47
import org .springframework .security .oauth2 .core .OAuth2AuthorizationException ;
48
48
import org .springframework .security .oauth2 .core .OAuth2Error ;
49
49
import org .springframework .security .oauth2 .core .OAuth2ErrorCodes ;
50
+ import org .springframework .security .oauth2 .core .endpoint .OAuth2ParameterNames ;
50
51
import org .springframework .util .Assert ;
52
+ import org .springframework .util .StringUtils ;
51
53
import org .springframework .web .reactive .function .client .ClientRequest ;
52
54
import org .springframework .web .reactive .function .client .ClientResponse ;
53
55
import org .springframework .web .reactive .function .client .ExchangeFilterFunction ;
62
64
import java .util .Map ;
63
65
import java .util .Optional ;
64
66
import java .util .function .Consumer ;
67
+ import java .util .stream .Collectors ;
68
+ import java .util .stream .Stream ;
65
69
66
70
/**
67
71
* Provides an easy mechanism for using an {@link OAuth2AuthorizedClient} to make OAuth2 requests by including the
@@ -614,32 +618,84 @@ private AuthorizationFailureForwarder(ReactiveOAuth2AuthorizationFailureHandler
614
618
}
615
619
616
620
@ Override
617
- public Mono <ClientResponse > handleResponse (
618
- ClientRequest request ,
619
- Mono <ClientResponse > responseMono ) {
620
-
621
+ public Mono <ClientResponse > handleResponse (ClientRequest request , Mono <ClientResponse > responseMono ) {
621
622
return responseMono
622
- .flatMap (response -> handleHttpStatus (request , response . rawStatusCode (), null )
623
+ .flatMap (response -> handleResponse (request , response )
623
624
.thenReturn (response ))
624
- .onErrorResume (WebClientResponseException .class , e -> handleHttpStatus (request , e . getRawStatusCode () , e )
625
+ .onErrorResume (WebClientResponseException .class , e -> handleWebClientResponseException (request , e )
625
626
.then (Mono .error (e )))
626
627
.onErrorResume (OAuth2AuthorizationException .class , e -> handleAuthorizationException (request , e )
627
628
.then (Mono .error (e )));
628
629
}
629
630
631
+ private Mono <Void > handleResponse (ClientRequest request , ClientResponse response ) {
632
+ return Mono .justOrEmpty (resolveErrorIfPossible (response ))
633
+ .flatMap (oauth2Error -> {
634
+ Mono <Optional <ServerWebExchange >> serverWebExchange = effectiveServerWebExchange (request );
635
+
636
+ Mono <String > clientRegistrationId = effectiveClientRegistrationId (request );
637
+
638
+ return Mono .zip (currentAuthenticationMono , serverWebExchange , clientRegistrationId )
639
+ .flatMap (tuple3 -> handleAuthorizationFailure (
640
+ tuple3 .getT1 (), // Authentication principal
641
+ tuple3 .getT2 ().orElse (null ), // ServerWebExchange exchange
642
+ new ClientAuthorizationException (
643
+ oauth2Error ,
644
+ tuple3 .getT3 ()))); // String clientRegistrationId
645
+ });
646
+ }
647
+
648
+ private OAuth2Error resolveErrorIfPossible (ClientResponse response ) {
649
+ // Try to resolve from 'WWW-Authenticate' header
650
+ if (!response .headers ().header (HttpHeaders .WWW_AUTHENTICATE ).isEmpty ()) {
651
+ String wwwAuthenticateHeader = response .headers ().header (HttpHeaders .WWW_AUTHENTICATE ).get (0 );
652
+ Map <String , String > authParameters = parseAuthParameters (wwwAuthenticateHeader );
653
+ if (authParameters .containsKey (OAuth2ParameterNames .ERROR )) {
654
+ return new OAuth2Error (
655
+ authParameters .get (OAuth2ParameterNames .ERROR ),
656
+ authParameters .get (OAuth2ParameterNames .ERROR_DESCRIPTION ),
657
+ authParameters .get (OAuth2ParameterNames .ERROR_URI ));
658
+ }
659
+ }
660
+ return resolveErrorIfPossible (response .rawStatusCode ());
661
+ }
662
+
663
+ private OAuth2Error resolveErrorIfPossible (int statusCode ) {
664
+ if (this .httpStatusToOAuth2ErrorCodeMap .containsKey (statusCode )) {
665
+ return new OAuth2Error (
666
+ this .httpStatusToOAuth2ErrorCodeMap .get (statusCode ),
667
+ null ,
668
+ "https://tools.ietf.org/html/rfc6750#section-3.1" );
669
+ }
670
+ return null ;
671
+ }
672
+
673
+ private Map <String , String > parseAuthParameters (String wwwAuthenticateHeader ) {
674
+ return Stream .of (wwwAuthenticateHeader )
675
+ .filter (header -> !StringUtils .isEmpty (header ))
676
+ .filter (header -> header .toLowerCase ().startsWith ("bearer" ))
677
+ .map (header -> header .substring ("bearer" .length ()))
678
+ .map (header -> header .split ("," ))
679
+ .flatMap (Stream ::of )
680
+ .map (parameter -> parameter .split ("=" ))
681
+ .filter (parameter -> parameter .length > 1 )
682
+ .collect (Collectors .toMap (
683
+ parameters -> parameters [0 ].trim (),
684
+ parameters -> parameters [1 ].trim ().replace ("\" " , "" )));
685
+ }
686
+
630
687
/**
631
688
* Handles the given http status code returned from a resource server
632
689
* by notifying the authorization failure handler if the http status
633
690
* code is in the {@link #httpStatusToOAuth2ErrorCodeMap}.
634
691
*
635
692
* @param request the request being processed
636
- * @param httpStatusCode the http status returned by the resource server
637
- * @param exception The root cause exception for the failure (nullable)
693
+ * @param exception The root cause exception for the failure
638
694
* @return a {@link Mono} that completes empty after the authorization failure handler completes.
639
695
*/
640
- private Mono <Void > handleHttpStatus (ClientRequest request , int httpStatusCode , @ Nullable Exception exception ) {
641
- return Mono .justOrEmpty (this . httpStatusToOAuth2ErrorCodeMap . get ( httpStatusCode ))
642
- .flatMap (oauth2ErrorCode -> {
696
+ private Mono <Void > handleWebClientResponseException (ClientRequest request , WebClientResponseException exception ) {
697
+ return Mono .justOrEmpty (resolveErrorIfPossible ( exception . getRawStatusCode () ))
698
+ .flatMap (oauth2Error -> {
643
699
Mono <Optional <ServerWebExchange >> serverWebExchange = effectiveServerWebExchange (request );
644
700
645
701
Mono <String > clientRegistrationId = effectiveClientRegistrationId (request );
@@ -648,9 +704,9 @@ private Mono<Void> handleHttpStatus(ClientRequest request, int httpStatusCode, @
648
704
.flatMap (tuple3 -> handleAuthorizationFailure (
649
705
tuple3 .getT1 (), // Authentication principal
650
706
tuple3 .getT2 ().orElse (null ), // ServerWebExchange exchange
651
- createAuthorizationException (
707
+ new ClientAuthorizationException (
708
+ oauth2Error ,
652
709
tuple3 .getT3 (), // String clientRegistrationId
653
- oauth2ErrorCode ,
654
710
exception )));
655
711
});
656
712
}
@@ -673,28 +729,6 @@ private Mono<Void> handleAuthorizationException(ClientRequest request, OAuth2Aut
673
729
exception ));
674
730
}
675
731
676
- /**
677
- * Creates an authorization exception using the given parameters.
678
- *
679
- * @param clientRegistrationId the client registration id of the client that failed authentication/authorization.
680
- * @param oauth2ErrorCode the OAuth 2.0 error code to use in the authorization failure event
681
- * @param exception The root cause exception for the failure (nullable)
682
- * @return an authorization exception using the given parameters.
683
- */
684
- private ClientAuthorizationException createAuthorizationException (
685
- String clientRegistrationId ,
686
- String oauth2ErrorCode ,
687
- @ Nullable Exception exception ) {
688
- return new ClientAuthorizationException (
689
- new OAuth2Error (
690
- oauth2ErrorCode ,
691
- null ,
692
- "https://tools.ietf.org/html/rfc6750#section-3.1" ),
693
- clientRegistrationId ,
694
- exception );
695
- }
696
-
697
-
698
732
/**
699
733
* Delegates to the authorization failure handler of the failed authorization.
700
734
*
0 commit comments