Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.reactive.function.client;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.context.Context;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
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.OAuth2AuthorizationFailureHandler;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RemoveAuthorizedClientOAuth2AuthorizationFailureHandler;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
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.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
/**
* Provides an easy mechanism for using an {@link OAuth2AuthorizedClient} to make OAuth
* 2.0 requests by including the {@link OAuth2AuthorizedClient#getAccessToken() access
* token} as a bearer token.
*
*
* NOTE:This class is intended to be used in a {@code Servlet} environment.
*
*
* Since 5.3, this filter function has the ability to forward authentication (HTTP 401
* Unauthorized) and authorization (HTTP 403 Forbidden) failures from an OAuth 2.0
* Resource Server to a {@link OAuth2AuthorizationFailureHandler}. A
* {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} can be used to remove
* the cached {@link OAuth2AuthorizedClient}, so that future requests will result in a new
* token being retrieved from an Authorization Server, and sent to the Resource Server.
*
*
* If the
* {@link #ServletOAuth2AuthorizedClientExchangeFilterFunction(ClientRegistrationRepository, OAuth2AuthorizedClientRepository)}
* constructor is used, a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler}
* will be configured automatically.
*
*
* If the
* {@link #ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager)}
* constructor is used, a {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler}
* will NOT be configured automatically. It is recommended that you configure one
* via {@link #setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)}.
*
* @author Rob Winch
* @author Joe Grandja
* @author Roman Matiushchenko
* @since 5.1
* @see OAuth2AuthorizedClientManager
* @see DefaultOAuth2AuthorizedClientManager
* @see OAuth2AuthorizedClientProvider
* @see OAuth2AuthorizedClientProviderBuilder
*/
public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
// Same key as in
// SecurityReactorContextConfiguration.SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES
static final String SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY = "org.springframework.security.SECURITY_CONTEXT_ATTRIBUTES";
/**
* 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 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 HTTP_SERVLET_REQUEST_ATTR_NAME = HttpServletRequest.class.getName();
private static final String HTTP_SERVLET_RESPONSE_ATTR_NAME = HttpServletResponse.class.getName();
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
@Deprecated
private Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
@Deprecated
private OAuth2AccessTokenResponseClient clientCredentialsTokenResponseClient;
private OAuth2AuthorizedClientManager authorizedClientManager;
private boolean defaultAuthorizedClientManager;
private boolean defaultOAuth2AuthorizedClient;
private String defaultClientRegistrationId;
private ClientResponseHandler clientResponseHandler;
public ServletOAuth2AuthorizedClientExchangeFilterFunction() {
}
/**
* Constructs a {@code ServletOAuth2AuthorizedClientExchangeFilterFunction} using the
* provided parameters.
*
*
* When this constructor is used, authentication (HTTP 401) and authorization (HTTP
* 403) failures returned from an OAuth 2.0 Resource Server will NOT 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.
* @param authorizedClientManager the {@link OAuth2AuthorizedClientManager} which
* manages the authorized client(s)
* @since 5.2
*/
public ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager authorizedClientManager) {
Assert.notNull(authorizedClientManager, "authorizedClientManager cannot be null");
this.authorizedClientManager = authorizedClientManager;
this.clientResponseHandler = (request, responseMono) -> responseMono;
}
/**
* Constructs a {@code ServletOAuth2AuthorizedClientExchangeFilterFunction} using the
* provided parameters.
*
*
* 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 authorizedClientRepository the repository of authorized clients
*/
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizationFailureHandler authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
(clientRegistrationId, principal, attributes) -> removeAuthorizedClient(authorizedClientRepository,
clientRegistrationId, principal, attributes));
DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
defaultAuthorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
this.authorizedClientManager = defaultAuthorizedClientManager;
this.defaultAuthorizedClientManager = true;
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
}
private void removeAuthorizedClient(OAuth2AuthorizedClientRepository authorizedClientRepository,
String clientRegistrationId, Authentication principal, Map attributes) {
HttpServletRequest request = getRequest(attributes);
HttpServletResponse response = getResponse(attributes);
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal, request, response);
}
/**
* Sets the {@link OAuth2AccessTokenResponseClient} used for getting an
* {@link OAuth2AuthorizedClient} for the client_credentials grant.
* @param clientCredentialsTokenResponseClient the client to use
* @deprecated Use
* {@link #ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager)}
* instead. Create an instance of
* {@link ClientCredentialsOAuth2AuthorizedClientProvider} configured with a
* {@link ClientCredentialsOAuth2AuthorizedClientProvider#setAccessTokenResponseClient(OAuth2AccessTokenResponseClient)
* DefaultClientCredentialsTokenResponseClient} (or a custom one) and than supply it
* to
* {@link DefaultOAuth2AuthorizedClientManager#setAuthorizedClientProvider(OAuth2AuthorizedClientProvider)
* DefaultOAuth2AuthorizedClientManager}.
*/
@Deprecated
public void setClientCredentialsTokenResponseClient(
OAuth2AccessTokenResponseClient clientCredentialsTokenResponseClient) {
Assert.notNull(clientCredentialsTokenResponseClient, "clientCredentialsTokenResponseClient cannot be null");
Assert.state(this.defaultAuthorizedClientManager,
"The client cannot be set when the constructor used is \"ServletOAuth2AuthorizedClientExchangeFilterFunction(OAuth2AuthorizedClientManager)\". "
+ "Instead, use the constructor \"ServletOAuth2AuthorizedClientExchangeFilterFunction(ClientRegistrationRepository, OAuth2AuthorizedClientRepository)\".");
this.clientCredentialsTokenResponseClient = clientCredentialsTokenResponseClient;
updateDefaultAuthorizedClientManager();
}
private void updateDefaultAuthorizedClientManager() {
// @formatter:off
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken((configurer) -> configurer.clockSkew(this.accessTokenExpiresSkew))
.clientCredentials(this::updateClientCredentialsProvider)
.password((configurer) -> configurer.clockSkew(this.accessTokenExpiresSkew))
.build();
// @formatter:on
((DefaultOAuth2AuthorizedClientManager) this.authorizedClientManager)
.setAuthorizedClientProvider(authorizedClientProvider);
}
private void updateClientCredentialsProvider(
OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilder builder) {
if (this.clientCredentialsTokenResponseClient != null) {
builder.accessTokenResponseClient(this.clientCredentialsTokenResponseClient);
}
builder.clockSkew(this.accessTokenExpiresSkew);
}
/**
* If true, a default {@link OAuth2AuthorizedClient} can be discovered from the
* current Authentication. It is recommended to be cautious with this feature since
* all HTTP requests will receive the access token if it can be resolved from the
* current Authentication.
* @param defaultOAuth2AuthorizedClient true if a default
* {@link OAuth2AuthorizedClient} should be used, else false. Default is false.
*/
public void setDefaultOAuth2AuthorizedClient(boolean defaultOAuth2AuthorizedClient) {
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
}
/**
* If set, will be used as the default {@link ClientRegistration#getRegistrationId()}.
* It is recommended to be cautious with this feature since all HTTP requests will
* receive the access token.
* @param clientRegistrationId the id to use
*/
public void setDefaultClientRegistrationId(String clientRegistrationId) {
this.defaultClientRegistrationId = clientRegistrationId;
}
/**
* Configures the builder with {@link #defaultRequest()} and adds this as a
* {@link ExchangeFilterFunction}
* @return the {@link Consumer} to configure the builder
*/
public Consumer oauth2Configuration() {
return (builder) -> builder.defaultRequest(defaultRequest()).filter(this);
}
/**
* Provides defaults for the {@link HttpServletRequest} and the
* {@link HttpServletResponse} using {@link RequestContextHolder}. It also provides
* defaults for the {@link Authentication} using {@link SecurityContextHolder}. It
* also can default the {@link OAuth2AuthorizedClient} using the
* {@link #clientRegistrationId(String)} or the
* {@link #authentication(Authentication)}.
* @return the {@link Consumer} to populate the attributes
*/
public Consumer> defaultRequest() {
return (spec) -> spec.attributes((attrs) -> {
populateDefaultRequestResponse(attrs);
populateDefaultAuthentication(attrs);
});
}
/**
* Modifies the {@link ClientRequest#attributes()} to include the
* {@link OAuth2AuthorizedClient} to be used for providing the Bearer Token.
* @param authorizedClient the {@link OAuth2AuthorizedClient} to use.
* @return the {@link Consumer} to populate the attributes
*/
public static Consumer