Add OAuth2Authorization success/failure handlers
Fixes gh-7840
This commit is contained in:
parent
1b68cdb650
commit
69156b741d
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,6 +19,11 @@ import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||||
|
import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientOAuth2AuthorizationFailureHandler;
|
||||||
|
import org.springframework.security.oauth2.client.web.SaveAuthorizedClientOAuth2AuthorizationSuccessHandler;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
@ -31,20 +36,50 @@ import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of an {@link OAuth2AuthorizedClientManager}
|
* An implementation of an {@link OAuth2AuthorizedClientManager}
|
||||||
* that is capable of operating outside of a {@code HttpServletRequest} context,
|
* that is capable of operating outside of the context of a {@code HttpServletRequest},
|
||||||
* e.g. in a scheduled/background thread and/or in the service-tier.
|
* e.g. in a scheduled/background thread and/or in the service-tier.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* (When operating <em>within</em> the context of a {@code HttpServletRequest},
|
||||||
|
* use {@link DefaultOAuth2AuthorizedClientManager} instead.)
|
||||||
|
*
|
||||||
|
* <h2>Authorized Client Persistence</h2>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This manager utilizes an {@link OAuth2AuthorizedClientService}
|
||||||
|
* to persist {@link OAuth2AuthorizedClient}s.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
|
||||||
|
* will be saved in the {@link OAuth2AuthorizedClientService}.
|
||||||
|
* This functionality can be changed by configuring a custom {@link OAuth2AuthorizationSuccessHandler}
|
||||||
|
* via {@link #setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* By default, when an authorization attempt fails due to an
|
||||||
|
* {@value OAuth2ErrorCodes#INVALID_GRANT} error,
|
||||||
|
* the previously saved {@link OAuth2AuthorizedClient}
|
||||||
|
* will be removed from the {@link OAuth2AuthorizedClientService}.
|
||||||
|
* (The {@value OAuth2ErrorCodes#INVALID_GRANT} error can occur
|
||||||
|
* when a refresh token that is no longer valid is used to retrieve a new access token.)
|
||||||
|
* This functionality can be changed by configuring a custom {@link OAuth2AuthorizationFailureHandler}
|
||||||
|
* via {@link #setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)}.
|
||||||
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @see OAuth2AuthorizedClientManager
|
* @see OAuth2AuthorizedClientManager
|
||||||
* @see OAuth2AuthorizedClientProvider
|
* @see OAuth2AuthorizedClientProvider
|
||||||
* @see OAuth2AuthorizedClientService
|
* @see OAuth2AuthorizedClientService
|
||||||
|
* @see OAuth2AuthorizationSuccessHandler
|
||||||
|
* @see OAuth2AuthorizationFailureHandler
|
||||||
*/
|
*/
|
||||||
public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
|
public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
|
||||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||||
private final OAuth2AuthorizedClientService authorizedClientService;
|
private final OAuth2AuthorizedClientService authorizedClientService;
|
||||||
private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
|
private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
|
||||||
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper = new DefaultContextAttributesMapper();
|
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper;
|
||||||
|
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
|
||||||
|
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code AuthorizedClientServiceOAuth2AuthorizedClientManager} using the provided parameters.
|
* Constructs an {@code AuthorizedClientServiceOAuth2AuthorizedClientManager} using the provided parameters.
|
||||||
|
@ -58,6 +93,9 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||||
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||||
this.authorizedClientService = authorizedClientService;
|
this.authorizedClientService = authorizedClientService;
|
||||||
|
this.contextAttributesMapper = new DefaultContextAttributesMapper();
|
||||||
|
this.authorizationSuccessHandler = new SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(authorizedClientService);
|
||||||
|
this.authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(authorizedClientService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -92,9 +130,16 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
try {
|
||||||
|
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||||
|
} catch (OAuth2AuthorizationException ex) {
|
||||||
|
this.authorizationFailureHandler.onAuthorizationFailure(ex, principal, Collections.emptyMap());
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
if (authorizedClient != null) {
|
if (authorizedClient != null) {
|
||||||
this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
|
this.authorizationSuccessHandler.onAuthorizationSuccess(
|
||||||
|
authorizedClient, principal, Collections.emptyMap());
|
||||||
} else {
|
} else {
|
||||||
// In the case of re-authorization, the returned `authorizedClient` may be null if re-authorization is not supported.
|
// In the case of re-authorization, the returned `authorizedClient` may be null if re-authorization is not supported.
|
||||||
// For these cases, return the provided `authorizationContext.authorizedClient`.
|
// For these cases, return the provided `authorizationContext.authorizedClient`.
|
||||||
|
@ -128,6 +173,36 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||||
this.contextAttributesMapper = contextAttributesMapper;
|
this.contextAttributesMapper = contextAttributesMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* A {@link SaveAuthorizedClientOAuth2AuthorizationSuccessHandler} is used by default.
|
||||||
|
*
|
||||||
|
* @param authorizationSuccessHandler the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations
|
||||||
|
* @see SaveAuthorizedClientOAuth2AuthorizationSuccessHandler
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public void setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
|
||||||
|
Assert.notNull(authorizationSuccessHandler, "authorizationSuccessHandler cannot be null");
|
||||||
|
this.authorizationSuccessHandler = authorizationSuccessHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* A {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} is used by default.
|
||||||
|
*
|
||||||
|
* @param authorizationFailureHandler the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures
|
||||||
|
* @see RemoveAuthorizedClientOAuth2AuthorizationFailureHandler
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public void setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||||
|
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
|
||||||
|
this.authorizationFailureHandler = authorizationFailureHandler;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
|
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.client;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles when an OAuth 2.0 Client fails to authorize (or re-authorize)
|
||||||
|
* via the Authorization Server or Resource Server.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.3
|
||||||
|
* @see OAuth2AuthorizedClient
|
||||||
|
* @see OAuth2AuthorizedClientManager
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface OAuth2AuthorizationFailureHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an OAuth 2.0 Client fails to authorize (or re-authorize)
|
||||||
|
* via the Authorization Server or Resource Server.
|
||||||
|
*
|
||||||
|
* @param authorizationException the exception that contains details about what failed
|
||||||
|
* @param principal the {@code Principal} associated with the attempted authorization
|
||||||
|
* @param attributes an immutable {@code Map} of (optional) attributes present under certain conditions.
|
||||||
|
* For example, this might contain a {@code javax.servlet.http.HttpServletRequest}
|
||||||
|
* and {@code javax.servlet.http.HttpServletResponse} if the authorization was performed
|
||||||
|
* within the context of a {@code javax.servlet.ServletContext}.
|
||||||
|
*/
|
||||||
|
void onAuthorizationFailure(OAuth2AuthorizationException authorizationException,
|
||||||
|
Authentication principal, Map<String, Object> attributes);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.client;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles when an OAuth 2.0 Client has been successfully
|
||||||
|
* authorized (or re-authorized) via the Authorization Server.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.3
|
||||||
|
* @see OAuth2AuthorizedClient
|
||||||
|
* @see OAuth2AuthorizedClientManager
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface OAuth2AuthorizationSuccessHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an OAuth 2.0 Client has been successfully
|
||||||
|
* authorized (or re-authorized) via the Authorization Server.
|
||||||
|
*
|
||||||
|
* @param authorizedClient the client that was successfully authorized (or re-authorized)
|
||||||
|
* @param principal the {@code Principal} associated with the authorized client
|
||||||
|
* @param attributes an immutable {@code Map} of (optional) attributes present under certain conditions.
|
||||||
|
* For example, this might contain a {@code javax.servlet.http.HttpServletRequest}
|
||||||
|
* and {@code javax.servlet.http.HttpServletResponse} if the authorization was performed
|
||||||
|
* within the context of a {@code javax.servlet.ServletContext}.
|
||||||
|
*/
|
||||||
|
void onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient,
|
||||||
|
Authentication principal, Map<String, Object> attributes);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,9 +20,9 @@ import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
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.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
|
@ -30,6 +30,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.client.ResponseErrorHandler;
|
import org.springframework.web.client.ResponseErrorHandler;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@ -74,9 +75,22 @@ public final class DefaultAuthorizationCodeTokenResponseClient implements OAuth2
|
||||||
try {
|
try {
|
||||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||||
} catch (RestClientException ex) {
|
} catch (RestClientException ex) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
int statusCode = 500;
|
||||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
|
if (ex instanceof RestClientResponseException) {
|
||||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
|
||||||
|
}
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
|
INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||||
|
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
|
||||||
|
null);
|
||||||
|
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
|
||||||
|
statusCode,
|
||||||
|
oauth2Error);
|
||||||
|
throw new ClientAuthorizationException(
|
||||||
|
oauth2Error,
|
||||||
|
authorizationCodeGrantRequest.getClientRegistration().getRegistrationId(),
|
||||||
|
message,
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,9 +20,9 @@ import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
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.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
|
@ -30,6 +30,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.client.ResponseErrorHandler;
|
import org.springframework.web.client.ResponseErrorHandler;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@ -74,9 +75,22 @@ public final class DefaultClientCredentialsTokenResponseClient implements OAuth2
|
||||||
try {
|
try {
|
||||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||||
} catch (RestClientException ex) {
|
} catch (RestClientException ex) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
int statusCode = 500;
|
||||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
|
if (ex instanceof RestClientResponseException) {
|
||||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
|
||||||
|
}
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
|
INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||||
|
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
|
||||||
|
null);
|
||||||
|
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
|
||||||
|
statusCode,
|
||||||
|
oauth2Error);
|
||||||
|
throw new ClientAuthorizationException(
|
||||||
|
oauth2Error,
|
||||||
|
clientCredentialsGrantRequest.getClientRegistration().getRegistrationId(),
|
||||||
|
message,
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,9 +20,9 @@ import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
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.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
|
@ -30,6 +30,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.client.ResponseErrorHandler;
|
import org.springframework.web.client.ResponseErrorHandler;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@ -74,9 +75,22 @@ public final class DefaultPasswordTokenResponseClient implements OAuth2AccessTok
|
||||||
try {
|
try {
|
||||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||||
} catch (RestClientException ex) {
|
} catch (RestClientException ex) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
int statusCode = 500;
|
||||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
|
if (ex instanceof RestClientResponseException) {
|
||||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
|
||||||
|
}
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
|
INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||||
|
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
|
||||||
|
null);
|
||||||
|
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
|
||||||
|
statusCode,
|
||||||
|
oauth2Error);
|
||||||
|
throw new ClientAuthorizationException(
|
||||||
|
oauth2Error,
|
||||||
|
passwordGrantRequest.getClientRegistration().getRegistrationId(),
|
||||||
|
message,
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,9 +20,9 @@ import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
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.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
|
@ -30,6 +30,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.client.ResponseErrorHandler;
|
import org.springframework.web.client.ResponseErrorHandler;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@ -73,9 +74,22 @@ public final class DefaultRefreshTokenTokenResponseClient implements OAuth2Acces
|
||||||
try {
|
try {
|
||||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||||
} catch (RestClientException ex) {
|
} catch (RestClientException ex) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
int statusCode = 500;
|
||||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
|
if (ex instanceof RestClientResponseException) {
|
||||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
statusCode = ((RestClientResponseException) ex).getRawStatusCode();
|
||||||
|
}
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
|
INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||||
|
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
|
||||||
|
null);
|
||||||
|
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
|
||||||
|
statusCode,
|
||||||
|
oauth2Error);
|
||||||
|
throw new ClientAuthorizationException(
|
||||||
|
oauth2Error,
|
||||||
|
refreshTokenGrantRequest.getClientRegistration().getRegistrationId(),
|
||||||
|
message,
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -31,10 +31,10 @@ import com.nimbusds.oauth2.sdk.auth.Secret;
|
||||||
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
|
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
|
||||||
import com.nimbusds.oauth2.sdk.id.ClientID;
|
import com.nimbusds.oauth2.sdk.id.ClientID;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
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.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
@ -100,9 +100,19 @@ public class NimbusAuthorizationCodeTokenResponseClient implements OAuth2AccessT
|
||||||
httpRequest.setReadTimeout(30000);
|
httpRequest.setReadTimeout(30000);
|
||||||
tokenResponse = com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send());
|
tokenResponse = com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send());
|
||||||
} catch (ParseException | IOException ex) {
|
} catch (ParseException | IOException ex) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
int statusCode = 500;
|
||||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(), null);
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||||
|
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " + ex.getMessage(),
|
||||||
|
null);
|
||||||
|
String message = String.format("Error retrieving OAuth 2.0 Access Token (HTTP Status Code: %s) %s",
|
||||||
|
statusCode,
|
||||||
|
oauth2Error);
|
||||||
|
throw new ClientAuthorizationException(
|
||||||
|
oauth2Error,
|
||||||
|
clientRegistration.getRegistrationId(),
|
||||||
|
message,
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tokenResponse.indicatesSuccess()) {
|
if (!tokenResponse.indicatesSuccess()) {
|
||||||
|
@ -117,7 +127,7 @@ public class NimbusAuthorizationCodeTokenResponseClient implements OAuth2AccessT
|
||||||
errorObject.getDescription(),
|
errorObject.getDescription(),
|
||||||
errorObject.getURI() != null ? errorObject.getURI().toString() : null);
|
errorObject.getURI() != null ? errorObject.getURI().toString() : null);
|
||||||
}
|
}
|
||||||
throw new OAuth2AuthorizationException(oauth2Error);
|
throw new ClientAuthorizationException(oauth2Error, clientRegistration.getRegistrationId());
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
|
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
|
||||||
|
|
|
@ -15,22 +15,20 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.client.web;
|
package org.springframework.security.oauth2.client.web;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationSuccessHandler;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
@ -39,19 +37,57 @@ import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default implementation of an {@link OAuth2AuthorizedClientManager}.
|
* The default implementation of an {@link OAuth2AuthorizedClientManager}
|
||||||
|
* for use within the context of a {@code HttpServletRequest}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* (When operating <em>outside</em> of the context of a {@code HttpServletRequest},
|
||||||
|
* use {@link AuthorizedClientServiceOAuth2AuthorizedClientManager} instead.)
|
||||||
|
*
|
||||||
|
* <h2>Authorized Client Persistence</h2>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This manager utilizes an {@link OAuth2AuthorizedClientRepository}
|
||||||
|
* to persist {@link OAuth2AuthorizedClient}s.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
|
||||||
|
* will be saved in the {@link OAuth2AuthorizedClientRepository}.
|
||||||
|
* This functionality can be changed by configuring a custom {@link OAuth2AuthorizationSuccessHandler}
|
||||||
|
* via {@link #setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* By default, when an authorization attempt fails due to an
|
||||||
|
* {@value OAuth2ErrorCodes#INVALID_GRANT} error,
|
||||||
|
* the previously saved {@link OAuth2AuthorizedClient}
|
||||||
|
* will be removed from the {@link OAuth2AuthorizedClientRepository}.
|
||||||
|
* (The {@value OAuth2ErrorCodes#INVALID_GRANT} error can occur
|
||||||
|
* when a refresh token that is no longer valid is used to retrieve a new access token.)
|
||||||
|
* This functionality can be changed by configuring a custom {@link OAuth2AuthorizationFailureHandler}
|
||||||
|
* via {@link #setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)}.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @see OAuth2AuthorizedClientManager
|
* @see OAuth2AuthorizedClientManager
|
||||||
* @see OAuth2AuthorizedClientProvider
|
* @see OAuth2AuthorizedClientProvider
|
||||||
|
* @see OAuth2AuthorizationSuccessHandler
|
||||||
|
* @see OAuth2AuthorizationFailureHandler
|
||||||
*/
|
*/
|
||||||
public final class DefaultOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
|
public final class DefaultOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
|
||||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||||
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
|
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||||
private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
|
private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
|
||||||
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper = new DefaultContextAttributesMapper();
|
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper;
|
||||||
|
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
|
||||||
|
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code DefaultOAuth2AuthorizedClientManager} using the provided parameters.
|
* Constructs a {@code DefaultOAuth2AuthorizedClientManager} using the provided parameters.
|
||||||
|
@ -65,6 +101,9 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
|
||||||
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
|
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
|
||||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||||
this.authorizedClientRepository = authorizedClientRepository;
|
this.authorizedClientRepository = authorizedClientRepository;
|
||||||
|
this.contextAttributesMapper = new DefaultContextAttributesMapper();
|
||||||
|
this.authorizationSuccessHandler = new SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(authorizedClientRepository);
|
||||||
|
this.authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(authorizedClientRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -105,9 +144,17 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
try {
|
||||||
|
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||||
|
} catch (OAuth2AuthorizationException ex) {
|
||||||
|
this.authorizationFailureHandler.onAuthorizationFailure(
|
||||||
|
ex, principal, createAttributes(servletRequest, servletResponse));
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
if (authorizedClient != null) {
|
if (authorizedClient != null) {
|
||||||
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, servletRequest, servletResponse);
|
this.authorizationSuccessHandler.onAuthorizationSuccess(
|
||||||
|
authorizedClient, principal, createAttributes(servletRequest, servletResponse));
|
||||||
} else {
|
} else {
|
||||||
// In the case of re-authorization, the returned `authorizedClient` may be null if re-authorization is not supported.
|
// In the case of re-authorization, the returned `authorizedClient` may be null if re-authorization is not supported.
|
||||||
// For these cases, return the provided `authorizationContext.authorizedClient`.
|
// For these cases, return the provided `authorizationContext.authorizedClient`.
|
||||||
|
@ -119,12 +166,19 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
|
||||||
return authorizedClient;
|
return authorizedClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createAttributes(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
|
||||||
|
Map<String, Object> attributes = new HashMap<>();
|
||||||
|
attributes.put(HttpServletRequest.class.getName(), servletRequest);
|
||||||
|
attributes.put(HttpServletResponse.class.getName(), servletResponse);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
private static HttpServletRequest getHttpServletRequestOrDefault(Map<String, Object> attributes) {
|
private static HttpServletRequest getHttpServletRequestOrDefault(Map<String, Object> attributes) {
|
||||||
HttpServletRequest servletRequest = (HttpServletRequest) attributes.get(HttpServletRequest.class.getName());
|
HttpServletRequest servletRequest = (HttpServletRequest) attributes.get(HttpServletRequest.class.getName());
|
||||||
if (servletRequest == null) {
|
if (servletRequest == null) {
|
||||||
RequestAttributes context = RequestContextHolder.getRequestAttributes();
|
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||||
if (context instanceof ServletRequestAttributes) {
|
if (requestAttributes instanceof ServletRequestAttributes) {
|
||||||
servletRequest = ((ServletRequestAttributes) context).getRequest();
|
servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return servletRequest;
|
return servletRequest;
|
||||||
|
@ -133,9 +187,9 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
|
||||||
private static HttpServletResponse getHttpServletResponseOrDefault(Map<String, Object> attributes) {
|
private static HttpServletResponse getHttpServletResponseOrDefault(Map<String, Object> attributes) {
|
||||||
HttpServletResponse servletResponse = (HttpServletResponse) attributes.get(HttpServletResponse.class.getName());
|
HttpServletResponse servletResponse = (HttpServletResponse) attributes.get(HttpServletResponse.class.getName());
|
||||||
if (servletResponse == null) {
|
if (servletResponse == null) {
|
||||||
RequestAttributes context = RequestContextHolder.getRequestAttributes();
|
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||||
if (context instanceof ServletRequestAttributes) {
|
if (requestAttributes instanceof ServletRequestAttributes) {
|
||||||
servletResponse = ((ServletRequestAttributes) context).getResponse();
|
servletResponse = ((ServletRequestAttributes) requestAttributes).getResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return servletResponse;
|
return servletResponse;
|
||||||
|
@ -163,6 +217,36 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
|
||||||
this.contextAttributesMapper = contextAttributesMapper;
|
this.contextAttributesMapper = contextAttributesMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* A {@link SaveAuthorizedClientOAuth2AuthorizationSuccessHandler} is used by default.
|
||||||
|
*
|
||||||
|
* @param authorizationSuccessHandler the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations
|
||||||
|
* @see SaveAuthorizedClientOAuth2AuthorizationSuccessHandler
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public void setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
|
||||||
|
Assert.notNull(authorizationSuccessHandler, "authorizationSuccessHandler cannot be null");
|
||||||
|
this.authorizationSuccessHandler = authorizationSuccessHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* A {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} is used by default.
|
||||||
|
*
|
||||||
|
* @param authorizationFailureHandler the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures
|
||||||
|
* @see RemoveAuthorizedClientOAuth2AuthorizationFailureHandler
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public void setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||||
|
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
|
||||||
|
this.authorizationFailureHandler = authorizationFailureHandler;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
|
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.client.web;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link OAuth2AuthorizationFailureHandler} that removes an {@link OAuth2AuthorizedClient}
|
||||||
|
* from an {@link OAuth2AuthorizedClientRepository} or {@link OAuth2AuthorizedClientService}
|
||||||
|
* for a specific set of OAuth 2.0 error codes.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.3
|
||||||
|
* @see OAuth2AuthorizedClient
|
||||||
|
* @see OAuth2AuthorizedClientRepository
|
||||||
|
* @see OAuth2AuthorizedClientService
|
||||||
|
*/
|
||||||
|
public class RemoveAuthorizedClientOAuth2AuthorizationFailureHandler implements OAuth2AuthorizationFailureHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default OAuth 2.0 error codes that will trigger removal of an {@link OAuth2AuthorizedClient}.
|
||||||
|
* @see OAuth2ErrorCodes
|
||||||
|
*/
|
||||||
|
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||||
|
/*
|
||||||
|
* Returned from Resource Servers when an access token provided is expired, revoked,
|
||||||
|
* malformed, or invalid for other reasons.
|
||||||
|
*
|
||||||
|
* Note that this is needed because ServletOAuth2AuthorizedClientExchangeFilterFunction
|
||||||
|
* delegates this type of failure received from a Resource Server
|
||||||
|
* to this failure handler.
|
||||||
|
*/
|
||||||
|
OAuth2ErrorCodes.INVALID_TOKEN,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returned from Authorization Servers when the authorization grant or refresh token is invalid, expired, revoked,
|
||||||
|
* does not match the redirection URI used in the authorization request, or was issued to another client.
|
||||||
|
*/
|
||||||
|
OAuth2ErrorCodes.INVALID_GRANT
|
||||||
|
)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The OAuth 2.0 error codes which will trigger removal of an {@link OAuth2AuthorizedClient}.
|
||||||
|
* @see OAuth2ErrorCodes
|
||||||
|
*/
|
||||||
|
private final Set<String> removeAuthorizedClientErrorCodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A delegate that removes an {@link OAuth2AuthorizedClient} from a
|
||||||
|
* {@link OAuth2AuthorizedClientRepository} or {@link OAuth2AuthorizedClientService}
|
||||||
|
* if the error code is one of the {@link #removeAuthorizedClientErrorCodes}.
|
||||||
|
*/
|
||||||
|
private final OAuth2AuthorizedClientRemover delegate;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface OAuth2AuthorizedClientRemover {
|
||||||
|
void removeAuthorizedClient(String clientRegistrationId, Authentication principal, Map<String, Object> attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizedClientRepository the repository from which authorized clients will be removed
|
||||||
|
* if the error code is one of the {@link #DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES}.
|
||||||
|
*/
|
||||||
|
public RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||||
|
this(authorizedClientRepository, DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizedClientRepository the repository from which authorized clients will be removed
|
||||||
|
* if the error code is one of the {@link #removeAuthorizedClientErrorCodes}.
|
||||||
|
* @param removeAuthorizedClientErrorCodes the OAuth 2.0 error codes which will trigger removal of an authorized client.
|
||||||
|
* @see OAuth2ErrorCodes
|
||||||
|
*/
|
||||||
|
public RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
|
||||||
|
OAuth2AuthorizedClientRepository authorizedClientRepository,
|
||||||
|
Set<String> removeAuthorizedClientErrorCodes) {
|
||||||
|
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
|
||||||
|
Assert.notNull(removeAuthorizedClientErrorCodes, "removeAuthorizedClientErrorCodes cannot be null");
|
||||||
|
this.removeAuthorizedClientErrorCodes = Collections.unmodifiableSet(new HashSet<>(removeAuthorizedClientErrorCodes));
|
||||||
|
this.delegate = (clientRegistrationId, principal, attributes) ->
|
||||||
|
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal,
|
||||||
|
(HttpServletRequest) attributes.get(HttpServletRequest.class.getName()),
|
||||||
|
(HttpServletResponse) attributes.get(HttpServletResponse.class.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizedClientService the service from which authorized clients will be removed
|
||||||
|
* if the error code is one of the {@link #DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES}.
|
||||||
|
*/
|
||||||
|
public RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(OAuth2AuthorizedClientService authorizedClientService) {
|
||||||
|
this(authorizedClientService, DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizedClientService the service from which authorized clients will be removed
|
||||||
|
* if the error code is one of the {@link #removeAuthorizedClientErrorCodes}.
|
||||||
|
* @param removeAuthorizedClientErrorCodes the OAuth 2.0 error codes which will trigger removal of an authorized client.
|
||||||
|
* @see OAuth2ErrorCodes
|
||||||
|
*/
|
||||||
|
public RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
|
||||||
|
OAuth2AuthorizedClientService authorizedClientService,
|
||||||
|
Set<String> removeAuthorizedClientErrorCodes) {
|
||||||
|
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||||
|
Assert.notNull(removeAuthorizedClientErrorCodes, "removeAuthorizedClientErrorCodes cannot be null");
|
||||||
|
this.removeAuthorizedClientErrorCodes = Collections.unmodifiableSet(new HashSet<>(removeAuthorizedClientErrorCodes));
|
||||||
|
this.delegate = (clientRegistrationId, principal, attributes) ->
|
||||||
|
authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthorizationFailure(OAuth2AuthorizationException authorizationException,
|
||||||
|
Authentication principal, Map<String, Object> attributes) {
|
||||||
|
|
||||||
|
if (authorizationException instanceof ClientAuthorizationException &&
|
||||||
|
hasRemovalErrorCode(authorizationException)) {
|
||||||
|
ClientAuthorizationException clientAuthorizationException = (ClientAuthorizationException) authorizationException;
|
||||||
|
this.delegate.removeAuthorizedClient(
|
||||||
|
clientAuthorizationException.getClientRegistrationId(), principal, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given exception has an error code that
|
||||||
|
* indicates that the authorized client should be removed.
|
||||||
|
*
|
||||||
|
* @param authorizationException the exception that caused the authorization failure
|
||||||
|
* @return true if the given exception has an error code that
|
||||||
|
* indicates that the authorized client should be removed.
|
||||||
|
*/
|
||||||
|
private boolean hasRemovalErrorCode(OAuth2AuthorizationException authorizationException) {
|
||||||
|
return this.removeAuthorizedClientErrorCodes.contains(authorizationException.getError().getErrorCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.client.web;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationSuccessHandler;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link OAuth2AuthorizationSuccessHandler} that saves an {@link OAuth2AuthorizedClient}
|
||||||
|
* in an {@link OAuth2AuthorizedClientRepository} or {@link OAuth2AuthorizedClientService}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.3
|
||||||
|
* @see OAuth2AuthorizedClient
|
||||||
|
* @see OAuth2AuthorizedClientRepository
|
||||||
|
* @see OAuth2AuthorizedClientService
|
||||||
|
*/
|
||||||
|
public class SaveAuthorizedClientOAuth2AuthorizationSuccessHandler implements OAuth2AuthorizationSuccessHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A delegate that saves an {@link OAuth2AuthorizedClient} in an
|
||||||
|
* {@link OAuth2AuthorizedClientRepository} or {@link OAuth2AuthorizedClientService}.
|
||||||
|
*/
|
||||||
|
private final OAuth2AuthorizationSuccessHandler delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code SaveAuthorizedClientOAuth2AuthorizationSuccessHandler} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizedClientRepository The repository in which authorized clients will be saved.
|
||||||
|
*/
|
||||||
|
public SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(
|
||||||
|
OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||||
|
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
|
||||||
|
this.delegate = (authorizedClient, principal, attributes) ->
|
||||||
|
authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal,
|
||||||
|
(HttpServletRequest) attributes.get(HttpServletRequest.class.getName()),
|
||||||
|
(HttpServletResponse) attributes.get(HttpServletResponse.class.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code SaveAuthorizedClientOAuth2AuthorizationSuccessHandler} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param authorizedClientService The service in which authorized clients will be saved.
|
||||||
|
*/
|
||||||
|
public SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(
|
||||||
|
OAuth2AuthorizedClientService authorizedClientService) {
|
||||||
|
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||||
|
this.delegate = (authorizedClient, principal, attributes) ->
|
||||||
|
authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient,
|
||||||
|
Authentication principal, Map<String, Object> attributes) {
|
||||||
|
this.delegate.onAuthorizationSuccess(authorizedClient, principal, attributes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,15 +13,18 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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.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.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||||
|
@ -35,7 +38,13 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||||
|
import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientOAuth2AuthorizationFailureHandler;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
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.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
@ -44,6 +53,7 @@ import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
import reactor.util.context.Context;
|
import reactor.util.context.Context;
|
||||||
|
@ -52,18 +62,25 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
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 OAuth 2.0 requests
|
||||||
* token as a Bearer Token. It also provides mechanisms for looking up the {@link OAuth2AuthorizedClient}. This class is
|
* by including the {@link OAuth2AuthorizedClient#getAccessToken() access token} as a bearer token.
|
||||||
* intended to be used in a servlet environment.
|
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE:</b>This class is intended to be used in a {@code Servlet} environment.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
* Example usage:
|
* Example usage:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, authorizedClientRepository);
|
* ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
|
||||||
* WebClient webClient = WebClient.builder()
|
* WebClient webClient = WebClient.builder()
|
||||||
* .apply(oauth2.oauth2Configuration())
|
* .apply(oauth2.oauth2Configuration())
|
||||||
* .build();
|
* .build();
|
||||||
|
@ -76,23 +93,35 @@ import java.util.function.Consumer;
|
||||||
* .bodyToMono(String.class);
|
* .bodyToMono(String.class);
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* An attempt to automatically refresh the token will be made if all of the following
|
* <h3>Authentication and Authorization Failures</h3>
|
||||||
* are true:
|
|
||||||
*
|
*
|
||||||
* <ul>
|
* <p>
|
||||||
* <li>The {@link OAuth2AuthorizedClientManager} is not null</li>
|
* Since 5.3, this filter function has the ability to forward authentication (HTTP 401 Unauthorized)
|
||||||
* <li>A refresh token is present on the {@link OAuth2AuthorizedClient}</li>
|
* and authorization (HTTP 403 Forbidden) failures from an OAuth 2.0 Resource Server
|
||||||
* <li>The access token is expired</li>
|
* to a {@link OAuth2AuthorizationFailureHandler}.
|
||||||
* <li>The {@link SecurityContextHolder} will be used to attempt to save
|
* A {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} can be used
|
||||||
* the token. If it is empty, then the principal name on the {@link OAuth2AuthorizedClient}
|
* to remove the cached {@link OAuth2AuthorizedClient}, so that future requests will result
|
||||||
* will be used to create an Authentication for saving.</li>
|
* in a new token being retrieved from an Authorization Server, and sent to the Resource Server.
|
||||||
* </ul>
|
*
|
||||||
|
* <p>
|
||||||
|
* If the {@link #ServletOAuth2AuthorizedClientExchangeFilterFunction(ClientRegistrationRepository, OAuth2AuthorizedClientRepository)}
|
||||||
|
* constructor is used, a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler}
|
||||||
|
* will be configured automatically.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the {@link #ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager)}
|
||||||
|
* constructor is used, a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler}
|
||||||
|
* will <em>NOT</em> be configured automatically.
|
||||||
|
* It is recommended that you configure one via {@link #setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)}.
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @author Roman Matiushchenko
|
* @author Roman Matiushchenko
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
* @see OAuth2AuthorizedClientManager
|
* @see OAuth2AuthorizedClientManager
|
||||||
|
* @see DefaultOAuth2AuthorizedClientManager
|
||||||
|
* @see OAuth2AuthorizedClientProvider
|
||||||
|
* @see OAuth2AuthorizedClientProviderBuilder
|
||||||
*/
|
*/
|
||||||
public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
|
public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
|
||||||
|
|
||||||
|
@ -103,6 +132,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
* The request attribute name used to locate the {@link OAuth2AuthorizedClient}.
|
* The request attribute name used to locate the {@link OAuth2AuthorizedClient}.
|
||||||
*/
|
*/
|
||||||
private static final String OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME = OAuth2AuthorizedClient.class.getName();
|
private static final String OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME = OAuth2AuthorizedClient.class.getName();
|
||||||
|
|
||||||
private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2AuthorizedClient.class.getName().concat(".CLIENT_REGISTRATION_ID");
|
private static final String CLIENT_REGISTRATION_ID_ATTR_NAME = OAuth2AuthorizedClient.class.getName().concat(".CLIENT_REGISTRATION_ID");
|
||||||
private static final String AUTHENTICATION_ATTR_NAME = Authentication.class.getName();
|
private static final String AUTHENTICATION_ATTR_NAME = Authentication.class.getName();
|
||||||
private static final String HTTP_SERVLET_REQUEST_ATTR_NAME = HttpServletRequest.class.getName();
|
private static final String HTTP_SERVLET_REQUEST_ATTR_NAME = HttpServletRequest.class.getName();
|
||||||
|
@ -125,35 +155,75 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
|
|
||||||
private String defaultClientRegistrationId;
|
private String defaultClientRegistrationId;
|
||||||
|
|
||||||
|
private ClientResponseHandler clientResponseHandler;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface ClientResponseHandler {
|
||||||
|
Mono<ClientResponse> handleResponse(ClientRequest request, Mono<ClientResponse> response);
|
||||||
|
}
|
||||||
|
|
||||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {
|
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code ServletOAuth2AuthorizedClientExchangeFilterFunction} using the provided parameters.
|
* Constructs a {@code ServletOAuth2AuthorizedClientExchangeFilterFunction} using the provided parameters.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* When this constructor is used, authentication (HTTP 401) and authorization (HTTP 403)
|
||||||
|
* failures returned from an OAuth 2.0 Resource Server will <em>NOT</em> be forwarded to an
|
||||||
|
* {@link OAuth2AuthorizationFailureHandler}.
|
||||||
|
* Therefore, future requests to the Resource Server will most likely use the same (likely invalid) token,
|
||||||
|
* resulting in the same errors returned from the Resource Server.
|
||||||
|
* It is recommended to configure a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler}
|
||||||
|
* via {@link #setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)}
|
||||||
|
* so that authentication and authorization failures returned from a Resource Server
|
||||||
|
* will result in removing the authorized client, so that a new token is retrieved for future requests.
|
||||||
|
*
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @param authorizedClientManager the {@link OAuth2AuthorizedClientManager} which manages the authorized client(s)
|
* @param authorizedClientManager the {@link OAuth2AuthorizedClientManager} which manages the authorized client(s)
|
||||||
*/
|
*/
|
||||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager authorizedClientManager) {
|
public ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||||
Assert.notNull(authorizedClientManager, "authorizedClientManager cannot be null");
|
Assert.notNull(authorizedClientManager, "authorizedClientManager cannot be null");
|
||||||
this.authorizedClientManager = authorizedClientManager;
|
this.authorizedClientManager = authorizedClientManager;
|
||||||
|
this.clientResponseHandler = (request, responseMono) -> responseMono;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code ServletOAuth2AuthorizedClientExchangeFilterFunction} using the provided parameters.
|
* Constructs a {@code ServletOAuth2AuthorizedClientExchangeFilterFunction} using the provided parameters.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Since 5.3, when this constructor is used, authentication (HTTP 401)
|
||||||
|
* and authorization (HTTP 403) failures returned from an OAuth 2.0 Resource Server
|
||||||
|
* will be forwarded to a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler},
|
||||||
|
* which will potentially remove the {@link OAuth2AuthorizedClient} from the given
|
||||||
|
* {@link OAuth2AuthorizedClientRepository}, depending on the OAuth 2.0 error code returned.
|
||||||
|
* Authentication failures returned from an OAuth 2.0 Resource Server typically indicate
|
||||||
|
* that the token is invalid, and should not be used in future requests.
|
||||||
|
* Removing the authorized client from the repository will ensure that the existing
|
||||||
|
* token will not be sent for future requests to the Resource Server,
|
||||||
|
* and a new token is retrieved from the Authorization Server and used for
|
||||||
|
* future requests to the Resource Server.
|
||||||
|
*
|
||||||
* @param clientRegistrationRepository the repository of client registrations
|
* @param clientRegistrationRepository the repository of client registrations
|
||||||
* @param authorizedClientRepository the repository of authorized clients
|
* @param authorizedClientRepository the repository of authorized clients
|
||||||
*/
|
*/
|
||||||
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
|
||||||
ClientRegistrationRepository clientRegistrationRepository,
|
ClientRegistrationRepository clientRegistrationRepository,
|
||||||
OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||||
this.authorizedClientManager = createDefaultAuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
|
|
||||||
|
OAuth2AuthorizationFailureHandler authorizationFailureHandler =
|
||||||
|
new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(authorizedClientRepository);
|
||||||
|
|
||||||
|
this.authorizedClientManager = createDefaultAuthorizedClientManager(
|
||||||
|
clientRegistrationRepository, authorizedClientRepository, authorizationFailureHandler);
|
||||||
this.defaultAuthorizedClientManager = true;
|
this.defaultAuthorizedClientManager = true;
|
||||||
|
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OAuth2AuthorizedClientManager createDefaultAuthorizedClientManager(
|
private static OAuth2AuthorizedClientManager createDefaultAuthorizedClientManager(
|
||||||
ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
ClientRegistrationRepository clientRegistrationRepository,
|
||||||
|
OAuth2AuthorizedClientRepository authorizedClientRepository,
|
||||||
|
OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||||
|
|
||||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||||
|
@ -165,6 +235,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||||
clientRegistrationRepository, authorizedClientRepository);
|
clientRegistrationRepository, authorizedClientRepository);
|
||||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||||
|
authorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
|
||||||
|
|
||||||
return authorizedClientManager;
|
return authorizedClientManager;
|
||||||
}
|
}
|
||||||
|
@ -333,19 +404,47 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
updateDefaultAuthorizedClientManager();
|
updateDefaultAuthorizedClientManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2AuthorizationFailureHandler} that handles
|
||||||
|
* authentication and authorization failures when communicating
|
||||||
|
* to the OAuth 2.0 Resource Server.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* For example, a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler}
|
||||||
|
* is typically used to remove the cached {@link OAuth2AuthorizedClient},
|
||||||
|
* so that the same token is no longer used in future requests to the Resource Server.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The failure handler used by default depends on which constructor was used
|
||||||
|
* to construct this {@link ServletOAuth2AuthorizedClientExchangeFilterFunction}.
|
||||||
|
* See the constructors for more details.
|
||||||
|
*
|
||||||
|
* @param authorizationFailureHandler the {@link OAuth2AuthorizationFailureHandler} that handles authentication and authorization failures
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public void setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||||
|
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
|
||||||
|
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
|
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
|
||||||
return mergeRequestAttributesIfNecessary(request)
|
return mergeRequestAttributesIfNecessary(request)
|
||||||
.filter(req -> req.attribute(OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME).isPresent())
|
.filter(req -> req.attribute(OAUTH2_AUTHORIZED_CLIENT_ATTR_NAME).isPresent())
|
||||||
.flatMap(req -> authorizedClient(getOAuth2AuthorizedClient(req.attributes()), req))
|
.flatMap(req -> reauthorizeClient(getOAuth2AuthorizedClient(req.attributes()), req))
|
||||||
.switchIfEmpty(Mono.defer(() ->
|
.switchIfEmpty(Mono.defer(() ->
|
||||||
mergeRequestAttributesIfNecessary(request)
|
mergeRequestAttributesIfNecessary(request)
|
||||||
.filter(req -> resolveClientRegistrationId(req) != null)
|
.filter(req -> resolveClientRegistrationId(req) != null)
|
||||||
.flatMap(req -> authorizeClient(resolveClientRegistrationId(req), req))
|
.flatMap(req -> authorizeClient(resolveClientRegistrationId(req), req))
|
||||||
))
|
))
|
||||||
.map(authorizedClient -> bearer(request, authorizedClient))
|
.map(authorizedClient -> bearer(request, authorizedClient))
|
||||||
.flatMap(next::exchange)
|
.flatMap(requestWithBearer -> exchangeAndHandleResponse(requestWithBearer, next))
|
||||||
.switchIfEmpty(Mono.defer(() -> next.exchange(request)));
|
.switchIfEmpty(Mono.defer(() -> exchangeAndHandleResponse(request, next)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<ClientResponse> exchangeAndHandleResponse(ClientRequest request, ExchangeFunction next) {
|
||||||
|
return next.exchange(request)
|
||||||
|
.transform(responseMono -> this.clientResponseHandler.handleResponse(request, responseMono));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<ClientRequest> mergeRequestAttributesIfNecessary(ClientRequest request) {
|
private Mono<ClientRequest> mergeRequestAttributesIfNecessary(ClientRequest request) {
|
||||||
|
@ -443,13 +542,14 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
});
|
});
|
||||||
OAuth2AuthorizeRequest authorizeRequest = builder.build();
|
OAuth2AuthorizeRequest authorizeRequest = builder.build();
|
||||||
|
|
||||||
// NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
|
// NOTE:
|
||||||
// NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
|
// 'authorizedClientManager.authorize()' needs to be executed
|
||||||
|
// on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
|
||||||
// since it performs a blocking I/O operation using RestTemplate internally
|
// since it performs a blocking I/O operation using RestTemplate internally
|
||||||
return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(authorizeRequest)).subscribeOn(Schedulers.boundedElastic());
|
return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(authorizeRequest)).subscribeOn(Schedulers.boundedElastic());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<OAuth2AuthorizedClient> authorizedClient(OAuth2AuthorizedClient authorizedClient, ClientRequest request) {
|
private Mono<OAuth2AuthorizedClient> reauthorizeClient(OAuth2AuthorizedClient authorizedClient, ClientRequest request) {
|
||||||
if (this.authorizedClientManager == null) {
|
if (this.authorizedClientManager == null) {
|
||||||
return Mono.just(authorizedClient);
|
return Mono.just(authorizedClient);
|
||||||
}
|
}
|
||||||
|
@ -472,7 +572,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
});
|
});
|
||||||
OAuth2AuthorizeRequest reauthorizeRequest = builder.build();
|
OAuth2AuthorizeRequest reauthorizeRequest = builder.build();
|
||||||
|
|
||||||
// NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
|
// NOTE:
|
||||||
|
// 'authorizedClientManager.authorize()' needs to be executed
|
||||||
|
// on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
|
||||||
// since it performs a blocking I/O operation using RestTemplate internally
|
// since it performs a blocking I/O operation using RestTemplate internally
|
||||||
return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(reauthorizeRequest)).subscribeOn(Schedulers.boundedElastic());
|
return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(reauthorizeRequest)).subscribeOn(Schedulers.boundedElastic());
|
||||||
}
|
}
|
||||||
|
@ -480,6 +582,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
private ClientRequest bearer(ClientRequest request, OAuth2AuthorizedClient authorizedClient) {
|
private ClientRequest bearer(ClientRequest request, OAuth2AuthorizedClient authorizedClient) {
|
||||||
return ClientRequest.from(request)
|
return ClientRequest.from(request)
|
||||||
.headers(headers -> headers.setBearerAuth(authorizedClient.getAccessToken().getTokenValue()))
|
.headers(headers -> headers.setBearerAuth(authorizedClient.getAccessToken().getTokenValue()))
|
||||||
|
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,4 +653,183 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||||
return new UnsupportedOperationException("Not Supported");
|
return new UnsupportedOperationException("Not Supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards authentication and authorization failures to an
|
||||||
|
* {@link OAuth2AuthorizationFailureHandler}.
|
||||||
|
*
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
private static class AuthorizationFailureForwarder implements ClientResponseHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of HTTP status code to OAuth 2.0 error code for
|
||||||
|
* HTTP status codes that should be interpreted as
|
||||||
|
* authentication or authorization failures.
|
||||||
|
*/
|
||||||
|
private final Map<Integer, String> httpStatusToOAuth2ErrorCodeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link OAuth2AuthorizationFailureHandler} to notify
|
||||||
|
* when an authentication/authorization failure occurs.
|
||||||
|
*/
|
||||||
|
private final OAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||||
|
|
||||||
|
private AuthorizationFailureForwarder(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||||
|
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
|
||||||
|
this.authorizationFailureHandler = authorizationFailureHandler;
|
||||||
|
|
||||||
|
Map<Integer, String> httpStatusToOAuth2Error = new HashMap<>();
|
||||||
|
httpStatusToOAuth2Error.put(HttpStatus.UNAUTHORIZED.value(), OAuth2ErrorCodes.INVALID_TOKEN);
|
||||||
|
httpStatusToOAuth2Error.put(HttpStatus.FORBIDDEN.value(), OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
|
this.httpStatusToOAuth2ErrorCodeMap = Collections.unmodifiableMap(httpStatusToOAuth2Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ClientResponse> handleResponse(ClientRequest request, Mono<ClientResponse> responseMono) {
|
||||||
|
return responseMono
|
||||||
|
.flatMap(response -> handleResponse(request, response)
|
||||||
|
.thenReturn(response))
|
||||||
|
.onErrorResume(WebClientResponseException.class, e -> handleWebClientResponseException(request, e)
|
||||||
|
.then(Mono.error(e)))
|
||||||
|
.onErrorResume(OAuth2AuthorizationException.class, e -> handleAuthorizationException(request, e)
|
||||||
|
.then(Mono.error(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<Void> handleResponse(ClientRequest request, ClientResponse response) {
|
||||||
|
return Mono.justOrEmpty(resolveErrorIfPossible(response))
|
||||||
|
.flatMap(oauth2Error -> {
|
||||||
|
Map<String, Object> attrs = request.attributes();
|
||||||
|
OAuth2AuthorizedClient authorizedClient = getOAuth2AuthorizedClient(attrs);
|
||||||
|
if (authorizedClient == null) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientAuthorizationException authorizationException = new ClientAuthorizationException(
|
||||||
|
oauth2Error, authorizedClient.getClientRegistration().getRegistrationId());
|
||||||
|
|
||||||
|
Authentication principal = new PrincipalNameAuthentication(authorizedClient.getPrincipalName());
|
||||||
|
HttpServletRequest servletRequest = getRequest(attrs);
|
||||||
|
HttpServletResponse servletResponse = getResponse(attrs);
|
||||||
|
|
||||||
|
return handleAuthorizationFailure(authorizationException, principal, servletRequest, servletResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
* by notifying the authorization failure handler if the http status
|
||||||
|
* code is in the {@link #httpStatusToOAuth2ErrorCodeMap}.
|
||||||
|
*
|
||||||
|
* @param request the request being processed
|
||||||
|
* @param exception The root cause exception for the failure
|
||||||
|
* @return a {@link Mono} that completes empty after the authorization failure handler completes
|
||||||
|
*/
|
||||||
|
private Mono<Void> handleWebClientResponseException(ClientRequest request, WebClientResponseException exception) {
|
||||||
|
return Mono.justOrEmpty(resolveErrorIfPossible(exception.getRawStatusCode()))
|
||||||
|
.flatMap(oauth2Error -> {
|
||||||
|
Map<String, Object> attrs = request.attributes();
|
||||||
|
OAuth2AuthorizedClient authorizedClient = getOAuth2AuthorizedClient(attrs);
|
||||||
|
if (authorizedClient == null) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientAuthorizationException authorizationException = new ClientAuthorizationException(
|
||||||
|
oauth2Error, authorizedClient.getClientRegistration().getRegistrationId(), exception);
|
||||||
|
|
||||||
|
Authentication principal = new PrincipalNameAuthentication(authorizedClient.getPrincipalName());
|
||||||
|
HttpServletRequest servletRequest = getRequest(attrs);
|
||||||
|
HttpServletResponse servletResponse = getResponse(attrs);
|
||||||
|
|
||||||
|
return handleAuthorizationFailure(authorizationException, principal, servletRequest, servletResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the given {@link OAuth2AuthorizationException} that occurred downstream
|
||||||
|
* by notifying the authorization failure handler.
|
||||||
|
*
|
||||||
|
* @param request the request being processed
|
||||||
|
* @param authorizationException the authorization exception to include in the failure event
|
||||||
|
* @return a {@link Mono} that completes empty after the authorization failure handler completes
|
||||||
|
*/
|
||||||
|
private Mono<Void> handleAuthorizationException(ClientRequest request, OAuth2AuthorizationException authorizationException) {
|
||||||
|
return Mono.justOrEmpty(request)
|
||||||
|
.flatMap(req -> {
|
||||||
|
Map<String, Object> attrs = req.attributes();
|
||||||
|
OAuth2AuthorizedClient authorizedClient = getOAuth2AuthorizedClient(attrs);
|
||||||
|
if (authorizedClient == null) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication principal = new PrincipalNameAuthentication(authorizedClient.getPrincipalName());
|
||||||
|
HttpServletRequest servletRequest = getRequest(attrs);
|
||||||
|
HttpServletResponse servletResponse = getResponse(attrs);
|
||||||
|
|
||||||
|
return handleAuthorizationFailure(authorizationException, principal, servletRequest, servletResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates the failed authorization to the {@link OAuth2AuthorizationFailureHandler}.
|
||||||
|
*
|
||||||
|
* @param exception the {@link OAuth2AuthorizationException} to include in the failure event
|
||||||
|
* @param principal the principal associated with the failed authorization attempt
|
||||||
|
* @param servletRequest the currently active {@code HttpServletRequest}
|
||||||
|
* @param servletResponse the currently active {@code HttpServletResponse}
|
||||||
|
* @return a {@link Mono} that completes empty after the {@link OAuth2AuthorizationFailureHandler} completes
|
||||||
|
*/
|
||||||
|
private Mono<Void> handleAuthorizationFailure(OAuth2AuthorizationException exception,
|
||||||
|
Authentication principal, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
|
||||||
|
Runnable runnable = () -> this.authorizationFailureHandler.onAuthorizationFailure(
|
||||||
|
exception, principal, createAttributes(servletRequest, servletResponse));
|
||||||
|
return Mono.fromRunnable(runnable).subscribeOn(Schedulers.boundedElastic()).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> createAttributes(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
|
||||||
|
Map<String, Object> attributes = new HashMap<>();
|
||||||
|
attributes.put(HttpServletRequest.class.getName(), servletRequest);
|
||||||
|
attributes.put(HttpServletResponse.class.getName(), servletResponse);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,10 @@ import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.client.web.RemoveAuthorizedClientOAuth2AuthorizationFailureHandler;
|
||||||
|
import org.springframework.security.oauth2.client.web.SaveAuthorizedClientOAuth2AuthorizationSuccessHandler;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
||||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
@ -30,10 +34,16 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link AuthorizedClientServiceOAuth2AuthorizedClientManager}.
|
* Tests for {@link AuthorizedClientServiceOAuth2AuthorizedClientManager}.
|
||||||
|
@ -45,6 +55,8 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
private OAuth2AuthorizedClientService authorizedClientService;
|
private OAuth2AuthorizedClientService authorizedClientService;
|
||||||
private OAuth2AuthorizedClientProvider authorizedClientProvider;
|
private OAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||||
private Function contextAttributesMapper;
|
private Function contextAttributesMapper;
|
||||||
|
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
|
||||||
|
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||||
private AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager;
|
private AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager;
|
||||||
private ClientRegistration clientRegistration;
|
private ClientRegistration clientRegistration;
|
||||||
private Authentication principal;
|
private Authentication principal;
|
||||||
|
@ -58,10 +70,14 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
this.authorizedClientService = mock(OAuth2AuthorizedClientService.class);
|
this.authorizedClientService = mock(OAuth2AuthorizedClientService.class);
|
||||||
this.authorizedClientProvider = mock(OAuth2AuthorizedClientProvider.class);
|
this.authorizedClientProvider = mock(OAuth2AuthorizedClientProvider.class);
|
||||||
this.contextAttributesMapper = mock(Function.class);
|
this.contextAttributesMapper = mock(Function.class);
|
||||||
|
this.authorizationSuccessHandler = spy(new SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(this.authorizedClientService));
|
||||||
|
this.authorizationFailureHandler = spy(new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(this.authorizedClientService));
|
||||||
this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
|
this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
|
||||||
this.clientRegistrationRepository, this.authorizedClientService);
|
this.clientRegistrationRepository, this.authorizedClientService);
|
||||||
this.authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);
|
this.authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);
|
||||||
this.authorizedClientManager.setContextAttributesMapper(this.contextAttributesMapper);
|
this.authorizedClientManager.setContextAttributesMapper(this.contextAttributesMapper);
|
||||||
|
this.authorizedClientManager.setAuthorizationSuccessHandler(this.authorizationSuccessHandler);
|
||||||
|
this.authorizedClientManager.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
this.clientRegistration = TestClientRegistrations.clientRegistration().build();
|
this.clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
this.principal = new TestingAuthenticationToken("principal", "password");
|
this.principal = new TestingAuthenticationToken("principal", "password");
|
||||||
this.authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principal.getName(),
|
this.authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principal.getName(),
|
||||||
|
@ -97,6 +113,20 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
.hasMessage("contextAttributesMapper cannot be null");
|
.hasMessage("contextAttributesMapper cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAuthorizationSuccessHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.authorizedClientManager.setAuthorizationSuccessHandler(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorizationSuccessHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAuthorizationFailureHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.authorizedClientManager.setAuthorizationFailureHandler(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorizationFailureHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authorizeWhenRequestIsNullThenThrowIllegalArgumentException() {
|
public void authorizeWhenRequestIsNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> this.authorizedClientManager.authorize(null))
|
assertThatThrownBy(() -> this.authorizedClientManager.authorize(null))
|
||||||
|
@ -134,8 +164,8 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isNull();
|
assertThat(authorizedClient).isNull();
|
||||||
verify(this.authorizedClientService, never()).saveAuthorizedClient(
|
verifyNoInteractions(this.authorizationSuccessHandler);
|
||||||
any(OAuth2AuthorizedClient.class), eq(this.principal));
|
verify(this.authorizedClientService, never()).saveAuthorizedClient(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -160,6 +190,8 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(this.authorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientService).saveAuthorizedClient(
|
verify(this.authorizedClientService).saveAuthorizedClient(
|
||||||
eq(this.authorizedClient), eq(this.principal));
|
eq(this.authorizedClient), eq(this.principal));
|
||||||
}
|
}
|
||||||
|
@ -192,6 +224,8 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(reauthorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientService).saveAuthorizedClient(
|
verify(this.authorizedClientService).saveAuthorizedClient(
|
||||||
eq(reauthorizedClient), eq(this.principal));
|
eq(reauthorizedClient), eq(this.principal));
|
||||||
}
|
}
|
||||||
|
@ -213,8 +247,8 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
||||||
verify(this.authorizedClientService, never()).saveAuthorizedClient(
|
verifyNoInteractions(this.authorizationSuccessHandler);
|
||||||
any(OAuth2AuthorizedClient.class), eq(this.principal));
|
verify(this.authorizedClientService, never()).saveAuthorizedClient(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -240,6 +274,8 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(reauthorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientService).saveAuthorizedClient(
|
verify(this.authorizedClientService).saveAuthorizedClient(
|
||||||
eq(reauthorizedClient), eq(this.principal));
|
eq(reauthorizedClient), eq(this.principal));
|
||||||
}
|
}
|
||||||
|
@ -274,7 +310,52 @@ public class AuthorizedClientServiceOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(requestScopeAttribute).contains("read", "write");
|
assertThat(requestScopeAttribute).contains("read", "write");
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(reauthorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientService).saveAuthorizedClient(
|
verify(this.authorizedClientService).saveAuthorizedClient(
|
||||||
eq(reauthorizedClient), eq(this.principal));
|
eq(reauthorizedClient), eq(this.principal));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reauthorizeWhenErrorCodeMatchThenRemoveAuthorizedClient() {
|
||||||
|
ClientAuthorizationException authorizationException = new ClientAuthorizationException(
|
||||||
|
new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT, null, null),
|
||||||
|
this.clientRegistration.getRegistrationId());
|
||||||
|
|
||||||
|
when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class)))
|
||||||
|
.thenThrow(authorizationException);
|
||||||
|
|
||||||
|
OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient)
|
||||||
|
.principal(this.principal)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatCode(() -> this.authorizedClientManager.authorize(reauthorizeRequest))
|
||||||
|
.isEqualTo(authorizationException);
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
eq(authorizationException), eq(this.principal), any());
|
||||||
|
verify(this.authorizedClientService).removeAuthorizedClient(
|
||||||
|
eq(this.clientRegistration.getRegistrationId()), eq(this.principal.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reauthorizeWhenErrorCodeDoesNotMatchThenDoNotRemoveAuthorizedClient() {
|
||||||
|
ClientAuthorizationException authorizationException = new ClientAuthorizationException(
|
||||||
|
new OAuth2Error("non-matching-error-code", null, null),
|
||||||
|
this.clientRegistration.getRegistrationId());
|
||||||
|
|
||||||
|
when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class)))
|
||||||
|
.thenThrow(authorizationException);
|
||||||
|
|
||||||
|
OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient)
|
||||||
|
.principal(this.principal)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatCode(() -> this.authorizedClientManager.authorize(reauthorizeRequest))
|
||||||
|
.isEqualTo(authorizationException);
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
eq(authorizationException), eq(this.principal), any());
|
||||||
|
verifyNoInteractions(this.authorizedClientService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -22,13 +22,18 @@ import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationSuccessHandler;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
||||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
@ -41,8 +46,16 @@ import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link DefaultOAuth2AuthorizedClientManager}.
|
* Tests for {@link DefaultOAuth2AuthorizedClientManager}.
|
||||||
|
@ -54,6 +67,8 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
private OAuth2AuthorizedClientRepository authorizedClientRepository;
|
private OAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||||
private OAuth2AuthorizedClientProvider authorizedClientProvider;
|
private OAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||||
private Function contextAttributesMapper;
|
private Function contextAttributesMapper;
|
||||||
|
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
|
||||||
|
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||||
private DefaultOAuth2AuthorizedClientManager authorizedClientManager;
|
private DefaultOAuth2AuthorizedClientManager authorizedClientManager;
|
||||||
private ClientRegistration clientRegistration;
|
private ClientRegistration clientRegistration;
|
||||||
private Authentication principal;
|
private Authentication principal;
|
||||||
|
@ -69,10 +84,14 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
this.authorizedClientRepository = mock(OAuth2AuthorizedClientRepository.class);
|
this.authorizedClientRepository = mock(OAuth2AuthorizedClientRepository.class);
|
||||||
this.authorizedClientProvider = mock(OAuth2AuthorizedClientProvider.class);
|
this.authorizedClientProvider = mock(OAuth2AuthorizedClientProvider.class);
|
||||||
this.contextAttributesMapper = mock(Function.class);
|
this.contextAttributesMapper = mock(Function.class);
|
||||||
|
this.authorizationSuccessHandler = spy(new SaveAuthorizedClientOAuth2AuthorizationSuccessHandler(this.authorizedClientRepository));
|
||||||
|
this.authorizationFailureHandler = spy(new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(this.authorizedClientRepository));
|
||||||
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||||
this.authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);
|
this.authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);
|
||||||
this.authorizedClientManager.setContextAttributesMapper(this.contextAttributesMapper);
|
this.authorizedClientManager.setContextAttributesMapper(this.contextAttributesMapper);
|
||||||
|
this.authorizedClientManager.setAuthorizationSuccessHandler(this.authorizationSuccessHandler);
|
||||||
|
this.authorizedClientManager.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
this.clientRegistration = TestClientRegistrations.clientRegistration().build();
|
this.clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
this.principal = new TestingAuthenticationToken("principal", "password");
|
this.principal = new TestingAuthenticationToken("principal", "password");
|
||||||
this.authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principal.getName(),
|
this.authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principal.getName(),
|
||||||
|
@ -110,6 +129,20 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
.hasMessage("contextAttributesMapper cannot be null");
|
.hasMessage("contextAttributesMapper cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAuthorizationSuccessHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.authorizedClientManager.setAuthorizationSuccessHandler(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorizationSuccessHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setAuthorizationFailureHandlerWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.authorizedClientManager.setAuthorizationFailureHandler(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("authorizationFailureHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authorizeWhenRequestIsNullThenThrowIllegalArgumentException() {
|
public void authorizeWhenRequestIsNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> this.authorizedClientManager.authorize(null))
|
assertThatThrownBy(() -> this.authorizedClientManager.authorize(null))
|
||||||
|
@ -176,8 +209,8 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isNull();
|
assertThat(authorizedClient).isNull();
|
||||||
verify(this.authorizedClientRepository, never()).saveAuthorizedClient(
|
verifyNoInteractions(this.authorizationSuccessHandler);
|
||||||
any(OAuth2AuthorizedClient.class), eq(this.principal), eq(this.request), eq(this.response));
|
verify(this.authorizedClientRepository, never()).saveAuthorizedClient(any(), any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -206,6 +239,8 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(this.authorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientRepository).saveAuthorizedClient(
|
verify(this.authorizedClientRepository).saveAuthorizedClient(
|
||||||
eq(this.authorizedClient), eq(this.principal), eq(this.request), eq(this.response));
|
eq(this.authorizedClient), eq(this.principal), eq(this.request), eq(this.response));
|
||||||
}
|
}
|
||||||
|
@ -242,6 +277,8 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(reauthorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientRepository).saveAuthorizedClient(
|
verify(this.authorizedClientRepository).saveAuthorizedClient(
|
||||||
eq(reauthorizedClient), eq(this.principal), eq(this.request), eq(this.response));
|
eq(reauthorizedClient), eq(this.principal), eq(this.request), eq(this.response));
|
||||||
}
|
}
|
||||||
|
@ -308,6 +345,7 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
assertThat(authorizedClient).isSameAs(this.authorizedClient);
|
||||||
|
verifyNoInteractions(this.authorizationSuccessHandler);
|
||||||
verify(this.authorizedClientRepository, never()).saveAuthorizedClient(
|
verify(this.authorizedClientRepository, never()).saveAuthorizedClient(
|
||||||
any(OAuth2AuthorizedClient.class), eq(this.principal), eq(this.request), eq(this.response));
|
any(OAuth2AuthorizedClient.class), eq(this.principal), eq(this.request), eq(this.response));
|
||||||
}
|
}
|
||||||
|
@ -339,6 +377,8 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal);
|
||||||
|
|
||||||
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
assertThat(authorizedClient).isSameAs(reauthorizedClient);
|
||||||
|
verify(this.authorizationSuccessHandler).onAuthorizationSuccess(
|
||||||
|
eq(reauthorizedClient), eq(this.principal), any());
|
||||||
verify(this.authorizedClientRepository).saveAuthorizedClient(
|
verify(this.authorizedClientRepository).saveAuthorizedClient(
|
||||||
eq(reauthorizedClient), eq(this.principal), eq(this.request), eq(this.response));
|
eq(reauthorizedClient), eq(this.principal), eq(this.request), eq(this.response));
|
||||||
}
|
}
|
||||||
|
@ -372,4 +412,55 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||||
String[] requestScopeAttribute = authorizationContext.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME);
|
String[] requestScopeAttribute = authorizationContext.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME);
|
||||||
assertThat(requestScopeAttribute).contains("read", "write");
|
assertThat(requestScopeAttribute).contains("read", "write");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reauthorizeWhenErrorCodeMatchThenRemoveAuthorizedClient() {
|
||||||
|
ClientAuthorizationException authorizationException = new ClientAuthorizationException(
|
||||||
|
new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT, null, null),
|
||||||
|
this.clientRegistration.getRegistrationId());
|
||||||
|
|
||||||
|
when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class)))
|
||||||
|
.thenThrow(authorizationException);
|
||||||
|
|
||||||
|
OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient)
|
||||||
|
.principal(this.principal)
|
||||||
|
.attributes(attrs -> {
|
||||||
|
attrs.put(HttpServletRequest.class.getName(), this.request);
|
||||||
|
attrs.put(HttpServletResponse.class.getName(), this.response);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatCode(() -> this.authorizedClientManager.authorize(reauthorizeRequest))
|
||||||
|
.isEqualTo(authorizationException);
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
eq(authorizationException), eq(this.principal), any());
|
||||||
|
verify(this.authorizedClientRepository).removeAuthorizedClient(
|
||||||
|
eq(this.clientRegistration.getRegistrationId()), eq(this.principal), eq(this.request), eq(this.response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reauthorizeWhenErrorCodeDoesNotMatchThenDoNotRemoveAuthorizedClient() {
|
||||||
|
ClientAuthorizationException authorizationException = new ClientAuthorizationException(
|
||||||
|
new OAuth2Error("non-matching-error-code", null, null),
|
||||||
|
this.clientRegistration.getRegistrationId());
|
||||||
|
|
||||||
|
when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class)))
|
||||||
|
.thenThrow(authorizationException);
|
||||||
|
|
||||||
|
OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient)
|
||||||
|
.principal(this.principal)
|
||||||
|
.attributes(attrs -> {
|
||||||
|
attrs.put(HttpServletRequest.class.getName(), this.request);
|
||||||
|
attrs.put(HttpServletResponse.class.getName(), this.response);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatCode(() -> this.authorizedClientManager.authorize(reauthorizeRequest))
|
||||||
|
.isEqualTo(authorizationException);
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
eq(authorizationException), eq(this.principal), any());
|
||||||
|
verifyNoInteractions(this.authorizedClientRepository);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,18 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.client.web.reactive.function.client;
|
package org.springframework.security.oauth2.client.web.reactive.function.client;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -35,8 +23,6 @@ import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import reactor.util.context.Context;
|
|
||||||
|
|
||||||
import org.springframework.core.codec.ByteBufferEncoder;
|
import org.springframework.core.codec.ByteBufferEncoder;
|
||||||
import org.springframework.core.codec.CharSequenceEncoder;
|
import org.springframework.core.codec.CharSequenceEncoder;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -60,7 +46,9 @@ import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||||
|
@ -78,6 +66,9 @@ import org.springframework.security.oauth2.client.registration.TestClientRegistr
|
||||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
@ -89,16 +80,37 @@ import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.reactive.function.BodyInserter;
|
import org.springframework.web.reactive.function.BodyInserter;
|
||||||
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.ExchangeFunction;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.http.HttpMethod.GET;
|
import static org.springframework.http.HttpMethod.GET;
|
||||||
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
|
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
|
||||||
|
@ -128,6 +140,14 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
@Mock
|
@Mock
|
||||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient;
|
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient;
|
||||||
@Mock
|
@Mock
|
||||||
|
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<OAuth2AuthorizationException> authorizationExceptionCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Authentication> authenticationCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Map<String, Object>> attributesCaptor;
|
||||||
|
@Mock
|
||||||
private WebClient.RequestHeadersSpec<?> spec;
|
private WebClient.RequestHeadersSpec<?> spec;
|
||||||
@Captor
|
@Captor
|
||||||
private ArgumentCaptor<Consumer<Map<String, Object>>> attrs;
|
private ArgumentCaptor<Consumer<Map<String, Object>>> attrs;
|
||||||
|
@ -167,7 +187,7 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||||
this.authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
this.authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||||
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
|
this.function = new ServletOAuth2AuthorizedClientExchangeFilterFunction(this.authorizedClientManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -233,7 +253,7 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
SecurityContextHolder.getContext().setAuthentication(this.authentication);
|
SecurityContextHolder.getContext().setAuthentication(this.authentication);
|
||||||
Map<String, Object> attrs = getDefaultRequestAttributes();
|
Map<String, Object> attrs = getDefaultRequestAttributes();
|
||||||
assertThat(getAuthentication(attrs)).isEqualTo(this.authentication);
|
assertThat(getAuthentication(attrs)).isEqualTo(this.authentication);
|
||||||
verifyZeroInteractions(this.authorizedClientRepository);
|
verifyNoInteractions(this.authorizedClientRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> getDefaultRequestAttributes() {
|
private Map<String, Object> getDefaultRequestAttributes() {
|
||||||
|
@ -647,6 +667,215 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
assertThat(getBody(request)).isEmpty();
|
assertThat(getBody(request)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenUnauthorizedThenInvokeFailureHandler() {
|
||||||
|
assertHttpStatusInvokesFailureHandler(HttpStatus.UNAUTHORIZED, OAuth2ErrorCodes.INVALID_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenForbiddenThenInvokeFailureHandler() {
|
||||||
|
assertHttpStatusInvokesFailureHandler(HttpStatus.FORBIDDEN, OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHttpStatusInvokesFailureHandler(HttpStatus httpStatus, String expectedErrorCode) {
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||||
|
this.registration, "principalName", this.accessToken);
|
||||||
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||||
|
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
|
||||||
|
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||||
|
.attributes(httpServletRequest(servletRequest))
|
||||||
|
.attributes(httpServletResponse(servletResponse))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
when(this.exchange.getResponse().rawStatusCode()).thenReturn(httpStatus.value());
|
||||||
|
when(this.exchange.getResponse().headers()).thenReturn(mock(ClientResponse.Headers.class));
|
||||||
|
this.function.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
|
|
||||||
|
this.function.filter(request, this.exchange).block();
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
this.authorizationExceptionCaptor.capture(),
|
||||||
|
this.authenticationCaptor.capture(),
|
||||||
|
this.attributesCaptor.capture());
|
||||||
|
|
||||||
|
assertThat(this.authorizationExceptionCaptor.getValue())
|
||||||
|
.isInstanceOfSatisfying(ClientAuthorizationException.class, e -> {
|
||||||
|
assertThat(e.getClientRegistrationId()).isEqualTo(this.registration.getRegistrationId());
|
||||||
|
assertThat(e.getError().getErrorCode()).isEqualTo(expectedErrorCode);
|
||||||
|
assertThat(e).hasNoCause();
|
||||||
|
assertThat(e).hasMessageContaining(expectedErrorCode);
|
||||||
|
});
|
||||||
|
assertThat(this.authenticationCaptor.getValue().getName())
|
||||||
|
.isEqualTo(authorizedClient.getPrincipalName());
|
||||||
|
assertThat(this.attributesCaptor.getValue())
|
||||||
|
.containsExactly(
|
||||||
|
entry(HttpServletRequest.class.getName(), servletRequest),
|
||||||
|
entry(HttpServletResponse.class.getName(), servletResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenWWWAuthenticateHeaderIncludesErrorThenInvokeFailureHandler() {
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||||
|
this.registration, "principalName", this.accessToken);
|
||||||
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||||
|
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
|
||||||
|
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||||
|
.attributes(httpServletRequest(servletRequest))
|
||||||
|
.attributes(httpServletResponse(servletResponse))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String wwwAuthenticateHeader = "Bearer error=\"insufficient_scope\", " +
|
||||||
|
"error_description=\"The request requires higher privileges than provided by the access token.\", " +
|
||||||
|
"error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"";
|
||||||
|
ClientResponse.Headers headers = mock(ClientResponse.Headers.class);
|
||||||
|
when(headers.header(eq(HttpHeaders.WWW_AUTHENTICATE)))
|
||||||
|
.thenReturn(Collections.singletonList(wwwAuthenticateHeader));
|
||||||
|
when(this.exchange.getResponse().headers()).thenReturn(headers);
|
||||||
|
this.function.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
|
|
||||||
|
this.function.filter(request, this.exchange).block();
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
this.authorizationExceptionCaptor.capture(),
|
||||||
|
this.authenticationCaptor.capture(),
|
||||||
|
this.attributesCaptor.capture());
|
||||||
|
|
||||||
|
assertThat(this.authorizationExceptionCaptor.getValue())
|
||||||
|
.isInstanceOfSatisfying(ClientAuthorizationException.class, e -> {
|
||||||
|
assertThat(e.getClientRegistrationId()).isEqualTo(this.registration.getRegistrationId());
|
||||||
|
assertThat(e.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
|
assertThat(e.getError().getDescription()).isEqualTo("The request requires higher privileges than provided by the access token.");
|
||||||
|
assertThat(e.getError().getUri()).isEqualTo("https://tools.ietf.org/html/rfc6750#section-3.1");
|
||||||
|
assertThat(e).hasNoCause();
|
||||||
|
assertThat(e).hasMessageContaining(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
|
});
|
||||||
|
assertThat(this.authenticationCaptor.getValue().getName())
|
||||||
|
.isEqualTo(authorizedClient.getPrincipalName());
|
||||||
|
assertThat(this.attributesCaptor.getValue())
|
||||||
|
.containsExactly(
|
||||||
|
entry(HttpServletRequest.class.getName(), servletRequest),
|
||||||
|
entry(HttpServletResponse.class.getName(), servletResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenUnauthorizedWithWebClientExceptionThenInvokeFailureHandler() {
|
||||||
|
assertHttpStatusWithWebClientExceptionInvokesFailureHandler(
|
||||||
|
HttpStatus.UNAUTHORIZED, OAuth2ErrorCodes.INVALID_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenForbiddenWithWebClientExceptionThenInvokeFailureHandler() {
|
||||||
|
assertHttpStatusWithWebClientExceptionInvokesFailureHandler(
|
||||||
|
HttpStatus.FORBIDDEN, OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHttpStatusWithWebClientExceptionInvokesFailureHandler(
|
||||||
|
HttpStatus httpStatus, String expectedErrorCode) {
|
||||||
|
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||||
|
this.registration, "principalName", this.accessToken);
|
||||||
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||||
|
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
|
||||||
|
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||||
|
.attributes(httpServletRequest(servletRequest))
|
||||||
|
.attributes(httpServletResponse(servletResponse))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
WebClientResponseException exception = WebClientResponseException.create(
|
||||||
|
httpStatus.value(),
|
||||||
|
httpStatus.getReasonPhrase(),
|
||||||
|
HttpHeaders.EMPTY,
|
||||||
|
new byte[0],
|
||||||
|
StandardCharsets.UTF_8);
|
||||||
|
ExchangeFunction throwingExchangeFunction = r -> Mono.error(exception);
|
||||||
|
this.function.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
|
|
||||||
|
assertThatCode(() -> this.function.filter(request, throwingExchangeFunction).block())
|
||||||
|
.isEqualTo(exception);
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
this.authorizationExceptionCaptor.capture(),
|
||||||
|
this.authenticationCaptor.capture(),
|
||||||
|
this.attributesCaptor.capture());
|
||||||
|
|
||||||
|
assertThat(this.authorizationExceptionCaptor.getValue())
|
||||||
|
.isInstanceOfSatisfying(ClientAuthorizationException.class, e -> {
|
||||||
|
assertThat(e.getClientRegistrationId()).isEqualTo(this.registration.getRegistrationId());
|
||||||
|
assertThat(e.getError().getErrorCode()).isEqualTo(expectedErrorCode);
|
||||||
|
assertThat(e).hasCause(exception);
|
||||||
|
assertThat(e).hasMessageContaining(expectedErrorCode);
|
||||||
|
});
|
||||||
|
assertThat(this.authenticationCaptor.getValue().getName())
|
||||||
|
.isEqualTo(authorizedClient.getPrincipalName());
|
||||||
|
assertThat(this.attributesCaptor.getValue())
|
||||||
|
.containsExactly(
|
||||||
|
entry(HttpServletRequest.class.getName(), servletRequest),
|
||||||
|
entry(HttpServletResponse.class.getName(), servletResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenAuthorizationExceptionThenInvokeFailureHandler() {
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||||
|
this.registration, "principalName", this.accessToken);
|
||||||
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||||
|
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
|
||||||
|
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||||
|
.attributes(httpServletRequest(servletRequest))
|
||||||
|
.attributes(httpServletResponse(servletResponse))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
OAuth2AuthorizationException authorizationException = new OAuth2AuthorizationException(
|
||||||
|
new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN));
|
||||||
|
ExchangeFunction throwingExchangeFunction = r -> Mono.error(authorizationException);
|
||||||
|
this.function.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
|
|
||||||
|
assertThatCode(() -> this.function.filter(request, throwingExchangeFunction).block())
|
||||||
|
.isEqualTo(authorizationException);
|
||||||
|
|
||||||
|
verify(this.authorizationFailureHandler).onAuthorizationFailure(
|
||||||
|
this.authorizationExceptionCaptor.capture(),
|
||||||
|
this.authenticationCaptor.capture(),
|
||||||
|
this.attributesCaptor.capture());
|
||||||
|
|
||||||
|
assertThat(this.authorizationExceptionCaptor.getValue())
|
||||||
|
.isInstanceOfSatisfying(OAuth2AuthorizationException.class, e -> {
|
||||||
|
assertThat(e.getError().getErrorCode()).isEqualTo(authorizationException.getError().getErrorCode());
|
||||||
|
assertThat(e).hasNoCause();
|
||||||
|
assertThat(e).hasMessageContaining(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||||
|
});
|
||||||
|
assertThat(this.authenticationCaptor.getValue().getName())
|
||||||
|
.isEqualTo(authorizedClient.getPrincipalName());
|
||||||
|
assertThat(this.attributesCaptor.getValue())
|
||||||
|
.containsExactly(
|
||||||
|
entry(HttpServletRequest.class.getName(), servletRequest),
|
||||||
|
entry(HttpServletResponse.class.getName(), servletResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenOtherHttpStatusThenDoesNotInvokeFailureHandler() {
|
||||||
|
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
|
||||||
|
this.registration, "principalName", this.accessToken);
|
||||||
|
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||||
|
ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com"))
|
||||||
|
.attributes(oauth2AuthorizedClient(authorizedClient))
|
||||||
|
.attributes(httpServletRequest(servletRequest))
|
||||||
|
.attributes(httpServletResponse(servletResponse))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
when(this.exchange.getResponse().rawStatusCode()).thenReturn(HttpStatus.BAD_REQUEST.value());
|
||||||
|
when(this.exchange.getResponse().headers()).thenReturn(mock(ClientResponse.Headers.class));
|
||||||
|
this.function.setAuthorizationFailureHandler(this.authorizationFailureHandler);
|
||||||
|
|
||||||
|
this.function.filter(request, this.exchange).block();
|
||||||
|
|
||||||
|
verifyNoInteractions(this.authorizationFailureHandler);
|
||||||
|
}
|
||||||
|
|
||||||
private Context context(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Authentication authentication) {
|
private Context context(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Authentication authentication) {
|
||||||
Map<Object, Object> contextAttributes = new HashMap<>();
|
Map<Object, Object> contextAttributes = new HashMap<>();
|
||||||
contextAttributes.put(HttpServletRequest.class, servletRequest);
|
contextAttributes.put(HttpServletRequest.class, servletRequest);
|
||||||
|
@ -688,5 +917,4 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||||
request.body().insert(body, context).block();
|
request.body().insert(body, context).block();
|
||||||
return body.getBodyAsString().block();
|
return body.getBodyAsString().block();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue