|
|
|
@ -16,8 +16,8 @@ |
|
|
|
|
|
|
|
|
|
|
|
package org.springframework.security.oauth2.client.web.reactive.function.client; |
|
|
|
package org.springframework.security.oauth2.client.web.reactive.function.client; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.http.HttpHeaders; |
|
|
|
import org.springframework.http.HttpStatus; |
|
|
|
import org.springframework.http.HttpStatus; |
|
|
|
import org.springframework.lang.Nullable; |
|
|
|
|
|
|
|
import org.springframework.security.authentication.AnonymousAuthenticationToken; |
|
|
|
import org.springframework.security.authentication.AnonymousAuthenticationToken; |
|
|
|
import org.springframework.security.core.Authentication; |
|
|
|
import org.springframework.security.core.Authentication; |
|
|
|
import org.springframework.security.core.authority.AuthorityUtils; |
|
|
|
import org.springframework.security.core.authority.AuthorityUtils; |
|
|
|
@ -34,20 +34,22 @@ import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClient |
|
|
|
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; |
|
|
|
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; |
|
|
|
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; |
|
|
|
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; |
|
|
|
import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider; |
|
|
|
import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider; |
|
|
|
import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.client.web.SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
|
|
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; |
|
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
|
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
|
|
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; |
|
|
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; |
|
|
|
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; |
|
|
|
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.client.web.SaveAuthorizedClientReactiveOAuth2AuthorizationSuccessHandler; |
|
|
|
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; |
|
|
|
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; |
|
|
|
import org.springframework.security.oauth2.client.web.server.UnAuthenticatedServerOAuth2AuthorizedClientRepository; |
|
|
|
import org.springframework.security.oauth2.client.web.server.UnAuthenticatedServerOAuth2AuthorizedClientRepository; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2Error; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2Error; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
|
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
import org.springframework.web.reactive.function.client.ClientRequest; |
|
|
|
import org.springframework.web.reactive.function.client.ClientRequest; |
|
|
|
import org.springframework.web.reactive.function.client.ClientResponse; |
|
|
|
import org.springframework.web.reactive.function.client.ClientResponse; |
|
|
|
import org.springframework.web.reactive.function.client.ExchangeFilterFunction; |
|
|
|
import org.springframework.web.reactive.function.client.ExchangeFilterFunction; |
|
|
|
@ -62,6 +64,8 @@ import java.util.HashMap; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Optional; |
|
|
|
import java.util.Optional; |
|
|
|
import java.util.function.Consumer; |
|
|
|
import java.util.function.Consumer; |
|
|
|
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
import java.util.stream.Stream; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Provides an easy mechanism for using an {@link OAuth2AuthorizedClient} to make OAuth2 requests by including the |
|
|
|
* Provides an easy mechanism for using an {@link OAuth2AuthorizedClient} to make OAuth2 requests by including the |
|
|
|
@ -614,32 +618,84 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Mono<ClientResponse> handleResponse( |
|
|
|
public Mono<ClientResponse> handleResponse(ClientRequest request, Mono<ClientResponse> responseMono) { |
|
|
|
ClientRequest request, |
|
|
|
|
|
|
|
Mono<ClientResponse> responseMono) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return responseMono |
|
|
|
return responseMono |
|
|
|
.flatMap(response -> handleHttpStatus(request, response.rawStatusCode(), null) |
|
|
|
.flatMap(response -> handleResponse(request, response) |
|
|
|
.thenReturn(response)) |
|
|
|
.thenReturn(response)) |
|
|
|
.onErrorResume(WebClientResponseException.class, e -> handleHttpStatus(request, e.getRawStatusCode(), e) |
|
|
|
.onErrorResume(WebClientResponseException.class, e -> handleWebClientResponseException(request, e) |
|
|
|
.then(Mono.error(e))) |
|
|
|
.then(Mono.error(e))) |
|
|
|
.onErrorResume(OAuth2AuthorizationException.class, e -> handleAuthorizationException(request, e) |
|
|
|
.onErrorResume(OAuth2AuthorizationException.class, e -> handleAuthorizationException(request, e) |
|
|
|
.then(Mono.error(e))); |
|
|
|
.then(Mono.error(e))); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Mono<Void> handleResponse(ClientRequest request, ClientResponse response) { |
|
|
|
|
|
|
|
return Mono.justOrEmpty(resolveErrorIfPossible(response)) |
|
|
|
|
|
|
|
.flatMap(oauth2Error -> { |
|
|
|
|
|
|
|
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mono<String> clientRegistrationId = effectiveClientRegistrationId(request); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Mono.zip(currentAuthenticationMono, serverWebExchange, clientRegistrationId) |
|
|
|
|
|
|
|
.flatMap(tuple3 -> handleAuthorizationFailure( |
|
|
|
|
|
|
|
tuple3.getT1(), // Authentication principal
|
|
|
|
|
|
|
|
tuple3.getT2().orElse(null), // ServerWebExchange exchange
|
|
|
|
|
|
|
|
new ClientAuthorizationException( |
|
|
|
|
|
|
|
oauth2Error, |
|
|
|
|
|
|
|
tuple3.getT3()))); // String clientRegistrationId
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private OAuth2Error resolveErrorIfPossible(ClientResponse response) { |
|
|
|
|
|
|
|
// Try to resolve from 'WWW-Authenticate' header
|
|
|
|
|
|
|
|
if (!response.headers().header(HttpHeaders.WWW_AUTHENTICATE).isEmpty()) { |
|
|
|
|
|
|
|
String wwwAuthenticateHeader = response.headers().header(HttpHeaders.WWW_AUTHENTICATE).get(0); |
|
|
|
|
|
|
|
Map<String, String> authParameters = parseAuthParameters(wwwAuthenticateHeader); |
|
|
|
|
|
|
|
if (authParameters.containsKey(OAuth2ParameterNames.ERROR)) { |
|
|
|
|
|
|
|
return new OAuth2Error( |
|
|
|
|
|
|
|
authParameters.get(OAuth2ParameterNames.ERROR), |
|
|
|
|
|
|
|
authParameters.get(OAuth2ParameterNames.ERROR_DESCRIPTION), |
|
|
|
|
|
|
|
authParameters.get(OAuth2ParameterNames.ERROR_URI)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return resolveErrorIfPossible(response.rawStatusCode()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private OAuth2Error resolveErrorIfPossible(int statusCode) { |
|
|
|
|
|
|
|
if (this.httpStatusToOAuth2ErrorCodeMap.containsKey(statusCode)) { |
|
|
|
|
|
|
|
return new OAuth2Error( |
|
|
|
|
|
|
|
this.httpStatusToOAuth2ErrorCodeMap.get(statusCode), |
|
|
|
|
|
|
|
null, |
|
|
|
|
|
|
|
"https://tools.ietf.org/html/rfc6750#section-3.1"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Map<String, String> parseAuthParameters(String wwwAuthenticateHeader) { |
|
|
|
|
|
|
|
return Stream.of(wwwAuthenticateHeader) |
|
|
|
|
|
|
|
.filter(header -> !StringUtils.isEmpty(header)) |
|
|
|
|
|
|
|
.filter(header -> header.toLowerCase().startsWith("bearer")) |
|
|
|
|
|
|
|
.map(header -> header.substring("bearer".length())) |
|
|
|
|
|
|
|
.map(header -> header.split(",")) |
|
|
|
|
|
|
|
.flatMap(Stream::of) |
|
|
|
|
|
|
|
.map(parameter -> parameter.split("=")) |
|
|
|
|
|
|
|
.filter(parameter -> parameter.length > 1) |
|
|
|
|
|
|
|
.collect(Collectors.toMap( |
|
|
|
|
|
|
|
parameters -> parameters[0].trim(), |
|
|
|
|
|
|
|
parameters -> parameters[1].trim().replace("\"", ""))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Handles the given http status code returned from a resource server |
|
|
|
* Handles the given http status code returned from a resource server |
|
|
|
* by notifying the authorization failure handler if the http status |
|
|
|
* by notifying the authorization failure handler if the http status |
|
|
|
* code is in the {@link #httpStatusToOAuth2ErrorCodeMap}. |
|
|
|
* code is in the {@link #httpStatusToOAuth2ErrorCodeMap}. |
|
|
|
* |
|
|
|
* |
|
|
|
* @param request the request being processed |
|
|
|
* @param request the request being processed |
|
|
|
* @param httpStatusCode the http status returned by the resource server |
|
|
|
* @param exception The root cause exception for the failure |
|
|
|
* @param exception The root cause exception for the failure (nullable) |
|
|
|
|
|
|
|
* @return a {@link Mono} that completes empty after the authorization failure handler completes. |
|
|
|
* @return a {@link Mono} that completes empty after the authorization failure handler completes. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private Mono<Void> handleHttpStatus(ClientRequest request, int httpStatusCode, @Nullable Exception exception) { |
|
|
|
private Mono<Void> handleWebClientResponseException(ClientRequest request, WebClientResponseException exception) { |
|
|
|
return Mono.justOrEmpty(this.httpStatusToOAuth2ErrorCodeMap.get(httpStatusCode)) |
|
|
|
return Mono.justOrEmpty(resolveErrorIfPossible(exception.getRawStatusCode())) |
|
|
|
.flatMap(oauth2ErrorCode -> { |
|
|
|
.flatMap(oauth2Error -> { |
|
|
|
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request); |
|
|
|
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request); |
|
|
|
|
|
|
|
|
|
|
|
Mono<String> clientRegistrationId = effectiveClientRegistrationId(request); |
|
|
|
Mono<String> clientRegistrationId = effectiveClientRegistrationId(request); |
|
|
|
@ -648,9 +704,9 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements |
|
|
|
.flatMap(tuple3 -> handleAuthorizationFailure( |
|
|
|
.flatMap(tuple3 -> handleAuthorizationFailure( |
|
|
|
tuple3.getT1(), // Authentication principal
|
|
|
|
tuple3.getT1(), // Authentication principal
|
|
|
|
tuple3.getT2().orElse(null), // ServerWebExchange exchange
|
|
|
|
tuple3.getT2().orElse(null), // ServerWebExchange exchange
|
|
|
|
createAuthorizationException( |
|
|
|
new ClientAuthorizationException( |
|
|
|
|
|
|
|
oauth2Error, |
|
|
|
tuple3.getT3(), // String clientRegistrationId
|
|
|
|
tuple3.getT3(), // String clientRegistrationId
|
|
|
|
oauth2ErrorCode, |
|
|
|
|
|
|
|
exception))); |
|
|
|
exception))); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -673,28 +729,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements |
|
|
|
exception)); |
|
|
|
exception)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Creates an authorization exception using the given parameters. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param clientRegistrationId the client registration id of the client that failed authentication/authorization. |
|
|
|
|
|
|
|
* @param oauth2ErrorCode the OAuth 2.0 error code to use in the authorization failure event |
|
|
|
|
|
|
|
* @param exception The root cause exception for the failure (nullable) |
|
|
|
|
|
|
|
* @return an authorization exception using the given parameters. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private ClientAuthorizationException createAuthorizationException( |
|
|
|
|
|
|
|
String clientRegistrationId, |
|
|
|
|
|
|
|
String oauth2ErrorCode, |
|
|
|
|
|
|
|
@Nullable Exception exception) { |
|
|
|
|
|
|
|
return new ClientAuthorizationException( |
|
|
|
|
|
|
|
new OAuth2Error( |
|
|
|
|
|
|
|
oauth2ErrorCode, |
|
|
|
|
|
|
|
null, |
|
|
|
|
|
|
|
"https://tools.ietf.org/html/rfc6750#section-3.1"), |
|
|
|
|
|
|
|
clientRegistrationId, |
|
|
|
|
|
|
|
exception); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Delegates to the authorization failure handler of the failed authorization. |
|
|
|
* Delegates to the authorization failure handler of the failed authorization. |
|
|
|
* |
|
|
|
* |
|
|
|
|