Merge Formatting Changes
Issue gh-8945
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -21,8 +22,8 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant.
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -31,24 +32,27 @@ import org.springframework.util.Assert;
|
||||
public final class AuthorizationCodeOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
|
||||
|
||||
/**
|
||||
* Attempt to authorize the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns {@code null} if authorization is not supported,
|
||||
* e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() authorization grant type}
|
||||
* is not {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} OR the client is already authorized.
|
||||
*
|
||||
* Attempt to authorize the {@link OAuth2AuthorizationContext#getClientRegistration()
|
||||
* client} in the provided {@code context}. Returns {@code null} if authorization is
|
||||
* not supported, e.g. the client's
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is
|
||||
* not {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} OR the
|
||||
* client is already authorized.
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not
|
||||
* supported
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getClientRegistration().getAuthorizationGrantType()) &&
|
||||
context.getAuthorizedClient() == null) {
|
||||
// ClientAuthorizationRequiredException is caught by OAuth2AuthorizationRequestRedirectFilter which initiates authorization
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
|
||||
context.getClientRegistration().getAuthorizationGrantType()) && context.getAuthorizedClient() == null) {
|
||||
// ClientAuthorizationRequiredException is caught by
|
||||
// OAuth2AuthorizationRequestRedirectFilter which initiates authorization
|
||||
throw new ClientAuthorizationRequiredException(context.getClientRegistration().getRegistrationId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,41 +13,48 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant.
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class AuthorizationCodeReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
public final class AuthorizationCodeReactiveOAuth2AuthorizedClientProvider
|
||||
implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
|
||||
/**
|
||||
* Attempt to authorize the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns an empty {@code Mono} if authorization is not supported,
|
||||
* e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() authorization grant type}
|
||||
* is not {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} OR the client is already authorized.
|
||||
*
|
||||
* Attempt to authorize the {@link OAuth2AuthorizationContext#getClientRegistration()
|
||||
* client} in the provided {@code context}. Returns an empty {@code Mono} if
|
||||
* authorization is not supported, e.g. the client's
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is
|
||||
* not {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} OR the
|
||||
* client is already authorized.
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* authorization is not supported
|
||||
*/
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getClientRegistration().getAuthorizationGrantType()) &&
|
||||
context.getAuthorizedClient() == null) {
|
||||
// ClientAuthorizationRequiredException is caught by OAuth2AuthorizationRequestRedirectWebFilter which initiates authorization
|
||||
return Mono.error(() -> new ClientAuthorizationRequiredException(context.getClientRegistration().getRegistrationId()));
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
|
||||
context.getClientRegistration().getAuthorizationGrantType()) && context.getAuthorizedClient() == null) {
|
||||
// ClientAuthorizationRequiredException is caught by
|
||||
// OAuth2AuthorizationRequestRedirectWebFilter which initiates authorization
|
||||
return Mono.error(() -> new ClientAuthorizationRequiredException(
|
||||
context.getClientRegistration().getRegistrationId()));
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -27,41 +33,36 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientManager}
|
||||
* 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.
|
||||
* An implementation of an {@link OAuth2AuthorizedClientManager} 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.
|
||||
*
|
||||
* <p>
|
||||
* (When operating <em>within</em> the context of a {@code HttpServletRequest},
|
||||
* use {@link DefaultOAuth2AuthorizedClientManager} instead.)
|
||||
* (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.
|
||||
* 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)}.
|
||||
* 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)}.
|
||||
* {@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
|
||||
* @since 5.2
|
||||
@@ -72,95 +73,113 @@ import java.util.function.Function;
|
||||
* @see OAuth2AuthorizationFailureHandler
|
||||
*/
|
||||
public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
|
||||
private static final OAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.clientCredentials()
|
||||
.build();
|
||||
|
||||
private static final OAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER = OAuth2AuthorizedClientProviderBuilder
|
||||
.builder().clientCredentials().build();
|
||||
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private final OAuth2AuthorizedClientService authorizedClientService;
|
||||
|
||||
private OAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||
|
||||
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.
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
* @param authorizedClientService the authorized client service
|
||||
*/
|
||||
public AuthorizedClientServiceOAuth2AuthorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientService authorizedClientService) {
|
||||
public AuthorizedClientServiceOAuth2AuthorizedClientManager(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientService authorizedClientService) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
this.authorizedClientService = authorizedClientService;
|
||||
this.authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
|
||||
this.contextAttributesMapper = new DefaultContextAttributesMapper();
|
||||
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) ->
|
||||
authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
|
||||
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) -> authorizedClientService
|
||||
.saveAuthorizedClient(authorizedClient, principal);
|
||||
this.authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
|
||||
(clientRegistrationId, principal, attributes) ->
|
||||
authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName()));
|
||||
(clientRegistrationId, principal, attributes) -> authorizedClientService
|
||||
.removeAuthorizedClient(clientRegistrationId, principal.getName()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
|
||||
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
|
||||
|
||||
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
|
||||
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();
|
||||
Authentication principal = authorizeRequest.getPrincipal();
|
||||
|
||||
OAuth2AuthorizationContext.Builder contextBuilder;
|
||||
if (authorizedClient != null) {
|
||||
contextBuilder = OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient);
|
||||
} else {
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId);
|
||||
Assert.notNull(clientRegistration, "Could not find ClientRegistration with id '" + clientRegistrationId + "'");
|
||||
authorizedClient = this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName());
|
||||
}
|
||||
else {
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository
|
||||
.findByRegistrationId(clientRegistrationId);
|
||||
Assert.notNull(clientRegistration,
|
||||
"Could not find ClientRegistration with id '" + clientRegistrationId + "'");
|
||||
authorizedClient = this.authorizedClientService.loadAuthorizedClient(clientRegistrationId,
|
||||
principal.getName());
|
||||
if (authorizedClient != null) {
|
||||
contextBuilder = OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
contextBuilder = OAuth2AuthorizationContext.withClientRegistration(clientRegistration);
|
||||
}
|
||||
}
|
||||
OAuth2AuthorizationContext authorizationContext = contextBuilder
|
||||
.principal(principal)
|
||||
.attributes(attributes -> {
|
||||
OAuth2AuthorizationContext authorizationContext = buildAuthorizationContext(authorizeRequest, principal,
|
||||
contextBuilder);
|
||||
try {
|
||||
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
this.authorizationFailureHandler.onAuthorizationFailure(ex, principal, Collections.emptyMap());
|
||||
throw ex;
|
||||
}
|
||||
if (authorizedClient != null) {
|
||||
this.authorizationSuccessHandler.onAuthorizationSuccess(authorizedClient, principal,
|
||||
Collections.emptyMap());
|
||||
}
|
||||
else {
|
||||
// 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`.
|
||||
if (authorizationContext.getAuthorizedClient() != null) {
|
||||
return authorizationContext.getAuthorizedClient();
|
||||
}
|
||||
}
|
||||
return authorizedClient;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationContext buildAuthorizationContext(OAuth2AuthorizeRequest authorizeRequest,
|
||||
Authentication principal, OAuth2AuthorizationContext.Builder contextBuilder) {
|
||||
// @formatter:off
|
||||
return contextBuilder.principal(principal)
|
||||
.attributes((attributes) -> {
|
||||
Map<String, Object> contextAttributes = this.contextAttributesMapper.apply(authorizeRequest);
|
||||
if (!CollectionUtils.isEmpty(contextAttributes)) {
|
||||
attributes.putAll(contextAttributes);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
try {
|
||||
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
this.authorizationFailureHandler.onAuthorizationFailure(ex, principal, Collections.emptyMap());
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (authorizedClient != null) {
|
||||
this.authorizationSuccessHandler.onAuthorizationSuccess(
|
||||
authorizedClient, principal, Collections.emptyMap());
|
||||
} else {
|
||||
// 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`.
|
||||
if (authorizationContext.getAuthorizedClient() != null) {
|
||||
return authorizationContext.getAuthorizedClient();
|
||||
}
|
||||
}
|
||||
|
||||
return authorizedClient;
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client.
|
||||
*
|
||||
* @param authorizedClientProvider the {@link OAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client
|
||||
* Sets the {@link OAuth2AuthorizedClientProvider} used for authorizing (or
|
||||
* re-authorizing) an OAuth 2.0 Client.
|
||||
* @param authorizedClientProvider the {@link OAuth2AuthorizedClientProvider} used for
|
||||
* authorizing (or re-authorizing) an OAuth 2.0 Client
|
||||
*/
|
||||
public void setAuthorizedClientProvider(OAuth2AuthorizedClientProvider authorizedClientProvider) {
|
||||
Assert.notNull(authorizedClientProvider, "authorizedClientProvider cannot be null");
|
||||
@@ -168,24 +187,28 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Function} used for mapping attribute(s) from the {@link OAuth2AuthorizeRequest} to a {@code Map} of attributes
|
||||
* to be associated to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}.
|
||||
*
|
||||
* @param contextAttributesMapper the {@code Function} used for supplying the {@code Map} of attributes
|
||||
* to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}
|
||||
* Sets the {@code Function} used for mapping attribute(s) from the
|
||||
* {@link OAuth2AuthorizeRequest} to a {@code Map} of attributes to be associated to
|
||||
* the {@link OAuth2AuthorizationContext#getAttributes() authorization context}.
|
||||
* @param contextAttributesMapper the {@code Function} used for supplying the
|
||||
* {@code Map} of attributes to the {@link OAuth2AuthorizationContext#getAttributes()
|
||||
* authorization context}
|
||||
*/
|
||||
public void setContextAttributesMapper(Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper) {
|
||||
public void setContextAttributesMapper(
|
||||
Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper) {
|
||||
Assert.notNull(contextAttributesMapper, "contextAttributesMapper cannot be null");
|
||||
this.contextAttributesMapper = contextAttributesMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations.
|
||||
* Sets the {@link OAuth2AuthorizationSuccessHandler} that handles successful
|
||||
* authorizations.
|
||||
*
|
||||
* <p>
|
||||
* The default saves {@link OAuth2AuthorizedClient}s in the {@link OAuth2AuthorizedClientService}.
|
||||
*
|
||||
* @param authorizationSuccessHandler the {@link OAuth2AuthorizationSuccessHandler} that handles successful authorizations
|
||||
* The default saves {@link OAuth2AuthorizedClient}s in the
|
||||
* {@link OAuth2AuthorizedClientService}.
|
||||
* @param authorizationSuccessHandler the {@link OAuth2AuthorizationSuccessHandler}
|
||||
* that handles successful authorizations
|
||||
* @since 5.3
|
||||
*/
|
||||
public void setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
|
||||
@@ -194,14 +217,16 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AuthorizationFailureHandler} that handles authorization failures.
|
||||
* 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
|
||||
* A {@link RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} is used by
|
||||
* default.
|
||||
* @param authorizationFailureHandler the {@link OAuth2AuthorizationFailureHandler}
|
||||
* that handles authorization failures
|
||||
* @since 5.3
|
||||
* @see RemoveAuthorizedClientOAuth2AuthorizationFailureHandler
|
||||
*/
|
||||
public void setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
|
||||
@@ -209,9 +234,11 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
|
||||
* The default implementation of the {@link #setContextAttributesMapper(Function)
|
||||
* contextAttributesMapper}.
|
||||
*/
|
||||
public static class DefaultContextAttributesMapper implements Function<OAuth2AuthorizeRequest, Map<String, Object>> {
|
||||
public static class DefaultContextAttributesMapper
|
||||
implements Function<OAuth2AuthorizeRequest, Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> apply(OAuth2AuthorizeRequest authorizeRequest) {
|
||||
@@ -224,5 +251,7 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
|
||||
}
|
||||
return contextAttributes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,78 +13,95 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientManager}
|
||||
* that is capable of operating outside of the context of a {@link ServerWebExchange},
|
||||
* e.g. in a scheduled/background thread and/or in the service-tier.
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientManager} that is capable of
|
||||
* operating outside of the context of a {@link ServerWebExchange}, e.g. in a
|
||||
* scheduled/background thread and/or in the service-tier.
|
||||
*
|
||||
* <p>(When operating <em>within</em> the context of a {@link ServerWebExchange},
|
||||
* use {@link DefaultReactiveOAuth2AuthorizedClientManager} instead.)</p>
|
||||
* <p>
|
||||
* (When operating <em>within</em> the context of a {@link ServerWebExchange}, use
|
||||
* {@link DefaultReactiveOAuth2AuthorizedClientManager} instead.)
|
||||
* </p>
|
||||
*
|
||||
* <p>This is a reactive equivalent of {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}.</p>
|
||||
* <p>
|
||||
* This is a reactive equivalent of
|
||||
* {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Authorized Client Persistence</h2>
|
||||
*
|
||||
* <p>This client manager utilizes a {@link ReactiveOAuth2AuthorizedClientService}
|
||||
* to persist {@link OAuth2AuthorizedClient}s.</p>
|
||||
* <p>
|
||||
* This client manager utilizes a {@link ReactiveOAuth2AuthorizedClientService} to persist
|
||||
* {@link OAuth2AuthorizedClient}s.
|
||||
* </p>
|
||||
*
|
||||
* <p>By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
|
||||
* will be saved in the authorized client service.
|
||||
* This functionality can be changed by configuring a custom {@link ReactiveOAuth2AuthorizationSuccessHandler}
|
||||
* via {@link #setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)}.</p>
|
||||
* <p>
|
||||
* By default, when an authorization attempt succeeds, the {@link OAuth2AuthorizedClient}
|
||||
* will be saved in the authorized client service. This functionality can be changed by
|
||||
* configuring a custom {@link ReactiveOAuth2AuthorizationSuccessHandler} via
|
||||
* {@link #setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>By default, when an authorization attempt fails due to an
|
||||
* <p>
|
||||
* By default, when an authorization attempt fails due to an
|
||||
* {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT} error,
|
||||
* the previously saved {@link OAuth2AuthorizedClient}
|
||||
* will be removed from the authorized client service.
|
||||
* (The {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT}
|
||||
* error generally occurs 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 ReactiveOAuth2AuthorizationFailureHandler}
|
||||
* via {@link #setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)}.</p>
|
||||
* the previously saved {@link OAuth2AuthorizedClient} will be removed from the authorized
|
||||
* client service. (The
|
||||
* {@value org.springframework.security.oauth2.core.OAuth2ErrorCodes#INVALID_GRANT} error
|
||||
* generally occurs 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 ReactiveOAuth2AuthorizationFailureHandler} via
|
||||
* {@link #setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)}.
|
||||
* </p>
|
||||
*
|
||||
* @author Ankur Pathak
|
||||
* @author Phil Clay
|
||||
* @since 5.2.2
|
||||
* @see ReactiveOAuth2AuthorizedClientManager
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
* @see ReactiveOAuth2AuthorizedClientService
|
||||
* @see ReactiveOAuth2AuthorizationSuccessHandler
|
||||
* @see ReactiveOAuth2AuthorizationFailureHandler
|
||||
* @since 5.2.2
|
||||
*/
|
||||
public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
implements ReactiveOAuth2AuthorizedClientManager {
|
||||
|
||||
private static final ReactiveOAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.clientCredentials()
|
||||
.build();
|
||||
private static final ReactiveOAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER = ReactiveOAuth2AuthorizedClientProviderBuilder
|
||||
.builder().clientCredentials().build();
|
||||
|
||||
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private final ReactiveOAuth2AuthorizedClientService authorizedClientService;
|
||||
|
||||
private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
|
||||
|
||||
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper = new DefaultContextAttributesMapper();
|
||||
|
||||
private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
|
||||
|
||||
private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler;
|
||||
|
||||
/**
|
||||
* Constructs an {@code AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager}
|
||||
* using the provided parameters.
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
* @param authorizedClientService the authorized client service
|
||||
* @param authorizedClientService the authorized client service
|
||||
*/
|
||||
public AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
@@ -93,19 +110,18 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
this.authorizedClientService = authorizedClientService;
|
||||
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) ->
|
||||
authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
|
||||
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) -> authorizedClientService
|
||||
.saveAuthorizedClient(authorizedClient, principal);
|
||||
this.authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(
|
||||
(clientRegistrationId, principal, attributes) ->
|
||||
this.authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName()));
|
||||
(clientRegistrationId, principal, attributes) -> this.authorizedClientService
|
||||
.removeAuthorizedClient(clientRegistrationId, principal.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
|
||||
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
|
||||
|
||||
return createAuthorizationContext(authorizeRequest)
|
||||
.flatMap(authorizationContext -> authorize(authorizationContext, authorizeRequest.getPrincipal()));
|
||||
.flatMap((authorizationContext) -> authorize(authorizationContext, authorizeRequest.getPrincipal()));
|
||||
}
|
||||
|
||||
private Mono<OAuth2AuthorizationContext> createAuthorizationContext(OAuth2AuthorizeRequest authorizeRequest) {
|
||||
@@ -113,56 +129,57 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
Authentication principal = authorizeRequest.getPrincipal();
|
||||
return Mono.justOrEmpty(authorizeRequest.getAuthorizedClient())
|
||||
.map(OAuth2AuthorizationContext::withAuthorizedClient)
|
||||
.switchIfEmpty(Mono.defer(() -> this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
|
||||
.flatMap(clientRegistration -> this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName())
|
||||
.switchIfEmpty(Mono.defer(() -> this.clientRegistrationRepository
|
||||
.findByRegistrationId(clientRegistrationId)
|
||||
.flatMap((clientRegistration) -> this.authorizedClientService
|
||||
.loadAuthorizedClient(clientRegistrationId, principal.getName())
|
||||
.map(OAuth2AuthorizationContext::withAuthorizedClient)
|
||||
.switchIfEmpty(Mono.fromSupplier(() -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration))))
|
||||
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("Could not find ClientRegistration with id '" + clientRegistrationId + "'")))))
|
||||
.flatMap(contextBuilder -> this.contextAttributesMapper.apply(authorizeRequest)
|
||||
.defaultIfEmpty(Collections.emptyMap())
|
||||
.map(contextAttributes -> {
|
||||
.switchIfEmpty(Mono.fromSupplier(
|
||||
() -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration))))
|
||||
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException(
|
||||
"Could not find ClientRegistration with id '" + clientRegistrationId + "'")))))
|
||||
.flatMap((contextBuilder) -> this.contextAttributesMapper.apply(authorizeRequest)
|
||||
.defaultIfEmpty(Collections.emptyMap()).map((contextAttributes) -> {
|
||||
OAuth2AuthorizationContext.Builder builder = contextBuilder.principal(principal);
|
||||
if (!contextAttributes.isEmpty()) {
|
||||
builder = builder.attributes(attributes -> attributes.putAll(contextAttributes));
|
||||
builder = builder.attributes((attributes) -> attributes.putAll(contextAttributes));
|
||||
}
|
||||
return builder.build();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs authorization and then delegates to either the {@link #authorizationSuccessHandler}
|
||||
* or {@link #authorizationFailureHandler}, depending on the authorization result.
|
||||
*
|
||||
* Performs authorization and then delegates to either the
|
||||
* {@link #authorizationSuccessHandler} or {@link #authorizationFailureHandler},
|
||||
* depending on the authorization result.
|
||||
* @param authorizationContext the context to authorize
|
||||
* @param principal the principle to authorize
|
||||
* @return a {@link Mono} that emits the authorized client after the authorization attempt succeeds
|
||||
* and the {@link #authorizationSuccessHandler} has completed,
|
||||
* or completes with an exception after the authorization attempt fails
|
||||
* and the {@link #authorizationFailureHandler} has completed
|
||||
* @return a {@link Mono} that emits the authorized client after the authorization
|
||||
* attempt succeeds and the {@link #authorizationSuccessHandler} has completed, or
|
||||
* completes with an exception after the authorization attempt fails and the
|
||||
* {@link #authorizationFailureHandler} has completed
|
||||
*/
|
||||
private Mono<OAuth2AuthorizedClient> authorize(
|
||||
OAuth2AuthorizationContext authorizationContext,
|
||||
private Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext authorizationContext,
|
||||
Authentication principal) {
|
||||
return this.authorizedClientProvider.authorize(authorizationContext)
|
||||
// Delegate to the authorizationSuccessHandler of the successful authorization
|
||||
.flatMap(authorizedClient -> this.authorizationSuccessHandler.onAuthorizationSuccess(
|
||||
authorizedClient,
|
||||
principal,
|
||||
Collections.emptyMap())
|
||||
// Delegate to the authorizationSuccessHandler of the successful
|
||||
// authorization
|
||||
.flatMap((authorizedClient) -> this.authorizationSuccessHandler
|
||||
.onAuthorizationSuccess(authorizedClient, principal, Collections.emptyMap())
|
||||
.thenReturn(authorizedClient))
|
||||
// Delegate to the authorizationFailureHandler of the failed authorization
|
||||
.onErrorResume(OAuth2AuthorizationException.class, authorizationException -> this.authorizationFailureHandler.onAuthorizationFailure(
|
||||
authorizationException,
|
||||
principal,
|
||||
Collections.emptyMap())
|
||||
.then(Mono.error(authorizationException)))
|
||||
.onErrorResume(OAuth2AuthorizationException.class,
|
||||
(authorizationException) -> this.authorizationFailureHandler
|
||||
.onAuthorizationFailure(authorizationException, principal, Collections.emptyMap())
|
||||
.then(Mono.error(authorizationException)))
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.justOrEmpty(authorizationContext.getAuthorizedClient())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client.
|
||||
*
|
||||
* @param authorizedClientProvider the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client
|
||||
* Sets the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or
|
||||
* re-authorizing) an OAuth 2.0 Client.
|
||||
* @param authorizedClientProvider the {@link ReactiveOAuth2AuthorizedClientProvider}
|
||||
* used for authorizing (or re-authorizing) an OAuth 2.0 Client
|
||||
*/
|
||||
public void setAuthorizedClientProvider(ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider) {
|
||||
Assert.notNull(authorizedClientProvider, "authorizedClientProvider cannot be null");
|
||||
@@ -170,13 +187,15 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Function} used for mapping attribute(s) from the {@link OAuth2AuthorizeRequest} to a {@code Map} of attributes
|
||||
* to be associated to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}.
|
||||
*
|
||||
* @param contextAttributesMapper the {@code Function} used for supplying the {@code Map} of attributes
|
||||
* to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}
|
||||
* Sets the {@code Function} used for mapping attribute(s) from the
|
||||
* {@link OAuth2AuthorizeRequest} to a {@code Map} of attributes to be associated to
|
||||
* the {@link OAuth2AuthorizationContext#getAttributes() authorization context}.
|
||||
* @param contextAttributesMapper the {@code Function} used for supplying the
|
||||
* {@code Map} of attributes to the {@link OAuth2AuthorizationContext#getAttributes()
|
||||
* authorization context}
|
||||
*/
|
||||
public void setContextAttributesMapper(Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper) {
|
||||
public void setContextAttributesMapper(
|
||||
Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper) {
|
||||
Assert.notNull(contextAttributesMapper, "contextAttributesMapper cannot be null");
|
||||
this.contextAttributesMapper = contextAttributesMapper;
|
||||
}
|
||||
@@ -184,9 +203,10 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
/**
|
||||
* Sets the handler that handles successful authorizations.
|
||||
*
|
||||
* The default saves {@link OAuth2AuthorizedClient}s in the {@link ReactiveOAuth2AuthorizedClientService}.
|
||||
*
|
||||
* @param authorizationSuccessHandler the handler that handles successful authorizations.
|
||||
* The default saves {@link OAuth2AuthorizedClient}s in the
|
||||
* {@link ReactiveOAuth2AuthorizedClientService}.
|
||||
* @param authorizationSuccessHandler the handler that handles successful
|
||||
* authorizations.
|
||||
* @since 5.3
|
||||
*/
|
||||
public void setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler) {
|
||||
@@ -197,12 +217,13 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
/**
|
||||
* Sets the handler that handles authorization failures.
|
||||
*
|
||||
* <p>A {@link RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler}
|
||||
* is used by default.</p>
|
||||
*
|
||||
* <p>
|
||||
* A {@link RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler} is used
|
||||
* by default.
|
||||
* </p>
|
||||
* @param authorizationFailureHandler the handler that handles authorization failures.
|
||||
* @see RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
|
||||
* @since 5.3
|
||||
* @see RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
|
||||
*/
|
||||
public void setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) {
|
||||
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
|
||||
@@ -210,16 +231,19 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
|
||||
* The default implementation of the {@link #setContextAttributesMapper(Function)
|
||||
* contextAttributesMapper}.
|
||||
*/
|
||||
public static class DefaultContextAttributesMapper implements Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> {
|
||||
public static class DefaultContextAttributesMapper
|
||||
implements Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> {
|
||||
|
||||
private final AuthorizedClientServiceOAuth2AuthorizedClientManager.DefaultContextAttributesMapper mapper =
|
||||
new AuthorizedClientServiceOAuth2AuthorizedClientManager.DefaultContextAttributesMapper();
|
||||
private final AuthorizedClientServiceOAuth2AuthorizedClientManager.DefaultContextAttributesMapper mapper = new AuthorizedClientServiceOAuth2AuthorizedClientManager.DefaultContextAttributesMapper();
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Object>> apply(OAuth2AuthorizeRequest authorizeRequest) {
|
||||
return Mono.fromCallable(() -> mapper.apply(authorizeRequest));
|
||||
return Mono.fromCallable(() -> this.mapper.apply(authorizeRequest));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
@@ -20,8 +21,8 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This exception is thrown on the client side when an attempt to authenticate
|
||||
* or authorize an OAuth 2.0 client fails.
|
||||
* This exception is thrown on the client side when an attempt to authenticate or
|
||||
* authorize an OAuth 2.0 client fails.
|
||||
*
|
||||
* @author Phil Clay
|
||||
* @since 5.3
|
||||
@@ -33,16 +34,15 @@ public class ClientAuthorizationException extends OAuth2AuthorizationException {
|
||||
|
||||
/**
|
||||
* Constructs a {@code ClientAuthorizationException} using the provided parameters.
|
||||
*
|
||||
* @param error the {@link OAuth2Error OAuth 2.0 Error}
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
*/
|
||||
public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId) {
|
||||
this(error, clientRegistrationId, error.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code ClientAuthorizationException} using the provided parameters.
|
||||
*
|
||||
* @param error the {@link OAuth2Error OAuth 2.0 Error}
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param message the exception message
|
||||
@@ -55,7 +55,6 @@ public class ClientAuthorizationException extends OAuth2AuthorizationException {
|
||||
|
||||
/**
|
||||
* Constructs a {@code ClientAuthorizationException} using the provided parameters.
|
||||
*
|
||||
* @param error the {@link OAuth2Error OAuth 2.0 Error}
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param cause the root cause
|
||||
@@ -66,13 +65,13 @@ public class ClientAuthorizationException extends OAuth2AuthorizationException {
|
||||
|
||||
/**
|
||||
* Constructs a {@code ClientAuthorizationException} using the provided parameters.
|
||||
*
|
||||
* @param error the {@link OAuth2Error OAuth 2.0 Error}
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param message the exception message
|
||||
* @param cause the root cause
|
||||
*/
|
||||
public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId, String message, Throwable cause) {
|
||||
public ClientAuthorizationException(OAuth2Error error, String clientRegistrationId, String message,
|
||||
Throwable cause) {
|
||||
super(error, message, cause);
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
this.clientRegistrationId = clientRegistrationId;
|
||||
@@ -80,10 +79,10 @@ public class ClientAuthorizationException extends OAuth2AuthorizationException {
|
||||
|
||||
/**
|
||||
* Returns the identifier for the client's registration.
|
||||
*
|
||||
* @return the identifier for the client's registration
|
||||
*/
|
||||
public String getClientRegistrationId() {
|
||||
return this.clientRegistrationId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,24 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
/**
|
||||
* This exception is thrown when an OAuth 2.0 Client is required
|
||||
* to obtain authorization from the Resource Owner.
|
||||
* This exception is thrown when an OAuth 2.0 Client is required to obtain authorization
|
||||
* from the Resource Owner.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizedClient
|
||||
*/
|
||||
public class ClientAuthorizationRequiredException extends ClientAuthorizationException {
|
||||
|
||||
private static final String CLIENT_AUTHORIZATION_REQUIRED_ERROR_CODE = "client_authorization_required";
|
||||
|
||||
/**
|
||||
* Constructs a {@code ClientAuthorizationRequiredException} using the provided parameters.
|
||||
*
|
||||
* Constructs a {@code ClientAuthorizationRequiredException} using the provided
|
||||
* parameters.
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
*/
|
||||
public ClientAuthorizationRequiredException(String clientRegistrationId) {
|
||||
@@ -38,4 +40,5 @@ public class ClientAuthorizationRequiredException extends ClientAuthorizationExc
|
||||
"Authorization required for Client Registration Id: " + clientRegistrationId, null),
|
||||
clientRegistrationId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
@@ -26,13 +31,9 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant.
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -40,55 +41,60 @@ import java.time.Instant;
|
||||
* @see DefaultClientCredentialsTokenResponseClient
|
||||
*/
|
||||
public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
|
||||
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient =
|
||||
new DefaultClientCredentialsTokenResponseClient();
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns {@code null} if authorization (or re-authorization) is not supported,
|
||||
* e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() authorization grant type}
|
||||
* is not {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} OR
|
||||
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns {@code null} if authorization (or re-authorization) is not
|
||||
* supported, e.g. the client's {@link ClientRegistration#getAuthorizationGrantType()
|
||||
* authorization grant type} is not {@link AuthorizationGrantType#CLIENT_CREDENTIALS
|
||||
* client_credentials} OR the {@link OAuth2AuthorizedClient#getAccessToken() access
|
||||
* token} is not expired.
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization (or re-authorization) is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization (or
|
||||
* re-authorization) is not supported
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
if (!AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
// If client is already authorized but access token is NOT expired than no need for re-authorization
|
||||
// If client is already authorized but access token is NOT expired than no
|
||||
// need for re-authorization
|
||||
return null;
|
||||
}
|
||||
|
||||
// As per spec, in section 4.4.3 Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-4.4.3
|
||||
// A refresh token SHOULD NOT be included.
|
||||
//
|
||||
// Therefore, renewing an expired access token (re-authorization)
|
||||
// is the same as acquiring a new access token (authorization).
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest(
|
||||
clientRegistration);
|
||||
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, clientCredentialsGrantRequest);
|
||||
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest =
|
||||
new OAuth2ClientCredentialsGrantRequest(clientRegistration);
|
||||
|
||||
OAuth2AccessTokenResponse tokenResponse;
|
||||
private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration,
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
|
||||
try {
|
||||
tokenResponse = this.accessTokenResponseClient.getTokenResponse(clientCredentialsGrantRequest);
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
return this.accessTokenResponseClient.getTokenResponse(clientCredentialsGrantRequest);
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
|
||||
}
|
||||
|
||||
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(), tokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(AbstractOAuth2Token token) {
|
||||
@@ -96,23 +102,26 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint for the {@code client_credentials} grant.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint for the {@code client_credentials} grant
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code client_credentials} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code client_credentials} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
public void setAccessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds.
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if {@code OAuth2AccessToken#getExpiresAt() - clockSkew}
|
||||
* is before the current time {@code clock#instant()}.
|
||||
*
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
@@ -122,12 +131,13 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient;
|
||||
@@ -23,65 +30,63 @@ import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant.
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
* @see WebClientReactiveClientCredentialsTokenResponseClient
|
||||
*/
|
||||
public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient =
|
||||
new WebClientReactiveClientCredentialsTokenResponseClient();
|
||||
public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider
|
||||
implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns an empty {@code Mono} if authorization (or re-authorization) is not supported,
|
||||
* e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() authorization grant type}
|
||||
* is not {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} OR
|
||||
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns an empty {@code Mono} if authorization (or
|
||||
* re-authorization) is not supported, e.g. the client's
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is
|
||||
* not {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} OR the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization (or re-authorization) is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* authorization (or re-authorization) is not supported
|
||||
*/
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
if (!AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
// If client is already authorized but access token is NOT expired than no need for re-authorization
|
||||
// If client is already authorized but access token is NOT expired than no
|
||||
// need for re-authorization
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
// As per spec, in section 4.4.3 Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-4.4.3
|
||||
// A refresh token SHOULD NOT be included.
|
||||
//
|
||||
// Therefore, renewing an expired access token (re-authorization)
|
||||
// is the same as acquiring a new access token (authorization).
|
||||
|
||||
return Mono.just(new OAuth2ClientCredentialsGrantRequest(clientRegistration))
|
||||
.flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
e -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(), e))
|
||||
.map(tokenResponse -> new OAuth2AuthorizedClient(
|
||||
clientRegistration, context.getPrincipal().getName(), tokenResponse.getAccessToken()));
|
||||
(ex) -> new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(),
|
||||
ex))
|
||||
.map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken()));
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(AbstractOAuth2Token token) {
|
||||
@@ -89,23 +94,26 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider imple
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint for the {@code client_credentials} grant.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint for the {@code client_credentials} grant
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code client_credentials} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code client_credentials} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
public void setAccessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds.
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if {@code OAuth2AccessToken#getExpiresAt() - clockSkew}
|
||||
* is before the current time {@code clock#instant()}.
|
||||
*
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
@@ -115,12 +123,13 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider imple
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,36 +13,39 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} that simply delegates
|
||||
* to it's internal {@code List} of {@link OAuth2AuthorizedClientProvider}(s).
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} that simply delegates to
|
||||
* it's internal {@code List} of {@link OAuth2AuthorizedClientProvider}(s).
|
||||
* <p>
|
||||
* Each provider is given a chance to
|
||||
* {@link OAuth2AuthorizedClientProvider#authorize(OAuth2AuthorizationContext) authorize}
|
||||
* the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided context
|
||||
* with the first {@code non-null} {@link OAuth2AuthorizedClient} being returned.
|
||||
* the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* context with the first {@code non-null} {@link OAuth2AuthorizedClient} being returned.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class DelegatingOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
|
||||
|
||||
private final List<OAuth2AuthorizedClientProvider> authorizedClientProviders;
|
||||
|
||||
/**
|
||||
* Constructs a {@code DelegatingOAuth2AuthorizedClientProvider} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientProviders a list of {@link OAuth2AuthorizedClientProvider}(s)
|
||||
* Constructs a {@code DelegatingOAuth2AuthorizedClientProvider} using the provided
|
||||
* parameters.
|
||||
* @param authorizedClientProviders a list of
|
||||
* {@link OAuth2AuthorizedClientProvider}(s)
|
||||
*/
|
||||
public DelegatingOAuth2AuthorizedClientProvider(OAuth2AuthorizedClientProvider... authorizedClientProviders) {
|
||||
Assert.notEmpty(authorizedClientProviders, "authorizedClientProviders cannot be empty");
|
||||
@@ -50,9 +53,10 @@ public final class DelegatingOAuth2AuthorizedClientProvider implements OAuth2Aut
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code DelegatingOAuth2AuthorizedClientProvider} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientProviders a {@code List} of {@link OAuth2AuthorizedClientProvider}(s)
|
||||
* Constructs a {@code DelegatingOAuth2AuthorizedClientProvider} using the provided
|
||||
* parameters.
|
||||
* @param authorizedClientProviders a {@code List} of
|
||||
* {@link OAuth2AuthorizedClientProvider}(s)
|
||||
*/
|
||||
public DelegatingOAuth2AuthorizedClientProvider(List<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
Assert.notEmpty(authorizedClientProviders, "authorizedClientProviders cannot be empty");
|
||||
@@ -63,7 +67,7 @@ public final class DelegatingOAuth2AuthorizedClientProvider implements OAuth2Aut
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
for (OAuth2AuthorizedClientProvider authorizedClientProvider : authorizedClientProviders) {
|
||||
for (OAuth2AuthorizedClientProvider authorizedClientProvider : this.authorizedClientProviders) {
|
||||
OAuth2AuthorizedClient oauth2AuthorizedClient = authorizedClientProvider.authorize(context);
|
||||
if (oauth2AuthorizedClient != null) {
|
||||
return oauth2AuthorizedClient;
|
||||
@@ -71,4 +75,5 @@ public final class DelegatingOAuth2AuthorizedClientProvider implements OAuth2Aut
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,49 +13,58 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} that simply delegates
|
||||
* to it's internal {@code List} of {@link ReactiveOAuth2AuthorizedClientProvider}(s).
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} that simply
|
||||
* delegates to it's internal {@code List} of
|
||||
* {@link ReactiveOAuth2AuthorizedClientProvider}(s).
|
||||
* <p>
|
||||
* Each provider is given a chance to
|
||||
* {@link ReactiveOAuth2AuthorizedClientProvider#authorize(OAuth2AuthorizationContext) authorize}
|
||||
* the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided context
|
||||
* with the first available {@link OAuth2AuthorizedClient} being returned.
|
||||
* {@link ReactiveOAuth2AuthorizedClientProvider#authorize(OAuth2AuthorizationContext)
|
||||
* authorize} the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the
|
||||
* provided context with the first available {@link OAuth2AuthorizedClient} being
|
||||
* returned.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class DelegatingReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
|
||||
private final List<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders;
|
||||
|
||||
/**
|
||||
* Constructs a {@code DelegatingReactiveOAuth2AuthorizedClientProvider} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientProviders a list of {@link ReactiveOAuth2AuthorizedClientProvider}(s)
|
||||
* Constructs a {@code DelegatingReactiveOAuth2AuthorizedClientProvider} using the
|
||||
* provided parameters.
|
||||
* @param authorizedClientProviders a list of
|
||||
* {@link ReactiveOAuth2AuthorizedClientProvider}(s)
|
||||
*/
|
||||
public DelegatingReactiveOAuth2AuthorizedClientProvider(ReactiveOAuth2AuthorizedClientProvider... authorizedClientProviders) {
|
||||
public DelegatingReactiveOAuth2AuthorizedClientProvider(
|
||||
ReactiveOAuth2AuthorizedClientProvider... authorizedClientProviders) {
|
||||
Assert.notEmpty(authorizedClientProviders, "authorizedClientProviders cannot be empty");
|
||||
this.authorizedClientProviders = Collections.unmodifiableList(Arrays.asList(authorizedClientProviders));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code DelegatingReactiveOAuth2AuthorizedClientProvider} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientProviders a {@code List} of {@link OAuth2AuthorizedClientProvider}(s)
|
||||
* Constructs a {@code DelegatingReactiveOAuth2AuthorizedClientProvider} using the
|
||||
* provided parameters.
|
||||
* @param authorizedClientProviders a {@code List} of
|
||||
* {@link OAuth2AuthorizedClientProvider}(s)
|
||||
*/
|
||||
public DelegatingReactiveOAuth2AuthorizedClientProvider(List<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
public DelegatingReactiveOAuth2AuthorizedClientProvider(
|
||||
List<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
Assert.notEmpty(authorizedClientProviders, "authorizedClientProviders cannot be empty");
|
||||
this.authorizedClientProviders = Collections.unmodifiableList(new ArrayList<>(authorizedClientProviders));
|
||||
}
|
||||
@@ -64,7 +73,7 @@ public final class DelegatingReactiveOAuth2AuthorizedClientProvider implements R
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
return Flux.fromIterable(this.authorizedClientProviders)
|
||||
.concatMap(authorizedClientProvider -> authorizedClientProvider.authorize(context))
|
||||
.next();
|
||||
.concatMap((authorizedClientProvider) -> authorizedClientProvider.authorize(context)).next();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,19 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthorizedClientService} that stores
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)} in-memory.
|
||||
* An {@link OAuth2AuthorizedClientService} that stores {@link OAuth2AuthorizedClient
|
||||
* Authorized Client(s)} in-memory.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Vedran Pavic
|
||||
@@ -37,12 +38,14 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @see Authentication
|
||||
*/
|
||||
public final class InMemoryOAuth2AuthorizedClientService implements OAuth2AuthorizedClientService {
|
||||
|
||||
private final Map<OAuth2AuthorizedClientId, OAuth2AuthorizedClient> authorizedClients;
|
||||
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizedClientService} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizedClientService} using the provided
|
||||
* parameters.
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
|
||||
@@ -52,14 +55,15 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizedClientService} using the provided parameters.
|
||||
*
|
||||
* @since 5.2
|
||||
* Constructs an {@code InMemoryOAuth2AuthorizedClientService} using the provided
|
||||
* parameters.
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
* @param authorizedClients the initial {@code Map} of authorized client(s) keyed by {@link OAuth2AuthorizedClientId}
|
||||
* @param authorizedClients the initial {@code Map} of authorized client(s) keyed by
|
||||
* {@link OAuth2AuthorizedClientId}
|
||||
* @since 5.2
|
||||
*/
|
||||
public InMemoryOAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository,
|
||||
Map<OAuth2AuthorizedClientId, OAuth2AuthorizedClient> authorizedClients) {
|
||||
Map<OAuth2AuthorizedClientId, OAuth2AuthorizedClient> authorizedClients) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
Assert.notEmpty(authorizedClients, "authorizedClients cannot be empty");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
@@ -68,7 +72,8 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
|
||||
String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
ClientRegistration registration = this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId);
|
||||
@@ -82,8 +87,8 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author
|
||||
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
|
||||
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.authorizedClients.put(new OAuth2AuthorizedClientId(authorizedClient.getClientRegistration().getRegistrationId(),
|
||||
principal.getName()), authorizedClient);
|
||||
this.authorizedClients.put(new OAuth2AuthorizedClientId(
|
||||
authorizedClient.getClientRegistration().getRegistrationId(), principal.getName()), authorizedClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,4 +100,5 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author
|
||||
this.authorizedClients.remove(new OAuth2AuthorizedClientId(clientRegistrationId, principalName));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,20 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthorizedClientService} that stores
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)} in-memory.
|
||||
* An {@link OAuth2AuthorizedClientService} that stores {@link OAuth2AuthorizedClient
|
||||
* Authorized Client(s)} in-memory.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
@@ -37,27 +39,31 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @see Authentication
|
||||
*/
|
||||
public final class InMemoryReactiveOAuth2AuthorizedClientService implements ReactiveOAuth2AuthorizedClientService {
|
||||
|
||||
private final Map<OAuth2AuthorizedClientId, OAuth2AuthorizedClient> authorizedClients = new ConcurrentHashMap<>();
|
||||
|
||||
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
/**
|
||||
* Constructs an {@code InMemoryReactiveOAuth2AuthorizedClientService} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code InMemoryReactiveOAuth2AuthorizedClientService} using the
|
||||
* provided parameters.
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
*/
|
||||
public InMemoryReactiveOAuth2AuthorizedClientService(ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
public InMemoryReactiveOAuth2AuthorizedClientService(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
public <T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId,
|
||||
String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return (Mono<T>) this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
|
||||
.map(clientRegistration -> new OAuth2AuthorizedClientId(clientRegistrationId, principalName))
|
||||
.flatMap(identifier -> Mono.justOrEmpty(this.authorizedClients.get(identifier)));
|
||||
.map((clientRegistration) -> new OAuth2AuthorizedClientId(clientRegistrationId, principalName))
|
||||
.flatMap((identifier) -> Mono.justOrEmpty(this.authorizedClients.get(identifier)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,9 +81,12 @@ public final class InMemoryReactiveOAuth2AuthorizedClientService implements Reac
|
||||
public Mono<Void> removeAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
// @formatter:off
|
||||
return this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
|
||||
.map(clientRegistration -> new OAuth2AuthorizedClientId(clientRegistrationId, principalName))
|
||||
.map((clientRegistration) -> new OAuth2AuthorizedClientId(clientRegistrationId, principalName))
|
||||
.doOnNext(this.authorizedClients::remove)
|
||||
.then(Mono.empty());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
|
||||
@@ -31,26 +44,15 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A JDBC implementation of an {@link OAuth2AuthorizedClientService}
|
||||
* that uses a {@link JdbcOperations} for {@link OAuth2AuthorizedClient} persistence.
|
||||
* A JDBC implementation of an {@link OAuth2AuthorizedClientService} that uses a
|
||||
* {@link JdbcOperations} for {@link OAuth2AuthorizedClient} persistence.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This {@code OAuth2AuthorizedClientService} depends on the table definition
|
||||
* described in "classpath:org/springframework/security/oauth2/client/oauth2-client-schema.sql"
|
||||
* and therefore MUST be defined in the database schema.
|
||||
* described in
|
||||
* "classpath:org/springframework/security/oauth2/client/oauth2-client-schema.sql" and
|
||||
* therefore MUST be defined in the database schema.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Stav Shamir
|
||||
@@ -61,42 +63,58 @@ import java.util.function.Function;
|
||||
* @see RowMapper
|
||||
*/
|
||||
public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClientService {
|
||||
private static final String COLUMN_NAMES =
|
||||
"client_registration_id, " +
|
||||
"principal_name, " +
|
||||
"access_token_type, " +
|
||||
"access_token_value, " +
|
||||
"access_token_issued_at, " +
|
||||
"access_token_expires_at, " +
|
||||
"access_token_scopes, " +
|
||||
"refresh_token_value, " +
|
||||
"refresh_token_issued_at";
|
||||
|
||||
// @formatter:off
|
||||
private static final String COLUMN_NAMES = "client_registration_id, "
|
||||
+ "principal_name, "
|
||||
+ "access_token_type, "
|
||||
+ "access_token_value, "
|
||||
+ "access_token_issued_at, "
|
||||
+ "access_token_expires_at, "
|
||||
+ "access_token_scopes, "
|
||||
+ "refresh_token_value, "
|
||||
+ "refresh_token_issued_at";
|
||||
// @formatter:on
|
||||
|
||||
private static final String TABLE_NAME = "oauth2_authorized_client";
|
||||
|
||||
private static final String PK_FILTER = "client_registration_id = ? AND principal_name = ?";
|
||||
private static final String LOAD_AUTHORIZED_CLIENT_SQL = "SELECT " + COLUMN_NAMES +
|
||||
" FROM " + TABLE_NAME + " WHERE " + PK_FILTER;
|
||||
private static final String SAVE_AUTHORIZED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME +
|
||||
" (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
private static final String REMOVE_AUTHORIZED_CLIENT_SQL = "DELETE FROM " + TABLE_NAME +
|
||||
" WHERE " + PK_FILTER;
|
||||
private static final String UPDATE_AUTHORIZED_CLIENT_SQL = "UPDATE " + TABLE_NAME +
|
||||
" SET access_token_type = ?, access_token_value = ?, access_token_issued_at = ?," +
|
||||
" access_token_expires_at = ?, access_token_scopes = ?," +
|
||||
" refresh_token_value = ?, refresh_token_issued_at = ?" +
|
||||
" WHERE " + PK_FILTER;
|
||||
|
||||
// @formatter:off
|
||||
private static final String LOAD_AUTHORIZED_CLIENT_SQL = "SELECT " + COLUMN_NAMES
|
||||
+ " FROM " + TABLE_NAME
|
||||
+ " WHERE " + PK_FILTER;
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
private static final String SAVE_AUTHORIZED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME
|
||||
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
// @formatter:on
|
||||
|
||||
private static final String REMOVE_AUTHORIZED_CLIENT_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER;
|
||||
|
||||
// @formatter:off
|
||||
private static final String UPDATE_AUTHORIZED_CLIENT_SQL = "UPDATE " + TABLE_NAME
|
||||
+ " SET access_token_type = ?, access_token_value = ?, access_token_issued_at = ?,"
|
||||
+ " access_token_expires_at = ?, access_token_scopes = ?,"
|
||||
+ " refresh_token_value = ?, refresh_token_issued_at = ?"
|
||||
+ " WHERE " + PK_FILTER;
|
||||
// @formatter:on
|
||||
|
||||
protected final JdbcOperations jdbcOperations;
|
||||
|
||||
protected RowMapper<OAuth2AuthorizedClient> authorizedClientRowMapper;
|
||||
|
||||
protected Function<OAuth2AuthorizedClientHolder, List<SqlParameterValue>> authorizedClientParametersMapper;
|
||||
|
||||
/**
|
||||
* Constructs a {@code JdbcOAuth2AuthorizedClientService} using the provided parameters.
|
||||
*
|
||||
* Constructs a {@code JdbcOAuth2AuthorizedClientService} using the provided
|
||||
* parameters.
|
||||
* @param jdbcOperations the JDBC operations
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
*/
|
||||
public JdbcOAuth2AuthorizedClientService(
|
||||
JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository) {
|
||||
|
||||
public JdbcOAuth2AuthorizedClientService(JdbcOperations jdbcOperations,
|
||||
ClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.jdbcOperations = jdbcOperations;
|
||||
@@ -106,19 +124,16 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
|
||||
String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
|
||||
SqlParameterValue[] parameters = new SqlParameterValue[] {
|
||||
new SqlParameterValue(Types.VARCHAR, clientRegistrationId),
|
||||
new SqlParameterValue(Types.VARCHAR, principalName)
|
||||
};
|
||||
new SqlParameterValue(Types.VARCHAR, principalName) };
|
||||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
|
||||
|
||||
List<OAuth2AuthorizedClient> result = this.jdbcOperations.query(
|
||||
LOAD_AUTHORIZED_CLIENT_SQL, pss, this.authorizedClientRowMapper);
|
||||
|
||||
List<OAuth2AuthorizedClient> result = this.jdbcOperations.query(LOAD_AUTHORIZED_CLIENT_SQL, pss,
|
||||
this.authorizedClientRowMapper);
|
||||
return !result.isEmpty() ? (T) result.get(0) : null;
|
||||
}
|
||||
|
||||
@@ -126,40 +141,36 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
|
||||
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
|
||||
boolean existsAuthorizedClient = null != this.loadAuthorizedClient(
|
||||
authorizedClient.getClientRegistration().getRegistrationId(), principal.getName());
|
||||
|
||||
if (existsAuthorizedClient) {
|
||||
updateAuthorizedClient(authorizedClient, principal);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
insertAuthorizedClient(authorizedClient, principal);
|
||||
} catch (DuplicateKeyException e) {
|
||||
}
|
||||
catch (DuplicateKeyException ex) {
|
||||
updateAuthorizedClient(authorizedClient, principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
|
||||
List<SqlParameterValue> parameters = this.authorizedClientParametersMapper.apply(
|
||||
new OAuth2AuthorizedClientHolder(authorizedClient, principal));
|
||||
|
||||
List<SqlParameterValue> parameters = this.authorizedClientParametersMapper
|
||||
.apply(new OAuth2AuthorizedClientHolder(authorizedClient, principal));
|
||||
SqlParameterValue clientRegistrationIdParameter = parameters.remove(0);
|
||||
SqlParameterValue principalNameParameter = parameters.remove(0);
|
||||
parameters.add(clientRegistrationIdParameter);
|
||||
parameters.add(principalNameParameter);
|
||||
|
||||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
|
||||
|
||||
this.jdbcOperations.update(UPDATE_AUTHORIZED_CLIENT_SQL, pss);
|
||||
}
|
||||
|
||||
private void insertAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
|
||||
List<SqlParameterValue> parameters = this.authorizedClientParametersMapper.apply(
|
||||
new OAuth2AuthorizedClientHolder(authorizedClient, principal));
|
||||
List<SqlParameterValue> parameters = this.authorizedClientParametersMapper
|
||||
.apply(new OAuth2AuthorizedClientHolder(authorizedClient, principal));
|
||||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
|
||||
|
||||
this.jdbcOperations.update(SAVE_AUTHORIZED_CLIENT_SQL, pss);
|
||||
}
|
||||
|
||||
@@ -167,21 +178,19 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
public void removeAuthorizedClient(String clientRegistrationId, String principalName) {
|
||||
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
|
||||
SqlParameterValue[] parameters = new SqlParameterValue[] {
|
||||
new SqlParameterValue(Types.VARCHAR, clientRegistrationId),
|
||||
new SqlParameterValue(Types.VARCHAR, principalName)
|
||||
};
|
||||
new SqlParameterValue(Types.VARCHAR, principalName) };
|
||||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
|
||||
|
||||
this.jdbcOperations.update(REMOVE_AUTHORIZED_CLIENT_SQL, pss);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RowMapper} used for mapping the current row in {@code java.sql.ResultSet} to {@link OAuth2AuthorizedClient}.
|
||||
* The default is {@link OAuth2AuthorizedClientRowMapper}.
|
||||
*
|
||||
* @param authorizedClientRowMapper the {@link RowMapper} used for mapping the current row in {@code java.sql.ResultSet} to {@link OAuth2AuthorizedClient}
|
||||
* Sets the {@link RowMapper} used for mapping the current row in
|
||||
* {@code java.sql.ResultSet} to {@link OAuth2AuthorizedClient}. The default is
|
||||
* {@link OAuth2AuthorizedClientRowMapper}.
|
||||
* @param authorizedClientRowMapper the {@link RowMapper} used for mapping the current
|
||||
* row in {@code java.sql.ResultSet} to {@link OAuth2AuthorizedClient}
|
||||
*/
|
||||
public final void setAuthorizedClientRowMapper(RowMapper<OAuth2AuthorizedClient> authorizedClientRowMapper) {
|
||||
Assert.notNull(authorizedClientRowMapper, "authorizedClientRowMapper cannot be null");
|
||||
@@ -189,21 +198,24 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Function} used for mapping {@link OAuth2AuthorizedClientHolder} to a {@code List} of {@link SqlParameterValue}.
|
||||
* The default is {@link OAuth2AuthorizedClientParametersMapper}.
|
||||
*
|
||||
* @param authorizedClientParametersMapper the {@code Function} used for mapping {@link OAuth2AuthorizedClientHolder} to a {@code List} of {@link SqlParameterValue}
|
||||
* Sets the {@code Function} used for mapping {@link OAuth2AuthorizedClientHolder} to
|
||||
* a {@code List} of {@link SqlParameterValue}. The default is
|
||||
* {@link OAuth2AuthorizedClientParametersMapper}.
|
||||
* @param authorizedClientParametersMapper the {@code Function} used for mapping
|
||||
* {@link OAuth2AuthorizedClientHolder} to a {@code List} of {@link SqlParameterValue}
|
||||
*/
|
||||
public final void setAuthorizedClientParametersMapper(Function<OAuth2AuthorizedClientHolder, List<SqlParameterValue>> authorizedClientParametersMapper) {
|
||||
public final void setAuthorizedClientParametersMapper(
|
||||
Function<OAuth2AuthorizedClientHolder, List<SqlParameterValue>> authorizedClientParametersMapper) {
|
||||
Assert.notNull(authorizedClientParametersMapper, "authorizedClientParametersMapper cannot be null");
|
||||
this.authorizedClientParametersMapper = authorizedClientParametersMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default {@link RowMapper} that maps the current row
|
||||
* in {@code java.sql.ResultSet} to {@link OAuth2AuthorizedClient}.
|
||||
* The default {@link RowMapper} that maps the current row in
|
||||
* {@code java.sql.ResultSet} to {@link OAuth2AuthorizedClient}.
|
||||
*/
|
||||
public static class OAuth2AuthorizedClientRowMapper implements RowMapper<OAuth2AuthorizedClient> {
|
||||
|
||||
protected final ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
public OAuth2AuthorizedClientRowMapper(ClientRegistrationRepository clientRegistrationRepository) {
|
||||
@@ -214,17 +226,15 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
@Override
|
||||
public OAuth2AuthorizedClient mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
String clientRegistrationId = rs.getString("client_registration_id");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(
|
||||
clientRegistrationId);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository
|
||||
.findByRegistrationId(clientRegistrationId);
|
||||
if (clientRegistration == null) {
|
||||
throw new DataRetrievalFailureException("The ClientRegistration with id '" +
|
||||
clientRegistrationId + "' exists in the data source, " +
|
||||
"however, it was not found in the ClientRegistrationRepository.");
|
||||
throw new DataRetrievalFailureException(
|
||||
"The ClientRegistration with id '" + clientRegistrationId + "' exists in the data source, "
|
||||
+ "however, it was not found in the ClientRegistrationRepository.");
|
||||
}
|
||||
|
||||
OAuth2AccessToken.TokenType tokenType = null;
|
||||
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(
|
||||
rs.getString("access_token_type"))) {
|
||||
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) {
|
||||
tokenType = OAuth2AccessToken.TokenType.BEARER;
|
||||
}
|
||||
String tokenValue = new String(rs.getBytes("access_token_value"), StandardCharsets.UTF_8);
|
||||
@@ -235,9 +245,7 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
if (accessTokenScopes != null) {
|
||||
scopes = StringUtils.commaDelimitedListToSet(accessTokenScopes);
|
||||
}
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
tokenType, tokenValue, issuedAt, expiresAt, scopes);
|
||||
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, tokenValue, issuedAt, expiresAt, scopes);
|
||||
OAuth2RefreshToken refreshToken = null;
|
||||
byte[] refreshTokenValue = rs.getBytes("refresh_token_value");
|
||||
if (refreshTokenValue != null) {
|
||||
@@ -249,19 +257,18 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
}
|
||||
refreshToken = new OAuth2RefreshToken(tokenValue, issuedAt);
|
||||
}
|
||||
|
||||
String principalName = rs.getString("principal_name");
|
||||
|
||||
return new OAuth2AuthorizedClient(
|
||||
clientRegistration, principalName, accessToken, refreshToken);
|
||||
return new OAuth2AuthorizedClient(clientRegistration, principalName, accessToken, refreshToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The default {@code Function} that maps {@link OAuth2AuthorizedClientHolder}
|
||||
* to a {@code List} of {@link SqlParameterValue}.
|
||||
* The default {@code Function} that maps {@link OAuth2AuthorizedClientHolder} to a
|
||||
* {@code List} of {@link SqlParameterValue}.
|
||||
*/
|
||||
public static class OAuth2AuthorizedClientParametersMapper implements Function<OAuth2AuthorizedClientHolder, List<SqlParameterValue>> {
|
||||
public static class OAuth2AuthorizedClientParametersMapper
|
||||
implements Function<OAuth2AuthorizedClientHolder, List<SqlParameterValue>> {
|
||||
|
||||
@Override
|
||||
public List<SqlParameterValue> apply(OAuth2AuthorizedClientHolder authorizedClientHolder) {
|
||||
@@ -270,26 +277,19 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
|
||||
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
|
||||
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
|
||||
|
||||
List<SqlParameterValue> parameters = new ArrayList<>();
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.VARCHAR, clientRegistration.getRegistrationId()));
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.VARCHAR, principal.getName()));
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.VARCHAR, accessToken.getTokenType().getValue()));
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.BLOB, accessToken.getTokenValue().getBytes(StandardCharsets.UTF_8)));
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.TIMESTAMP, Timestamp.from(accessToken.getIssuedAt())));
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.TIMESTAMP, Timestamp.from(accessToken.getExpiresAt())));
|
||||
parameters.add(new SqlParameterValue(Types.VARCHAR, clientRegistration.getRegistrationId()));
|
||||
parameters.add(new SqlParameterValue(Types.VARCHAR, principal.getName()));
|
||||
parameters.add(new SqlParameterValue(Types.VARCHAR, accessToken.getTokenType().getValue()));
|
||||
parameters.add(
|
||||
new SqlParameterValue(Types.BLOB, accessToken.getTokenValue().getBytes(StandardCharsets.UTF_8)));
|
||||
parameters.add(new SqlParameterValue(Types.TIMESTAMP, Timestamp.from(accessToken.getIssuedAt())));
|
||||
parameters.add(new SqlParameterValue(Types.TIMESTAMP, Timestamp.from(accessToken.getExpiresAt())));
|
||||
String accessTokenScopes = null;
|
||||
if (!CollectionUtils.isEmpty(accessToken.getScopes())) {
|
||||
accessTokenScopes = StringUtils.collectionToDelimitedString(accessToken.getScopes(), ",");
|
||||
}
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.VARCHAR, accessTokenScopes));
|
||||
parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenScopes));
|
||||
byte[] refreshTokenValue = null;
|
||||
Timestamp refreshTokenIssuedAt = null;
|
||||
if (refreshToken != null) {
|
||||
@@ -298,25 +298,26 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
refreshTokenIssuedAt = Timestamp.from(refreshToken.getIssuedAt());
|
||||
}
|
||||
}
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.BLOB, refreshTokenValue));
|
||||
parameters.add(new SqlParameterValue(
|
||||
Types.TIMESTAMP, refreshTokenIssuedAt));
|
||||
|
||||
parameters.add(new SqlParameterValue(Types.BLOB, refreshTokenValue));
|
||||
parameters.add(new SqlParameterValue(Types.TIMESTAMP, refreshTokenIssuedAt));
|
||||
return parameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder for an {@link OAuth2AuthorizedClient} and End-User {@link Authentication} (Resource Owner).
|
||||
* A holder for an {@link OAuth2AuthorizedClient} and End-User {@link Authentication}
|
||||
* (Resource Owner).
|
||||
*/
|
||||
public static final class OAuth2AuthorizedClientHolder {
|
||||
|
||||
private final OAuth2AuthorizedClient authorizedClient;
|
||||
|
||||
private final Authentication principal;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizedClientHolder} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code OAuth2AuthorizedClientHolder} using the provided
|
||||
* parameters.
|
||||
* @param authorizedClient the authorized client
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
*/
|
||||
@@ -329,7 +330,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizedClient}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizedClient}
|
||||
*/
|
||||
public OAuth2AuthorizedClient getAuthorizedClient() {
|
||||
@@ -338,11 +338,12 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
|
||||
|
||||
/**
|
||||
* Returns the End-User {@link Authentication} (Resource Owner).
|
||||
*
|
||||
* @return the End-User {@link Authentication} (Resource Owner)
|
||||
*/
|
||||
public Authentication getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,13 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -27,34 +22,50 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A context that holds authorization-specific state and is used by an {@link OAuth2AuthorizedClientProvider}
|
||||
* when attempting to authorize (or re-authorize) an OAuth 2.0 Client.
|
||||
* A context that holds authorization-specific state and is used by an
|
||||
* {@link OAuth2AuthorizedClientProvider} when attempting to authorize (or re-authorize)
|
||||
* an OAuth 2.0 Client.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class OAuth2AuthorizationContext {
|
||||
/**
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated to the value for the "request scope(s)".
|
||||
* The value of the attribute is a {@code String[]} of scope(s) to be requested by the {@link #getClientRegistration() client}.
|
||||
*/
|
||||
public static final String REQUEST_SCOPE_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".REQUEST_SCOPE");
|
||||
|
||||
/**
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated to the value for the resource owner's username.
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated
|
||||
* to the value for the "request scope(s)". The value of the attribute is a
|
||||
* {@code String[]} of scope(s) to be requested by the {@link #getClientRegistration()
|
||||
* client}.
|
||||
*/
|
||||
public static final String REQUEST_SCOPE_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName()
|
||||
.concat(".REQUEST_SCOPE");
|
||||
|
||||
/**
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated
|
||||
* to the value for the resource owner's username.
|
||||
*/
|
||||
public static final String USERNAME_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".USERNAME");
|
||||
|
||||
/**
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated to the value for the resource owner's password.
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated
|
||||
* to the value for the resource owner's password.
|
||||
*/
|
||||
public static final String PASSWORD_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".PASSWORD");
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private OAuth2AuthorizationContext() {
|
||||
@@ -62,7 +73,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -70,10 +80,11 @@ public final class OAuth2AuthorizationContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizedClient authorized client} or {@code null}
|
||||
* if the {@link #withClientRegistration(ClientRegistration) client registration} was supplied.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if the client registration was supplied
|
||||
* Returns the {@link OAuth2AuthorizedClient authorized client} or {@code null} if the
|
||||
* {@link #withClientRegistration(ClientRegistration) client registration} was
|
||||
* supplied.
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if the client
|
||||
* registration was supplied
|
||||
*/
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient getAuthorizedClient() {
|
||||
@@ -82,7 +93,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Returns the {@code Principal} (to be) associated to the authorized client.
|
||||
*
|
||||
* @return the {@code Principal} (to be) associated to the authorized client
|
||||
*/
|
||||
public Authentication getPrincipal() {
|
||||
@@ -91,7 +101,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Returns the attributes associated to the context.
|
||||
*
|
||||
* @return a {@code Map} of the attributes associated to the context
|
||||
*/
|
||||
public Map<String, Object> getAttributes() {
|
||||
@@ -99,8 +108,8 @@ public final class OAuth2AuthorizationContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an attribute associated to the context or {@code null} if not available.
|
||||
*
|
||||
* Returns the value of an attribute associated to the context or {@code null} if not
|
||||
* available.
|
||||
* @param name the name of the attribute
|
||||
* @param <T> the type of the attribute
|
||||
* @return the value of the attribute associated to the context
|
||||
@@ -113,7 +122,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder} initialized with the {@link ClientRegistration}.
|
||||
*
|
||||
* @param clientRegistration the {@link ClientRegistration client registration}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -123,7 +131,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder} initialized with the {@link OAuth2AuthorizedClient}.
|
||||
*
|
||||
* @param authorizedClient the {@link OAuth2AuthorizedClient authorized client}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -134,10 +141,14 @@ public final class OAuth2AuthorizationContext {
|
||||
/**
|
||||
* A builder for {@link OAuth2AuthorizationContext}.
|
||||
*/
|
||||
public static class Builder {
|
||||
public static final class Builder {
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private Builder(ClientRegistration clientRegistration) {
|
||||
@@ -152,8 +163,8 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Sets the {@code Principal} (to be) associated to the authorized client.
|
||||
*
|
||||
* @param principal the {@code Principal} (to be) associated to the authorized client
|
||||
* @param principal the {@code Principal} (to be) associated to the authorized
|
||||
* client
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder principal(Authentication principal) {
|
||||
@@ -163,8 +174,8 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Provides a {@link Consumer} access to the attributes associated to the context.
|
||||
*
|
||||
* @param attributesConsumer a {@link Consumer} of the attributes associated to the context
|
||||
* @param attributesConsumer a {@link Consumer} of the attributes associated to
|
||||
* the context
|
||||
* @return the {@link OAuth2AuthorizeRequest.Builder}
|
||||
*/
|
||||
public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
|
||||
@@ -177,7 +188,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Sets an attribute associated to the context.
|
||||
*
|
||||
* @param name the name of the attribute
|
||||
* @param value the value of the attribute
|
||||
* @return the {@link Builder}
|
||||
@@ -192,7 +202,6 @@ public final class OAuth2AuthorizationContext {
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2AuthorizationContext}.
|
||||
*
|
||||
* @return a {@link OAuth2AuthorizationContext}
|
||||
*/
|
||||
public OAuth2AuthorizationContext build() {
|
||||
@@ -201,14 +210,16 @@ public final class OAuth2AuthorizationContext {
|
||||
if (this.authorizedClient != null) {
|
||||
context.clientRegistration = this.authorizedClient.getClientRegistration();
|
||||
context.authorizedClient = this.authorizedClient;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
context.clientRegistration = this.clientRegistration;
|
||||
}
|
||||
context.principal = this.principal;
|
||||
context.attributes = Collections.unmodifiableMap(
|
||||
CollectionUtils.isEmpty(this.attributes) ?
|
||||
Collections.emptyMap() : new LinkedHashMap<>(this.attributes));
|
||||
context.attributes = Collections.unmodifiableMap(CollectionUtils.isEmpty(this.attributes)
|
||||
? Collections.emptyMap() : new LinkedHashMap<>(this.attributes));
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
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.
|
||||
* 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
|
||||
@@ -33,16 +34,17 @@ import java.util.Map;
|
||||
public interface OAuth2AuthorizationFailureHandler {
|
||||
|
||||
/**
|
||||
* Called when an OAuth 2.0 Client fails to authorize (or re-authorize)
|
||||
* via the Authorization Server or Resource Server.
|
||||
*
|
||||
* 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}.
|
||||
* @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);
|
||||
void onAuthorizationFailure(OAuth2AuthorizationException authorizationException, Authentication principal,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,15 +13,16 @@
|
||||
* 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;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* Handles when an OAuth 2.0 Client has been successfully
|
||||
* authorized (or re-authorized) via the Authorization Server.
|
||||
* Handles when an OAuth 2.0 Client has been successfully authorized (or re-authorized)
|
||||
* via the Authorization Server.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.3
|
||||
@@ -32,16 +33,18 @@ import java.util.Map;
|
||||
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)
|
||||
* 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}.
|
||||
* @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);
|
||||
void onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient, Authentication principal,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -22,25 +29,24 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a request the {@link OAuth2AuthorizedClientManager} uses to
|
||||
* {@link OAuth2AuthorizedClientManager#authorize(OAuth2AuthorizeRequest) authorize} (or re-authorize)
|
||||
* the {@link ClientRegistration client} identified by the provided {@link #getClientRegistrationId() clientRegistrationId}.
|
||||
* {@link OAuth2AuthorizedClientManager#authorize(OAuth2AuthorizeRequest) authorize} (or
|
||||
* re-authorize) the {@link ClientRegistration client} identified by the provided
|
||||
* {@link #getClientRegistrationId() clientRegistrationId}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AuthorizedClientManager
|
||||
*/
|
||||
public final class OAuth2AuthorizeRequest {
|
||||
|
||||
private String clientRegistrationId;
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private OAuth2AuthorizeRequest() {
|
||||
@@ -48,7 +54,6 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
/**
|
||||
* Returns the identifier for the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the identifier for the client registration
|
||||
*/
|
||||
public String getClientRegistrationId() {
|
||||
@@ -56,8 +61,8 @@ public final class OAuth2AuthorizeRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizedClient authorized client} or {@code null} if it was not provided.
|
||||
*
|
||||
* Returns the {@link OAuth2AuthorizedClient authorized client} or {@code null} if it
|
||||
* was not provided.
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if it was not provided
|
||||
*/
|
||||
@Nullable
|
||||
@@ -67,7 +72,6 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
/**
|
||||
* Returns the {@code Principal} (to be) associated to the authorized client.
|
||||
*
|
||||
* @return the {@code Principal} (to be) associated to the authorized client
|
||||
*/
|
||||
public Authentication getPrincipal() {
|
||||
@@ -76,7 +80,6 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
/**
|
||||
* Returns the attributes associated to the request.
|
||||
*
|
||||
* @return a {@code Map} of the attributes associated to the request
|
||||
*/
|
||||
public Map<String, Object> getAttributes() {
|
||||
@@ -84,8 +87,8 @@ public final class OAuth2AuthorizeRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an attribute associated to the request or {@code null} if not available.
|
||||
*
|
||||
* Returns the value of an attribute associated to the request or {@code null} if not
|
||||
* available.
|
||||
* @param name the name of the attribute
|
||||
* @param <T> the type of the attribute
|
||||
* @return the value of the attribute associated to the request
|
||||
@@ -97,9 +100,10 @@ public final class OAuth2AuthorizeRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder} initialized with the identifier for the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @param clientRegistrationId the identifier for the {@link ClientRegistration client registration}
|
||||
* Returns a new {@link Builder} initialized with the identifier for the
|
||||
* {@link ClientRegistration client registration}.
|
||||
* @param clientRegistrationId the identifier for the {@link ClientRegistration client
|
||||
* registration}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withClientRegistrationId(String clientRegistrationId) {
|
||||
@@ -107,8 +111,8 @@ public final class OAuth2AuthorizeRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder} initialized with the {@link OAuth2AuthorizedClient authorized client}.
|
||||
*
|
||||
* Returns a new {@link Builder} initialized with the {@link OAuth2AuthorizedClient
|
||||
* authorized client}.
|
||||
* @param authorizedClient the {@link OAuth2AuthorizedClient authorized client}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -119,10 +123,14 @@ public final class OAuth2AuthorizeRequest {
|
||||
/**
|
||||
* A builder for {@link OAuth2AuthorizeRequest}.
|
||||
*/
|
||||
public static class Builder {
|
||||
public static final class Builder {
|
||||
|
||||
private String clientRegistrationId;
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private Builder(String clientRegistrationId) {
|
||||
@@ -136,11 +144,12 @@ public final class OAuth2AuthorizeRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the {@code Principal} (to be) associated to the authorized client.
|
||||
*
|
||||
* @since 5.3
|
||||
* @param principalName the name of the {@code Principal} (to be) associated to the authorized client
|
||||
* Sets the name of the {@code Principal} (to be) associated to the authorized
|
||||
* client.
|
||||
* @param principalName the name of the {@code Principal} (to be) associated to
|
||||
* the authorized client
|
||||
* @return the {@link Builder}
|
||||
* @since 5.3
|
||||
*/
|
||||
public Builder principal(String principalName) {
|
||||
return principal(createAuthentication(principalName));
|
||||
@@ -148,8 +157,8 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
private static Authentication createAuthentication(final String principalName) {
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
|
||||
return new AbstractAuthenticationToken(null) {
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return "";
|
||||
@@ -159,13 +168,14 @@ public final class OAuth2AuthorizeRequest {
|
||||
public Object getPrincipal() {
|
||||
return principalName;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Principal} (to be) associated to the authorized client.
|
||||
*
|
||||
* @param principal the {@code Principal} (to be) associated to the authorized client
|
||||
* @param principal the {@code Principal} (to be) associated to the authorized
|
||||
* client
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder principal(Authentication principal) {
|
||||
@@ -175,8 +185,8 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
/**
|
||||
* Provides a {@link Consumer} access to the attributes associated to the request.
|
||||
*
|
||||
* @param attributesConsumer a {@link Consumer} of the attributes associated to the request
|
||||
* @param attributesConsumer a {@link Consumer} of the attributes associated to
|
||||
* the request
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
|
||||
@@ -189,7 +199,6 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
/**
|
||||
* Sets an attribute associated to the request.
|
||||
*
|
||||
* @param name the name of the attribute
|
||||
* @param value the value of the attribute
|
||||
* @return the {@link Builder}
|
||||
@@ -204,23 +213,25 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2AuthorizeRequest}.
|
||||
*
|
||||
* @return a {@link OAuth2AuthorizeRequest}
|
||||
*/
|
||||
public OAuth2AuthorizeRequest build() {
|
||||
Assert.notNull(this.principal, "principal cannot be null");
|
||||
OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest();
|
||||
if (this.authorizedClient != null) {
|
||||
authorizeRequest.clientRegistrationId = this.authorizedClient.getClientRegistration().getRegistrationId();
|
||||
authorizeRequest.clientRegistrationId = this.authorizedClient.getClientRegistration()
|
||||
.getRegistrationId();
|
||||
authorizeRequest.authorizedClient = this.authorizedClient;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
authorizeRequest.clientRegistrationId = this.clientRegistrationId;
|
||||
}
|
||||
authorizeRequest.principal = this.principal;
|
||||
authorizeRequest.attributes = Collections.unmodifiableMap(
|
||||
CollectionUtils.isEmpty(this.attributes) ?
|
||||
Collections.emptyMap() : new LinkedHashMap<>(this.attributes));
|
||||
authorizeRequest.attributes = Collections.unmodifiableMap(CollectionUtils.isEmpty(this.attributes)
|
||||
? Collections.emptyMap() : new LinkedHashMap<>(this.attributes));
|
||||
return authorizeRequest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -22,17 +25,15 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A representation of an OAuth 2.0 "Authorized Client".
|
||||
* <p>
|
||||
* A client is considered "authorized" when the End-User (Resource Owner)
|
||||
* has granted authorization to the client to access it's protected resources.
|
||||
* A client is considered "authorized" when the End-User (Resource Owner) has
|
||||
* granted authorization to the client to access it's protected resources.
|
||||
* <p>
|
||||
* This class associates the {@link #getClientRegistration() Client}
|
||||
* to the {@link #getAccessToken() Access Token}
|
||||
* granted/authorized by the {@link #getPrincipalName() Resource Owner}.
|
||||
* This class associates the {@link #getClientRegistration() Client} to the
|
||||
* {@link #getAccessToken() Access Token} granted/authorized by the
|
||||
* {@link #getPrincipalName() Resource Owner}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -41,33 +42,37 @@ import java.io.Serializable;
|
||||
* @see OAuth2RefreshToken
|
||||
*/
|
||||
public class OAuth2AuthorizedClient implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
private final String principalName;
|
||||
|
||||
private final OAuth2AccessToken accessToken;
|
||||
|
||||
private final OAuth2RefreshToken refreshToken;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizedClient} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistration the authorized client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
* @param accessToken the access token credential granted
|
||||
*/
|
||||
public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName, OAuth2AccessToken accessToken) {
|
||||
public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName,
|
||||
OAuth2AccessToken accessToken) {
|
||||
this(clientRegistration, principalName, accessToken, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizedClient} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistration the authorized client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
* @param accessToken the access token credential granted
|
||||
* @param refreshToken the refresh token credential granted
|
||||
*/
|
||||
public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName,
|
||||
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
|
||||
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||
@@ -79,7 +84,6 @@ public class OAuth2AuthorizedClient implements Serializable {
|
||||
|
||||
/**
|
||||
* Returns the authorized client's {@link ClientRegistration registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -88,7 +92,6 @@ public class OAuth2AuthorizedClient implements Serializable {
|
||||
|
||||
/**
|
||||
* Returns the End-User's {@code Principal} name.
|
||||
*
|
||||
* @return the End-User's {@code Principal} name
|
||||
*/
|
||||
public String getPrincipalName() {
|
||||
@@ -97,7 +100,6 @@ public class OAuth2AuthorizedClient implements Serializable {
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AccessToken access token} credential granted.
|
||||
*
|
||||
* @return the {@link OAuth2AccessToken}
|
||||
*/
|
||||
public OAuth2AccessToken getAccessToken() {
|
||||
@@ -106,11 +108,11 @@ public class OAuth2AuthorizedClient implements Serializable {
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2RefreshToken refresh token} credential granted.
|
||||
*
|
||||
* @since 5.1
|
||||
* @return the {@link OAuth2RefreshToken}
|
||||
* @since 5.1
|
||||
*/
|
||||
public @Nullable OAuth2RefreshToken getRefreshToken() {
|
||||
return this.refreshToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
* 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.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The identifier for {@link OAuth2AuthorizedClient}.
|
||||
*
|
||||
@@ -30,13 +31,15 @@ import java.util.Objects;
|
||||
* @see OAuth2AuthorizedClientService
|
||||
*/
|
||||
public final class OAuth2AuthorizedClientId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private final String clientRegistrationId;
|
||||
|
||||
private final String principalName;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizedClientId} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
*/
|
||||
@@ -64,4 +67,5 @@ public final class OAuth2AuthorizedClientId implements Serializable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.clientRegistrationId, this.principalName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -20,16 +21,16 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the overall management
|
||||
* of {@link OAuth2AuthorizedClient Authorized Client(s)}.
|
||||
* Implementations of this interface are responsible for the overall management of
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)}.
|
||||
*
|
||||
* <p>
|
||||
* The primary responsibilities include:
|
||||
* <ol>
|
||||
* <li>Authorizing (or re-authorizing) an OAuth 2.0 Client
|
||||
* by leveraging an {@link OAuth2AuthorizedClientProvider}(s).</li>
|
||||
* <li>Delegating the persistence of an {@link OAuth2AuthorizedClient},
|
||||
* typically using an {@link OAuth2AuthorizedClientService} OR {@link OAuth2AuthorizedClientRepository}.</li>
|
||||
* <li>Authorizing (or re-authorizing) an OAuth 2.0 Client by leveraging an
|
||||
* {@link OAuth2AuthorizedClientProvider}(s).</li>
|
||||
* <li>Delegating the persistence of an {@link OAuth2AuthorizedClient}, typically using an
|
||||
* {@link OAuth2AuthorizedClientService} OR {@link OAuth2AuthorizedClientRepository}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Joe Grandja
|
||||
@@ -43,20 +44,23 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepo
|
||||
public interface OAuth2AuthorizedClientManager {
|
||||
|
||||
/**
|
||||
* Attempt to authorize or re-authorize (if required) the {@link ClientRegistration client}
|
||||
* identified by the provided {@link OAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
|
||||
* Implementations must return {@code null} if authorization is not supported for the specified client,
|
||||
* e.g. the associated {@link OAuth2AuthorizedClientProvider}(s) does not support
|
||||
* the {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type configured for the client.
|
||||
* Attempt to authorize or re-authorize (if required) the {@link ClientRegistration
|
||||
* client} identified by the provided
|
||||
* {@link OAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
|
||||
* Implementations must return {@code null} if authorization is not supported for the
|
||||
* specified client, e.g. the associated {@link OAuth2AuthorizedClientProvider}(s)
|
||||
* does not support the {@link ClientRegistration#getAuthorizationGrantType()
|
||||
* authorization grant} type configured for the client.
|
||||
*
|
||||
* <p>
|
||||
* In the case of re-authorization, implementations must return the provided {@link OAuth2AuthorizeRequest#getAuthorizedClient() authorized client}
|
||||
* if re-authorization is not supported for the client OR is not required,
|
||||
* e.g. a {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available OR
|
||||
* In the case of re-authorization, implementations must return the provided
|
||||
* {@link OAuth2AuthorizeRequest#getAuthorizedClient() authorized client} if
|
||||
* re-authorization is not supported for the client OR is not required, e.g. a
|
||||
* {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available OR
|
||||
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* @param authorizeRequest the authorize request
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not supported for the specified client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not
|
||||
* supported for the specified client
|
||||
*/
|
||||
@Nullable
|
||||
OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -20,25 +21,30 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
|
||||
/**
|
||||
* A strategy for authorizing (or re-authorizing) an OAuth 2.0 Client.
|
||||
* Implementations will typically implement a specific {@link AuthorizationGrantType authorization grant} type.
|
||||
* A strategy for authorizing (or re-authorizing) an OAuth 2.0 Client. Implementations
|
||||
* will typically implement a specific {@link AuthorizationGrantType authorization grant}
|
||||
* type.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AuthorizedClient
|
||||
* @see OAuth2AuthorizationContext
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
||||
* 1.3 Authorization Grant</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OAuth2AuthorizedClientProvider {
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided context.
|
||||
* Implementations must return {@code null} if authorization is not supported for the specified client,
|
||||
* e.g. the provider doesn't support the {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type configured for the client.
|
||||
*
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* context. Implementations must return {@code null} if authorization is not supported
|
||||
* for the specified client, e.g. the provider doesn't support the
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type
|
||||
* configured for the client.
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not supported for the specified client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not
|
||||
* supported for the specified client
|
||||
*/
|
||||
@Nullable
|
||||
OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context);
|
||||
|
||||
@@ -13,13 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.util.Assert;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
@@ -30,13 +25,19 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A builder that builds a {@link DelegatingOAuth2AuthorizedClientProvider} composed of
|
||||
* one or more {@link OAuth2AuthorizedClientProvider}(s) that implement specific authorization grants.
|
||||
* The supported authorization grants are {@link #authorizationCode() authorization_code},
|
||||
* {@link #refreshToken() refresh_token}, {@link #clientCredentials() client_credentials}
|
||||
* and {@link #password() password}.
|
||||
* In addition to the standard authorization grants, an implementation of an extension grant
|
||||
* one or more {@link OAuth2AuthorizedClientProvider}(s) that implement specific
|
||||
* authorization grants. The supported authorization grants are
|
||||
* {@link #authorizationCode() authorization_code}, {@link #refreshToken() refresh_token},
|
||||
* {@link #clientCredentials() client_credentials} and {@link #password() password}. In
|
||||
* addition to the standard authorization grants, an implementation of an extension grant
|
||||
* may be supplied via {@link #provider(OAuth2AuthorizedClientProvider)}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
@@ -49,14 +50,15 @@ import java.util.function.Consumer;
|
||||
* @see DelegatingOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
private final Map<Class<?>, Builder> builders = new LinkedHashMap<>();
|
||||
|
||||
private OAuth2AuthorizedClientProviderBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link OAuth2AuthorizedClientProviderBuilder} for configuring the supported authorization grant(s).
|
||||
*
|
||||
* Returns a new {@link OAuth2AuthorizedClientProviderBuilder} for configuring the
|
||||
* supported authorization grant(s).
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public static OAuth2AuthorizedClientProviderBuilder builder() {
|
||||
@@ -64,273 +66,146 @@ public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an {@link OAuth2AuthorizedClientProvider} to be composed with the {@link DelegatingOAuth2AuthorizedClientProvider}.
|
||||
* This may be used for implementations of extension authorization grants.
|
||||
*
|
||||
* Configures an {@link OAuth2AuthorizedClientProvider} to be composed with the
|
||||
* {@link DelegatingOAuth2AuthorizedClientProvider}. This may be used for
|
||||
* implementations of extension authorization grants.
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder provider(OAuth2AuthorizedClientProvider provider) {
|
||||
Assert.notNull(provider, "provider cannot be null");
|
||||
this.builders.computeIfAbsent(provider.getClass(), k -> () -> provider);
|
||||
this.builders.computeIfAbsent(provider.getClass(), (k) -> () -> provider);
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code authorization_code} grant.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder authorizationCode() {
|
||||
this.builders.computeIfAbsent(AuthorizationCodeOAuth2AuthorizedClientProvider.class, k -> new AuthorizationCodeGrantBuilder());
|
||||
this.builders.computeIfAbsent(AuthorizationCodeOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new AuthorizationCodeGrantBuilder());
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code authorization_code} grant.
|
||||
*/
|
||||
public class AuthorizationCodeGrantBuilder implements Builder {
|
||||
|
||||
private AuthorizationCodeGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link AuthorizationCodeOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @return the {@link AuthorizationCodeOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
return new AuthorizationCodeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code refresh_token} grant.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder refreshToken() {
|
||||
this.builders.computeIfAbsent(RefreshTokenOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder());
|
||||
this.builders.computeIfAbsent(RefreshTokenOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new RefreshTokenGrantBuilder());
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code refresh_token} grant.
|
||||
*
|
||||
* @param builderConsumer a {@code Consumer} of {@link RefreshTokenGrantBuilder} used for further configuration
|
||||
* @param builderConsumer a {@code Consumer} of {@link RefreshTokenGrantBuilder} used
|
||||
* for further configuration
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder refreshToken(Consumer<RefreshTokenGrantBuilder> builderConsumer) {
|
||||
RefreshTokenGrantBuilder builder = (RefreshTokenGrantBuilder) this.builders.computeIfAbsent(
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder());
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class, (k) -> new RefreshTokenGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code refresh_token} grant.
|
||||
*/
|
||||
public class RefreshTokenGrantBuilder implements Builder {
|
||||
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
|
||||
private Duration clockSkew;
|
||||
private Clock clock;
|
||||
|
||||
private RefreshTokenGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder accessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access token expiry.
|
||||
* An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}.
|
||||
*
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* @param clock the clock
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link RefreshTokenOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @return the {@link RefreshTokenOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code client_credentials} grant.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder clientCredentials() {
|
||||
this.builders.computeIfAbsent(ClientCredentialsOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder());
|
||||
this.builders.computeIfAbsent(ClientCredentialsOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new ClientCredentialsGrantBuilder());
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code client_credentials} grant.
|
||||
*
|
||||
* @param builderConsumer a {@code Consumer} of {@link ClientCredentialsGrantBuilder} used for further configuration
|
||||
* @param builderConsumer a {@code Consumer} of {@link ClientCredentialsGrantBuilder}
|
||||
* used for further configuration
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder clientCredentials(Consumer<ClientCredentialsGrantBuilder> builderConsumer) {
|
||||
public OAuth2AuthorizedClientProviderBuilder clientCredentials(
|
||||
Consumer<ClientCredentialsGrantBuilder> builderConsumer) {
|
||||
ClientCredentialsGrantBuilder builder = (ClientCredentialsGrantBuilder) this.builders.computeIfAbsent(
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder());
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class, (k) -> new ClientCredentialsGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code client_credentials} grant.
|
||||
*/
|
||||
public class ClientCredentialsGrantBuilder implements Builder {
|
||||
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
|
||||
private Duration clockSkew;
|
||||
private Clock clock;
|
||||
|
||||
private ClientCredentialsGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder accessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access token expiry.
|
||||
* An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}.
|
||||
*
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* @param clock the clock
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link ClientCredentialsOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @return the {@link ClientCredentialsOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder password() {
|
||||
this.builders.computeIfAbsent(PasswordOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder());
|
||||
this.builders.computeIfAbsent(PasswordOAuth2AuthorizedClientProvider.class, (k) -> new PasswordGrantBuilder());
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
*
|
||||
* @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for further configuration
|
||||
* @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for
|
||||
* further configuration
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProviderBuilder password(Consumer<PasswordGrantBuilder> builderConsumer) {
|
||||
PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders.computeIfAbsent(
|
||||
PasswordOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder());
|
||||
PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders
|
||||
.computeIfAbsent(PasswordOAuth2AuthorizedClientProvider.class, (k) -> new PasswordGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link DelegatingOAuth2AuthorizedClientProvider} composed of
|
||||
* one or more {@link OAuth2AuthorizedClientProvider}(s).
|
||||
* @return the {@link DelegatingOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
|
||||
for (Builder builder : this.builders.values()) {
|
||||
authorizedClientProviders.add(builder.build());
|
||||
}
|
||||
return new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
OAuth2AuthorizedClientProvider build();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code password} grant.
|
||||
*/
|
||||
public class PasswordGrantBuilder implements Builder {
|
||||
public final class PasswordGrantBuilder implements Builder {
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private PasswordGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
public PasswordGrantBuilder accessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
public PasswordGrantBuilder accessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access token expiry.
|
||||
* An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}.
|
||||
*
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if it's before
|
||||
* {@code Instant.now(this.clock) - clockSkew}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
@@ -340,8 +215,8 @@ public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
@@ -352,7 +227,6 @@ public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link PasswordOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @return the {@link PasswordOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
@@ -369,23 +243,168 @@ public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link DelegatingOAuth2AuthorizedClientProvider}
|
||||
* composed of one or more {@link OAuth2AuthorizedClientProvider}(s).
|
||||
*
|
||||
* @return the {@link DelegatingOAuth2AuthorizedClientProvider}
|
||||
* A builder for the {@code client_credentials} grant.
|
||||
*/
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
|
||||
for (Builder builder : this.builders.values()) {
|
||||
authorizedClientProviders.add(builder.build());
|
||||
public final class ClientCredentialsGrantBuilder implements Builder {
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private ClientCredentialsGrantBuilder() {
|
||||
}
|
||||
return new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder accessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if it's before
|
||||
* {@code Instant.now(this.clock) - clockSkew}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link ClientCredentialsOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link ClientCredentialsOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
OAuth2AuthorizedClientProvider build();
|
||||
/**
|
||||
* A builder for the {@code authorization_code} grant.
|
||||
*/
|
||||
public final class AuthorizationCodeGrantBuilder implements Builder {
|
||||
|
||||
private AuthorizationCodeGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link AuthorizationCodeOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link AuthorizationCodeOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
return new AuthorizationCodeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code refresh_token} grant.
|
||||
*/
|
||||
public final class RefreshTokenGrantBuilder implements Builder {
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private RefreshTokenGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder accessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if it's before
|
||||
* {@code Instant.now(this.clock) - clockSkew}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link RefreshTokenOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link RefreshTokenOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* 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;
|
||||
@@ -20,12 +21,12 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the management
|
||||
* of {@link OAuth2AuthorizedClient Authorized Client(s)}, which provide the purpose
|
||||
* of associating an {@link OAuth2AuthorizedClient#getAccessToken() Access Token} credential
|
||||
* Implementations of this interface are responsible for the management of
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)}, which provide the purpose of
|
||||
* associating an {@link OAuth2AuthorizedClient#getAccessToken() Access Token} credential
|
||||
* to a {@link OAuth2AuthorizedClient#getClientRegistration() Client} and Resource Owner,
|
||||
* who is the {@link OAuth2AuthorizedClient#getPrincipalName() Principal}
|
||||
* that originally granted the authorization.
|
||||
* who is the {@link OAuth2AuthorizedClient#getPrincipalName() Principal} that originally
|
||||
* granted the authorization.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -37,10 +38,9 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
public interface OAuth2AuthorizedClientService {
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User's {@code Principal} name
|
||||
* or {@code null} if not available.
|
||||
*
|
||||
* Returns the {@link OAuth2AuthorizedClient} associated to the provided client
|
||||
* registration identifier and End-User's {@code Principal} name or {@code null} if
|
||||
* not available.
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
* @param <T> a type of OAuth2AuthorizedClient
|
||||
@@ -49,18 +49,16 @@ public interface OAuth2AuthorizedClientService {
|
||||
<T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName);
|
||||
|
||||
/**
|
||||
* Saves the {@link OAuth2AuthorizedClient} associating it to
|
||||
* the provided End-User {@link Authentication} (Resource Owner).
|
||||
*
|
||||
* Saves the {@link OAuth2AuthorizedClient} associating it to the provided End-User
|
||||
* {@link Authentication} (Resource Owner).
|
||||
* @param authorizedClient the authorized client
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
*/
|
||||
void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal);
|
||||
|
||||
/**
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User's {@code Principal} name.
|
||||
*
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the provided client
|
||||
* registration identifier and End-User's {@code Principal} name.
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
*/
|
||||
|
||||
@@ -13,8 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
@@ -27,13 +32,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenRespon
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -41,96 +42,105 @@ import java.time.Instant;
|
||||
* @see DefaultPasswordTokenResponseClient
|
||||
*/
|
||||
public final class PasswordOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient =
|
||||
new DefaultPasswordTokenResponseClient();
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = new DefaultPasswordTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns {@code null} if authorization (or re-authorization) is not supported,
|
||||
* e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() authorization grant type}
|
||||
* is not {@link AuthorizationGrantType#PASSWORD password} OR
|
||||
* the {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME username} and/or
|
||||
* {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME password} attributes
|
||||
* are not available in the provided {@code context} OR
|
||||
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns {@code null} if authorization (or re-authorization) is not
|
||||
* supported, e.g. the client's {@link ClientRegistration#getAuthorizationGrantType()
|
||||
* authorization grant type} is not {@link AuthorizationGrantType#PASSWORD password}
|
||||
* OR the {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME username} and/or
|
||||
* {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME password} attributes are
|
||||
* not available in the provided {@code context} OR the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* <p>
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes} are supported:
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes}
|
||||
* are supported:
|
||||
* <ol>
|
||||
* <li>{@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a {@code String} value for the resource owner's username</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a {@code String} value for the resource owner's password</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's username</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's password</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization (or re-authorization) is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization (or
|
||||
* re-authorization) is not supported
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
|
||||
if (!AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String username = context.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
|
||||
String password = context.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
|
||||
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
// If client is already authorized and access token is NOT expired than no need for re-authorization
|
||||
// If client is already authorized and access token is NOT expired than no
|
||||
// need for re-authorization
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken()) && authorizedClient.getRefreshToken() != null) {
|
||||
// If client is already authorized and access token is expired and a refresh token is available,
|
||||
// than return and allow RefreshTokenOAuth2AuthorizedClientProvider to handle the refresh
|
||||
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken())
|
||||
&& authorizedClient.getRefreshToken() != null) {
|
||||
// If client is already authorized and access token is expired and a refresh
|
||||
// token is available, than return and allow
|
||||
// RefreshTokenOAuth2AuthorizedClientProvider to handle the refresh
|
||||
return null;
|
||||
}
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest =
|
||||
new OAuth2PasswordGrantRequest(clientRegistration, username, password);
|
||||
|
||||
OAuth2AccessTokenResponse tokenResponse;
|
||||
try {
|
||||
tokenResponse = this.accessTokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
|
||||
}
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, username,
|
||||
password);
|
||||
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, passwordGrantRequest);
|
||||
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration,
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
try {
|
||||
return this.accessTokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(AbstractOAuth2Token token) {
|
||||
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint for the {@code password} grant.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint for the {@code password} grant
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code password} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code password} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
public void setAccessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds.
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if {@code OAuth2AccessToken#getExpiresAt() - clockSkew}
|
||||
* is before the current time {@code clock#instant()}.
|
||||
*
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
@@ -140,12 +150,13 @@ public final class PasswordOAuth2AuthorizedClientProvider implements OAuth2Autho
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactivePasswordTokenResponseClient;
|
||||
@@ -24,15 +31,10 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -40,67 +42,71 @@ import java.time.Instant;
|
||||
* @see WebClientReactivePasswordTokenResponseClient
|
||||
*/
|
||||
public final class PasswordReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient =
|
||||
new WebClientReactivePasswordTokenResponseClient();
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns an empty {@code Mono} if authorization (or re-authorization) is not supported,
|
||||
* e.g. the client's {@link ClientRegistration#getAuthorizationGrantType() authorization grant type}
|
||||
* is not {@link AuthorizationGrantType#PASSWORD password} OR
|
||||
* the {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME username} and/or
|
||||
* {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME password} attributes
|
||||
* are not available in the provided {@code context} OR
|
||||
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns an empty {@code Mono} if authorization (or
|
||||
* re-authorization) is not supported, e.g. the client's
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is
|
||||
* not {@link AuthorizationGrantType#PASSWORD password} OR the
|
||||
* {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME username} and/or
|
||||
* {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME password} attributes are
|
||||
* not available in the provided {@code context} OR the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* <p>
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes} are supported:
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes}
|
||||
* are supported:
|
||||
* <ol>
|
||||
* <li>{@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a {@code String} value for the resource owner's username</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a {@code String} value for the resource owner's password</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's username</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's password</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization (or re-authorization) is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* authorization (or re-authorization) is not supported
|
||||
*/
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
|
||||
if (!AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
String username = context.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
|
||||
String password = context.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
|
||||
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
// If client is already authorized and access token is NOT expired than no need for re-authorization
|
||||
// If client is already authorized and access token is NOT expired than no
|
||||
// need for re-authorization
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken()) && authorizedClient.getRefreshToken() != null) {
|
||||
// If client is already authorized and access token is expired and a refresh token is available,
|
||||
// than return and allow RefreshTokenReactiveOAuth2AuthorizedClientProvider to handle the refresh
|
||||
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken())
|
||||
&& authorizedClient.getRefreshToken() != null) {
|
||||
// If client is already authorized and access token is expired and a refresh
|
||||
// token is available,
|
||||
// than return and allow RefreshTokenReactiveOAuth2AuthorizedClientProvider to
|
||||
// handle the refresh
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest =
|
||||
new OAuth2PasswordGrantRequest(clientRegistration, username, password);
|
||||
|
||||
return Mono.just(passwordGrantRequest)
|
||||
.flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, username,
|
||||
password);
|
||||
return Mono.just(passwordGrantRequest).flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
e -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(), e))
|
||||
.map(tokenResponse -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(),
|
||||
e))
|
||||
.map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()));
|
||||
}
|
||||
|
||||
@@ -109,23 +115,26 @@ public final class PasswordReactiveOAuth2AuthorizedClientProvider implements Rea
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint for the {@code password} grant.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint for the {@code password} grant
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code password} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code password} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
public void setAccessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds.
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if {@code OAuth2AccessToken#getExpiresAt() - clockSkew}
|
||||
* is before the current time {@code clock#instant()}.
|
||||
*
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
@@ -135,12 +144,13 @@ public final class PasswordReactiveOAuth2AuthorizedClientProvider implements Rea
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,18 +13,19 @@
|
||||
* 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 reactor.core.publisher.Mono;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
|
||||
/**
|
||||
* Handles when an OAuth 2.0 Client
|
||||
* fails to authorize (or re-authorize)
|
||||
* via the authorization server or resource server.
|
||||
* Handles when an OAuth 2.0 Client fails to authorize (or re-authorize) via the
|
||||
* authorization server or resource server.
|
||||
*
|
||||
* @author Phil Clay
|
||||
* @since 5.3
|
||||
@@ -33,19 +34,18 @@ import java.util.Map;
|
||||
public interface ReactiveOAuth2AuthorizationFailureHandler {
|
||||
|
||||
/**
|
||||
* Called when an OAuth 2.0 Client
|
||||
* fails to authorize (or re-authorize)
|
||||
* via the authorization server or resource server.
|
||||
*
|
||||
* 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} that was attempted to be authorized
|
||||
* @param attributes an immutable {@code Map} of extra optional attributes present under certain conditions.
|
||||
* For example, this might contain a {@link org.springframework.web.server.ServerWebExchange ServerWebExchange}
|
||||
* if the authorization was performed within the context of a {@code ServerWebExchange}.
|
||||
* @return an empty {@link Mono} that completes after this handler has finished handling the event.
|
||||
* @param attributes an immutable {@code Map} of extra optional attributes present
|
||||
* under certain conditions. For example, this might contain a
|
||||
* {@link org.springframework.web.server.ServerWebExchange ServerWebExchange} if the
|
||||
* authorization was performed within the context of a {@code ServerWebExchange}.
|
||||
* @return an empty {@link Mono} that completes after this handler has finished
|
||||
* handling the event.
|
||||
*/
|
||||
Mono<Void> onAuthorizationFailure(
|
||||
OAuth2AuthorizationException authorizationException,
|
||||
Authentication principal,
|
||||
Mono<Void> onAuthorizationFailure(OAuth2AuthorizationException authorizationException, Authentication principal,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,17 @@
|
||||
* 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 reactor.core.publisher.Mono;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* Handles when an OAuth 2.0 Client
|
||||
* has been successfully authorized (or re-authorized)
|
||||
* Handles when an OAuth 2.0 Client has been successfully authorized (or re-authorized)
|
||||
* via the authorization server.
|
||||
*
|
||||
* @author Phil Clay
|
||||
@@ -32,20 +33,18 @@ import java.util.Map;
|
||||
public interface ReactiveOAuth2AuthorizationSuccessHandler {
|
||||
|
||||
/**
|
||||
* Called when an OAuth 2.0 Client
|
||||
* has been successfully authorized (or re-authorized)
|
||||
* 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
|
||||
* @param principal the {@code Principal} associated with the authorized client
|
||||
* @param attributes an immutable {@code Map} of extra optional attributes present under certain conditions.
|
||||
* For example, this might contain a {@link org.springframework.web.server.ServerWebExchange ServerWebExchange}
|
||||
* if the authorization was performed within the context of a {@code ServerWebExchange}.
|
||||
* @return an empty {@link Mono} that completes after this handler has finished handling the event.
|
||||
* @param attributes an immutable {@code Map} of extra optional attributes present
|
||||
* under certain conditions. For example, this might contain a
|
||||
* {@link org.springframework.web.server.ServerWebExchange ServerWebExchange} if the
|
||||
* authorization was performed within the context of a {@code ServerWebExchange}.
|
||||
* @return an empty {@link Mono} that completes after this handler has finished
|
||||
* handling the event.
|
||||
*/
|
||||
Mono<Void> onAuthorizationSuccess(
|
||||
OAuth2AuthorizedClient authorizedClient,
|
||||
Authentication principal,
|
||||
Mono<Void> onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient, Authentication principal,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
}
|
||||
|
||||
@@ -13,23 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the overall management
|
||||
* of {@link OAuth2AuthorizedClient Authorized Client(s)}.
|
||||
* Implementations of this interface are responsible for the overall management of
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)}.
|
||||
*
|
||||
* <p>
|
||||
* The primary responsibilities include:
|
||||
* <ol>
|
||||
* <li>Authorizing (or re-authorizing) an OAuth 2.0 Client
|
||||
* by leveraging a {@link ReactiveOAuth2AuthorizedClientProvider}(s).</li>
|
||||
* <li>Delegating the persistence of an {@link OAuth2AuthorizedClient},
|
||||
* typically using a {@link ReactiveOAuth2AuthorizedClientService} OR {@link ServerOAuth2AuthorizedClientRepository}.</li>
|
||||
* <li>Authorizing (or re-authorizing) an OAuth 2.0 Client by leveraging a
|
||||
* {@link ReactiveOAuth2AuthorizedClientProvider}(s).</li>
|
||||
* <li>Delegating the persistence of an {@link OAuth2AuthorizedClient}, typically using a
|
||||
* {@link ReactiveOAuth2AuthorizedClientService} OR
|
||||
* {@link ServerOAuth2AuthorizedClientRepository}.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Joe Grandja
|
||||
@@ -43,20 +46,24 @@ import reactor.core.publisher.Mono;
|
||||
public interface ReactiveOAuth2AuthorizedClientManager {
|
||||
|
||||
/**
|
||||
* Attempt to authorize or re-authorize (if required) the {@link ClientRegistration client}
|
||||
* identified by the provided {@link OAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
|
||||
* Implementations must return an empty {@code Mono} if authorization is not supported for the specified client,
|
||||
* e.g. the associated {@link ReactiveOAuth2AuthorizedClientProvider}(s) does not support
|
||||
* the {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type configured for the client.
|
||||
* Attempt to authorize or re-authorize (if required) the {@link ClientRegistration
|
||||
* client} identified by the provided
|
||||
* {@link OAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
|
||||
* Implementations must return an empty {@code Mono} if authorization is not supported
|
||||
* for the specified client, e.g. the associated
|
||||
* {@link ReactiveOAuth2AuthorizedClientProvider}(s) does not support the
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type
|
||||
* configured for the client.
|
||||
*
|
||||
* <p>
|
||||
* In the case of re-authorization, implementations must return the provided {@link OAuth2AuthorizeRequest#getAuthorizedClient() authorized client}
|
||||
* if re-authorization is not supported for the client OR is not required,
|
||||
* e.g. a {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available OR
|
||||
* In the case of re-authorization, implementations must return the provided
|
||||
* {@link OAuth2AuthorizeRequest#getAuthorizedClient() authorized client} if
|
||||
* re-authorization is not supported for the client OR is not required, e.g. a
|
||||
* {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available OR
|
||||
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* @param authorizeRequest the authorize request
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization is not supported for the specified client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* authorization is not supported for the specified client
|
||||
*/
|
||||
Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest);
|
||||
|
||||
|
||||
@@ -13,32 +13,39 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A strategy for authorizing (or re-authorizing) an OAuth 2.0 Client.
|
||||
* Implementations will typically implement a specific {@link AuthorizationGrantType authorization grant} type.
|
||||
* A strategy for authorizing (or re-authorizing) an OAuth 2.0 Client. Implementations
|
||||
* will typically implement a specific {@link AuthorizationGrantType authorization grant}
|
||||
* type.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AuthorizedClient
|
||||
* @see OAuth2AuthorizationContext
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
||||
* 1.3 Authorization Grant</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveOAuth2AuthorizedClientProvider {
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided context.
|
||||
* Implementations must return an empty {@code Mono} if authorization is not supported for the specified client,
|
||||
* e.g. the provider doesn't support the {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type configured for the client.
|
||||
*
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* context. Implementations must return an empty {@code Mono} if authorization is not
|
||||
* supported for the specified client, e.g. the provider doesn't support the
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type
|
||||
* configured for the client.
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization is not supported for the specified client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* authorization is not supported for the specified client
|
||||
*/
|
||||
Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context);
|
||||
|
||||
|
||||
@@ -13,13 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.util.Assert;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
@@ -30,13 +25,19 @@ import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A builder that builds a {@link DelegatingReactiveOAuth2AuthorizedClientProvider} composed of
|
||||
* one or more {@link ReactiveOAuth2AuthorizedClientProvider}(s) that implement specific authorization grants.
|
||||
* The supported authorization grants are {@link #authorizationCode() authorization_code},
|
||||
* {@link #refreshToken() refresh_token}, {@link #clientCredentials() client_credentials}
|
||||
* and {@link #password() password}.
|
||||
* In addition to the standard authorization grants, an implementation of an extension grant
|
||||
* A builder that builds a {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
* composed of one or more {@link ReactiveOAuth2AuthorizedClientProvider}(s) that
|
||||
* implement specific authorization grants. The supported authorization grants are
|
||||
* {@link #authorizationCode() authorization_code}, {@link #refreshToken() refresh_token},
|
||||
* {@link #clientCredentials() client_credentials} and {@link #password() password}. In
|
||||
* addition to the standard authorization grants, an implementation of an extension grant
|
||||
* may be supplied via {@link #provider(ReactiveOAuth2AuthorizedClientProvider)}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
@@ -49,14 +50,15 @@ import java.util.stream.Collectors;
|
||||
* @see DelegatingReactiveOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
private final Map<Class<?>, Builder> builders = new LinkedHashMap<>();
|
||||
|
||||
private ReactiveOAuth2AuthorizedClientProviderBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link ReactiveOAuth2AuthorizedClientProviderBuilder} for configuring the supported authorization grant(s).
|
||||
*
|
||||
* Returns a new {@link ReactiveOAuth2AuthorizedClientProviderBuilder} for configuring
|
||||
* the supported authorization grant(s).
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public static ReactiveOAuth2AuthorizedClientProviderBuilder builder() {
|
||||
@@ -64,184 +66,167 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a {@link ReactiveOAuth2AuthorizedClientProvider} to be composed with the {@link DelegatingReactiveOAuth2AuthorizedClientProvider}.
|
||||
* This may be used for implementations of extension authorization grants.
|
||||
*
|
||||
* Configures a {@link ReactiveOAuth2AuthorizedClientProvider} to be composed with the
|
||||
* {@link DelegatingReactiveOAuth2AuthorizedClientProvider}. This may be used for
|
||||
* implementations of extension authorization grants.
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder provider(ReactiveOAuth2AuthorizedClientProvider provider) {
|
||||
Assert.notNull(provider, "provider cannot be null");
|
||||
this.builders.computeIfAbsent(provider.getClass(), k -> () -> provider);
|
||||
this.builders.computeIfAbsent(provider.getClass(), (k) -> () -> provider);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code authorization_code} grant.
|
||||
*
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder authorizationCode() {
|
||||
this.builders.computeIfAbsent(AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class, k -> new AuthorizationCodeGrantBuilder());
|
||||
this.builders.computeIfAbsent(AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new AuthorizationCodeGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code refresh_token} grant.
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken() {
|
||||
this.builders.computeIfAbsent(RefreshTokenReactiveOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new RefreshTokenGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code refresh_token} grant.
|
||||
* @param builderConsumer a {@code Consumer} of {@link RefreshTokenGrantBuilder} used
|
||||
* for further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken(
|
||||
Consumer<RefreshTokenGrantBuilder> builderConsumer) {
|
||||
RefreshTokenGrantBuilder builder = (RefreshTokenGrantBuilder) this.builders.computeIfAbsent(
|
||||
RefreshTokenReactiveOAuth2AuthorizedClientProvider.class, (k) -> new RefreshTokenGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code client_credentials} grant.
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials() {
|
||||
this.builders.computeIfAbsent(ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new ClientCredentialsGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code client_credentials} grant.
|
||||
* @param builderConsumer a {@code Consumer} of {@link ClientCredentialsGrantBuilder}
|
||||
* used for further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials(
|
||||
Consumer<ClientCredentialsGrantBuilder> builderConsumer) {
|
||||
ClientCredentialsGrantBuilder builder = (ClientCredentialsGrantBuilder) this.builders.computeIfAbsent(
|
||||
ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new ClientCredentialsGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder password() {
|
||||
this.builders.computeIfAbsent(PasswordReactiveOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new PasswordGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
* @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for
|
||||
* further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder password(Consumer<PasswordGrantBuilder> builderConsumer) {
|
||||
PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders.computeIfAbsent(
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider.class, (k) -> new PasswordGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
* composed of one or more {@link ReactiveOAuth2AuthorizedClientProvider}(s).
|
||||
* @return the {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProvider build() {
|
||||
List<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders = this.builders.values().stream()
|
||||
.map(Builder::build).collect(Collectors.toList());
|
||||
return new DelegatingReactiveOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider build();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code authorization_code} grant.
|
||||
*/
|
||||
public class AuthorizationCodeGrantBuilder implements Builder {
|
||||
public final class AuthorizationCodeGrantBuilder implements Builder {
|
||||
|
||||
private AuthorizationCodeGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link AuthorizationCodeReactiveOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* Builds an instance of
|
||||
* {@link AuthorizationCodeReactiveOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link AuthorizationCodeReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public ReactiveOAuth2AuthorizedClientProvider build() {
|
||||
return new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code refresh_token} grant.
|
||||
*
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken() {
|
||||
this.builders.computeIfAbsent(RefreshTokenReactiveOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code refresh_token} grant.
|
||||
*
|
||||
* @param builderConsumer a {@code Consumer} of {@link RefreshTokenGrantBuilder} used for further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder refreshToken(Consumer<RefreshTokenGrantBuilder> builderConsumer) {
|
||||
RefreshTokenGrantBuilder builder = (RefreshTokenGrantBuilder) this.builders.computeIfAbsent(
|
||||
RefreshTokenReactiveOAuth2AuthorizedClientProvider.class, k -> new RefreshTokenGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code refresh_token} grant.
|
||||
*/
|
||||
public class RefreshTokenGrantBuilder implements Builder {
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
|
||||
private Duration clockSkew;
|
||||
private Clock clock;
|
||||
|
||||
private RefreshTokenGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder accessTokenResponseClient(ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access token expiry.
|
||||
* An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}.
|
||||
*
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* @param clock the clock
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link RefreshTokenReactiveOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @return the {@link RefreshTokenReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public ReactiveOAuth2AuthorizedClientProvider build() {
|
||||
RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code client_credentials} grant.
|
||||
*
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials() {
|
||||
this.builders.computeIfAbsent(ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code client_credentials} grant.
|
||||
*
|
||||
* @param builderConsumer a {@code Consumer} of {@link ClientCredentialsGrantBuilder} used for further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder clientCredentials(Consumer<ClientCredentialsGrantBuilder> builderConsumer) {
|
||||
ClientCredentialsGrantBuilder builder = (ClientCredentialsGrantBuilder) this.builders.computeIfAbsent(
|
||||
ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class, k -> new ClientCredentialsGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code client_credentials} grant.
|
||||
*/
|
||||
public class ClientCredentialsGrantBuilder implements Builder {
|
||||
public final class ClientCredentialsGrantBuilder implements Builder {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private ClientCredentialsGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
public ClientCredentialsGrantBuilder accessTokenResponseClient(ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
public ClientCredentialsGrantBuilder accessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access token expiry.
|
||||
* An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}.
|
||||
*
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if it's before
|
||||
* {@code Instant.now(this.clock) - clockSkew}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
@@ -251,8 +236,8 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link ClientCredentialsGrantBuilder}
|
||||
*/
|
||||
@@ -262,8 +247,8 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link ClientCredentialsReactiveOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* Builds an instance of
|
||||
* {@link ClientCredentialsReactiveOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link ClientCredentialsReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
@@ -280,57 +265,40 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
*
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder password() {
|
||||
this.builders.computeIfAbsent(PasswordReactiveOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
*
|
||||
* @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder password(Consumer<PasswordGrantBuilder> builderConsumer) {
|
||||
PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders.computeIfAbsent(
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider.class, k -> new PasswordGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code password} grant.
|
||||
*/
|
||||
public class PasswordGrantBuilder implements Builder {
|
||||
public final class PasswordGrantBuilder implements Builder {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private PasswordGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
public PasswordGrantBuilder accessTokenResponseClient(ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
public PasswordGrantBuilder accessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access token expiry.
|
||||
* An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}.
|
||||
*
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if it's before
|
||||
* {@code Instant.now(this.clock) - clockSkew}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
@@ -340,8 +308,8 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
@@ -352,7 +320,6 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link PasswordReactiveOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @return the {@link PasswordReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
@@ -369,23 +336,79 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
* composed of one or more {@link ReactiveOAuth2AuthorizedClientProvider}(s).
|
||||
*
|
||||
* @return the {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
* A builder for the {@code refresh_token} grant.
|
||||
*/
|
||||
public ReactiveOAuth2AuthorizedClientProvider build() {
|
||||
List<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders =
|
||||
this.builders.values().stream()
|
||||
.map(Builder::build)
|
||||
.collect(Collectors.toList());
|
||||
return new DelegatingReactiveOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
public final class RefreshTokenGrantBuilder implements Builder {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private RefreshTokenGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder accessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if it's before
|
||||
* {@code Instant.now(this.clock) - clockSkew}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link RefreshTokenGrantBuilder}
|
||||
*/
|
||||
public RefreshTokenGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of
|
||||
* {@link RefreshTokenReactiveOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link RefreshTokenReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public ReactiveOAuth2AuthorizedClientProvider build() {
|
||||
RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
ReactiveOAuth2AuthorizedClientProvider build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for the management
|
||||
* of {@link OAuth2AuthorizedClient Authorized Client(s)}, which provide the purpose
|
||||
* of associating an {@link OAuth2AuthorizedClient#getAccessToken() Access Token} credential
|
||||
* Implementations of this interface are responsible for the management of
|
||||
* {@link OAuth2AuthorizedClient Authorized Client(s)}, which provide the purpose of
|
||||
* associating an {@link OAuth2AuthorizedClient#getAccessToken() Access Token} credential
|
||||
* to a {@link OAuth2AuthorizedClient#getClientRegistration() Client} and Resource Owner,
|
||||
* who is the {@link OAuth2AuthorizedClient#getPrincipalName() Principal}
|
||||
* that originally granted the authorization.
|
||||
* who is the {@link OAuth2AuthorizedClient#getPrincipalName() Principal} that originally
|
||||
* granted the authorization.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
@@ -39,32 +40,27 @@ import reactor.core.publisher.Mono;
|
||||
public interface ReactiveOAuth2AuthorizedClientService {
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User's {@code Principal} name
|
||||
* or {@code null} if not available.
|
||||
*
|
||||
* Returns the {@link OAuth2AuthorizedClient} associated to the provided client
|
||||
* registration identifier and End-User's {@code Principal} name or {@code null} if
|
||||
* not available.
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
* @param <T> a type of OAuth2AuthorizedClient
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if not available
|
||||
*/
|
||||
<T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId,
|
||||
String principalName);
|
||||
<T extends OAuth2AuthorizedClient> Mono<T> loadAuthorizedClient(String clientRegistrationId, String principalName);
|
||||
|
||||
/**
|
||||
* Saves the {@link OAuth2AuthorizedClient} associating it to
|
||||
* the provided End-User {@link Authentication} (Resource Owner).
|
||||
*
|
||||
* Saves the {@link OAuth2AuthorizedClient} associating it to the provided End-User
|
||||
* {@link Authentication} (Resource Owner).
|
||||
* @param authorizedClient the authorized client
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
*/
|
||||
Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient,
|
||||
Authentication principal);
|
||||
Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal);
|
||||
|
||||
/**
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User's {@code Principal} name.
|
||||
*
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the provided client
|
||||
* registration identifier and End-User's {@code Principal} name.
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principalName the name of the End-User {@code Principal} (Resource Owner)
|
||||
*/
|
||||
|
||||
@@ -13,8 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
@@ -25,17 +34,9 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -43,84 +44,93 @@ import java.util.Set;
|
||||
* @see DefaultRefreshTokenTokenResponseClient
|
||||
*/
|
||||
public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
|
||||
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient =
|
||||
new DefaultRefreshTokenTokenResponseClient();
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to re-authorize the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns {@code null} if re-authorization is not supported,
|
||||
* e.g. the client is not authorized OR the {@link OAuth2AuthorizedClient#getRefreshToken() refresh token}
|
||||
* is not available for the authorized client OR the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
* Attempt to re-authorize the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns {@code null} if re-authorization is not supported, e.g.
|
||||
* the client is not authorized OR the {@link OAuth2AuthorizedClient#getRefreshToken()
|
||||
* refresh token} is not available for the authorized client OR the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* <p>
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes} are supported:
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes}
|
||||
* are supported:
|
||||
* <ol>
|
||||
* <li>{@link OAuth2AuthorizationContext#REQUEST_SCOPE_ATTRIBUTE_NAME} (optional) - a {@code String[]} of scope(s)
|
||||
* to be requested by the {@link OAuth2AuthorizationContext#getClientRegistration() client}</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#REQUEST_SCOPE_ATTRIBUTE_NAME} (optional) - a
|
||||
* {@code String[]} of scope(s) to be requested by the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if re-authorization is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if re-authorization is
|
||||
* not supported
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
if (authorizedClient == null ||
|
||||
authorizedClient.getRefreshToken() == null ||
|
||||
!hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
if (authorizedClient == null || authorizedClient.getRefreshToken() == null
|
||||
|| !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object requestScope = context.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME);
|
||||
Set<String> scopes = Collections.emptySet();
|
||||
if (requestScope != null) {
|
||||
Assert.isInstanceOf(String[].class, requestScope,
|
||||
"The context attribute must be of type String[] '" + OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME + "'");
|
||||
Assert.isInstanceOf(String[].class, requestScope, "The context attribute must be of type String[] '"
|
||||
+ OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME + "'");
|
||||
scopes = new HashSet<>(Arrays.asList((String[]) requestScope));
|
||||
}
|
||||
|
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
|
||||
authorizedClient.getClientRegistration(), authorizedClient.getAccessToken(),
|
||||
authorizedClient.getRefreshToken(), scopes);
|
||||
|
||||
OAuth2AccessTokenResponse tokenResponse;
|
||||
try {
|
||||
tokenResponse = this.accessTokenResponseClient.getTokenResponse(refreshTokenGrantRequest);
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
throw new ClientAuthorizationException(ex.getError(), authorizedClient.getClientRegistration().getRegistrationId(), ex);
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(authorizedClient, refreshTokenGrantRequest);
|
||||
return new OAuth2AuthorizedClient(context.getAuthorizedClient().getClientRegistration(),
|
||||
context.getPrincipal().getName(), tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizedClient authorizedClient,
|
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
|
||||
try {
|
||||
return this.accessTokenResponseClient.getTokenResponse(refreshTokenGrantRequest);
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
throw new ClientAuthorizationException(ex.getError(),
|
||||
authorizedClient.getClientRegistration().getRegistrationId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(AbstractOAuth2Token token) {
|
||||
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint for the {@code refresh_token} grant.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint for the {@code refresh_token} grant
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code refresh_token} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code refresh_token} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
public void setAccessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds.
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if {@code OAuth2AccessToken#getExpiresAt() - clockSkew}
|
||||
* is before the current time {@code clock#instant()}.
|
||||
*
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
@@ -130,12 +140,13 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,17 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
@@ -33,65 +24,79 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider}
|
||||
* for the {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
* @see WebClientReactiveRefreshTokenTokenResponseClient
|
||||
*/
|
||||
public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient =
|
||||
new WebClientReactiveRefreshTokenTokenResponseClient();
|
||||
public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to re-authorize the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}.
|
||||
* Returns an empty {@code Mono} if re-authorization is not supported,
|
||||
* e.g. the client is not authorized OR the {@link OAuth2AuthorizedClient#getRefreshToken() refresh token}
|
||||
* is not available for the authorized client OR the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
* Attempt to re-authorize the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns an empty {@code Mono} if re-authorization is not
|
||||
* supported, e.g. the client is not authorized OR the
|
||||
* {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available for
|
||||
* the authorized client OR the {@link OAuth2AuthorizedClient#getAccessToken() access
|
||||
* token} is not expired.
|
||||
*
|
||||
* <p>
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes} are supported:
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes}
|
||||
* are supported:
|
||||
* <ol>
|
||||
* <li>{@code "org.springframework.security.oauth2.client.REQUEST_SCOPE"} (optional) - a {@code String[]} of scope(s)
|
||||
* to be requested by the {@link OAuth2AuthorizationContext#getClientRegistration() client}</li>
|
||||
* <li>{@code "org.springframework.security.oauth2.client.REQUEST_SCOPE"} (optional) -
|
||||
* a {@code String[]} of scope(s) to be requested by the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if re-authorization is not supported
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* re-authorization is not supported
|
||||
*/
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
if (authorizedClient == null ||
|
||||
authorizedClient.getRefreshToken() == null ||
|
||||
!hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
if (authorizedClient == null || authorizedClient.getRefreshToken() == null
|
||||
|| !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
Object requestScope = context.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME);
|
||||
Set<String> scopes = Collections.emptySet();
|
||||
if (requestScope != null) {
|
||||
Assert.isInstanceOf(String[].class, requestScope,
|
||||
"The context attribute must be of type String[] '" + OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME + "'");
|
||||
Assert.isInstanceOf(String[].class, requestScope, "The context attribute must be of type String[] '"
|
||||
+ OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME + "'");
|
||||
scopes = new HashSet<>(Arrays.asList((String[]) requestScope));
|
||||
}
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
|
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
|
||||
clientRegistration, authorizedClient.getAccessToken(), authorizedClient.getRefreshToken(), scopes);
|
||||
|
||||
return Mono.just(refreshTokenGrantRequest)
|
||||
.flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration,
|
||||
authorizedClient.getAccessToken(), authorizedClient.getRefreshToken(), scopes);
|
||||
return Mono.just(refreshTokenGrantRequest).flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
e -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(), e))
|
||||
.map(tokenResponse -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(),
|
||||
e))
|
||||
.map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()));
|
||||
}
|
||||
|
||||
@@ -100,23 +105,26 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token Endpoint for the {@code refresh_token} grant.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used when requesting an access token credential at the Token Endpoint for the {@code refresh_token} grant
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code refresh_token} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code refresh_token} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
public void setAccessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds.
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if {@code OAuth2AccessToken#getExpiresAt() - clockSkew}
|
||||
* is before the current time {@code clock#instant()}.
|
||||
*
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
@@ -126,12 +134,13 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry.
|
||||
*
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
@@ -22,16 +29,10 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
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}
|
||||
* when the {@link OAuth2Error#getErrorCode()} matches
|
||||
* one of the configured {@link OAuth2ErrorCodes OAuth 2.0 error codes}.
|
||||
* An {@link OAuth2AuthorizationFailureHandler} that removes an
|
||||
* {@link OAuth2AuthorizedClient} when the {@link OAuth2Error#getErrorCode()} matches one
|
||||
* of the configured {@link OAuth2ErrorCodes OAuth 2.0 error codes}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.3
|
||||
@@ -42,29 +43,28 @@ import java.util.Set;
|
||||
public class RemoveAuthorizedClientOAuth2AuthorizationFailureHandler implements OAuth2AuthorizationFailureHandler {
|
||||
|
||||
/**
|
||||
* The default OAuth 2.0 error codes that will trigger removal of an {@link OAuth2AuthorizedClient}.
|
||||
* 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
|
||||
)));
|
||||
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES;
|
||||
static {
|
||||
Set<String> codes = new LinkedHashSet<>();
|
||||
// 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.
|
||||
codes.add(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.
|
||||
codes.add(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections.unmodifiableSet(codes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The OAuth 2.0 error codes which will trigger removal of an {@link OAuth2AuthorizedClient}.
|
||||
* The OAuth 2.0 error codes which will trigger removal of an
|
||||
* {@link OAuth2AuthorizedClient}.
|
||||
* @see OAuth2ErrorCodes
|
||||
*/
|
||||
private final Set<String> removeAuthorizedClientErrorCodes;
|
||||
@@ -76,6 +76,59 @@ public class RemoveAuthorizedClientOAuth2AuthorizationFailureHandler implements
|
||||
*/
|
||||
private final OAuth2AuthorizedClientRemover delegate;
|
||||
|
||||
/**
|
||||
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using
|
||||
* the provided parameters.
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for
|
||||
* removing an {@link OAuth2AuthorizedClient} if the error code is one of the
|
||||
* {@link #DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES}.
|
||||
*/
|
||||
public RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
|
||||
OAuth2AuthorizedClientRemover authorizedClientRemover) {
|
||||
this(authorizedClientRemover, DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using
|
||||
* the provided parameters.
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for
|
||||
* removing an {@link OAuth2AuthorizedClient} 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(
|
||||
OAuth2AuthorizedClientRemover authorizedClientRemover, Set<String> removeAuthorizedClientErrorCodes) {
|
||||
Assert.notNull(authorizedClientRemover, "authorizedClientRemover cannot be null");
|
||||
Assert.notNull(removeAuthorizedClientErrorCodes, "removeAuthorizedClientErrorCodes cannot be null");
|
||||
this.removeAuthorizedClientErrorCodes = Collections
|
||||
.unmodifiableSet(new HashSet<>(removeAuthorizedClientErrorCodes));
|
||||
this.delegate = authorizedClientRemover;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an {@link OAuth2AuthorizedClient} from an
|
||||
* {@link OAuth2AuthorizedClientRepository} or {@link OAuth2AuthorizedClientService}.
|
||||
@@ -84,68 +137,19 @@ public class RemoveAuthorizedClientOAuth2AuthorizationFailureHandler implements
|
||||
public interface OAuth2AuthorizedClientRemover {
|
||||
|
||||
/**
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User {@link Authentication} (Resource Owner).
|
||||
*
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the provided client
|
||||
* registration identifier and End-User {@link Authentication} (Resource Owner).
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
* @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}.
|
||||
* @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 removeAuthorizedClient(String clientRegistrationId, Authentication principal, Map<String, Object> attributes);
|
||||
void removeAuthorizedClient(String clientRegistrationId, Authentication principal,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for removing an {@link OAuth2AuthorizedClient}
|
||||
* if the error code is one of the {@link #DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES}.
|
||||
*/
|
||||
public RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(OAuth2AuthorizedClientRemover authorizedClientRemover) {
|
||||
this(authorizedClientRemover, DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code RemoveAuthorizedClientOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for removing an {@link OAuth2AuthorizedClient}
|
||||
* 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(
|
||||
OAuth2AuthorizedClientRemover authorizedClientRemover,
|
||||
Set<String> removeAuthorizedClientErrorCodes) {
|
||||
Assert.notNull(authorizedClientRemover, "authorizedClientRemover cannot be null");
|
||||
Assert.notNull(removeAuthorizedClientErrorCodes, "removeAuthorizedClientErrorCodes cannot be null");
|
||||
this.removeAuthorizedClientErrorCodes = Collections.unmodifiableSet(new HashSet<>(removeAuthorizedClientErrorCodes));
|
||||
this.delegate = authorizedClientRemover;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,56 +13,60 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveOAuth2AuthorizationFailureHandler} that removes an {@link OAuth2AuthorizedClient}
|
||||
* when the {@link OAuth2Error#getErrorCode()} matches
|
||||
* one of the configured {@link OAuth2ErrorCodes OAuth 2.0 error codes}.
|
||||
* A {@link ReactiveOAuth2AuthorizationFailureHandler} that removes an
|
||||
* {@link OAuth2AuthorizedClient} when the {@link OAuth2Error#getErrorCode()} matches one
|
||||
* of the configured {@link OAuth2ErrorCodes OAuth 2.0 error codes}.
|
||||
*
|
||||
* @author Phil Clay
|
||||
* @since 5.3
|
||||
*/
|
||||
public class RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler implements ReactiveOAuth2AuthorizationFailureHandler {
|
||||
public class RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
|
||||
implements ReactiveOAuth2AuthorizationFailureHandler {
|
||||
|
||||
/**
|
||||
* The default OAuth 2.0 error codes that will trigger removal of the authorized client.
|
||||
* The default OAuth 2.0 error codes that will trigger removal of the authorized
|
||||
* client.
|
||||
* @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 the ServerOAuth2AuthorizedClientExchangeFilterFunction
|
||||
* delegates this type of failure received from a resource server
|
||||
* to this failure handler.
|
||||
*/
|
||||
OAuth2ErrorCodes.INVALID_TOKEN,
|
||||
/*
|
||||
* Returned from authorization servers when a 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)));
|
||||
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES;
|
||||
static {
|
||||
Set<String> codes = new LinkedHashSet<>();
|
||||
// Returned from resource servers when an access token provided is expired,
|
||||
// revoked, malformed, or invalid for other reasons. Note that this is needed
|
||||
// because the ServerOAuth2AuthorizedClientExchangeFilterFunction delegates this
|
||||
// type of failure received from a resource server to this failure handler.
|
||||
codes.add(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
// Returned from authorization servers when a refresh token is invalid, expired,
|
||||
// revoked, does not match the redirection URI used in the authorization request,
|
||||
// or was issued to another client.
|
||||
codes.add(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections.unmodifiableSet(codes);
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegate that removes an {@link OAuth2AuthorizedClient} from a
|
||||
* {@link ServerOAuth2AuthorizedClientRepository} or {@link ReactiveOAuth2AuthorizedClientService}
|
||||
* if the error code is one of the {@link #removeAuthorizedClientErrorCodes}.
|
||||
* {@link ServerOAuth2AuthorizedClientRepository} or
|
||||
* {@link ReactiveOAuth2AuthorizedClientService} if the error code is one of the
|
||||
* {@link #removeAuthorizedClientErrorCodes}.
|
||||
*/
|
||||
private final OAuth2AuthorizedClientRemover delegate;
|
||||
|
||||
@@ -73,77 +77,85 @@ public class RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler imp
|
||||
private final Set<String> removeAuthorizedClientErrorCodes;
|
||||
|
||||
/**
|
||||
* Removes an {@link OAuth2AuthorizedClient} from a
|
||||
* {@link ServerOAuth2AuthorizedClientRepository} or {@link ReactiveOAuth2AuthorizedClientService}.
|
||||
* Constructs a
|
||||
* {@code RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler} using the
|
||||
* provided parameters.
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for
|
||||
* removing an {@link OAuth2AuthorizedClient} if the error code is one of the
|
||||
* {@link #DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OAuth2AuthorizedClientRemover {
|
||||
|
||||
/**
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the
|
||||
* provided client registration identifier and End-User {@link Authentication} (Resource Owner).
|
||||
*
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
* @param attributes an immutable {@code Map} of extra optional attributes present under certain conditions.
|
||||
* For example, this might contain a {@link org.springframework.web.server.ServerWebExchange ServerWebExchange}
|
||||
* if the authorization was performed within the context of a {@code ServerWebExchange}.
|
||||
* @return an empty {@link Mono} that completes after this handler has finished handling the event.
|
||||
*/
|
||||
Mono<Void> removeAuthorizedClient(String clientRegistrationId, Authentication principal, Map<String, Object> attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for removing an {@link OAuth2AuthorizedClient}
|
||||
* if the error code is one of the {@link #DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES}.
|
||||
*/
|
||||
public RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(OAuth2AuthorizedClientRemover authorizedClientRemover) {
|
||||
public RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(
|
||||
OAuth2AuthorizedClientRemover authorizedClientRemover) {
|
||||
this(authorizedClientRemover, DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler} using the provided parameters.
|
||||
*
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for removing an {@link OAuth2AuthorizedClient}
|
||||
* 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.
|
||||
* Constructs a
|
||||
* {@code RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler} using the
|
||||
* provided parameters.
|
||||
* @param authorizedClientRemover the {@link OAuth2AuthorizedClientRemover} used for
|
||||
* removing an {@link OAuth2AuthorizedClient} 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 RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(
|
||||
OAuth2AuthorizedClientRemover authorizedClientRemover,
|
||||
Set<String> removeAuthorizedClientErrorCodes) {
|
||||
OAuth2AuthorizedClientRemover authorizedClientRemover, Set<String> removeAuthorizedClientErrorCodes) {
|
||||
Assert.notNull(authorizedClientRemover, "authorizedClientRemover cannot be null");
|
||||
Assert.notNull(removeAuthorizedClientErrorCodes, "removeAuthorizedClientErrorCodes cannot be null");
|
||||
this.removeAuthorizedClientErrorCodes = Collections.unmodifiableSet(new HashSet<>(removeAuthorizedClientErrorCodes));
|
||||
this.removeAuthorizedClientErrorCodes = Collections
|
||||
.unmodifiableSet(new HashSet<>(removeAuthorizedClientErrorCodes));
|
||||
this.delegate = authorizedClientRemover;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> onAuthorizationFailure(OAuth2AuthorizationException authorizationException,
|
||||
Authentication principal, Map<String, Object> attributes) {
|
||||
|
||||
if (authorizationException instanceof ClientAuthorizationException
|
||||
&& hasRemovalErrorCode(authorizationException)) {
|
||||
|
||||
ClientAuthorizationException clientAuthorizationException = (ClientAuthorizationException) authorizationException;
|
||||
return this.delegate.removeAuthorizedClient(
|
||||
clientAuthorizationException.getClientRegistrationId(), principal, attributes);
|
||||
} else {
|
||||
return Mono.empty();
|
||||
return this.delegate.removeAuthorizedClient(clientAuthorizationException.getClientRegistrationId(),
|
||||
principal, attributes);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given exception has an error code that
|
||||
* indicates that the authorized client should be removed.
|
||||
*
|
||||
* 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.
|
||||
* @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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an {@link OAuth2AuthorizedClient} from a
|
||||
* {@link ServerOAuth2AuthorizedClientRepository} or
|
||||
* {@link ReactiveOAuth2AuthorizedClientService}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OAuth2AuthorizedClientRemover {
|
||||
|
||||
/**
|
||||
* Removes the {@link OAuth2AuthorizedClient} associated to the provided client
|
||||
* registration identifier and End-User {@link Authentication} (Resource Owner).
|
||||
* @param clientRegistrationId the identifier for the client's registration
|
||||
* @param principal the End-User {@link Authentication} (Resource Owner)
|
||||
* @param attributes an immutable {@code Map} of extra optional attributes present
|
||||
* under certain conditions. For example, this might contain a
|
||||
* {@link org.springframework.web.server.ServerWebExchange ServerWebExchange} if
|
||||
* the authorization was performed within the context of a
|
||||
* {@code ServerWebExchange}.
|
||||
* @return an empty {@link Mono} that completes after this handler has finished
|
||||
* handling the event.
|
||||
*/
|
||||
Mono<Void> removeAuthorizedClient(String clientRegistrationId, Authentication principal,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,11 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.annotation;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.web.method.annotation.OAuth2AuthorizedClientArgumentResolver;
|
||||
package org.springframework.security.oauth2.client.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -25,13 +22,16 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.web.method.annotation.OAuth2AuthorizedClientArgumentResolver;
|
||||
|
||||
/**
|
||||
* This annotation may be used to resolve a method parameter
|
||||
* to an argument value of type {@link OAuth2AuthorizedClient}.
|
||||
* This annotation may be used to resolve a method parameter to an argument value of type
|
||||
* {@link OAuth2AuthorizedClient}.
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
* <pre>
|
||||
* For example: <pre>
|
||||
* @Controller
|
||||
* public class MyController {
|
||||
* @GetMapping("/authorized-client")
|
||||
@@ -52,18 +52,16 @@ public @interface RegisteredOAuth2AuthorizedClient {
|
||||
|
||||
/**
|
||||
* Sets the client registration identifier.
|
||||
*
|
||||
* @return the client registration identifier
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String registrationId() default "";
|
||||
|
||||
/**
|
||||
* The default attribute for this annotation.
|
||||
* This is an alias for {@link #registrationId()}.
|
||||
* For example, {@code @RegisteredOAuth2AuthorizedClient("login-client")} is equivalent to
|
||||
* The default attribute for this annotation. This is an alias for
|
||||
* {@link #registrationId()}. For example,
|
||||
* {@code @RegisteredOAuth2AuthorizedClient("login-client")} is equivalent to
|
||||
* {@code @RegisteredOAuth2AuthorizedClient(registrationId="login-client")}.
|
||||
*
|
||||
* @return the client registration identifier
|
||||
*/
|
||||
@AliasFor("registrationId")
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -23,16 +26,14 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractAuthenticationToken}
|
||||
* that represents an OAuth 2.0 {@link Authentication}.
|
||||
* An implementation of an {@link AbstractAuthenticationToken} that represents an OAuth
|
||||
* 2.0 {@link Authentication}.
|
||||
* <p>
|
||||
* The {@link Authentication} associates an {@link OAuth2User} {@code Principal}
|
||||
* to the identifier of the {@link #getAuthorizedClientRegistrationId() Authorized Client},
|
||||
* which the End-User ({@code Principal}) granted authorization to
|
||||
* so that it can access it's protected resources at the UserInfo Endpoint.
|
||||
* The {@link Authentication} associates an {@link OAuth2User} {@code Principal} to the
|
||||
* identifier of the {@link #getAuthorizedClientRegistrationId() Authorized Client}, which
|
||||
* the End-User ({@code Principal}) granted authorization to so that it can access it's
|
||||
* protected resources at the UserInfo Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -41,20 +42,22 @@ import java.util.Collection;
|
||||
* @see OAuth2AuthorizedClient
|
||||
*/
|
||||
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private final OAuth2User principal;
|
||||
|
||||
private final String authorizedClientRegistrationId;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthenticationToken} using the provided parameters.
|
||||
*
|
||||
* @param principal the user {@code Principal} registered with the OAuth 2.0 Provider
|
||||
* @param authorities the authorities granted to the user
|
||||
* @param authorizedClientRegistrationId the registration identifier of the {@link OAuth2AuthorizedClient Authorized Client}
|
||||
* @param authorizedClientRegistrationId the registration identifier of the
|
||||
* {@link OAuth2AuthorizedClient Authorized Client}
|
||||
*/
|
||||
public OAuth2AuthenticationToken(OAuth2User principal,
|
||||
Collection<? extends GrantedAuthority> authorities,
|
||||
String authorizedClientRegistrationId) {
|
||||
public OAuth2AuthenticationToken(OAuth2User principal, Collection<? extends GrantedAuthority> authorities,
|
||||
String authorizedClientRegistrationId) {
|
||||
super(authorities);
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
Assert.hasText(authorizedClientRegistrationId, "authorizedClientRegistrationId cannot be empty");
|
||||
@@ -75,11 +78,12 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registration identifier of the {@link OAuth2AuthorizedClient Authorized Client}.
|
||||
*
|
||||
* Returns the registration identifier of the {@link OAuth2AuthorizedClient Authorized
|
||||
* Client}.
|
||||
* @return the registration identifier of the Authorized Client.
|
||||
*/
|
||||
public String getAuthorizedClientRegistrationId() {
|
||||
return this.authorizedClientRegistrationId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
@@ -28,70 +29,67 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthenticationProvider} for the OAuth 2.0 Authorization Code Grant.
|
||||
* An implementation of an {@link AuthenticationProvider} for the OAuth 2.0 Authorization
|
||||
* Code Grant.
|
||||
*
|
||||
* <p>
|
||||
* This {@link AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* and if valid, exchanging it for an Access Token credential.
|
||||
* This {@link AuthenticationProvider} is responsible for authenticating an Authorization
|
||||
* Code credential with the Authorization Server's Token Endpoint and if valid, exchanging
|
||||
* it for an Access Token credential.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizationCodeAuthenticationToken
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token
|
||||
* Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token
|
||||
* Response</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||
|
||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used for requesting the access token credential from the Token Endpoint
|
||||
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the
|
||||
* provided parameters.
|
||||
* @param accessTokenResponseClient the client used for requesting the access token
|
||||
* credential from the Token Endpoint
|
||||
*/
|
||||
public OAuth2AuthorizationCodeAuthenticationProvider(
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
||||
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||
|
||||
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
|
||||
.getAuthorizationExchange().getAuthorizationResponse();
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationResponse();
|
||||
if (authorizationResponse.statusError()) {
|
||||
throw new OAuth2AuthorizationException(authorizationResponse.getError());
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
|
||||
.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationRequest();
|
||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||
throw new OAuth2AuthorizationException(oauth2Error);
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponse accessTokenResponse =
|
||||
this.accessTokenResponseClient.getTokenResponse(
|
||||
new OAuth2AuthorizationCodeGrantRequest(
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange()));
|
||||
|
||||
OAuth2AuthorizationCodeAuthenticationToken authenticationResult =
|
||||
new OAuth2AuthorizationCodeAuthenticationToken(
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
|
||||
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange()));
|
||||
OAuth2AuthorizationCodeAuthenticationToken authenticationResult = new OAuth2AuthorizationCodeAuthenticationToken(
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange(),
|
||||
accessTokenResponse.getAccessToken(),
|
||||
accessTokenResponse.getRefreshToken(),
|
||||
accessTokenResponse.getAdditionalParameters());
|
||||
authorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(),
|
||||
accessTokenResponse.getRefreshToken(), accessTokenResponse.getAdditionalParameters());
|
||||
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
@@ -99,4 +97,5 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
@@ -24,10 +29,6 @@ import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An {@link AbstractAuthenticationToken} for the OAuth 2.0 Authorization Code Grant.
|
||||
*
|
||||
@@ -37,24 +38,31 @@ import java.util.Map;
|
||||
* @see ClientRegistration
|
||||
* @see OAuth2AuthorizationExchange
|
||||
* @see OAuth2AccessToken
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private Map<String, Object> additionalParameters = new HashMap<>();
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private OAuth2AuthorizationExchange authorizationExchange;
|
||||
|
||||
private OAuth2AccessToken accessToken;
|
||||
|
||||
private OAuth2RefreshToken refreshToken;
|
||||
|
||||
/**
|
||||
* This constructor should be used when the Authorization Request/Response is complete.
|
||||
*
|
||||
* This constructor should be used when the Authorization Request/Response is
|
||||
* complete.
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
*/
|
||||
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange) {
|
||||
OAuth2AuthorizationExchange authorizationExchange) {
|
||||
super(Collections.emptyList());
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
|
||||
@@ -65,35 +73,32 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
||||
/**
|
||||
* This constructor should be used when the Access Token Request/Response is complete,
|
||||
* which indicates that the Authorization Code Grant flow has fully completed.
|
||||
*
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
* @param accessToken the access token credential
|
||||
*/
|
||||
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange,
|
||||
OAuth2AccessToken accessToken) {
|
||||
OAuth2AuthorizationExchange authorizationExchange, OAuth2AccessToken accessToken) {
|
||||
this(clientRegistration, authorizationExchange, accessToken, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor should be used when the Access Token Request/Response is complete,
|
||||
* which indicates that the Authorization Code Grant flow has fully completed.
|
||||
*
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
* @param accessToken the access token credential
|
||||
* @param refreshToken the refresh token credential
|
||||
*/
|
||||
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange,
|
||||
OAuth2AccessToken accessToken,
|
||||
@Nullable OAuth2RefreshToken refreshToken) {
|
||||
OAuth2AuthorizationExchange authorizationExchange, OAuth2AccessToken accessToken,
|
||||
@Nullable OAuth2RefreshToken refreshToken) {
|
||||
this(clientRegistration, authorizationExchange, accessToken, refreshToken, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration, OAuth2AuthorizationExchange authorizationExchange, OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken,
|
||||
Map<String, Object> additionalParameters) {
|
||||
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange, OAuth2AccessToken accessToken,
|
||||
OAuth2RefreshToken refreshToken, Map<String, Object> additionalParameters) {
|
||||
this(clientRegistration, authorizationExchange);
|
||||
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||
this.accessToken = accessToken;
|
||||
@@ -109,14 +114,12 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.accessToken != null ?
|
||||
this.accessToken.getTokenValue() :
|
||||
this.authorizationExchange.getAuthorizationResponse().getCode();
|
||||
return (this.accessToken != null) ? this.accessToken.getTokenValue()
|
||||
: this.authorizationExchange.getAuthorizationResponse().getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -125,7 +128,6 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizationExchange authorization exchange}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationExchange}
|
||||
*/
|
||||
public OAuth2AuthorizationExchange getAuthorizationExchange() {
|
||||
@@ -134,7 +136,6 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AccessToken access token}.
|
||||
*
|
||||
* @return the {@link OAuth2AccessToken}
|
||||
*/
|
||||
public OAuth2AccessToken getAccessToken() {
|
||||
@@ -143,7 +144,6 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2RefreshToken refresh token}.
|
||||
*
|
||||
* @return the {@link OAuth2RefreshToken}
|
||||
*/
|
||||
public @Nullable OAuth2RefreshToken getRefreshToken() {
|
||||
@@ -152,10 +152,10 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
|
||||
|
||||
/**
|
||||
* Returns the additional parameters
|
||||
*
|
||||
* @return the additional parameters
|
||||
*/
|
||||
public Map<String, Object> getAdditionalParameters() {
|
||||
return this.additionalParameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
@@ -31,23 +36,22 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
||||
* which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
* An implementation of an
|
||||
* {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth
|
||||
* 2.0 Login, which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
*
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* and if valid, exchanging it for an Access Token credential.
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is
|
||||
* responsible for authenticating an Authorization Code credential with the Authorization
|
||||
* Server's Token Endpoint and if valid, exchanging it for an Access Token credential.
|
||||
* <p>
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner)
|
||||
* from the UserInfo Endpoint using an {@link org.springframework.security.oauth2.client.userinfo.OAuth2UserService},
|
||||
* which will create a {@code Principal} in the form of an {@link OAuth2User}.
|
||||
* The {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken}
|
||||
* to complete the authentication.
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner) from the
|
||||
* UserInfo Endpoint using an
|
||||
* {@link org.springframework.security.oauth2.client.userinfo.OAuth2UserService}, which
|
||||
* will create a {@code Principal} in the form of an {@link OAuth2User}. The
|
||||
* {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken} to
|
||||
* complete the authentication.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
@@ -55,12 +59,19 @@ import java.util.function.Function;
|
||||
* @see ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see ReactiveOAuth2UserService
|
||||
* @see OAuth2User
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token
|
||||
* Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token
|
||||
* Response</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||
|
||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||
|
||||
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||
|
||||
public OAuth2AuthorizationCodeReactiveAuthenticationManager(
|
||||
@@ -73,34 +84,33 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements Rea
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
return Mono.defer(() -> {
|
||||
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||
|
||||
OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange().getAuthorizationResponse();
|
||||
OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange()
|
||||
.getAuthorizationResponse();
|
||||
if (authorizationResponse.statusError()) {
|
||||
return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError()));
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange()
|
||||
.getAuthorizationRequest();
|
||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||
return Mono.error(new OAuth2AuthorizationException(oauth2Error));
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
|
||||
token.getClientRegistration(),
|
||||
token.getAuthorizationExchange());
|
||||
|
||||
return this.accessTokenResponseClient.getTokenResponse(authzRequest)
|
||||
.map(onSuccess(token));
|
||||
token.getClientRegistration(), token.getAuthorizationExchange());
|
||||
return this.accessTokenResponseClient.getTokenResponse(authzRequest).map(onSuccess(token));
|
||||
});
|
||||
}
|
||||
|
||||
private Function<OAuth2AccessTokenResponse, OAuth2AuthorizationCodeAuthenticationToken> onSuccess(OAuth2AuthorizationCodeAuthenticationToken token) {
|
||||
return accessTokenResponse -> {
|
||||
private Function<OAuth2AccessTokenResponse, OAuth2AuthorizationCodeAuthenticationToken> onSuccess(
|
||||
OAuth2AuthorizationCodeAuthenticationToken token) {
|
||||
return (accessTokenResponse) -> {
|
||||
ClientRegistration registration = token.getClientRegistration();
|
||||
OAuth2AuthorizationExchange exchange = token.getAuthorizationExchange();
|
||||
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
|
||||
OAuth2RefreshToken refreshToken = accessTokenResponse.getRefreshToken();
|
||||
return new OAuth2AuthorizationCodeAuthenticationToken(registration, exchange, accessToken, refreshToken, accessTokenResponse.getAdditionalParameters());
|
||||
return new OAuth2AuthorizationCodeAuthenticationToken(registration, exchange, accessToken, refreshToken,
|
||||
accessTokenResponse.getAdditionalParameters());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -31,22 +35,19 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthenticationProvider} for OAuth 2.0 Login,
|
||||
* which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
* An implementation of an {@link AuthenticationProvider} for OAuth 2.0 Login, which
|
||||
* leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
*
|
||||
* This {@link AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* and if valid, exchanging it for an Access Token credential.
|
||||
* This {@link AuthenticationProvider} is responsible for authenticating an Authorization
|
||||
* Code credential with the Authorization Server's Token Endpoint and if valid, exchanging
|
||||
* it for an Access Token credential.
|
||||
* <p>
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner)
|
||||
* from the UserInfo Endpoint using an {@link OAuth2UserService},
|
||||
* which will create a {@code Principal} in the form of an {@link OAuth2User}.
|
||||
* The {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken}
|
||||
* to complete the authentication.
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner) from the
|
||||
* UserInfo Endpoint using an {@link OAuth2UserService}, which will create a
|
||||
* {@code Principal} in the form of an {@link OAuth2User}. The {@code OAuth2User} is then
|
||||
* associated to the {@link OAuth2LoginAuthenticationToken} to complete the
|
||||
* authentication.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -54,82 +55,82 @@ import java.util.Map;
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2UserService
|
||||
* @see OAuth2User
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token
|
||||
* Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token
|
||||
* Response</a>
|
||||
*/
|
||||
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private final OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider;
|
||||
|
||||
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2LoginAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used for requesting the access token credential from the Token Endpoint
|
||||
* @param userService the service used for obtaining the user attributes of the End-User from the UserInfo Endpoint
|
||||
* Constructs an {@code OAuth2LoginAuthenticationProvider} using the provided
|
||||
* parameters.
|
||||
* @param accessTokenResponseClient the client used for requesting the access token
|
||||
* credential from the Token Endpoint
|
||||
* @param userService the service used for obtaining the user attributes of the
|
||||
* End-User from the UserInfo Endpoint
|
||||
*/
|
||||
public OAuth2LoginAuthenticationProvider(
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
|
||||
Assert.notNull(userService, "userService cannot be null");
|
||||
this.authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(accessTokenResponseClient);
|
||||
this.authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||
accessTokenResponseClient);
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2LoginAuthenticationToken loginAuthenticationToken =
|
||||
(OAuth2LoginAuthenticationToken) authentication;
|
||||
|
||||
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
// scope
|
||||
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (loginAuthenticationToken.getAuthorizationExchange()
|
||||
.getAuthorizationRequest().getScopes().contains("openid")) {
|
||||
OAuth2LoginAuthenticationToken loginAuthenticationToken = (OAuth2LoginAuthenticationToken) authentication;
|
||||
// Section 3.1.2.1 Authentication Request -
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest scope
|
||||
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (loginAuthenticationToken.getAuthorizationExchange().getAuthorizationRequest().getScopes()
|
||||
.contains("openid")) {
|
||||
// This is an OpenID Connect Authentication Request so return null
|
||||
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
|
||||
return null;
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken;
|
||||
try {
|
||||
authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider
|
||||
.authenticate(new OAuth2AuthorizationCodeAuthenticationToken(
|
||||
loginAuthenticationToken.getClientRegistration(),
|
||||
loginAuthenticationToken.getAuthorizationExchange()));
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
OAuth2Error oauth2Error = ex.getError();
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
|
||||
Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
|
||||
|
||||
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
|
||||
loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
|
||||
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities =
|
||||
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
|
||||
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
|
||||
.mapAuthorities(oauth2User.getAuthorities());
|
||||
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
|
||||
loginAuthenticationToken.getClientRegistration(),
|
||||
loginAuthenticationToken.getAuthorizationExchange(),
|
||||
oauth2User,
|
||||
mappedAuthorities,
|
||||
accessToken,
|
||||
authorizationCodeAuthenticationToken.getRefreshToken());
|
||||
loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(),
|
||||
oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken());
|
||||
authenticationResult.setDetails(loginAuthenticationToken.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OAuth2User#getAuthorities()}
|
||||
* to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
*
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the user's authorities
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping
|
||||
* {@link OAuth2User#getAuthorities()} to a new set of authorities which will be
|
||||
* associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the
|
||||
* user's authorities
|
||||
*/
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
@@ -140,4 +141,5 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -26,12 +30,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* An {@link AbstractAuthenticationToken} for OAuth 2.0 Login,
|
||||
* which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
* An {@link AbstractAuthenticationToken} for OAuth 2.0 Login, which leverages the OAuth
|
||||
* 2.0 Authorization Code Grant Flow.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -40,25 +41,31 @@ import java.util.Collections;
|
||||
* @see ClientRegistration
|
||||
* @see OAuth2AuthorizationExchange
|
||||
* @see OAuth2AccessToken
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
*/
|
||||
public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
private OAuth2User principal;
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private OAuth2AuthorizationExchange authorizationExchange;
|
||||
|
||||
private OAuth2AccessToken accessToken;
|
||||
|
||||
private OAuth2RefreshToken refreshToken;
|
||||
|
||||
/**
|
||||
* This constructor should be used when the Authorization Request/Response is complete.
|
||||
*
|
||||
* This constructor should be used when the Authorization Request/Response is
|
||||
* complete.
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
*/
|
||||
public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange) {
|
||||
|
||||
OAuth2AuthorizationExchange authorizationExchange) {
|
||||
super(Collections.emptyList());
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
|
||||
@@ -69,9 +76,8 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
|
||||
/**
|
||||
* This constructor should be used when the Access Token Request/Response is complete,
|
||||
* which indicates that the Authorization Code Grant flow has fully completed
|
||||
* and OAuth 2.0 Login has been achieved.
|
||||
*
|
||||
* which indicates that the Authorization Code Grant flow has fully completed and
|
||||
* OAuth 2.0 Login has been achieved.
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
* @param principal the user {@code Principal} registered with the OAuth 2.0 Provider
|
||||
@@ -79,18 +85,15 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
* @param accessToken the access token credential
|
||||
*/
|
||||
public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange,
|
||||
OAuth2User principal,
|
||||
Collection<? extends GrantedAuthority> authorities,
|
||||
OAuth2AccessToken accessToken) {
|
||||
OAuth2AuthorizationExchange authorizationExchange, OAuth2User principal,
|
||||
Collection<? extends GrantedAuthority> authorities, OAuth2AccessToken accessToken) {
|
||||
this(clientRegistration, authorizationExchange, principal, authorities, accessToken, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor should be used when the Access Token Request/Response is complete,
|
||||
* which indicates that the Authorization Code Grant flow has fully completed
|
||||
* and OAuth 2.0 Login has been achieved.
|
||||
*
|
||||
* which indicates that the Authorization Code Grant flow has fully completed and
|
||||
* OAuth 2.0 Login has been achieved.
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
* @param principal the user {@code Principal} registered with the OAuth 2.0 Provider
|
||||
@@ -99,11 +102,9 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
* @param refreshToken the refresh token credential
|
||||
*/
|
||||
public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange,
|
||||
OAuth2User principal,
|
||||
Collection<? extends GrantedAuthority> authorities,
|
||||
OAuth2AccessToken accessToken,
|
||||
@Nullable OAuth2RefreshToken refreshToken) {
|
||||
OAuth2AuthorizationExchange authorizationExchange, OAuth2User principal,
|
||||
Collection<? extends GrantedAuthority> authorities, OAuth2AccessToken accessToken,
|
||||
@Nullable OAuth2RefreshToken refreshToken) {
|
||||
super(authorities);
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
|
||||
@@ -129,7 +130,6 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -138,7 +138,6 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizationExchange authorization exchange}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationExchange}
|
||||
*/
|
||||
public OAuth2AuthorizationExchange getAuthorizationExchange() {
|
||||
@@ -147,7 +146,6 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AccessToken access token}.
|
||||
*
|
||||
* @return the {@link OAuth2AccessToken}
|
||||
*/
|
||||
public OAuth2AccessToken getAccessToken() {
|
||||
@@ -156,11 +154,11 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2RefreshToken refresh token}.
|
||||
*
|
||||
* @since 5.1
|
||||
* @return the {@link OAuth2RefreshToken}
|
||||
* @since 5.1
|
||||
*/
|
||||
public @Nullable OAuth2RefreshToken getRefreshToken() {
|
||||
return this.refreshToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -28,24 +34,22 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
||||
* which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
* An implementation of an
|
||||
* {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth
|
||||
* 2.0 Login, which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
*
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* and if valid, exchanging it for an Access Token credential.
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is
|
||||
* responsible for authenticating an Authorization Code credential with the Authorization
|
||||
* Server's Token Endpoint and if valid, exchanging it for an Access Token credential.
|
||||
* <p>
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner)
|
||||
* from the UserInfo Endpoint using an {@link org.springframework.security.oauth2.client.userinfo.OAuth2UserService},
|
||||
* which will create a {@code Principal} in the form of an {@link OAuth2User}.
|
||||
* The {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken}
|
||||
* to complete the authentication.
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner) from the
|
||||
* UserInfo Endpoint using an
|
||||
* {@link org.springframework.security.oauth2.client.userinfo.OAuth2UserService}, which
|
||||
* will create a {@code Principal} in the form of an {@link OAuth2User}. The
|
||||
* {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken} to
|
||||
* complete the authentication.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
@@ -53,24 +57,30 @@ import java.util.Map;
|
||||
* @see org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService
|
||||
* @see OAuth2User
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token
|
||||
* Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token
|
||||
* Response</a>
|
||||
*/
|
||||
public class OAuth2LoginReactiveAuthenticationManager implements
|
||||
ReactiveAuthenticationManager {
|
||||
public class OAuth2LoginReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||
|
||||
private final ReactiveAuthenticationManager authorizationCodeManager;
|
||||
|
||||
private final ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
|
||||
|
||||
public OAuth2LoginReactiveAuthenticationManager(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||
ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
Assert.notNull(userService, "userService cannot be null");
|
||||
this.authorizationCodeManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(accessTokenResponseClient);
|
||||
this.authorizationCodeManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(
|
||||
accessTokenResponseClient);
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@@ -78,29 +88,29 @@ public class OAuth2LoginReactiveAuthenticationManager implements
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
return Mono.defer(() -> {
|
||||
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||
|
||||
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
// scope REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (token.getAuthorizationExchange()
|
||||
.getAuthorizationRequest().getScopes().contains("openid")) {
|
||||
// Section 3.1.2.1 Authentication Request -
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest scope
|
||||
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (token.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid")) {
|
||||
// This is an OpenID Connect Authentication Request so return null
|
||||
// and let OidcAuthorizationCodeReactiveAuthenticationManager handle it instead once one is created
|
||||
// and let OidcAuthorizationCodeReactiveAuthenticationManager handle it
|
||||
// instead once one is created
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return this.authorizationCodeManager.authenticate(token)
|
||||
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
|
||||
.cast(OAuth2AuthorizationCodeAuthenticationToken.class)
|
||||
.flatMap(this::onSuccess);
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
(e) -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
|
||||
.cast(OAuth2AuthorizationCodeAuthenticationToken.class).flatMap(this::onSuccess);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OAuth2User#getAuthorities()}
|
||||
* to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
*
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping
|
||||
* {@link OAuth2User#getAuthorities()} to a new set of authorities which will be
|
||||
* associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the
|
||||
* user's authorities
|
||||
* @since 5.4
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the user's authorities
|
||||
*/
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
@@ -110,20 +120,16 @@ public class OAuth2LoginReactiveAuthenticationManager implements
|
||||
private Mono<OAuth2LoginAuthenticationToken> onSuccess(OAuth2AuthorizationCodeAuthenticationToken authentication) {
|
||||
OAuth2AccessToken accessToken = authentication.getAccessToken();
|
||||
Map<String, Object> additionalParameters = authentication.getAdditionalParameters();
|
||||
OAuth2UserRequest userRequest = new OAuth2UserRequest(authentication.getClientRegistration(), accessToken, additionalParameters);
|
||||
return this.userService.loadUser(userRequest)
|
||||
.map(oauth2User -> {
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities =
|
||||
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
|
||||
|
||||
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
|
||||
authentication.getClientRegistration(),
|
||||
authentication.getAuthorizationExchange(),
|
||||
oauth2User,
|
||||
mappedAuthorities,
|
||||
accessToken,
|
||||
authentication.getRefreshToken());
|
||||
return authenticationResult;
|
||||
});
|
||||
OAuth2UserRequest userRequest = new OAuth2UserRequest(authentication.getClientRegistration(), accessToken,
|
||||
additionalParameters);
|
||||
return this.userService.loadUser(userRequest).map((oauth2User) -> {
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
|
||||
.mapAuthorities(oauth2User.getAuthorities());
|
||||
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
|
||||
authentication.getClientRegistration(), authentication.getAuthorizationExchange(), oauth2User,
|
||||
mappedAuthorities, accessToken, authentication.getRefreshToken());
|
||||
return authenticationResult;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support classes and interfaces for authenticating and authorizing a client
|
||||
* with an OAuth 2.0 Authorization Server using a specific authorization grant flow.
|
||||
* Support classes and interfaces for authenticating and authorizing a client with an
|
||||
* OAuth 2.0 Authorization Server using a specific authorization grant flow.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
@@ -13,27 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base implementation of an OAuth 2.0 Authorization Grant request
|
||||
* that holds an authorization grant credential and is used when
|
||||
* initiating a request to the Authorization Server's Token Endpoint.
|
||||
* Base implementation of an OAuth 2.0 Authorization Grant request that holds an
|
||||
* authorization grant credential and is used when initiating a request to the
|
||||
* Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AuthorizationGrantType
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
||||
* 1.3 Authorization Grant</a>
|
||||
*/
|
||||
public abstract class AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
private final AuthorizationGrantType authorizationGrantType;
|
||||
|
||||
/**
|
||||
* Sub-class constructor.
|
||||
*
|
||||
* @param authorizationGrantType the authorization grant type
|
||||
*/
|
||||
protected AbstractOAuth2AuthorizationGrantRequest(AuthorizationGrantType authorizationGrantType) {
|
||||
@@ -43,10 +45,10 @@ public abstract class AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
/**
|
||||
* Returns the authorization grant type.
|
||||
*
|
||||
* @return the authorization grant type
|
||||
*/
|
||||
public AuthorizationGrantType getGrantType() {
|
||||
return this.authorizationGrantType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,63 +13,74 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
|
||||
|
||||
/**
|
||||
* Abstract base class for all of the {@code WebClientReactive*TokenResponseClient}s
|
||||
* that communicate to the Authorization Server's Token Endpoint.
|
||||
* Abstract base class for all of the {@code WebClientReactive*TokenResponseClient}s that
|
||||
* communicate to the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* <p>Submits a form request body specific to the type of grant request.</p>
|
||||
* <p>
|
||||
* Submits a form request body specific to the type of grant request.
|
||||
* </p>
|
||||
*
|
||||
* <p>Accepts a JSON response body containing an OAuth 2.0 Access token or error.</p>
|
||||
* <p>
|
||||
* Accepts a JSON response body containing an OAuth 2.0 Access token or error.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> type of grant request
|
||||
* @author Phil Clay
|
||||
* @since 5.3
|
||||
* @param <T> type of grant request
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-3.2">RFC-6749 Token Endpoint</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-3.2">RFC-6749 Token
|
||||
* Endpoint</a>
|
||||
* @see WebClientReactiveAuthorizationCodeTokenResponseClient
|
||||
* @see WebClientReactiveClientCredentialsTokenResponseClient
|
||||
* @see WebClientReactivePasswordTokenResponseClient
|
||||
* @see WebClientReactiveRefreshTokenTokenResponseClient
|
||||
*/
|
||||
abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
|
||||
public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
|
||||
implements ReactiveOAuth2AccessTokenResponseClient<T> {
|
||||
|
||||
private WebClient webClient = WebClient.builder().build();
|
||||
|
||||
AbstractWebClientReactiveOAuth2AccessTokenResponseClient() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<OAuth2AccessTokenResponse> getTokenResponse(T grantRequest) {
|
||||
Assert.notNull(grantRequest, "grantRequest cannot be null");
|
||||
// @formatter:off
|
||||
return Mono.defer(() -> this.webClient.post()
|
||||
.uri(clientRegistration(grantRequest).getProviderDetails().getTokenUri())
|
||||
.headers(headers -> populateTokenRequestHeaders(grantRequest, headers))
|
||||
.headers((headers) -> populateTokenRequestHeaders(grantRequest, headers))
|
||||
.body(createTokenRequestBody(grantRequest))
|
||||
.exchange()
|
||||
.flatMap(response -> readTokenResponse(grantRequest, response)));
|
||||
.flatMap((response) -> readTokenResponse(grantRequest, response))
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration} for the given {@code grantRequest}.
|
||||
*
|
||||
* @param grantRequest the grant request
|
||||
* @return the {@link ClientRegistration} for the given {@code grantRequest}.
|
||||
*/
|
||||
@@ -77,7 +88,6 @@ abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extend
|
||||
|
||||
/**
|
||||
* Populates the headers for the token request.
|
||||
*
|
||||
* @param grantRequest the grant request
|
||||
* @param headers the headers to populate
|
||||
*/
|
||||
@@ -93,30 +103,34 @@ abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extend
|
||||
/**
|
||||
* Creates and returns the body for the token request.
|
||||
*
|
||||
* <p>This method pre-populates the body with some standard properties,
|
||||
* and then delegates to {@link #populateTokenRequestBody(AbstractOAuth2AuthorizationGrantRequest, BodyInserters.FormInserter)}
|
||||
* for subclasses to further populate the body before returning.</p>
|
||||
*
|
||||
* <p>
|
||||
* This method pre-populates the body with some standard properties, and then
|
||||
* delegates to
|
||||
* {@link #populateTokenRequestBody(AbstractOAuth2AuthorizationGrantRequest, BodyInserters.FormInserter)}
|
||||
* for subclasses to further populate the body before returning.
|
||||
* </p>
|
||||
* @param grantRequest the grant request
|
||||
* @return the body for the token request.
|
||||
*/
|
||||
private BodyInserters.FormInserter<String> createTokenRequestBody(T grantRequest) {
|
||||
BodyInserters.FormInserter<String> body = BodyInserters
|
||||
.fromFormData(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue());
|
||||
BodyInserters.FormInserter<String> body = BodyInserters.fromFormData(OAuth2ParameterNames.GRANT_TYPE,
|
||||
grantRequest.getGrantType().getValue());
|
||||
return populateTokenRequestBody(grantRequest, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the body of the token request.
|
||||
*
|
||||
* <p>By default, populates properties that are common to all grant types.
|
||||
* Subclasses can extend this method to populate grant type specific properties.</p>
|
||||
*
|
||||
* <p>
|
||||
* By default, populates properties that are common to all grant types. Subclasses can
|
||||
* extend this method to populate grant type specific properties.
|
||||
* </p>
|
||||
* @param grantRequest the grant request
|
||||
* @param body the body to populate
|
||||
* @return the populated body
|
||||
*/
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(T grantRequest, BodyInserters.FormInserter<String> body) {
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(T grantRequest,
|
||||
BodyInserters.FormInserter<String> body) {
|
||||
ClientRegistration clientRegistration = clientRegistration(grantRequest);
|
||||
if (!ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
|
||||
@@ -126,31 +140,30 @@ abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extend
|
||||
}
|
||||
Set<String> scopes = scopes(grantRequest);
|
||||
if (!CollectionUtils.isEmpty(scopes)) {
|
||||
body.with(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(scopes, " "));
|
||||
body.with(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(scopes, " "));
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scopes to include as a property in the token request.
|
||||
*
|
||||
* @param grantRequest the grant request
|
||||
* @return the scopes to include as a property in the token request.
|
||||
*/
|
||||
abstract Set<String> scopes(T grantRequest);
|
||||
|
||||
/**
|
||||
* Returns the scopes to include in the response if the authorization
|
||||
* server returned no scopes in the response.
|
||||
*
|
||||
* <p>As per <a href="https://tools.ietf.org/html/rfc6749#section-5.1">RFC-6749 Section 5.1 Successful Access Token Response</a>,
|
||||
* if AccessTokenResponse.scope is empty, then default to the scope
|
||||
* originally requested by the client in the Token Request.</p>
|
||||
* Returns the scopes to include in the response if the authorization server returned
|
||||
* no scopes in the response.
|
||||
*
|
||||
* <p>
|
||||
* As per <a href="https://tools.ietf.org/html/rfc6749#section-5.1">RFC-6749 Section
|
||||
* 5.1 Successful Access Token Response</a>, if AccessTokenResponse.scope is empty,
|
||||
* then default to the scope originally requested by the client in the Token Request.
|
||||
* </p>
|
||||
* @param grantRequest the grant request
|
||||
* @return the scopes to include in the response if the authorization
|
||||
* server returned no scopes.
|
||||
* @return the scopes to include in the response if the authorization server returned
|
||||
* no scopes.
|
||||
*/
|
||||
Set<String> defaultScopes(T grantRequest) {
|
||||
return scopes(grantRequest);
|
||||
@@ -158,41 +171,45 @@ abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extend
|
||||
|
||||
/**
|
||||
* Reads the token response from the response body.
|
||||
*
|
||||
* @param grantRequest the request for which the response was received.
|
||||
* @param response the client response from which to read
|
||||
* @return the token response from the response body.
|
||||
*/
|
||||
private Mono<OAuth2AccessTokenResponse> readTokenResponse(T grantRequest, ClientResponse response) {
|
||||
return response.body(oauth2AccessTokenResponse())
|
||||
.map(tokenResponse -> populateTokenResponse(grantRequest, tokenResponse));
|
||||
return response.body(OAuth2BodyExtractors.oauth2AccessTokenResponse())
|
||||
.map((tokenResponse) -> populateTokenResponse(grantRequest, tokenResponse));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given {@link OAuth2AccessTokenResponse} with additional details
|
||||
* from the grant request.
|
||||
*
|
||||
* Populates the given {@link OAuth2AccessTokenResponse} with additional details from
|
||||
* the grant request.
|
||||
* @param grantRequest the request for which the response was received.
|
||||
* @param tokenResponse the original token response
|
||||
* @return a token response optionally populated with additional details from the request.
|
||||
* @return a token response optionally populated with additional details from the
|
||||
* request.
|
||||
*/
|
||||
OAuth2AccessTokenResponse populateTokenResponse(T grantRequest, OAuth2AccessTokenResponse tokenResponse) {
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
Set<String> defaultScopes = defaultScopes(grantRequest);
|
||||
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
|
||||
// @formatter:off
|
||||
tokenResponse = OAuth2AccessTokenResponse
|
||||
.withResponse(tokenResponse)
|
||||
.scopes(defaultScopes)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link WebClient} used when requesting the OAuth 2.0 Access Token Response.
|
||||
*
|
||||
* @param webClient the {@link WebClient} used when requesting the Access Token Response
|
||||
* Sets the {@link WebClient} used when requesting the OAuth 2.0 Access Token
|
||||
* Response.
|
||||
* @param webClient the {@link WebClient} used when requesting the Access Token
|
||||
* Response
|
||||
*/
|
||||
public void setWebClient(WebClient webClient) {
|
||||
Assert.notNull(webClient, "webClient cannot be null");
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -33,92 +36,105 @@ import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient}
|
||||
* for the {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant.
|
||||
* This implementation uses a {@link RestOperations} when requesting
|
||||
* an access token credential at the Authorization Server's Token Endpoint.
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant. This
|
||||
* implementation uses a {@link RestOperations} when requesting an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2AuthorizationCodeGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response
|
||||
* (Authorization Code Grant)</a>
|
||||
*/
|
||||
public final class DefaultAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
public final class DefaultAuthorizationCodeTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter =
|
||||
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
|
||||
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
|
||||
|
||||
private RestOperations restOperations;
|
||||
|
||||
public DefaultAuthorizationCodeTokenResponseClient() {
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
RestTemplate restTemplate = new RestTemplate(
|
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
this.restOperations = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
public OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null");
|
||||
|
||||
RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest);
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response;
|
||||
try {
|
||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
} catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
|
||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Token Request
|
||||
// @formatter:off
|
||||
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
|
||||
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
|
||||
try {
|
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationCodeGrantRequest}
|
||||
* to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request.
|
||||
*
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request
|
||||
* Sets the {@link Converter} used for converting the
|
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link RequestEntity}
|
||||
* representation of the OAuth 2.0 Access Token Request.
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a
|
||||
* {@link RequestEntity} representation of the Access Token Request
|
||||
*/
|
||||
public void setRequestEntityConverter(Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
public void setRequestEntityConverter(
|
||||
Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
|
||||
this.requestEntityConverter = requestEntityConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token Response.
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token
|
||||
* Response.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured with the following:
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured
|
||||
* with the following:
|
||||
* <ol>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and
|
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access
|
||||
* Token Response
|
||||
*/
|
||||
public void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -33,92 +36,105 @@ import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient}
|
||||
* for the {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant.
|
||||
* This implementation uses a {@link RestOperations} when requesting
|
||||
* an access token credential at the Authorization Server's Token Endpoint.
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant. This
|
||||
* implementation uses a {@link RestOperations} when requesting an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2ClientCredentialsGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.4.2">Section 4.4.2 Access Token Request (Client Credentials Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.4.3">Section 4.4.3 Access Token Response (Client Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.4.2">Section 4.4.2 Access Token Request
|
||||
* (Client Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.4.3">Section 4.4.3 Access Token Response
|
||||
* (Client Credentials Grant)</a>
|
||||
*/
|
||||
public final class DefaultClientCredentialsTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
|
||||
public final class DefaultClientCredentialsTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
|
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
private Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> requestEntityConverter =
|
||||
new OAuth2ClientCredentialsGrantRequestEntityConverter();
|
||||
private Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> requestEntityConverter = new OAuth2ClientCredentialsGrantRequestEntityConverter();
|
||||
|
||||
private RestOperations restOperations;
|
||||
|
||||
public DefaultClientCredentialsTokenResponseClient() {
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
RestTemplate restTemplate = new RestTemplate(
|
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
this.restOperations = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
|
||||
public OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
|
||||
Assert.notNull(clientCredentialsGrantRequest, "clientCredentialsGrantRequest cannot be null");
|
||||
|
||||
RequestEntity<?> request = this.requestEntityConverter.convert(clientCredentialsGrantRequest);
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response;
|
||||
try {
|
||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
} catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
|
||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Token Request
|
||||
// @formatter:off
|
||||
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
|
||||
.scopes(clientCredentialsGrantRequest.getClientRegistration().getScopes())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
|
||||
try {
|
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2ClientCredentialsGrantRequest}
|
||||
* to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request.
|
||||
*
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request
|
||||
* Sets the {@link Converter} used for converting the
|
||||
* {@link OAuth2ClientCredentialsGrantRequest} to a {@link RequestEntity}
|
||||
* representation of the OAuth 2.0 Access Token Request.
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a
|
||||
* {@link RequestEntity} representation of the Access Token Request
|
||||
*/
|
||||
public void setRequestEntityConverter(Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
public void setRequestEntityConverter(
|
||||
Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
|
||||
this.requestEntityConverter = requestEntityConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token Response.
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token
|
||||
* Response.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured with the following:
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured
|
||||
* with the following:
|
||||
* <ol>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and
|
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access
|
||||
* Token Response
|
||||
*/
|
||||
public void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -33,33 +36,36 @@ import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient}
|
||||
* for the {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
* This implementation uses a {@link RestOperations} when requesting
|
||||
* an access token credential at the Authorization Server's Token Endpoint.
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant. This implementation uses a
|
||||
* {@link RestOperations} when requesting an access token credential at the Authorization
|
||||
* Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2PasswordGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.3.2">Section 4.3.2 Access Token Request (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.3.3">Section 4.3.3 Access Token Response (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.2">Section 4.3.2 Access Token Request
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.3">Section 4.3.3 Access Token Response
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
*/
|
||||
public final class DefaultPasswordTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
public final class DefaultPasswordTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
private Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> requestEntityConverter =
|
||||
new OAuth2PasswordGrantRequestEntityConverter();
|
||||
private Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> requestEntityConverter = new OAuth2PasswordGrantRequestEntityConverter();
|
||||
|
||||
private RestOperations restOperations;
|
||||
|
||||
public DefaultPasswordTokenResponseClient() {
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
RestTemplate restTemplate = new RestTemplate(
|
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
this.restOperations = restTemplate;
|
||||
}
|
||||
@@ -67,58 +73,64 @@ public final class DefaultPasswordTokenResponseClient implements OAuth2AccessTok
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
Assert.notNull(passwordGrantRequest, "passwordGrantRequest cannot be null");
|
||||
|
||||
RequestEntity<?> request = this.requestEntityConverter.convert(passwordGrantRequest);
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response;
|
||||
try {
|
||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
} catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
|
||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Token Request
|
||||
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
|
||||
.scopes(passwordGrantRequest.getClientRegistration().getScopes())
|
||||
.build();
|
||||
.scopes(passwordGrantRequest.getClientRegistration().getScopes()).build();
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
|
||||
try {
|
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2PasswordGrantRequest}
|
||||
* to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request.
|
||||
*
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request
|
||||
* Sets the {@link Converter} used for converting the
|
||||
* {@link OAuth2PasswordGrantRequest} to a {@link RequestEntity} representation of the
|
||||
* OAuth 2.0 Access Token Request.
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a
|
||||
* {@link RequestEntity} representation of the Access Token Request
|
||||
*/
|
||||
public void setRequestEntityConverter(Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
public void setRequestEntityConverter(
|
||||
Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
|
||||
this.requestEntityConverter = requestEntityConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token Response.
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token
|
||||
* Response.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured with the following:
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured
|
||||
* with the following:
|
||||
* <ol>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and
|
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access
|
||||
* Token Response
|
||||
*/
|
||||
public void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -33,32 +36,32 @@ import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient}
|
||||
* for the {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
* This implementation uses a {@link RestOperations} when requesting
|
||||
* an access token credential at the Authorization Server's Token Endpoint.
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant. This implementation
|
||||
* uses a {@link RestOperations} when requesting an access token credential at the
|
||||
* Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2RefreshTokenGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 Refreshing an Access Token</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6
|
||||
* Refreshing an Access Token</a>
|
||||
*/
|
||||
public final class DefaultRefreshTokenTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
|
||||
public final class DefaultRefreshTokenTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
|
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
private Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> requestEntityConverter =
|
||||
new OAuth2RefreshTokenGrantRequestEntityConverter();
|
||||
private Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> requestEntityConverter = new OAuth2RefreshTokenGrantRequestEntityConverter();
|
||||
|
||||
private RestOperations restOperations;
|
||||
|
||||
public DefaultRefreshTokenTokenResponseClient() {
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
RestTemplate restTemplate = new RestTemplate(
|
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
this.restOperations = restTemplate;
|
||||
}
|
||||
@@ -66,24 +69,13 @@ public final class DefaultRefreshTokenTokenResponseClient implements OAuth2Acces
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
|
||||
Assert.notNull(refreshTokenGrantRequest, "refreshTokenGrantRequest cannot be null");
|
||||
|
||||
RequestEntity<?> request = this.requestEntityConverter.convert(refreshTokenGrantRequest);
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response;
|
||||
try {
|
||||
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
} catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
|
||||
OAuth2AccessTokenResponse tokenResponse = response.getBody();
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes()) ||
|
||||
tokenResponse.getRefreshToken() == null) {
|
||||
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse.withResponse(tokenResponse);
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())
|
||||
|| tokenResponse.getRefreshToken() == null) {
|
||||
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse
|
||||
.withResponse(tokenResponse);
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
@@ -91,43 +83,59 @@ public final class DefaultRefreshTokenTokenResponseClient implements OAuth2Acces
|
||||
// originally requested by the client in the Token Request
|
||||
tokenResponseBuilder.scopes(refreshTokenGrantRequest.getAccessToken().getScopes());
|
||||
}
|
||||
|
||||
if (tokenResponse.getRefreshToken() == null) {
|
||||
// Reuse existing refresh token
|
||||
tokenResponseBuilder.refreshToken(refreshTokenGrantRequest.getRefreshToken().getTokenValue());
|
||||
}
|
||||
|
||||
tokenResponse = tokenResponseBuilder.build();
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
|
||||
try {
|
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
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);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2RefreshTokenGrantRequest}
|
||||
* to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request.
|
||||
*
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request
|
||||
* Sets the {@link Converter} used for converting the
|
||||
* {@link OAuth2RefreshTokenGrantRequest} to a {@link RequestEntity} representation of
|
||||
* the OAuth 2.0 Access Token Request.
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a
|
||||
* {@link RequestEntity} representation of the Access Token Request
|
||||
*/
|
||||
public void setRequestEntityConverter(Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
public void setRequestEntityConverter(
|
||||
Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
|
||||
this.requestEntityConverter = requestEntityConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token Response.
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token
|
||||
* Response.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured with the following:
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured
|
||||
* with the following:
|
||||
* <ol>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and
|
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access
|
||||
* Token Response
|
||||
*/
|
||||
public void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
|
||||
import com.nimbusds.oauth2.sdk.AuthorizationCode;
|
||||
@@ -30,6 +37,7 @@ import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
|
||||
import com.nimbusds.oauth2.sdk.auth.Secret;
|
||||
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
|
||||
import com.nimbusds.oauth2.sdk.id.ClientID;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
@@ -40,16 +48,9 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AccessTokenResponseClient} that "exchanges"
|
||||
* an authorization code credential for an access token credential
|
||||
* An implementation of an {@link OAuth2AccessTokenResponseClient} that
|
||||
* "exchanges" an authorization code credential for an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* <p>
|
||||
@@ -61,36 +62,76 @@ import java.util.Set;
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2AuthorizationCodeGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0
|
||||
* SDK</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response
|
||||
* (Authorization Code Grant)</a>
|
||||
*/
|
||||
@Deprecated
|
||||
public class NimbusAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
public class NimbusAuthorizationCodeTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
|
||||
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
|
||||
|
||||
// Build the authorization code grant request for the token endpoint
|
||||
AuthorizationCode authorizationCode = new AuthorizationCode(
|
||||
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
|
||||
URI redirectUri = toURI(authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri());
|
||||
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
|
||||
URI redirectUri = toURI(
|
||||
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri());
|
||||
AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri);
|
||||
URI tokenUri = toURI(clientRegistration.getProviderDetails().getTokenUri());
|
||||
|
||||
// Set the credentials to authenticate the client at the token endpoint
|
||||
ClientID clientId = new ClientID(clientRegistration.getClientId());
|
||||
Secret clientSecret = new Secret(clientRegistration.getClientSecret());
|
||||
ClientAuthentication clientAuthentication;
|
||||
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
clientAuthentication = new ClientSecretPost(clientId, clientSecret);
|
||||
} else {
|
||||
clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
|
||||
boolean isPost = ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod());
|
||||
ClientAuthentication clientAuthentication = isPost ? new ClientSecretPost(clientId, clientSecret)
|
||||
: new ClientSecretBasic(clientId, clientSecret);
|
||||
com.nimbusds.oauth2.sdk.TokenResponse tokenResponse = getTokenResponse(authorizationCodeGrant, tokenUri,
|
||||
clientAuthentication);
|
||||
if (!tokenResponse.indicatesSuccess()) {
|
||||
TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse;
|
||||
ErrorObject errorObject = tokenErrorResponse.getErrorObject();
|
||||
throw new OAuth2AuthorizationException(getOAuthError(errorObject));
|
||||
}
|
||||
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
|
||||
String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue();
|
||||
OAuth2AccessToken.TokenType accessTokenType = null;
|
||||
if (OAuth2AccessToken.TokenType.BEARER.getValue()
|
||||
.equalsIgnoreCase(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) {
|
||||
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
|
||||
}
|
||||
long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime();
|
||||
// As per spec, in section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Authorization Request
|
||||
Set<String> scopes = getScopes(authorizationGrantRequest, accessTokenResponse);
|
||||
String refreshToken = null;
|
||||
if (accessTokenResponse.getTokens().getRefreshToken() != null) {
|
||||
refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue();
|
||||
}
|
||||
Map<String, Object> additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters());
|
||||
// @formatter:off
|
||||
return OAuth2AccessTokenResponse.withToken(accessToken)
|
||||
.tokenType(accessTokenType)
|
||||
.expiresIn(expiresIn)
|
||||
.scopes(scopes)
|
||||
.refreshToken(refreshToken)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
com.nimbusds.oauth2.sdk.TokenResponse tokenResponse;
|
||||
private com.nimbusds.oauth2.sdk.TokenResponse getTokenResponse(AuthorizationGrant authorizationCodeGrant,
|
||||
URI tokenUri, ClientAuthentication clientAuthentication) {
|
||||
try {
|
||||
// Send the Access Token request
|
||||
TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant);
|
||||
@@ -98,71 +139,43 @@ public class NimbusAuthorizationCodeTokenResponseClient implements OAuth2AccessT
|
||||
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
||||
httpRequest.setConnectTimeout(30000);
|
||||
httpRequest.setReadTimeout(30000);
|
||||
tokenResponse = com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send());
|
||||
} catch (ParseException | IOException ex) {
|
||||
return com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send());
|
||||
}
|
||||
catch (ParseException | IOException ex) {
|
||||
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);
|
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
|
||||
+ ex.getMessage(),
|
||||
null);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tokenResponse.indicatesSuccess()) {
|
||||
TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse;
|
||||
ErrorObject errorObject = tokenErrorResponse.getErrorObject();
|
||||
OAuth2Error oauth2Error;
|
||||
if (errorObject == null) {
|
||||
oauth2Error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
} else {
|
||||
oauth2Error = new OAuth2Error(
|
||||
errorObject.getCode() != null ? errorObject.getCode() : OAuth2ErrorCodes.SERVER_ERROR,
|
||||
errorObject.getDescription(),
|
||||
errorObject.getURI() != null ? errorObject.getURI().toString() : null);
|
||||
}
|
||||
throw new OAuth2AuthorizationException(oauth2Error);
|
||||
}
|
||||
|
||||
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
|
||||
|
||||
String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue();
|
||||
OAuth2AccessToken.TokenType accessTokenType = null;
|
||||
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) {
|
||||
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
|
||||
}
|
||||
long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime();
|
||||
|
||||
// As per spec, in section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Authorization Request
|
||||
Set<String> scopes;
|
||||
private Set<String> getScopes(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest,
|
||||
AccessTokenResponse accessTokenResponse) {
|
||||
if (CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) {
|
||||
scopes = new LinkedHashSet<>(
|
||||
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes());
|
||||
} else {
|
||||
scopes = new LinkedHashSet<>(
|
||||
accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());
|
||||
return new LinkedHashSet<>(
|
||||
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes());
|
||||
}
|
||||
return new LinkedHashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());
|
||||
}
|
||||
|
||||
String refreshToken = null;
|
||||
if (accessTokenResponse.getTokens().getRefreshToken() != null) {
|
||||
refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue();
|
||||
private OAuth2Error getOAuthError(ErrorObject errorObject) {
|
||||
if (errorObject == null) {
|
||||
return new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters());
|
||||
|
||||
return OAuth2AccessTokenResponse.withToken(accessToken)
|
||||
.tokenType(accessTokenType)
|
||||
.expiresIn(expiresIn)
|
||||
.scopes(scopes)
|
||||
.refreshToken(refreshToken)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
String errorCode = (errorObject.getCode() != null) ? errorObject.getCode() : OAuth2ErrorCodes.SERVER_ERROR;
|
||||
String description = errorObject.getDescription();
|
||||
String uri = (errorObject.getURI() != null) ? errorObject.getURI().toString() : null;
|
||||
return new OAuth2Error(errorCode, description, uri);
|
||||
}
|
||||
|
||||
private static URI toURI(String uriStr) {
|
||||
try {
|
||||
return new URI(uriStr);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException("An error occurred parsing URI: " + uriStr, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,37 +13,45 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
|
||||
/**
|
||||
* A strategy for "exchanging" an authorization grant credential
|
||||
* (e.g. an Authorization Code) for an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
* A strategy for "exchanging" an authorization grant credential (e.g. an
|
||||
* Authorization Code) for an access token credential at the Authorization Server's Token
|
||||
* Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see AuthorizationGrantType
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
||||
* 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response
|
||||
* (Authorization Code Grant)</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest> {
|
||||
public interface OAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest> {
|
||||
|
||||
/**
|
||||
* Exchanges the authorization grant credential, provided in the authorization grant request,
|
||||
* for an access token credential at the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @param authorizationGrantRequest the authorization grant request that contains the authorization grant credential
|
||||
* @return an {@link OAuth2AccessTokenResponse} that contains the {@link OAuth2AccessTokenResponse#getAccessToken() access token} credential
|
||||
* @throws OAuth2AuthorizationException if an error occurs while attempting to exchange for the access token credential
|
||||
* Exchanges the authorization grant credential, provided in the authorization grant
|
||||
* request, for an access token credential at the Authorization Server's Token
|
||||
* Endpoint.
|
||||
* @param authorizationGrantRequest the authorization grant request that contains the
|
||||
* authorization grant credential
|
||||
* @return an {@link OAuth2AccessTokenResponse} that contains the
|
||||
* {@link OAuth2AccessTokenResponse#getAccessToken() access token} credential
|
||||
* @throws OAuth2AuthorizationException if an error occurs while attempting to
|
||||
* exchange for the access token credential
|
||||
*/
|
||||
OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -21,28 +22,33 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An OAuth 2.0 Authorization Code Grant request that holds an Authorization Code credential,
|
||||
* which was granted by the Resource Owner to the {@link #getClientRegistration() Client}.
|
||||
* An OAuth 2.0 Authorization Code Grant request that holds an Authorization Code
|
||||
* credential, which was granted by the Resource Owner to the
|
||||
* {@link #getClientRegistration() Client}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see ClientRegistration
|
||||
* @see OAuth2AuthorizationExchange
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3.1">Section 1.3.1 Authorization Code Grant</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-1.3.1">Section 1.3.1 Authorization Code
|
||||
* Grant</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
private final OAuth2AuthorizationExchange authorizationExchange;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeGrantRequest} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code OAuth2AuthorizationCodeGrantRequest} using the provided
|
||||
* parameters.
|
||||
* @param clientRegistration the client registration
|
||||
* @param authorizationExchange the authorization exchange
|
||||
*/
|
||||
public OAuth2AuthorizationCodeGrantRequest(ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizationExchange authorizationExchange) {
|
||||
OAuth2AuthorizationExchange authorizationExchange) {
|
||||
super(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
|
||||
@@ -52,7 +58,6 @@ public class OAuth2AuthorizationCodeGrantRequest extends AbstractOAuth2Authoriza
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -61,10 +66,10 @@ public class OAuth2AuthorizationCodeGrantRequest extends AbstractOAuth2Authoriza
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizationExchange authorization exchange}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationExchange}
|
||||
*/
|
||||
public OAuth2AuthorizationExchange getAuthorizationExchange() {
|
||||
return this.authorizationExchange;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -28,12 +31,10 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A {@link Converter} that converts the provided {@link OAuth2AuthorizationCodeGrantRequest}
|
||||
* to a {@link RequestEntity} representation of an OAuth 2.0 Access Token Request
|
||||
* for the Authorization Code Grant.
|
||||
* A {@link Converter} that converts the provided
|
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link RequestEntity} representation
|
||||
* of an OAuth 2.0 Access Token Request for the Authorization Code Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
@@ -41,42 +42,41 @@ import java.net.URI;
|
||||
* @see OAuth2AuthorizationCodeGrantRequest
|
||||
* @see RequestEntity
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeGrantRequestEntityConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
|
||||
public class OAuth2AuthorizationCodeGrantRequestEntityConverter
|
||||
implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
|
||||
|
||||
/**
|
||||
* Returns the {@link RequestEntity} used for the Access Token Request.
|
||||
*
|
||||
* @param authorizationCodeGrantRequest the authorization code grant request
|
||||
* @return the {@link RequestEntity} used for the Access Token Request
|
||||
*/
|
||||
@Override
|
||||
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
|
||||
|
||||
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
|
||||
MultiValueMap<String, String> formParameters = this.buildFormParameters(authorizationCodeGrantRequest);
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
|
||||
.build()
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
|
||||
.toUri();
|
||||
|
||||
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token Request body.
|
||||
*
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body.
|
||||
* @param authorizationCodeGrantRequest the authorization code grant request
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body
|
||||
*/
|
||||
private MultiValueMap<String, String> buildFormParameters(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
private MultiValueMap<String, String> buildFormParameters(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
|
||||
OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();
|
||||
|
||||
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
|
||||
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
|
||||
formParameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
|
||||
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
|
||||
String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
|
||||
String codeVerifier = authorizationExchange.getAuthorizationRequest()
|
||||
.getAttribute(PkceParameterNames.CODE_VERIFIER);
|
||||
if (redirectUri != null) {
|
||||
formParameters.add(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
@@ -89,7 +89,7 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverter implements Conve
|
||||
if (codeVerifier != null) {
|
||||
formParameters.add(PkceParameterNames.CODE_VERIFIER, codeVerifier);
|
||||
}
|
||||
|
||||
return formParameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -22,15 +25,11 @@ import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
|
||||
|
||||
/**
|
||||
* Utility methods used by the {@link Converter}'s that convert
|
||||
* from an implementation of an {@link AbstractOAuth2AuthorizationGrantRequest}
|
||||
* to a {@link RequestEntity} representation of an OAuth 2.0 Access Token Request
|
||||
* for the specific Authorization Grant.
|
||||
* Utility methods used by the {@link Converter}'s that convert from an implementation of
|
||||
* an {@link AbstractOAuth2AuthorizationGrantRequest} to a {@link RequestEntity}
|
||||
* representation of an OAuth 2.0 Access Token Request for the specific Authorization
|
||||
* Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
@@ -38,8 +37,12 @@ import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VAL
|
||||
* @see OAuth2ClientCredentialsGrantRequestEntityConverter
|
||||
*/
|
||||
final class OAuth2AuthorizationGrantRequestEntityUtils {
|
||||
|
||||
private static HttpHeaders DEFAULT_TOKEN_REQUEST_HEADERS = getDefaultTokenRequestHeaders();
|
||||
|
||||
private OAuth2AuthorizationGrantRequestEntityUtils() {
|
||||
}
|
||||
|
||||
static HttpHeaders getTokenRequestHeaders(ClientRegistration clientRegistration) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS);
|
||||
@@ -52,8 +55,9 @@ final class OAuth2AuthorizationGrantRequestEntityUtils {
|
||||
private static HttpHeaders getDefaultTokenRequestHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
|
||||
final MediaType contentType = MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
|
||||
final MediaType contentType = MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
|
||||
headers.setContentType(contentType);
|
||||
return headers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -20,21 +21,24 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An OAuth 2.0 Client Credentials Grant request that holds
|
||||
* the client's credentials in {@link #getClientRegistration()}.
|
||||
* An OAuth 2.0 Client Credentials Grant request that holds the client's credentials in
|
||||
* {@link #getClientRegistration()}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see ClientRegistration
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3.4">Section 1.3.4 Client Credentials Grant</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-1.3.4">Section 1.3.4 Client Credentials
|
||||
* Grant</a>
|
||||
*/
|
||||
public class OAuth2ClientCredentialsGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2ClientCredentialsGrantRequest} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code OAuth2ClientCredentialsGrantRequest} using the provided
|
||||
* parameters.
|
||||
* @param clientRegistration the client registration
|
||||
*/
|
||||
public OAuth2ClientCredentialsGrantRequest(ClientRegistration clientRegistration) {
|
||||
@@ -47,10 +51,10 @@ public class OAuth2ClientCredentialsGrantRequest extends AbstractOAuth2Authoriza
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
return this.clientRegistration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -28,12 +31,10 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A {@link Converter} that converts the provided {@link OAuth2ClientCredentialsGrantRequest}
|
||||
* to a {@link RequestEntity} representation of an OAuth 2.0 Access Token Request
|
||||
* for the Client Credentials Grant.
|
||||
* A {@link Converter} that converts the provided
|
||||
* {@link OAuth2ClientCredentialsGrantRequest} to a {@link RequestEntity} representation
|
||||
* of an OAuth 2.0 Access Token Request for the Client Credentials Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
@@ -41,36 +42,34 @@ import java.net.URI;
|
||||
* @see OAuth2ClientCredentialsGrantRequest
|
||||
* @see RequestEntity
|
||||
*/
|
||||
public class OAuth2ClientCredentialsGrantRequestEntityConverter implements Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> {
|
||||
public class OAuth2ClientCredentialsGrantRequestEntityConverter
|
||||
implements Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> {
|
||||
|
||||
/**
|
||||
* Returns the {@link RequestEntity} used for the Access Token Request.
|
||||
*
|
||||
* @param clientCredentialsGrantRequest the client credentials grant request
|
||||
* @return the {@link RequestEntity} used for the Access Token Request
|
||||
*/
|
||||
@Override
|
||||
public RequestEntity<?> convert(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
|
||||
ClientRegistration clientRegistration = clientCredentialsGrantRequest.getClientRegistration();
|
||||
|
||||
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
|
||||
MultiValueMap<String, String> formParameters = this.buildFormParameters(clientCredentialsGrantRequest);
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
|
||||
.build()
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
|
||||
.toUri();
|
||||
|
||||
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token Request body.
|
||||
*
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body.
|
||||
* @param clientCredentialsGrantRequest the client credentials grant request
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body
|
||||
*/
|
||||
private MultiValueMap<String, String> buildFormParameters(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
|
||||
private MultiValueMap<String, String> buildFormParameters(
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
|
||||
ClientRegistration clientRegistration = clientCredentialsGrantRequest.getClientRegistration();
|
||||
|
||||
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
|
||||
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, clientCredentialsGrantRequest.getGrantType().getValue());
|
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
|
||||
@@ -81,7 +80,7 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverter implements Conve
|
||||
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
|
||||
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
|
||||
}
|
||||
|
||||
return formParameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -20,22 +21,26 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An OAuth 2.0 Resource Owner Password Credentials Grant request
|
||||
* that holds the resource owner's credentials.
|
||||
* An OAuth 2.0 Resource Owner Password Credentials Grant request that holds the resource
|
||||
* owner's credentials.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3.3">Section 1.3.3 Resource Owner Password Credentials</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-1.3.3">Section 1.3.3 Resource Owner
|
||||
* Password Credentials</a>
|
||||
*/
|
||||
public class OAuth2PasswordGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String password;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2PasswordGrantRequest} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistration the client registration
|
||||
* @param username the resource owner's username
|
||||
* @param password the resource owner's password
|
||||
@@ -54,7 +59,6 @@ public class OAuth2PasswordGrantRequest extends AbstractOAuth2AuthorizationGrant
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientRegistration client registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -63,7 +67,6 @@ public class OAuth2PasswordGrantRequest extends AbstractOAuth2AuthorizationGrant
|
||||
|
||||
/**
|
||||
* Returns the resource owner's username.
|
||||
*
|
||||
* @return the resource owner's username
|
||||
*/
|
||||
public String getUsername() {
|
||||
@@ -72,10 +75,10 @@ public class OAuth2PasswordGrantRequest extends AbstractOAuth2AuthorizationGrant
|
||||
|
||||
/**
|
||||
* Returns the resource owner's password.
|
||||
*
|
||||
* @return the resource owner's password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -28,12 +31,10 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A {@link Converter} that converts the provided {@link OAuth2PasswordGrantRequest}
|
||||
* to a {@link RequestEntity} representation of an OAuth 2.0 Access Token Request
|
||||
* for the Resource Owner Password Credentials Grant.
|
||||
* A {@link Converter} that converts the provided {@link OAuth2PasswordGrantRequest} to a
|
||||
* {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the
|
||||
* Resource Owner Password Credentials Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -41,36 +42,33 @@ import java.net.URI;
|
||||
* @see OAuth2PasswordGrantRequest
|
||||
* @see RequestEntity
|
||||
*/
|
||||
public class OAuth2PasswordGrantRequestEntityConverter implements Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> {
|
||||
public class OAuth2PasswordGrantRequestEntityConverter
|
||||
implements Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> {
|
||||
|
||||
/**
|
||||
* Returns the {@link RequestEntity} used for the Access Token Request.
|
||||
*
|
||||
* @param passwordGrantRequest the password grant request
|
||||
* @return the {@link RequestEntity} used for the Access Token Request
|
||||
*/
|
||||
@Override
|
||||
public RequestEntity<?> convert(OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
|
||||
|
||||
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
|
||||
MultiValueMap<String, String> formParameters = buildFormParameters(passwordGrantRequest);
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
|
||||
.build()
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
|
||||
.toUri();
|
||||
|
||||
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token Request body.
|
||||
*
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body.
|
||||
* @param passwordGrantRequest the password grant request
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body
|
||||
*/
|
||||
private MultiValueMap<String, String> buildFormParameters(OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
|
||||
|
||||
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
|
||||
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, passwordGrantRequest.getGrantType().getValue());
|
||||
formParameters.add(OAuth2ParameterNames.USERNAME, passwordGrantRequest.getUsername());
|
||||
@@ -83,7 +81,7 @@ public class OAuth2PasswordGrantRequestEntityConverter implements Converter<OAut
|
||||
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
|
||||
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
|
||||
}
|
||||
|
||||
return formParameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,56 +13,60 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An OAuth 2.0 Refresh Token Grant request that holds the {@link OAuth2RefreshToken refresh token} credential
|
||||
* granted to the {@link #getClientRegistration() client}.
|
||||
* An OAuth 2.0 Refresh Token Grant request that holds the {@link OAuth2RefreshToken
|
||||
* refresh token} credential granted to the {@link #getClientRegistration() client}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see OAuth2RefreshToken
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 Refreshing an Access Token</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6
|
||||
* Refreshing an Access Token</a>
|
||||
*/
|
||||
public class OAuth2RefreshTokenGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
private final OAuth2AccessToken accessToken;
|
||||
|
||||
private final OAuth2RefreshToken refreshToken;
|
||||
|
||||
private final Set<String> scopes;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2RefreshTokenGrantRequest} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistration the authorized client's registration
|
||||
* @param accessToken the access token credential granted
|
||||
* @param refreshToken the refresh token credential granted
|
||||
*/
|
||||
public OAuth2RefreshTokenGrantRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken,
|
||||
OAuth2RefreshToken refreshToken) {
|
||||
OAuth2RefreshToken refreshToken) {
|
||||
this(clientRegistration, accessToken, refreshToken, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2RefreshTokenGrantRequest} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistration the authorized client's registration
|
||||
* @param accessToken the access token credential granted
|
||||
* @param refreshToken the refresh token credential granted
|
||||
* @param scopes the scopes to request
|
||||
*/
|
||||
public OAuth2RefreshTokenGrantRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken,
|
||||
OAuth2RefreshToken refreshToken, Set<String> scopes) {
|
||||
OAuth2RefreshToken refreshToken, Set<String> scopes) {
|
||||
super(AuthorizationGrantType.REFRESH_TOKEN);
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||
@@ -70,13 +74,12 @@ public class OAuth2RefreshTokenGrantRequest extends AbstractOAuth2AuthorizationG
|
||||
this.clientRegistration = clientRegistration;
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
this.scopes = Collections.unmodifiableSet(scopes != null ?
|
||||
new LinkedHashSet<>(scopes) : Collections.emptySet());
|
||||
this.scopes = Collections
|
||||
.unmodifiableSet((scopes != null) ? new LinkedHashSet<>(scopes) : Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authorized client's {@link ClientRegistration registration}.
|
||||
*
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
public ClientRegistration getClientRegistration() {
|
||||
@@ -85,7 +88,6 @@ public class OAuth2RefreshTokenGrantRequest extends AbstractOAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AccessToken access token} credential granted.
|
||||
*
|
||||
* @return the {@link OAuth2AccessToken}
|
||||
*/
|
||||
public OAuth2AccessToken getAccessToken() {
|
||||
@@ -94,7 +96,6 @@ public class OAuth2RefreshTokenGrantRequest extends AbstractOAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2RefreshToken refresh token} credential granted.
|
||||
*
|
||||
* @return the {@link OAuth2RefreshToken}
|
||||
*/
|
||||
public OAuth2RefreshToken getRefreshToken() {
|
||||
@@ -103,10 +104,10 @@ public class OAuth2RefreshTokenGrantRequest extends AbstractOAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the scope(s) to request.
|
||||
*
|
||||
* @return the scope(s) to request
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -28,12 +31,10 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A {@link Converter} that converts the provided {@link OAuth2RefreshTokenGrantRequest}
|
||||
* to a {@link RequestEntity} representation of an OAuth 2.0 Access Token Request
|
||||
* for the Refresh Token Grant.
|
||||
* to a {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the
|
||||
* Refresh Token Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@@ -41,36 +42,33 @@ import java.net.URI;
|
||||
* @see OAuth2RefreshTokenGrantRequest
|
||||
* @see RequestEntity
|
||||
*/
|
||||
public class OAuth2RefreshTokenGrantRequestEntityConverter implements Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> {
|
||||
public class OAuth2RefreshTokenGrantRequestEntityConverter
|
||||
implements Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> {
|
||||
|
||||
/**
|
||||
* Returns the {@link RequestEntity} used for the Access Token Request.
|
||||
*
|
||||
* @param refreshTokenGrantRequest the refresh token grant request
|
||||
* @return the {@link RequestEntity} used for the Access Token Request
|
||||
*/
|
||||
@Override
|
||||
public RequestEntity<?> convert(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
|
||||
ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration();
|
||||
|
||||
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
|
||||
MultiValueMap<String, String> formParameters = buildFormParameters(refreshTokenGrantRequest);
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
|
||||
.build()
|
||||
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
|
||||
.toUri();
|
||||
|
||||
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token Request body.
|
||||
*
|
||||
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body.
|
||||
* @param refreshTokenGrantRequest the refresh token grant request
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
|
||||
* @return a {@link MultiValueMap} of the form parameters used for the Access Token
|
||||
* Request body
|
||||
*/
|
||||
private MultiValueMap<String, String> buildFormParameters(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
|
||||
ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration();
|
||||
|
||||
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
|
||||
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, refreshTokenGrantRequest.getGrantType().getValue());
|
||||
formParameters.add(OAuth2ParameterNames.REFRESH_TOKEN,
|
||||
@@ -83,7 +81,7 @@ public class OAuth2RefreshTokenGrantRequestEntityConverter implements Converter<
|
||||
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
|
||||
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
|
||||
}
|
||||
|
||||
return formParameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,37 +13,47 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A reactive strategy for "exchanging" an authorization grant credential
|
||||
* (e.g. an Authorization Code) for an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
* A reactive strategy for "exchanging" an authorization grant credential (e.g.
|
||||
* an Authorization Code) for an access token credential at the Authorization Server's
|
||||
* Token Endpoint.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see AuthorizationGrantType
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section
|
||||
* 1.3 Authorization Grant</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response
|
||||
* (Authorization Code Grant)</a>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveOAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest> {
|
||||
public interface ReactiveOAuth2AccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest> {
|
||||
|
||||
/**
|
||||
* Exchanges the authorization grant credential, provided in the authorization grant request,
|
||||
* for an access token credential at the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @param authorizationGrantRequest the authorization grant request that contains the authorization grant credential
|
||||
* @return an {@link OAuth2AccessTokenResponse} that contains the {@link OAuth2AccessTokenResponse#getAccessToken() access token} credential
|
||||
* @throws OAuth2AuthorizationException if an error occurs while attempting to exchange for the access token credential
|
||||
* Exchanges the authorization grant credential, provided in the authorization grant
|
||||
* request, for an access token credential at the Authorization Server's Token
|
||||
* Endpoint.
|
||||
* @param authorizationGrantRequest the authorization grant request that contains the
|
||||
* authorization grant credential
|
||||
* @return an {@link OAuth2AccessTokenResponse} that contains the
|
||||
* {@link OAuth2AccessTokenResponse#getAccessToken() access token} credential
|
||||
* @throws OAuth2AuthorizationException if an error occurs while attempting to
|
||||
* exchange for the access token credential
|
||||
*/
|
||||
Mono<OAuth2AccessTokenResponse> getTokenResponse(T authorizationGrantRequest);
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
@@ -23,12 +27,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges"
|
||||
* an authorization code credential for an access token credential
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that
|
||||
* "exchanges" an authorization code credential for an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
*
|
||||
* <p>
|
||||
@@ -39,13 +40,20 @@ import java.util.Set;
|
||||
* @see ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see OAuth2AuthorizationCodeGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">Section 4.2 Client Creates the Code Challenge</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0
|
||||
* SDK</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">Section
|
||||
* 4.2 Client Creates the Code Challenge</a>
|
||||
*/
|
||||
public class WebClientReactiveAuthorizationCodeTokenResponseClient extends
|
||||
AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
public class WebClientReactiveAuthorizationCodeTokenResponseClient
|
||||
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
|
||||
@Override
|
||||
ClientRegistration clientRegistration(OAuth2AuthorizationCodeGrantRequest grantRequest) {
|
||||
@@ -63,8 +71,7 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient extends
|
||||
}
|
||||
|
||||
@Override
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(
|
||||
OAuth2AuthorizationCodeGrantRequest grantRequest,
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(OAuth2AuthorizationCodeGrantRequest grantRequest,
|
||||
BodyInserters.FormInserter<String> body) {
|
||||
super.populateTokenRequestBody(grantRequest, body);
|
||||
OAuth2AuthorizationExchange authorizationExchange = grantRequest.getAuthorizationExchange();
|
||||
@@ -74,10 +81,12 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient extends
|
||||
if (redirectUri != null) {
|
||||
body.with(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
|
||||
String codeVerifier = authorizationExchange.getAuthorizationRequest()
|
||||
.getAttribute(PkceParameterNames.CODE_VERIFIER);
|
||||
if (codeVerifier != null) {
|
||||
body.with(PkceParameterNames.CODE_VERIFIER, codeVerifier);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,29 +13,36 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that "exchanges"
|
||||
* a client credential for an access token credential
|
||||
* at the Authorization Server's Token Endpoint.
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that
|
||||
* "exchanges" a client credential for an access token credential at the
|
||||
* Authorization Server's Token Endpoint.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
* @see ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see OAuth2AuthorizationCodeGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0
|
||||
* SDK</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request
|
||||
* (Authorization Code Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response
|
||||
* (Authorization Code Grant)</a>
|
||||
*/
|
||||
public class WebClientReactiveClientCredentialsTokenResponseClient extends
|
||||
AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
|
||||
public class WebClientReactiveClientCredentialsTokenResponseClient
|
||||
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
|
||||
|
||||
@Override
|
||||
ClientRegistration clientRegistration(OAuth2ClientCredentialsGrantRequest grantRequest) {
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
@@ -22,24 +25,26 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient}
|
||||
* for the {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
* This implementation uses {@link WebClient} when requesting
|
||||
* an access token credential at the Authorization Server's Token Endpoint.
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant. This implementation uses
|
||||
* {@link WebClient} when requesting an access token credential at the Authorization
|
||||
* Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see OAuth2PasswordGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.3.2">Section 4.3.2 Access Token Request (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.3.3">Section 4.3.3 Access Token Response (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.2">Section 4.3.2 Access Token Request
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.3">Section 4.3.3 Access Token Response
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
*/
|
||||
public final class WebClientReactivePasswordTokenResponseClient extends
|
||||
AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
public final class WebClientReactivePasswordTokenResponseClient
|
||||
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
|
||||
@Override
|
||||
ClientRegistration clientRegistration(OAuth2PasswordGrantRequest grantRequest) {
|
||||
@@ -52,12 +57,11 @@ public final class WebClientReactivePasswordTokenResponseClient extends
|
||||
}
|
||||
|
||||
@Override
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(
|
||||
OAuth2PasswordGrantRequest grantRequest,
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(OAuth2PasswordGrantRequest grantRequest,
|
||||
BodyInserters.FormInserter<String> body) {
|
||||
return super.populateTokenRequestBody(grantRequest, body)
|
||||
.with(OAuth2ParameterNames.USERNAME, grantRequest.getUsername())
|
||||
.with(OAuth2ParameterNames.PASSWORD, grantRequest.getPassword());
|
||||
.with(OAuth2ParameterNames.USERNAME, grantRequest.getUsername())
|
||||
.with(OAuth2ParameterNames.PASSWORD, grantRequest.getPassword());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
@@ -23,23 +26,22 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient}
|
||||
* for the {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant.
|
||||
* This implementation uses {@link WebClient} when requesting
|
||||
* an access token credential at the Authorization Server's Token Endpoint.
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant. This implementation
|
||||
* uses {@link WebClient} when requesting an access token credential at the Authorization
|
||||
* Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see OAuth2RefreshTokenGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 Refreshing an Access Token</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6
|
||||
* Refreshing an Access Token</a>
|
||||
*/
|
||||
public final class WebClientReactiveRefreshTokenTokenResponseClient extends
|
||||
AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
|
||||
public final class WebClientReactiveRefreshTokenTokenResponseClient
|
||||
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
|
||||
|
||||
@Override
|
||||
ClientRegistration clientRegistration(OAuth2RefreshTokenGrantRequest grantRequest) {
|
||||
@@ -57,24 +59,21 @@ public final class WebClientReactiveRefreshTokenTokenResponseClient extends
|
||||
}
|
||||
|
||||
@Override
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(
|
||||
OAuth2RefreshTokenGrantRequest grantRequest,
|
||||
BodyInserters.FormInserter<String> populateTokenRequestBody(OAuth2RefreshTokenGrantRequest grantRequest,
|
||||
BodyInserters.FormInserter<String> body) {
|
||||
return super.populateTokenRequestBody(grantRequest, body)
|
||||
.with(OAuth2ParameterNames.REFRESH_TOKEN, grantRequest.getRefreshToken().getTokenValue());
|
||||
return super.populateTokenRequestBody(grantRequest, body).with(OAuth2ParameterNames.REFRESH_TOKEN,
|
||||
grantRequest.getRefreshToken().getTokenValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
OAuth2AccessTokenResponse populateTokenResponse(
|
||||
OAuth2RefreshTokenGrantRequest grantRequest,
|
||||
OAuth2AccessTokenResponse populateTokenResponse(OAuth2RefreshTokenGrantRequest grantRequest,
|
||||
OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
|
||||
if (!CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes()) &&
|
||||
accessTokenResponse.getRefreshToken() != null) {
|
||||
if (!CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes())
|
||||
&& accessTokenResponse.getRefreshToken() != null) {
|
||||
return accessTokenResponse;
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse.withResponse(accessTokenResponse);
|
||||
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse
|
||||
.withResponse(accessTokenResponse);
|
||||
if (CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes())) {
|
||||
tokenResponseBuilder.scopes(defaultScopes(grantRequest));
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes and interfaces providing support to the client
|
||||
* for initiating requests to the Authorization Server's Protocol Endpoints.
|
||||
* Classes and interfaces providing support to the client for initiating requests to the
|
||||
* Authorization Server's Protocol Endpoints.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
@@ -13,9 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.token.BearerTokenError;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
@@ -27,18 +31,18 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@link ResponseErrorHandler} that handles an {@link OAuth2Error OAuth 2.0 Error}.
|
||||
*
|
||||
* @see ResponseErrorHandler
|
||||
* @see OAuth2Error
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see ResponseErrorHandler
|
||||
* @see OAuth2Error
|
||||
*/
|
||||
public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
|
||||
|
||||
private final OAuth2ErrorHttpMessageConverter oauth2ErrorConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
private final ResponseErrorHandler defaultErrorHandler = new DefaultResponseErrorHandler();
|
||||
|
||||
@Override
|
||||
@@ -51,14 +55,12 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
|
||||
if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode())) {
|
||||
this.defaultErrorHandler.handleError(response);
|
||||
}
|
||||
|
||||
// A Bearer Token Error may be in the WWW-Authenticate response header
|
||||
// See https://tools.ietf.org/html/rfc6750#section-3
|
||||
OAuth2Error oauth2Error = this.readErrorFromWwwAuthenticate(response.getHeaders());
|
||||
if (oauth2Error == null) {
|
||||
oauth2Error = this.oauth2ErrorConverter.read(OAuth2Error.class, response);
|
||||
}
|
||||
|
||||
throw new OAuth2AuthorizationException(oauth2Error);
|
||||
}
|
||||
|
||||
@@ -67,20 +69,21 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
|
||||
if (!StringUtils.hasText(wwwAuthenticateHeader)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BearerTokenError bearerTokenError;
|
||||
try {
|
||||
bearerTokenError = BearerTokenError.parse(wwwAuthenticateHeader);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String errorCode = bearerTokenError.getCode() != null ?
|
||||
bearerTokenError.getCode() : OAuth2ErrorCodes.SERVER_ERROR;
|
||||
BearerTokenError bearerTokenError = getBearerToken(wwwAuthenticateHeader);
|
||||
String errorCode = (bearerTokenError.getCode() != null) ? bearerTokenError.getCode()
|
||||
: OAuth2ErrorCodes.SERVER_ERROR;
|
||||
String errorDescription = bearerTokenError.getDescription();
|
||||
String errorUri = bearerTokenError.getURI() != null ?
|
||||
bearerTokenError.getURI().toString() : null;
|
||||
|
||||
String errorUri = (bearerTokenError.getURI() != null) ? bearerTokenError.getURI().toString() : null;
|
||||
return new OAuth2Error(errorCode, errorDescription, errorUri);
|
||||
}
|
||||
|
||||
private BearerTokenError getBearerToken(String wwwAuthenticateHeader) {
|
||||
try {
|
||||
return BearerTokenError.parse(wwwAuthenticateHeader);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,27 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.MAP_TYPE_REFERENCE;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.SET_TYPE_REFERENCE;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findObjectNode;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findStringValue;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findValue;
|
||||
|
||||
/**
|
||||
* A {@code JsonDeserializer} for {@link ClientRegistration}.
|
||||
*
|
||||
@@ -43,43 +39,41 @@ import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.
|
||||
* @see ClientRegistrationMixin
|
||||
*/
|
||||
final class ClientRegistrationDeserializer extends JsonDeserializer<ClientRegistration> {
|
||||
private static final StdConverter<JsonNode, ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHOD_CONVERTER =
|
||||
new StdConverters.ClientAuthenticationMethodConverter();
|
||||
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER =
|
||||
new StdConverters.AuthorizationGrantTypeConverter();
|
||||
private static final StdConverter<JsonNode, AuthenticationMethod> AUTHENTICATION_METHOD_CONVERTER =
|
||||
new StdConverters.AuthenticationMethodConverter();
|
||||
|
||||
private static final StdConverter<JsonNode, ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHOD_CONVERTER = new StdConverters.ClientAuthenticationMethodConverter();
|
||||
|
||||
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
|
||||
|
||||
private static final StdConverter<JsonNode, AuthenticationMethod> AUTHENTICATION_METHOD_CONVERTER = new StdConverters.AuthenticationMethodConverter();
|
||||
|
||||
@Override
|
||||
public ClientRegistration deserialize(JsonParser parser, DeserializationContext context) throws IOException {
|
||||
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
|
||||
JsonNode clientRegistrationNode = mapper.readTree(parser);
|
||||
JsonNode providerDetailsNode = findObjectNode(clientRegistrationNode, "providerDetails");
|
||||
JsonNode userInfoEndpointNode = findObjectNode(providerDetailsNode, "userInfoEndpoint");
|
||||
|
||||
JsonNode providerDetailsNode = JsonNodeUtils.findObjectNode(clientRegistrationNode, "providerDetails");
|
||||
JsonNode userInfoEndpointNode = JsonNodeUtils.findObjectNode(providerDetailsNode, "userInfoEndpoint");
|
||||
return ClientRegistration
|
||||
.withRegistrationId(findStringValue(clientRegistrationNode, "registrationId"))
|
||||
.clientId(findStringValue(clientRegistrationNode, "clientId"))
|
||||
.clientSecret(findStringValue(clientRegistrationNode, "clientSecret"))
|
||||
.clientAuthenticationMethod(
|
||||
CLIENT_AUTHENTICATION_METHOD_CONVERTER.convert(
|
||||
findObjectNode(clientRegistrationNode, "clientAuthenticationMethod")))
|
||||
.authorizationGrantType(
|
||||
AUTHORIZATION_GRANT_TYPE_CONVERTER.convert(
|
||||
findObjectNode(clientRegistrationNode, "authorizationGrantType")))
|
||||
.redirectUri(findStringValue(clientRegistrationNode, "redirectUri"))
|
||||
.scope(findValue(clientRegistrationNode, "scopes", SET_TYPE_REFERENCE, mapper))
|
||||
.clientName(findStringValue(clientRegistrationNode, "clientName"))
|
||||
.authorizationUri(findStringValue(providerDetailsNode, "authorizationUri"))
|
||||
.tokenUri(findStringValue(providerDetailsNode, "tokenUri"))
|
||||
.userInfoUri(findStringValue(userInfoEndpointNode, "uri"))
|
||||
.userInfoAuthenticationMethod(
|
||||
AUTHENTICATION_METHOD_CONVERTER.convert(
|
||||
findObjectNode(userInfoEndpointNode, "authenticationMethod")))
|
||||
.userNameAttributeName(findStringValue(userInfoEndpointNode, "userNameAttributeName"))
|
||||
.jwkSetUri(findStringValue(providerDetailsNode, "jwkSetUri"))
|
||||
.issuerUri(findStringValue(providerDetailsNode, "issuerUri"))
|
||||
.providerConfigurationMetadata(findValue(providerDetailsNode, "configurationMetadata", MAP_TYPE_REFERENCE, mapper))
|
||||
.withRegistrationId(JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId"))
|
||||
.clientId(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId"))
|
||||
.clientSecret(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret"))
|
||||
.clientAuthenticationMethod(CLIENT_AUTHENTICATION_METHOD_CONVERTER
|
||||
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "clientAuthenticationMethod")))
|
||||
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE_CONVERTER
|
||||
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "authorizationGrantType")))
|
||||
.redirectUri(JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri"))
|
||||
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET, mapper))
|
||||
.clientName(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName"))
|
||||
.authorizationUri(JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri"))
|
||||
.tokenUri(JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri"))
|
||||
.userInfoUri(JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri"))
|
||||
.userInfoAuthenticationMethod(AUTHENTICATION_METHOD_CONVERTER
|
||||
.convert(JsonNodeUtils.findObjectNode(userInfoEndpointNode, "authenticationMethod")))
|
||||
.userNameAttributeName(JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName"))
|
||||
.jwkSetUri(JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri"))
|
||||
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
|
||||
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
|
||||
JsonNodeUtils.STRING_OBJECT_MAP, mapper))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,17 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link ClientRegistration}.
|
||||
* It also registers a custom deserializer {@link ClientRegistrationDeserializer}.
|
||||
* This mixin class is used to serialize/deserialize {@link ClientRegistration}. It also
|
||||
* registers a custom deserializer {@link ClientRegistrationDeserializer}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.3
|
||||
@@ -37,4 +39,5 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class ClientRegistrationMixin {
|
||||
|
||||
}
|
||||
|
||||
@@ -13,19 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link DefaultOAuth2User}.
|
||||
*
|
||||
@@ -41,9 +43,9 @@ import java.util.Map;
|
||||
abstract class DefaultOAuth2UserMixin {
|
||||
|
||||
@JsonCreator
|
||||
DefaultOAuth2UserMixin(
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
DefaultOAuth2UserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
@JsonProperty("attributes") Map<String, Object> attributes,
|
||||
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,20 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link DefaultOidcUser}.
|
||||
*
|
||||
@@ -38,14 +40,13 @@ import java.util.Collection;
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(value = {"attributes"}, ignoreUnknown = true)
|
||||
@JsonIgnoreProperties(value = { "attributes" }, ignoreUnknown = true)
|
||||
abstract class DefaultOidcUserMixin {
|
||||
|
||||
@JsonCreator
|
||||
DefaultOidcUserMixin(
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
@JsonProperty("idToken") OidcIdToken idToken,
|
||||
@JsonProperty("userInfo") OidcUserInfo userInfo,
|
||||
DefaultOidcUserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
@JsonProperty("idToken") OidcIdToken idToken, @JsonProperty("userInfo") OidcUserInfo userInfo,
|
||||
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,15 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utility class for {@code JsonNode}.
|
||||
*
|
||||
@@ -29,40 +30,36 @@ import java.util.Set;
|
||||
* @since 5.3
|
||||
*/
|
||||
abstract class JsonNodeUtils {
|
||||
static final TypeReference<Set<String>> SET_TYPE_REFERENCE = new TypeReference<Set<String>>() {};
|
||||
static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {};
|
||||
|
||||
static final TypeReference<Set<String>> STRING_SET = new TypeReference<Set<String>>() {
|
||||
};
|
||||
|
||||
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<Map<String, Object>>() {
|
||||
};
|
||||
|
||||
static String findStringValue(JsonNode jsonNode, String fieldName) {
|
||||
if (jsonNode == null) {
|
||||
return null;
|
||||
}
|
||||
JsonNode nodeValue = jsonNode.findValue(fieldName);
|
||||
if (nodeValue != null && nodeValue.isTextual()) {
|
||||
return nodeValue.asText();
|
||||
}
|
||||
return null;
|
||||
JsonNode value = jsonNode.findValue(fieldName);
|
||||
return (value != null && value.isTextual()) ? value.asText() : null;
|
||||
}
|
||||
|
||||
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference, ObjectMapper mapper) {
|
||||
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
|
||||
ObjectMapper mapper) {
|
||||
if (jsonNode == null) {
|
||||
return null;
|
||||
}
|
||||
JsonNode nodeValue = jsonNode.findValue(fieldName);
|
||||
if (nodeValue != null && nodeValue.isContainerNode()) {
|
||||
return (T) mapper.convertValue(nodeValue, valueTypeReference);
|
||||
}
|
||||
return null;
|
||||
JsonNode value = jsonNode.findValue(fieldName);
|
||||
return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
|
||||
}
|
||||
|
||||
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
|
||||
if (jsonNode == null) {
|
||||
return null;
|
||||
}
|
||||
JsonNode nodeValue = jsonNode.findValue(fieldName);
|
||||
if (nodeValue != null && nodeValue.isObject()) {
|
||||
return nodeValue;
|
||||
}
|
||||
return null;
|
||||
JsonNode value = jsonNode.findValue(fieldName);
|
||||
return (value != null && value.isObject()) ? value : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,18 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OAuth2AccessToken}.
|
||||
@@ -42,10 +44,10 @@ abstract class OAuth2AccessTokenMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2AccessTokenMixin(
|
||||
@JsonProperty("tokenType") @JsonDeserialize(converter = StdConverters.AccessTokenTypeConverter.class) OAuth2AccessToken.TokenType tokenType,
|
||||
@JsonProperty("tokenValue") String tokenValue,
|
||||
@JsonProperty("issuedAt") Instant issuedAt,
|
||||
@JsonProperty("expiresAt") Instant expiresAt,
|
||||
@JsonProperty("scopes") Set<String> scopes) {
|
||||
@JsonProperty("tokenType") @JsonDeserialize(
|
||||
converter = StdConverters.AccessTokenTypeConverter.class) OAuth2AccessToken.TokenType tokenType,
|
||||
@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt,
|
||||
@JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("scopes") Set<String> scopes) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
@@ -20,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
@@ -34,13 +36,13 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true, value = {"cause", "stackTrace", "suppressedExceptions"})
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true, value = { "cause", "stackTrace", "suppressedExceptions" })
|
||||
abstract class OAuth2AuthenticationExceptionMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2AuthenticationExceptionMixin(
|
||||
@JsonProperty("error") OAuth2Error error,
|
||||
OAuth2AuthenticationExceptionMixin(@JsonProperty("error") OAuth2Error error,
|
||||
@JsonProperty("detailMessage") String message) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,19 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OAuth2AuthenticationToken}.
|
||||
*
|
||||
@@ -37,13 +39,13 @@ import java.util.Collection;
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(value = {"authenticated"}, ignoreUnknown = true)
|
||||
@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true)
|
||||
abstract class OAuth2AuthenticationTokenMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2AuthenticationTokenMixin(
|
||||
@JsonProperty("principal") OAuth2User principal,
|
||||
OAuth2AuthenticationTokenMixin(@JsonProperty("principal") OAuth2User principal,
|
||||
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
|
||||
@JsonProperty("authorizedClientRegistrationId") String authorizedClientRegistrationId) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
@@ -22,16 +25,10 @@ import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.MAP_TYPE_REFERENCE;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.SET_TYPE_REFERENCE;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findObjectNode;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findStringValue;
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findValue;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
|
||||
|
||||
/**
|
||||
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
|
||||
@@ -42,35 +39,43 @@ import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.
|
||||
* @see OAuth2AuthorizationRequestMixin
|
||||
*/
|
||||
final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAuth2AuthorizationRequest> {
|
||||
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER =
|
||||
new StdConverters.AuthorizationGrantTypeConverter();
|
||||
|
||||
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context) throws IOException {
|
||||
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context)
|
||||
throws IOException {
|
||||
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
|
||||
JsonNode authorizationRequestNode = mapper.readTree(parser);
|
||||
|
||||
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER.convert(
|
||||
findObjectNode(authorizationRequestNode, "authorizationGrantType"));
|
||||
|
||||
OAuth2AuthorizationRequest.Builder builder;
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
|
||||
builder = OAuth2AuthorizationRequest.authorizationCode();
|
||||
} else if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) {
|
||||
builder = OAuth2AuthorizationRequest.implicit();
|
||||
} else {
|
||||
throw new JsonParseException(parser, "Invalid authorizationGrantType");
|
||||
}
|
||||
|
||||
return builder
|
||||
.authorizationUri(findStringValue(authorizationRequestNode, "authorizationUri"))
|
||||
.clientId(findStringValue(authorizationRequestNode, "clientId"))
|
||||
.redirectUri(findStringValue(authorizationRequestNode, "redirectUri"))
|
||||
.scopes(findValue(authorizationRequestNode, "scopes", SET_TYPE_REFERENCE, mapper))
|
||||
.state(findStringValue(authorizationRequestNode, "state"))
|
||||
.additionalParameters(findValue(authorizationRequestNode, "additionalParameters", MAP_TYPE_REFERENCE, mapper))
|
||||
.authorizationRequestUri(findStringValue(authorizationRequestNode, "authorizationRequestUri"))
|
||||
.attributes(findValue(authorizationRequestNode, "attributes", MAP_TYPE_REFERENCE, mapper))
|
||||
.build();
|
||||
JsonNode root = mapper.readTree(parser);
|
||||
return deserialize(parser, mapper, root);
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationRequest deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
|
||||
throws JsonParseException {
|
||||
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
|
||||
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
|
||||
Builder builder = getBuilder(parser, authorizationGrantType);
|
||||
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
|
||||
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
|
||||
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
|
||||
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, mapper));
|
||||
builder.state(JsonNodeUtils.findStringValue(root, "state"));
|
||||
builder.additionalParameters(
|
||||
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
|
||||
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
|
||||
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationRequest.Builder getBuilder(JsonParser parser,
|
||||
AuthorizationGrantType authorizationGrantType) throws JsonParseException {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
|
||||
return OAuth2AuthorizationRequest.authorizationCode();
|
||||
}
|
||||
if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) {
|
||||
return OAuth2AuthorizationRequest.implicit();
|
||||
}
|
||||
throw new JsonParseException(parser, "Invalid authorizationGrantType");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
|
||||
/**
|
||||
@@ -37,4 +39,5 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class OAuth2AuthorizationRequestMixin {
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
@@ -20,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
@@ -40,10 +42,10 @@ import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
abstract class OAuth2AuthorizedClientMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2AuthorizedClientMixin(
|
||||
@JsonProperty("clientRegistration") ClientRegistration clientRegistration,
|
||||
OAuth2AuthorizedClientMixin(@JsonProperty("clientRegistration") ClientRegistration clientRegistration,
|
||||
@JsonProperty("principalName") String principalName,
|
||||
@JsonProperty("accessToken") OAuth2AccessToken accessToken,
|
||||
@JsonProperty("refreshToken") OAuth2RefreshToken refreshToken) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
@@ -33,39 +37,38 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Jackson {@code Module} for {@code spring-security-oauth2-client},
|
||||
* that registers the following mix-in annotations:
|
||||
* Jackson {@code Module} for {@code spring-security-oauth2-client}, that registers the
|
||||
* following mix-in annotations:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
|
||||
* <li>{@link ClientRegistrationMixin}</li>
|
||||
* <li>{@link OAuth2AccessTokenMixin}</li>
|
||||
* <li>{@link OAuth2RefreshTokenMixin}</li>
|
||||
* <li>{@link OAuth2AuthorizedClientMixin}</li>
|
||||
* <li>{@link OAuth2UserAuthorityMixin}</li>
|
||||
* <li>{@link DefaultOAuth2UserMixin}</li>
|
||||
* <li>{@link OidcIdTokenMixin}</li>
|
||||
* <li>{@link OidcUserInfoMixin}</li>
|
||||
* <li>{@link OidcUserAuthorityMixin}</li>
|
||||
* <li>{@link DefaultOidcUserMixin}</li>
|
||||
* <li>{@link OAuth2AuthenticationTokenMixin}</li>
|
||||
* <li>{@link OAuth2AuthenticationExceptionMixin}</li>
|
||||
* <li>{@link OAuth2ErrorMixin}</li>
|
||||
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
|
||||
* <li>{@link ClientRegistrationMixin}</li>
|
||||
* <li>{@link OAuth2AccessTokenMixin}</li>
|
||||
* <li>{@link OAuth2RefreshTokenMixin}</li>
|
||||
* <li>{@link OAuth2AuthorizedClientMixin}</li>
|
||||
* <li>{@link OAuth2UserAuthorityMixin}</li>
|
||||
* <li>{@link DefaultOAuth2UserMixin}</li>
|
||||
* <li>{@link OidcIdTokenMixin}</li>
|
||||
* <li>{@link OidcUserInfoMixin}</li>
|
||||
* <li>{@link OidcUserAuthorityMixin}</li>
|
||||
* <li>{@link DefaultOidcUserMixin}</li>
|
||||
* <li>{@link OAuth2AuthenticationTokenMixin}</li>
|
||||
* <li>{@link OAuth2AuthenticationExceptionMixin}</li>
|
||||
* <li>{@link OAuth2ErrorMixin}</li>
|
||||
* </ul>
|
||||
*
|
||||
* If not already enabled, default typing will be automatically enabled
|
||||
* as type info is required to properly serialize/deserialize objects.
|
||||
* In order to use this module just add it to your {@code ObjectMapper} configuration.
|
||||
* If not already enabled, default typing will be automatically enabled as type info is
|
||||
* required to properly serialize/deserialize objects. In order to use this module just
|
||||
* add it to your {@code ObjectMapper} configuration.
|
||||
*
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new OAuth2ClientJackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* <b>NOTE:</b> Use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get a list of all security modules.
|
||||
* <b>NOTE:</b> Use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get a list
|
||||
* of all security modules.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.3
|
||||
@@ -94,7 +97,8 @@ public class OAuth2ClientJackson2Module extends SimpleModule {
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
|
||||
context.setMixInAnnotations(Collections.unmodifiableMap(Collections.emptyMap()).getClass(), UnmodifiableMapMixin.class);
|
||||
context.setMixInAnnotations(Collections.unmodifiableMap(Collections.emptyMap()).getClass(),
|
||||
UnmodifiableMapMixin.class);
|
||||
context.setMixInAnnotations(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
|
||||
context.setMixInAnnotations(ClientRegistration.class, ClientRegistrationMixin.class);
|
||||
context.setMixInAnnotations(OAuth2AccessToken.class, OAuth2AccessTokenMixin.class);
|
||||
@@ -110,4 +114,5 @@ public class OAuth2ClientJackson2Module extends SimpleModule {
|
||||
context.setMixInAnnotations(OAuth2AuthenticationException.class, OAuth2AuthenticationExceptionMixin.class);
|
||||
context.setMixInAnnotations(OAuth2Error.class, OAuth2ErrorMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
@@ -20,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
/**
|
||||
@@ -34,14 +36,13 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class OAuth2ErrorMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2ErrorMixin(
|
||||
@JsonProperty("errorCode") String errorCode,
|
||||
@JsonProperty("description") String description,
|
||||
OAuth2ErrorMixin(@JsonProperty("errorCode") String errorCode, @JsonProperty("description") String description,
|
||||
@JsonProperty("uri") String uri) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
|
||||
import java.time.Instant;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OAuth2RefreshToken}.
|
||||
@@ -39,8 +41,7 @@ import java.time.Instant;
|
||||
abstract class OAuth2RefreshTokenMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2RefreshTokenMixin(
|
||||
@JsonProperty("tokenValue") String tokenValue,
|
||||
@JsonProperty("issuedAt") Instant issuedAt) {
|
||||
OAuth2RefreshTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OAuth2UserAuthority}.
|
||||
@@ -39,8 +41,8 @@ import java.util.Map;
|
||||
abstract class OAuth2UserAuthorityMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2UserAuthorityMixin(
|
||||
@JsonProperty("authority") String authority,
|
||||
OAuth2UserAuthorityMixin(@JsonProperty("authority") String authority,
|
||||
@JsonProperty("attributes") Map<String, Object> attributes) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,17 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OidcIdToken}.
|
||||
@@ -40,10 +42,8 @@ import java.util.Map;
|
||||
abstract class OidcIdTokenMixin {
|
||||
|
||||
@JsonCreator
|
||||
OidcIdTokenMixin(
|
||||
@JsonProperty("tokenValue") String tokenValue,
|
||||
@JsonProperty("issuedAt") Instant issuedAt,
|
||||
@JsonProperty("expiresAt") Instant expiresAt,
|
||||
@JsonProperty("claims") Map<String, Object> claims) {
|
||||
OidcIdTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt,
|
||||
@JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("claims") Map<String, Object> claims) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
@@ -20,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
@@ -35,13 +37,12 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(value = {"attributes"}, ignoreUnknown = true)
|
||||
@JsonIgnoreProperties(value = { "attributes" }, ignoreUnknown = true)
|
||||
abstract class OidcUserAuthorityMixin {
|
||||
|
||||
@JsonCreator
|
||||
OidcUserAuthorityMixin(
|
||||
@JsonProperty("authority") String authority,
|
||||
@JsonProperty("idToken") OidcIdToken idToken,
|
||||
OidcUserAuthorityMixin(@JsonProperty("authority") String authority, @JsonProperty("idToken") OidcIdToken idToken,
|
||||
@JsonProperty("userInfo") OidcUserInfo userInfo) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OidcUserInfo}.
|
||||
@@ -41,4 +43,5 @@ abstract class OidcUserInfoMixin {
|
||||
@JsonCreator
|
||||
OidcUserInfoMixin(@JsonProperty("claims") Map<String, Object> claims) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
|
||||
import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.findStringValue;
|
||||
|
||||
/**
|
||||
* {@code StdConverter} implementations.
|
||||
*
|
||||
@@ -33,60 +33,76 @@ import static org.springframework.security.oauth2.client.jackson2.JsonNodeUtils.
|
||||
abstract class StdConverters {
|
||||
|
||||
static final class AccessTokenTypeConverter extends StdConverter<JsonNode, OAuth2AccessToken.TokenType> {
|
||||
|
||||
@Override
|
||||
public OAuth2AccessToken.TokenType convert(JsonNode jsonNode) {
|
||||
String value = findStringValue(jsonNode, "value");
|
||||
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
|
||||
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
|
||||
return OAuth2AccessToken.TokenType.BEARER;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class ClientAuthenticationMethodConverter extends StdConverter<JsonNode, ClientAuthenticationMethod> {
|
||||
|
||||
@Override
|
||||
public ClientAuthenticationMethod convert(JsonNode jsonNode) {
|
||||
String value = findStringValue(jsonNode, "value");
|
||||
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
|
||||
if (ClientAuthenticationMethod.BASIC.getValue().equalsIgnoreCase(value)) {
|
||||
return ClientAuthenticationMethod.BASIC;
|
||||
} else if (ClientAuthenticationMethod.POST.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (ClientAuthenticationMethod.POST.getValue().equalsIgnoreCase(value)) {
|
||||
return ClientAuthenticationMethod.POST;
|
||||
} else if (ClientAuthenticationMethod.NONE.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (ClientAuthenticationMethod.NONE.getValue().equalsIgnoreCase(value)) {
|
||||
return ClientAuthenticationMethod.NONE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class AuthorizationGrantTypeConverter extends StdConverter<JsonNode, AuthorizationGrantType> {
|
||||
|
||||
@Override
|
||||
public AuthorizationGrantType convert(JsonNode jsonNode) {
|
||||
String value = findStringValue(jsonNode, "value");
|
||||
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthorizationGrantType.AUTHORIZATION_CODE;
|
||||
} else if (AuthorizationGrantType.IMPLICIT.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (AuthorizationGrantType.IMPLICIT.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthorizationGrantType.IMPLICIT;
|
||||
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthorizationGrantType.CLIENT_CREDENTIALS;
|
||||
} else if (AuthorizationGrantType.PASSWORD.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (AuthorizationGrantType.PASSWORD.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthorizationGrantType.PASSWORD;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class AuthenticationMethodConverter extends StdConverter<JsonNode, AuthenticationMethod> {
|
||||
|
||||
@Override
|
||||
public AuthenticationMethod convert(JsonNode jsonNode) {
|
||||
String value = findStringValue(jsonNode, "value");
|
||||
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
|
||||
if (AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthenticationMethod.HEADER;
|
||||
} else if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthenticationMethod.FORM;
|
||||
} else if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
|
||||
}
|
||||
if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthenticationMethod.QUERY;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,19 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@code JsonDeserializer} for {@link Collections#unmodifiableMap(Map)}.
|
||||
*
|
||||
@@ -49,4 +50,5 @@ final class UnmodifiableMapDeserializer extends JsonDeserializer<Map<?, ?>> {
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,18 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link Collections#unmodifiableMap(Map)}.
|
||||
* It also registers a custom deserializer {@link UnmodifiableMapDeserializer}.
|
||||
* This mixin class is used to serialize/deserialize
|
||||
* {@link Collections#unmodifiableMap(Map)}. It also registers a custom deserializer
|
||||
* {@link UnmodifiableMapDeserializer}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.3
|
||||
@@ -39,4 +41,5 @@ abstract class UnmodifiableMapMixin {
|
||||
@JsonCreator
|
||||
UnmodifiableMapMixin(Map<?, ?> map) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
*/
|
||||
@@ -32,7 +32,8 @@ class DefaultOidcIdTokenValidatorFactory implements Function<ClientRegistration,
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
|
||||
return new DelegatingOAuth2TokenValidator<>(
|
||||
new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration));
|
||||
return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(),
|
||||
new OidcIdTokenValidator(clientRegistration));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -43,26 +51,19 @@ import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthenticationProvider}
|
||||
* for the OpenID Connect Core 1.0 Authorization Code Grant Flow.
|
||||
* An implementation of an {@link AuthenticationProvider} for the OpenID Connect Core 1.0
|
||||
* Authorization Code Grant Flow.
|
||||
* <p>
|
||||
* This {@link AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* and if valid, exchanging it for an Access Token credential.
|
||||
* This {@link AuthenticationProvider} is responsible for authenticating an Authorization
|
||||
* Code credential with the Authorization Server's Token Endpoint and if valid, exchanging
|
||||
* it for an Access Token credential.
|
||||
* <p>
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner)
|
||||
* from the UserInfo Endpoint using an {@link OAuth2UserService},
|
||||
* which will create a {@code Principal} in the form of an {@link OidcUser}.
|
||||
* The {@code OidcUser} is then associated to the {@link OAuth2LoginAuthenticationToken}
|
||||
* to complete the authentication.
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner) from the
|
||||
* UserInfo Endpoint using an {@link OAuth2UserService}, which will create a
|
||||
* {@code Principal} in the form of an {@link OidcUser}. The {@code OidcUser} is then
|
||||
* associated to the {@link OAuth2LoginAuthenticationToken} to complete the
|
||||
* authentication.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Mark Heckler
|
||||
@@ -72,29 +73,43 @@ import java.util.Map;
|
||||
* @see OidcUserService
|
||||
* @see OidcUser
|
||||
* @see OidcIdTokenDecoderFactory
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest">Section 3.1.3.1 Token Request</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 Token Response</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1
|
||||
* Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest">Section 3.1.3.1
|
||||
* Token Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3
|
||||
* Token Response</a>
|
||||
*/
|
||||
public class OidcAuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||
|
||||
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
||||
|
||||
private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce";
|
||||
|
||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
||||
|
||||
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
|
||||
|
||||
/**
|
||||
* Constructs an {@code OidcAuthorizationCodeAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
* @param accessTokenResponseClient the client used for requesting the access token credential from the Token Endpoint
|
||||
* @param userService the service used for obtaining the user attributes of the End-User from the UserInfo Endpoint
|
||||
* Constructs an {@code OidcAuthorizationCodeAuthenticationProvider} using the
|
||||
* provided parameters.
|
||||
* @param accessTokenResponseClient the client used for requesting the access token
|
||||
* credential from the Token Endpoint
|
||||
* @param userService the service used for obtaining the user attributes of the
|
||||
* End-User from the UserInfo Endpoint
|
||||
*/
|
||||
public OidcAuthorizationCodeAuthenticationProvider(
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||
OAuth2UserService<OidcUserRequest, OidcUser> userService) {
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||
OAuth2UserService<OidcUserRequest, OidcUser> userService) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
Assert.notNull(userService, "userService cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
@@ -103,97 +118,95 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
|
||||
(OAuth2LoginAuthenticationToken) authentication;
|
||||
|
||||
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
OAuth2LoginAuthenticationToken authorizationCodeAuthentication = (OAuth2LoginAuthenticationToken) authentication;
|
||||
// Section 3.1.2.1 Authentication Request -
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
// scope
|
||||
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (!authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID)) {
|
||||
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (!authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes()
|
||||
.contains(OidcScopes.OPENID)) {
|
||||
// This is NOT an OpenID Connect Authentication Request so return null
|
||||
// and let OAuth2LoginAuthenticationProvider handle it instead
|
||||
return null;
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
|
||||
.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
|
||||
.getAuthorizationExchange().getAuthorizationResponse();
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationResponse();
|
||||
if (authorizationResponse.statusError()) {
|
||||
throw new OAuth2AuthenticationException(
|
||||
authorizationResponse.getError(), authorizationResponse.getError().toString());
|
||||
throw new OAuth2AuthenticationException(authorizationResponse.getError(),
|
||||
authorizationResponse.getError().toString());
|
||||
}
|
||||
|
||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponse accessTokenResponse;
|
||||
try {
|
||||
accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
|
||||
new OAuth2AuthorizationCodeGrantRequest(
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange()));
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
OAuth2Error oauth2Error = ex.getError();
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponse accessTokenResponse = getResponse(authorizationCodeAuthentication);
|
||||
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
|
||||
|
||||
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
|
||||
if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(
|
||||
INVALID_ID_TOKEN_ERROR_CODE,
|
||||
"Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(),
|
||||
null);
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
|
||||
"Missing (required) ID Token in Token Response for Client Registration: "
|
||||
+ clientRegistration.getRegistrationId(),
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
|
||||
}
|
||||
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
|
||||
|
||||
// Validate nonce
|
||||
String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
|
||||
if (requestNonce != null) {
|
||||
String nonceHash;
|
||||
try {
|
||||
nonceHash = createHash(requestNonce);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
String nonceHashClaim = idToken.getNonce();
|
||||
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(
|
||||
clientRegistration, accessTokenResponse.getAccessToken(), idToken, additionalParameters));
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities =
|
||||
this.authoritiesMapper.mapAuthorities(oidcUser.getAuthorities());
|
||||
|
||||
validateNonce(authorizationRequest, idToken);
|
||||
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
|
||||
accessTokenResponse.getAccessToken(), idToken, additionalParameters));
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
|
||||
.mapAuthorities(oidcUser.getAuthorities());
|
||||
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange(),
|
||||
oidcUser,
|
||||
mappedAuthorities,
|
||||
accessTokenResponse.getAccessToken(),
|
||||
accessTokenResponse.getRefreshToken());
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,
|
||||
accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
|
||||
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponse getResponse(OAuth2LoginAuthenticationToken authorizationCodeAuthentication) {
|
||||
try {
|
||||
return this.accessTokenResponseClient.getTokenResponse(
|
||||
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange()));
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
OAuth2Error oauth2Error = ex.getError();
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNonce(OAuth2AuthorizationRequest authorizationRequest, OidcIdToken idToken) {
|
||||
String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
|
||||
if (requestNonce == null) {
|
||||
return;
|
||||
}
|
||||
String nonceHash = getNonceHash(requestNonce);
|
||||
String nonceHashClaim = idToken.getNonce();
|
||||
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String getNonceHash(String requestNonce) {
|
||||
try {
|
||||
return createHash(requestNonce);
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JwtDecoderFactory} used for {@link OidcIdToken} signature verification.
|
||||
* The factory returns a {@link JwtDecoder} associated to the provided {@link ClientRegistration}.
|
||||
*
|
||||
* Sets the {@link JwtDecoderFactory} used for {@link OidcIdToken} signature
|
||||
* verification. The factory returns a {@link JwtDecoder} associated to the provided
|
||||
* {@link ClientRegistration}.
|
||||
* @param jwtDecoderFactory the {@link JwtDecoderFactory} used for {@link OidcIdToken}
|
||||
* signature verification
|
||||
* @since 5.2
|
||||
* @param jwtDecoderFactory the {@link JwtDecoderFactory} used for {@link OidcIdToken} signature verification
|
||||
*/
|
||||
public final void setJwtDecoderFactory(JwtDecoderFactory<ClientRegistration> jwtDecoderFactory) {
|
||||
Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
|
||||
@@ -201,10 +214,11 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OidcUser#getAuthorities()}}
|
||||
* to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
*
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the user's authorities
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping
|
||||
* {@link OidcUser#getAuthorities()}} to a new set of authorities which will be
|
||||
* associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the
|
||||
* user's authorities
|
||||
*/
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
@@ -216,17 +230,24 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||
return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private OidcIdToken createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
|
||||
OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||
Jwt jwt;
|
||||
Jwt jwt = getJwt(accessTokenResponse, jwtDecoder);
|
||||
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
|
||||
jwt.getClaims());
|
||||
return idToken;
|
||||
}
|
||||
|
||||
private Jwt getJwt(OAuth2AccessTokenResponse accessTokenResponse, JwtDecoder jwtDecoder) {
|
||||
try {
|
||||
jwt = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
|
||||
} catch (JwtException ex) {
|
||||
Map<String, Object> parameters = accessTokenResponse.getAdditionalParameters();
|
||||
return jwtDecoder.decode((String) parameters.get(OidcParameterNames.ID_TOKEN));
|
||||
}
|
||||
catch (JwtException ex) {
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
|
||||
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
|
||||
}
|
||||
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
||||
return idToken;
|
||||
}
|
||||
|
||||
static String createHash(String nonce) throws NoSuchAlgorithmException {
|
||||
@@ -234,4 +255,5 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||
byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -41,28 +51,22 @@ import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
||||
* which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
* An implementation of an
|
||||
* {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth
|
||||
* 2.0 Login, which leverages the OAuth 2.0 Authorization Code Grant Flow.
|
||||
* <p>
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is responsible for authenticating
|
||||
* an Authorization Code credential with the Authorization Server's Token Endpoint
|
||||
* and if valid, exchanging it for an Access Token credential.
|
||||
* This {@link org.springframework.security.authentication.AuthenticationProvider} is
|
||||
* responsible for authenticating an Authorization Code credential with the Authorization
|
||||
* Server's Token Endpoint and if valid, exchanging it for an Access Token credential.
|
||||
* <p>
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner)
|
||||
* from the UserInfo Endpoint using an {@link org.springframework.security.oauth2.client.userinfo.OAuth2UserService},
|
||||
* which will create a {@code Principal} in the form of an {@link OAuth2User}.
|
||||
* The {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken}
|
||||
* to complete the authentication.
|
||||
* It will also obtain the user attributes of the End-User (Resource Owner) from the
|
||||
* UserInfo Endpoint using an
|
||||
* {@link org.springframework.security.oauth2.client.userinfo.OAuth2UserService}, which
|
||||
* will create a {@code Principal} in the form of an {@link OAuth2User}. The
|
||||
* {@code OAuth2User} is then associated to the {@link OAuth2LoginAuthenticationToken} to
|
||||
* complete the authentication.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Mark Heckler
|
||||
@@ -72,22 +76,28 @@ import java.util.Map;
|
||||
* @see ReactiveOAuth2UserService
|
||||
* @see OAuth2User
|
||||
* @see ReactiveOidcIdTokenDecoderFactory
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section
|
||||
* 4.1 Authorization Code Grant Flow</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token
|
||||
* Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token
|
||||
* Response</a>
|
||||
*/
|
||||
public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||
ReactiveAuthenticationManager {
|
||||
public class OidcAuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||
|
||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||
|
||||
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
||||
|
||||
private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce";
|
||||
|
||||
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private final ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService;
|
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||
private GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
|
||||
|
||||
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||
|
||||
@@ -104,53 +114,51 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
return Mono.defer(() -> {
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||
|
||||
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
// scope REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||
if (!authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationRequest().getScopes().contains("openid")) {
|
||||
// Section 3.1.2.1 Authentication Request -
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
// scope REQUIRED. OpenID Connect requests MUST contain the "openid" scope
|
||||
// value.
|
||||
if (!authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes()
|
||||
.contains("openid")) {
|
||||
// This is an OpenID Connect Authentication Request so return empty
|
||||
// and let OAuth2LoginReactiveAuthenticationManager handle it instead
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
|
||||
.getAuthorizationExchange().getAuthorizationRequest();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
|
||||
.getAuthorizationExchange().getAuthorizationResponse();
|
||||
|
||||
if (authorizationResponse.statusError()) {
|
||||
return Mono.error(new OAuth2AuthenticationException(
|
||||
authorizationResponse.getError(), authorizationResponse.getError().toString()));
|
||||
return Mono.error(new OAuth2AuthenticationException(authorizationResponse.getError(),
|
||||
authorizationResponse.getError().toString()));
|
||||
}
|
||||
|
||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||
return Mono.error(new OAuth2AuthenticationException(
|
||||
oauth2Error, oauth2Error.toString()));
|
||||
return Mono.error(new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()));
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange());
|
||||
|
||||
return this.accessTokenResponseClient.getTokenResponse(authzRequest)
|
||||
.flatMap(accessTokenResponse -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse))
|
||||
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
|
||||
.onErrorMap(JwtException.class, e -> {
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, e.getMessage(), null);
|
||||
return new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), e);
|
||||
return this.accessTokenResponseClient.getTokenResponse(authzRequest).flatMap(
|
||||
(accessTokenResponse) -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse))
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
(e) -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
|
||||
.onErrorMap(JwtException.class, (e) -> {
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, e.getMessage(),
|
||||
null);
|
||||
return new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(),
|
||||
e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ReactiveJwtDecoderFactory} used for {@link OidcIdToken} signature verification.
|
||||
* The factory returns a {@link ReactiveJwtDecoder} associated to the provided {@link ClientRegistration}.
|
||||
*
|
||||
* Sets the {@link ReactiveJwtDecoderFactory} used for {@link OidcIdToken} signature
|
||||
* verification. The factory returns a {@link ReactiveJwtDecoder} associated to the
|
||||
* provided {@link ClientRegistration}.
|
||||
* @param jwtDecoderFactory the {@link ReactiveJwtDecoderFactory} used for
|
||||
* {@link OidcIdToken} signature verification
|
||||
* @since 5.2
|
||||
* @param jwtDecoderFactory the {@link ReactiveJwtDecoderFactory} used for {@link OidcIdToken} signature verification
|
||||
*/
|
||||
public final void setJwtDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory) {
|
||||
Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
|
||||
@@ -158,66 +166,64 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OidcUser#getAuthorities()}
|
||||
* to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
*
|
||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping
|
||||
* {@link OidcUser#getAuthorities()} to a new set of authorities which will be
|
||||
* associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the
|
||||
* user's authorities
|
||||
* @since 5.4
|
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the user's authorities
|
||||
*/
|
||||
public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||
Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
|
||||
this.authoritiesMapper = authoritiesMapper;
|
||||
}
|
||||
|
||||
private Mono<OAuth2LoginAuthenticationToken> authenticationResult(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
private Mono<OAuth2LoginAuthenticationToken> authenticationResult(
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication,
|
||||
OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
|
||||
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
|
||||
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
|
||||
|
||||
if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(
|
||||
INVALID_ID_TOKEN_ERROR_CODE,
|
||||
"Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(),
|
||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
|
||||
"Missing (required) ID Token in Token Response for Client Registration: "
|
||||
+ clientRegistration.getRegistrationId(),
|
||||
null);
|
||||
return Mono.error(new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()));
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
return createOidcToken(clientRegistration, accessTokenResponse)
|
||||
.doOnNext(idToken -> validateNonce(authorizationCodeAuthentication, idToken))
|
||||
.map(idToken -> new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters))
|
||||
.doOnNext((idToken) -> validateNonce(authorizationCodeAuthentication, idToken))
|
||||
.map((idToken) -> new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters))
|
||||
.flatMap(this.userService::loadUser)
|
||||
.map(oauth2User -> {
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities =
|
||||
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
|
||||
|
||||
return new OAuth2LoginAuthenticationToken(
|
||||
authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange(),
|
||||
oauth2User,
|
||||
mappedAuthorities,
|
||||
accessToken,
|
||||
accessTokenResponse.getRefreshToken());
|
||||
.map((oauth2User) -> {
|
||||
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
|
||||
.mapAuthorities(oauth2User.getAuthorities());
|
||||
return new OAuth2LoginAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(),
|
||||
authorizationCodeAuthentication.getAuthorizationExchange(), oauth2User, mappedAuthorities,
|
||||
accessToken, accessTokenResponse.getRefreshToken());
|
||||
});
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private Mono<OidcIdToken> createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
private Mono<OidcIdToken> createOidcToken(ClientRegistration clientRegistration,
|
||||
OAuth2AccessTokenResponse accessTokenResponse) {
|
||||
ReactiveJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||
String rawIdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
||||
// @formatter:off
|
||||
return jwtDecoder.decode(rawIdToken)
|
||||
.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()));
|
||||
.map((jwt) ->
|
||||
new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims())
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static Mono<OidcIdToken> validateNonce(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OidcIdToken idToken) {
|
||||
String requestNonce = authorizationCodeAuthentication.getAuthorizationExchange()
|
||||
.getAuthorizationRequest().getAttribute(OidcParameterNames.NONCE);
|
||||
private static Mono<OidcIdToken> validateNonce(
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OidcIdToken idToken) {
|
||||
String requestNonce = authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest()
|
||||
.getAttribute(OidcParameterNames.NONCE);
|
||||
if (requestNonce != null) {
|
||||
String nonceHash;
|
||||
try {
|
||||
nonceHash = createHash(requestNonce);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
String nonceHash = getNonceHash(requestNonce);
|
||||
String nonceHashClaim = idToken.getNonce();
|
||||
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
@@ -228,9 +234,20 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||
return Mono.just(idToken);
|
||||
}
|
||||
|
||||
private static String getNonceHash(String requestNonce) {
|
||||
try {
|
||||
return createHash(requestNonce);
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static String createHash(String nonce) throws NoSuchAlgorithmException {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
@@ -47,13 +50,10 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
|
||||
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withSecretKey;
|
||||
|
||||
/**
|
||||
* A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder}
|
||||
* used for {@link OidcIdToken} signature verification.
|
||||
* The provided {@link JwtDecoder} is associated to a specific {@link ClientRegistration}.
|
||||
* A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder} used for
|
||||
* {@link OidcIdToken} signature verification. The provided {@link JwtDecoder} is
|
||||
* associated to a specific {@link ClientRegistration}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rafael Dominguez
|
||||
@@ -64,26 +64,36 @@ import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withSecre
|
||||
* @see OidcIdToken
|
||||
*/
|
||||
public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
|
||||
|
||||
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
||||
private static Map<JwsAlgorithm, String> jcaAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
|
||||
{
|
||||
put(MacAlgorithm.HS256, "HmacSHA256");
|
||||
put(MacAlgorithm.HS384, "HmacSHA384");
|
||||
put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
}
|
||||
|
||||
private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
|
||||
static {
|
||||
Map<JwsAlgorithm, String> mappings = new HashMap<>();
|
||||
mappings.put(MacAlgorithm.HS256, "HmacSHA256");
|
||||
mappings.put(MacAlgorithm.HS384, "HmacSHA384");
|
||||
mappings.put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
|
||||
};
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
|
||||
private static final ClaimTypeConverter DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
|
||||
createDefaultClaimTypeConverters());
|
||||
|
||||
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||
|
||||
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = new DefaultOidcIdTokenValidatorFactory();
|
||||
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = (
|
||||
clientRegistration) -> SignatureAlgorithm.RS256;
|
||||
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = (
|
||||
clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
*
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames claim name}
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values
|
||||
* for an {@link OidcIdToken}.
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames
|
||||
* claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
@@ -92,34 +102,34 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
||||
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return claimTypeConverters;
|
||||
Map<String, Converter<Object, ?>> converters = new HashMap<>();
|
||||
converters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
converters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
converters.put(IdTokenClaimNames.NONCE, stringConverter);
|
||||
converters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
converters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
converters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
converters.put(IdTokenClaimNames.AMR, collectionStringConverter);
|
||||
converters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
converters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
converters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return converters;
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return (source) -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor,
|
||||
targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), (key) -> {
|
||||
NimbusJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
|
||||
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration));
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(clientRegistration);
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter = this.claimTypeConverterFactory
|
||||
.apply(clientRegistration);
|
||||
if (claimTypeConverter != null) {
|
||||
jwtDecoder.setClaimSetConverter(claimTypeConverter);
|
||||
}
|
||||
@@ -134,68 +144,66 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
||||
//
|
||||
// 6. If the ID Token is received via direct communication between the Client
|
||||
// and the Token Endpoint (which it is in this flow),
|
||||
// the TLS server validation MAY be used to validate the issuer in place of checking the token signature.
|
||||
// The Client MUST validate the signature of all other ID Tokens according to JWS [JWS]
|
||||
// the TLS server validation MAY be used to validate the issuer in place of
|
||||
// checking the token signature.
|
||||
// The Client MUST validate the signature of all other ID Tokens according to
|
||||
// JWS [JWS]
|
||||
// using the algorithm specified in the JWT alg Header Parameter.
|
||||
// The Client MUST use the keys provided by the Issuer.
|
||||
//
|
||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client
|
||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by
|
||||
// the Client
|
||||
// in the id_token_signed_response_alg parameter during Registration.
|
||||
|
||||
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
||||
if (!StringUtils.hasText(jwkSetUri)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||
clientRegistration.getRegistrationId() +
|
||||
"'. Check to ensure you have configured the JwkSet URI.",
|
||||
null
|
||||
);
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '"
|
||||
+ clientRegistration.getRegistrationId()
|
||||
+ "'. Check to ensure you have configured the JwkSet URI.",
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
return withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
|
||||
} else if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
|
||||
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
|
||||
}
|
||||
if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
//
|
||||
// 8. If the JWT alg Header Parameter uses a MAC based algorithm such as HS256, HS384, or HS512,
|
||||
// 8. If the JWT alg Header Parameter uses a MAC based algorithm such as
|
||||
// HS256, HS384, or HS512,
|
||||
// the octets of the UTF-8 representation of the client_secret
|
||||
// corresponding to the client_id contained in the aud (audience) Claim
|
||||
// are used as the key to validate the signature.
|
||||
// For MAC based algorithms, the behavior is unspecified if the aud is multi-valued or
|
||||
// For MAC based algorithms, the behavior is unspecified if the aud is
|
||||
// multi-valued or
|
||||
// if an azp value is present that is different than the aud value.
|
||||
|
||||
String clientSecret = clientRegistration.getClientSecret();
|
||||
if (!StringUtils.hasText(clientSecret)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||
clientRegistration.getRegistrationId() +
|
||||
"'. Check to ensure you have configured the client secret.",
|
||||
null
|
||||
);
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '"
|
||||
+ clientRegistration.getRegistrationId()
|
||||
+ "'. Check to ensure you have configured the client secret.",
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(
|
||||
clientSecret.getBytes(StandardCharsets.UTF_8), jcaAlgorithmMappings.get(jwsAlgorithm));
|
||||
return withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
|
||||
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
|
||||
return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
|
||||
}
|
||||
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||
clientRegistration.getRegistrationId() +
|
||||
"'. Check to ensure you have configured a valid JWS Algorithm: '" +
|
||||
jwsAlgorithm + "'",
|
||||
null
|
||||
);
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '"
|
||||
+ clientRegistration.getRegistrationId()
|
||||
+ "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'",
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides an {@link OAuth2TokenValidator}, which is used by the {@link JwtDecoder}.
|
||||
* The default composes {@link JwtTimestampValidator} and {@link OidcIdTokenValidator}.
|
||||
*
|
||||
* @param jwtValidatorFactory the factory that provides an {@link OAuth2TokenValidator}
|
||||
* Sets the factory that provides an {@link OAuth2TokenValidator}, which is used by
|
||||
* the {@link JwtDecoder}. The default composes {@link JwtTimestampValidator} and
|
||||
* {@link OidcIdTokenValidator}.
|
||||
* @param jwtValidatorFactory the factory that provides an
|
||||
* {@link OAuth2TokenValidator}
|
||||
*/
|
||||
public void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
|
||||
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
|
||||
@@ -204,11 +212,11 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
||||
|
||||
/**
|
||||
* Sets the resolver that provides the expected {@link JwsAlgorithm JWS algorithm}
|
||||
* used for the signature or MAC on the {@link OidcIdToken ID Token}.
|
||||
* The default resolves to {@link SignatureAlgorithm#RS256 RS256} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @param jwsAlgorithmResolver the resolver that provides the expected {@link JwsAlgorithm JWS algorithm}
|
||||
* for a specific {@link ClientRegistration client}
|
||||
* used for the signature or MAC on the {@link OidcIdToken ID Token}. The default
|
||||
* resolves to {@link SignatureAlgorithm#RS256 RS256} for all
|
||||
* {@link ClientRegistration clients}.
|
||||
* @param jwsAlgorithmResolver the resolver that provides the expected
|
||||
* {@link JwsAlgorithm JWS algorithm} for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public void setJwsAlgorithmResolver(Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver) {
|
||||
Assert.notNull(jwsAlgorithmResolver, "jwsAlgorithmResolver cannot be null");
|
||||
@@ -216,14 +224,17 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of
|
||||
* claim values for an {@link OidcIdToken}. The default is {@link ClaimTypeConverter}
|
||||
* for all {@link ClientRegistration clients}.
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used
|
||||
* for type conversion of claim values for a specific {@link ClientRegistration
|
||||
* client}
|
||||
*/
|
||||
public void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
public void setClaimTypeConverterFactory(
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
@@ -26,30 +36,27 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2TokenValidator} responsible for
|
||||
* validating the claims in an {@link OidcIdToken ID Token}.
|
||||
* An {@link OAuth2TokenValidator} responsible for validating the claims in an
|
||||
* {@link OidcIdToken ID Token}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Joe Grandja
|
||||
* @since 5.1
|
||||
* @see OAuth2TokenValidator
|
||||
* @see Jwt
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation">ID Token Validation</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation">ID Token
|
||||
* Validation</a>
|
||||
*/
|
||||
public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private static final Duration DEFAULT_CLOCK_SKEW = Duration.ofSeconds(60);
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
private Duration clockSkew = DEFAULT_CLOCK_SKEW;
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
public OidcIdTokenValidator(ClientRegistration clientRegistration) {
|
||||
@@ -59,75 +66,68 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt idToken) {
|
||||
// 3.1.3.7 ID Token Validation
|
||||
// 3.1.3.7 ID Token Validation
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
|
||||
Map<String, Object> invalidClaims = validateRequiredClaims(idToken);
|
||||
if (!invalidClaims.isEmpty()) {
|
||||
return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
|
||||
}
|
||||
|
||||
// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
|
||||
// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained
|
||||
// during Discovery)
|
||||
// MUST exactly match the value of the iss (issuer) Claim.
|
||||
String metadataIssuer = this.clientRegistration.getProviderDetails().getIssuerUri();
|
||||
|
||||
if (metadataIssuer != null && !Objects.equals(metadataIssuer, idToken.getIssuer().toExternalForm())) {
|
||||
invalidClaims.put(IdTokenClaimNames.ISS, idToken.getIssuer());
|
||||
}
|
||||
|
||||
// 3. The Client MUST validate that the aud (audience) Claim contains its client_id value
|
||||
// 3. The Client MUST validate that the aud (audience) Claim contains its
|
||||
// client_id value
|
||||
// registered at the Issuer identified by the iss (issuer) Claim as an audience.
|
||||
// The aud (audience) Claim MAY contain an array with more than one element.
|
||||
// The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
|
||||
// The ID Token MUST be rejected if the ID Token does not list the Client as a
|
||||
// valid audience,
|
||||
// or if it contains additional audiences not trusted by the Client.
|
||||
if (!idToken.getAudience().contains(this.clientRegistration.getClientId())) {
|
||||
invalidClaims.put(IdTokenClaimNames.AUD, idToken.getAudience());
|
||||
}
|
||||
|
||||
// 4. If the ID Token contains multiple audiences,
|
||||
// the Client SHOULD verify that an azp Claim is present.
|
||||
String authorizedParty = idToken.getClaimAsString(IdTokenClaimNames.AZP);
|
||||
if (idToken.getAudience().size() > 1 && authorizedParty == null) {
|
||||
invalidClaims.put(IdTokenClaimNames.AZP, authorizedParty);
|
||||
}
|
||||
|
||||
// 5. If an azp (authorized party) Claim is present,
|
||||
// the Client SHOULD verify that its client_id is the Claim Value.
|
||||
if (authorizedParty != null && !authorizedParty.equals(this.clientRegistration.getClientId())) {
|
||||
invalidClaims.put(IdTokenClaimNames.AZP, authorizedParty);
|
||||
}
|
||||
|
||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client
|
||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the
|
||||
// Client
|
||||
// in the id_token_signed_response_alg parameter during Registration.
|
||||
// TODO Depends on gh-4413
|
||||
|
||||
// 9. The current time MUST be before the time represented by the exp Claim.
|
||||
Instant now = Instant.now(this.clock);
|
||||
if (now.minus(this.clockSkew).isAfter(idToken.getExpiresAt())) {
|
||||
invalidClaims.put(IdTokenClaimNames.EXP, idToken.getExpiresAt());
|
||||
}
|
||||
|
||||
// 10. The iat Claim can be used to reject tokens that were issued too far away from the current time,
|
||||
// 10. The iat Claim can be used to reject tokens that were issued too far away
|
||||
// from the current time,
|
||||
// limiting the amount of time that nonces need to be stored to prevent attacks.
|
||||
// The acceptable range is Client specific.
|
||||
if (now.plus(this.clockSkew).isBefore(idToken.getIssuedAt())) {
|
||||
invalidClaims.put(IdTokenClaimNames.IAT, idToken.getIssuedAt());
|
||||
}
|
||||
|
||||
if (!invalidClaims.isEmpty()) {
|
||||
return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
|
||||
}
|
||||
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew. The default is 60 seconds.
|
||||
* The clock skew is used when validating the {@link JwtClaimNames#EXP exp}
|
||||
* and {@link JwtClaimNames#IAT iat} claims.
|
||||
*
|
||||
* @since 5.2
|
||||
* Sets the maximum acceptable clock skew. The default is 60 seconds. The clock skew
|
||||
* is used when validating the {@link JwtClaimNames#EXP exp} and
|
||||
* {@link JwtClaimNames#IAT iat} claims.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
Assert.notNull(clockSkew, "clockSkew cannot be null");
|
||||
@@ -136,12 +136,10 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)}
|
||||
* when validating the {@link JwtClaimNames#EXP exp}
|
||||
* and {@link JwtClaimNames#IAT iat} claims.
|
||||
*
|
||||
* @since 5.3
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when validating the
|
||||
* {@link JwtClaimNames#EXP exp} and {@link JwtClaimNames#IAT iat} claims.
|
||||
* @param clock the clock
|
||||
* @since 5.3
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
@@ -149,14 +147,12 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
}
|
||||
|
||||
private static OAuth2Error invalidIdToken(Map<String, Object> invalidClaims) {
|
||||
return new OAuth2Error("invalid_id_token",
|
||||
"The ID Token contains invalid claims: " + invalidClaims,
|
||||
return new OAuth2Error("invalid_id_token", "The ID Token contains invalid claims: " + invalidClaims,
|
||||
"https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation");
|
||||
}
|
||||
|
||||
private static Map<String, Object> validateRequiredClaims(Jwt idToken) {
|
||||
Map<String, Object> requiredClaims = new HashMap<>();
|
||||
|
||||
URL issuer = idToken.getIssuer();
|
||||
if (issuer == null) {
|
||||
requiredClaims.put(IdTokenClaimNames.ISS, issuer);
|
||||
@@ -177,7 +173,7 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
if (issuedAt == null) {
|
||||
requiredClaims.put(IdTokenClaimNames.IAT, issuedAt);
|
||||
}
|
||||
|
||||
return requiredClaims;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
@@ -47,13 +50,10 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.withJwkSetUri;
|
||||
import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.withSecretKey;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveJwtDecoderFactory factory} that provides a {@link ReactiveJwtDecoder}
|
||||
* used for {@link OidcIdToken} signature verification.
|
||||
* The provided {@link ReactiveJwtDecoder} is associated to a specific {@link ClientRegistration}.
|
||||
* used for {@link OidcIdToken} signature verification. The provided
|
||||
* {@link ReactiveJwtDecoder} is associated to a specific {@link ClientRegistration}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rafael Dominguez
|
||||
@@ -64,26 +64,36 @@ import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.w
|
||||
* @see OidcIdToken
|
||||
*/
|
||||
public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
||||
|
||||
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
||||
private static Map<JwsAlgorithm, String> jcaAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
|
||||
{
|
||||
put(MacAlgorithm.HS256, "HmacSHA256");
|
||||
put(MacAlgorithm.HS384, "HmacSHA384");
|
||||
put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
}
|
||||
};
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
|
||||
private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
|
||||
static {
|
||||
Map<JwsAlgorithm, String> mappings = new HashMap<JwsAlgorithm, String>();
|
||||
mappings.put(MacAlgorithm.HS256, "HmacSHA256");
|
||||
mappings.put(MacAlgorithm.HS384, "HmacSHA384");
|
||||
mappings.put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
|
||||
}
|
||||
|
||||
private static final ClaimTypeConverter DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
|
||||
createDefaultClaimTypeConverters());
|
||||
|
||||
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||
|
||||
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = new DefaultOidcIdTokenValidatorFactory();
|
||||
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = (
|
||||
clientRegistration) -> SignatureAlgorithm.RS256;
|
||||
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = (
|
||||
clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
*
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames claim name}
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values
|
||||
* for an {@link OidcIdToken}.
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames
|
||||
* claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
@@ -92,34 +102,34 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
||||
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return claimTypeConverters;
|
||||
Map<String, Converter<Object, ?>> converters = new HashMap<>();
|
||||
converters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
converters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
converters.put(IdTokenClaimNames.NONCE, stringConverter);
|
||||
converters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
converters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
converters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
converters.put(IdTokenClaimNames.AMR, collectionStringConverter);
|
||||
converters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
converters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
converters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return converters;
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
return (source) -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor,
|
||||
targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), (key) -> {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
|
||||
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration));
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(clientRegistration);
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter = this.claimTypeConverterFactory
|
||||
.apply(clientRegistration);
|
||||
if (claimTypeConverter != null) {
|
||||
jwtDecoder.setClaimSetConverter(claimTypeConverter);
|
||||
}
|
||||
@@ -134,68 +144,68 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
||||
//
|
||||
// 6. If the ID Token is received via direct communication between the Client
|
||||
// and the Token Endpoint (which it is in this flow),
|
||||
// the TLS server validation MAY be used to validate the issuer in place of checking the token signature.
|
||||
// The Client MUST validate the signature of all other ID Tokens according to JWS [JWS]
|
||||
// the TLS server validation MAY be used to validate the issuer in place of
|
||||
// checking the token signature.
|
||||
// The Client MUST validate the signature of all other ID Tokens according to
|
||||
// JWS [JWS]
|
||||
// using the algorithm specified in the JWT alg Header Parameter.
|
||||
// The Client MUST use the keys provided by the Issuer.
|
||||
//
|
||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client
|
||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by
|
||||
// the Client
|
||||
// in the id_token_signed_response_alg parameter during Registration.
|
||||
|
||||
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
||||
if (!StringUtils.hasText(jwkSetUri)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||
clientRegistration.getRegistrationId() +
|
||||
"'. Check to ensure you have configured the JwkSet URI.",
|
||||
null
|
||||
);
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '"
|
||||
+ clientRegistration.getRegistrationId()
|
||||
+ "'. Check to ensure you have configured the JwkSet URI.",
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
return withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
|
||||
} else if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
|
||||
return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm)
|
||||
.build();
|
||||
}
|
||||
if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||
//
|
||||
// 8. If the JWT alg Header Parameter uses a MAC based algorithm such as HS256, HS384, or HS512,
|
||||
// 8. If the JWT alg Header Parameter uses a MAC based algorithm such as
|
||||
// HS256, HS384, or HS512,
|
||||
// the octets of the UTF-8 representation of the client_secret
|
||||
// corresponding to the client_id contained in the aud (audience) Claim
|
||||
// are used as the key to validate the signature.
|
||||
// For MAC based algorithms, the behavior is unspecified if the aud is multi-valued or
|
||||
// For MAC based algorithms, the behavior is unspecified if the aud is
|
||||
// multi-valued or
|
||||
// if an azp value is present that is different than the aud value.
|
||||
|
||||
String clientSecret = clientRegistration.getClientSecret();
|
||||
if (!StringUtils.hasText(clientSecret)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||
clientRegistration.getRegistrationId() +
|
||||
"'. Check to ensure you have configured the client secret.",
|
||||
null
|
||||
);
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '"
|
||||
+ clientRegistration.getRegistrationId()
|
||||
+ "'. Check to ensure you have configured the client secret.",
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(
|
||||
clientSecret.getBytes(StandardCharsets.UTF_8), jcaAlgorithmMappings.get(jwsAlgorithm));
|
||||
return withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
|
||||
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
|
||||
return NimbusReactiveJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm)
|
||||
.build();
|
||||
}
|
||||
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||
clientRegistration.getRegistrationId() +
|
||||
"'. Check to ensure you have configured a valid JWS Algorithm: '" +
|
||||
jwsAlgorithm + "'",
|
||||
null
|
||||
);
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||
"Failed to find a Signature Verifier for Client Registration: '"
|
||||
+ clientRegistration.getRegistrationId()
|
||||
+ "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'",
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides an {@link OAuth2TokenValidator}, which is used by the {@link ReactiveJwtDecoder}.
|
||||
* The default composes {@link JwtTimestampValidator} and {@link OidcIdTokenValidator}.
|
||||
*
|
||||
* @param jwtValidatorFactory the factory that provides an {@link OAuth2TokenValidator}
|
||||
* Sets the factory that provides an {@link OAuth2TokenValidator}, which is used by
|
||||
* the {@link ReactiveJwtDecoder}. The default composes {@link JwtTimestampValidator}
|
||||
* and {@link OidcIdTokenValidator}.
|
||||
* @param jwtValidatorFactory the factory that provides an
|
||||
* {@link OAuth2TokenValidator}
|
||||
*/
|
||||
public void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
|
||||
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
|
||||
@@ -204,11 +214,11 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
||||
|
||||
/**
|
||||
* Sets the resolver that provides the expected {@link JwsAlgorithm JWS algorithm}
|
||||
* used for the signature or MAC on the {@link OidcIdToken ID Token}.
|
||||
* The default resolves to {@link SignatureAlgorithm#RS256 RS256} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @param jwsAlgorithmResolver the resolver that provides the expected {@link JwsAlgorithm JWS algorithm}
|
||||
* for a specific {@link ClientRegistration client}
|
||||
* used for the signature or MAC on the {@link OidcIdToken ID Token}. The default
|
||||
* resolves to {@link SignatureAlgorithm#RS256 RS256} for all
|
||||
* {@link ClientRegistration clients}.
|
||||
* @param jwsAlgorithmResolver the resolver that provides the expected
|
||||
* {@link JwsAlgorithm JWS algorithm} for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public void setJwsAlgorithmResolver(Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver) {
|
||||
Assert.notNull(jwsAlgorithmResolver, "jwsAlgorithmResolver cannot be null");
|
||||
@@ -216,14 +226,17 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of
|
||||
* claim values for an {@link OidcIdToken}. The default is {@link ClaimTypeConverter}
|
||||
* for all {@link ClientRegistration clients}.
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used
|
||||
* for type conversion of claim values for a specific {@link ClientRegistration
|
||||
* client}
|
||||
*/
|
||||
public void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
public void setClaimTypeConverterFactory(
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support classes and interfaces for authenticating and authorizing a client
|
||||
* with an OpenID Connect 1.0 Provider using a specific authorization grant flow.
|
||||
* Support classes and interfaces for authenticating and authorizing a client with an
|
||||
* OpenID Connect 1.0 Provider using a specific authorization grant flow.
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
@@ -13,8 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -36,17 +46,10 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link ReactiveOAuth2UserService} that supports OpenID Connect 1.0 Provider's.
|
||||
* An implementation of an {@link ReactiveOAuth2UserService} that supports OpenID Connect
|
||||
* 1.0 Provider's.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
@@ -56,29 +59,28 @@ import java.util.function.Function;
|
||||
* @see DefaultOidcUser
|
||||
* @see OidcUserInfo
|
||||
*/
|
||||
public class OidcReactiveOAuth2UserService implements
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
|
||||
public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
|
||||
|
||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
||||
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
|
||||
createDefaultClaimTypeConverters());
|
||||
|
||||
private ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new DefaultReactiveOAuth2UserService();
|
||||
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = (
|
||||
clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values
|
||||
* for an {@link OidcUserInfo}.
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link StandardClaimNames
|
||||
* claim name}
|
||||
* @since 5.2
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link StandardClaimNames claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
@@ -88,57 +90,63 @@ public class OidcReactiveOAuth2UserService implements
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
return (source) -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor,
|
||||
targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
Assert.notNull(userRequest, "userRequest cannot be null");
|
||||
// @formatter:off
|
||||
return getUserInfo(userRequest)
|
||||
.map(userInfo -> new OidcUserAuthority(userRequest.getIdToken(), userInfo))
|
||||
.defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null))
|
||||
.map(authority -> {
|
||||
OidcUserInfo userInfo = authority.getUserInfo();
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
authorities.add(authority);
|
||||
OAuth2AccessToken token = userRequest.getAccessToken();
|
||||
for (String scope : token.getScopes()) {
|
||||
authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
|
||||
}
|
||||
String userNameAttributeName = userRequest.getClientRegistration()
|
||||
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
|
||||
} else {
|
||||
.map((userInfo) ->
|
||||
new OidcUserAuthority(userRequest.getIdToken(), userInfo)
|
||||
)
|
||||
.defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null))
|
||||
.map((authority) -> {
|
||||
OidcUserInfo userInfo = authority.getUserInfo();
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
authorities.add(authority);
|
||||
OAuth2AccessToken token = userRequest.getAccessToken();
|
||||
for (String scope : token.getScopes()) {
|
||||
authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
|
||||
}
|
||||
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
|
||||
.getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo,
|
||||
userNameAttributeName);
|
||||
}
|
||||
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
|
||||
}
|
||||
});
|
||||
});
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private Mono<OidcUserInfo> getUserInfo(OidcUserRequest userRequest) {
|
||||
if (!OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return this.oauth2UserService.loadUser(userRequest)
|
||||
.map(OAuth2User::getAttributes)
|
||||
.map(claims -> convertClaims(claims, userRequest.getClientRegistration()))
|
||||
.map(OidcUserInfo::new)
|
||||
.doOnNext(userInfo -> {
|
||||
String subject = userInfo.getSubject();
|
||||
if (subject == null || !subject.equals(userRequest.getIdToken().getSubject())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
});
|
||||
// @formatter:off
|
||||
return this.oauth2UserService
|
||||
.loadUser(userRequest)
|
||||
.map(OAuth2User::getAttributes)
|
||||
.map((claims) -> convertClaims(claims, userRequest.getClientRegistration()))
|
||||
.map(OidcUserInfo::new)
|
||||
.doOnNext((userInfo) -> {
|
||||
String subject = userInfo.getSubject();
|
||||
if (subject == null || !subject.equals(userRequest.getIdToken().getSubject())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
});
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private Map<String, Object> convertClaims(Map<String, Object> claims, ClientRegistration clientRegistration) {
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(clientRegistration);
|
||||
return claimTypeConverter != null ?
|
||||
claimTypeConverter.convert(claims) :
|
||||
DEFAULT_CLAIM_TYPE_CONVERTER.convert(claims);
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter = this.claimTypeConverterFactory
|
||||
.apply(clientRegistration);
|
||||
return (claimTypeConverter != null) ? claimTypeConverter.convert(claims)
|
||||
: DEFAULT_CLAIM_TYPE_CONVERTER.convert(claims);
|
||||
}
|
||||
|
||||
public void setOauth2UserService(ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService) {
|
||||
@@ -147,15 +155,18 @@ public class OidcReactiveOAuth2UserService implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of
|
||||
* claim values for an {@link OidcUserInfo}. The default is {@link ClaimTypeConverter}
|
||||
* for all {@link ClientRegistration clients}.
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used
|
||||
* for type conversion of claim values for a specific {@link ClientRegistration
|
||||
* client}
|
||||
* @since 5.2
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
public final void setClaimTypeConverterFactory(
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,20 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a request the {@link OidcUserService} uses
|
||||
* when initiating a request to the UserInfo Endpoint.
|
||||
* Represents a request the {@link OidcUserService} uses when initiating a request to the
|
||||
* UserInfo Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -36,33 +37,29 @@ import java.util.Map;
|
||||
* @see OidcUserService
|
||||
*/
|
||||
public class OidcUserRequest extends OAuth2UserRequest {
|
||||
|
||||
private final OidcIdToken idToken;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OidcUserRequest} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistration the client registration
|
||||
* @param accessToken the access token credential
|
||||
* @param idToken the ID Token
|
||||
*/
|
||||
public OidcUserRequest(ClientRegistration clientRegistration,
|
||||
OAuth2AccessToken accessToken, OidcIdToken idToken) {
|
||||
|
||||
public OidcUserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken, OidcIdToken idToken) {
|
||||
this(clientRegistration, accessToken, idToken, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code OidcUserRequest} using the provided parameters.
|
||||
*
|
||||
* @since 5.1
|
||||
* @param clientRegistration the client registration
|
||||
* @param accessToken the access token credential
|
||||
* @param idToken the ID Token
|
||||
* @param additionalParameters the additional parameters, may be empty
|
||||
* @since 5.1
|
||||
*/
|
||||
public OidcUserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken,
|
||||
OidcIdToken idToken, Map<String, Object> additionalParameters) {
|
||||
|
||||
public OidcUserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken, OidcIdToken idToken,
|
||||
Map<String, Object> additionalParameters) {
|
||||
super(clientRegistration, accessToken, additionalParameters);
|
||||
Assert.notNull(idToken, "idToken cannot be null");
|
||||
this.idToken = idToken;
|
||||
@@ -70,10 +67,10 @@ public class OidcUserRequest extends OAuth2UserRequest {
|
||||
|
||||
/**
|
||||
* Returns the {@link OidcIdToken ID Token} containing claims about the user.
|
||||
*
|
||||
* @return the {@link OidcIdToken} containing claims about the user.
|
||||
*/
|
||||
public OidcIdToken getIdToken() {
|
||||
return this.idToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,13 +30,14 @@ import org.springframework.util.StringUtils;
|
||||
final class OidcUserRequestUtils {
|
||||
|
||||
/**
|
||||
* Determines if an {@link OidcUserRequest} should attempt to retrieve the user info endpoint. Will return true if
|
||||
* all of the following are true:
|
||||
* Determines if an {@link OidcUserRequest} should attempt to retrieve the user info
|
||||
* endpoint. Will return true if all of the following are true:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The user info endpoint is defined on the ClientRegistration</li>
|
||||
* <li>The Client Registration uses the {@link AuthorizationGrantType#AUTHORIZATION_CODE} and scopes in the
|
||||
* access token are defined in the {@link ClientRegistration}</li>
|
||||
* <li>The user info endpoint is defined on the ClientRegistration</li>
|
||||
* <li>The Client Registration uses the
|
||||
* {@link AuthorizationGrantType#AUTHORIZATION_CODE} and scopes in the access token
|
||||
* are defined in the {@link ClientRegistration}</li>
|
||||
* </ul>
|
||||
* @param userRequest
|
||||
* @return
|
||||
@@ -44,27 +45,28 @@ final class OidcUserRequestUtils {
|
||||
static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
|
||||
// Auto-disabled if UserInfo Endpoint URI is not provided
|
||||
ClientRegistration clientRegistration = userRequest.getClientRegistration();
|
||||
if (StringUtils.isEmpty(clientRegistration.getProviderDetails()
|
||||
.getUserInfoEndpoint().getUri())) {
|
||||
|
||||
if (StringUtils.isEmpty(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The Claims requested by the profile, email, address, and phone scope values
|
||||
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
|
||||
// when a response_type value is used that results in an Access Token being issued.
|
||||
// However, when no Access Token is issued, which is the case for the response_type=id_token,
|
||||
// when a response_type value is used that results in an Access Token being
|
||||
// issued.
|
||||
// However, when no Access Token is issued, which is the case for the
|
||||
// response_type=id_token,
|
||||
// the resulting Claims are returned in the ID Token.
|
||||
// The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued.
|
||||
// The Authorization Code Grant Flow, which is response_type=code, results in an
|
||||
// Access Token being issued.
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
|
||||
// Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s)
|
||||
return CollectionUtils
|
||||
.containsAny(userRequest.getAccessToken().getScopes(), userRequest.getClientRegistration().getScopes());
|
||||
// Return true if there is at least one match between the authorized scope(s)
|
||||
// and UserInfo scope(s)
|
||||
return CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(),
|
||||
userRequest.getClientRegistration().getScopes());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private OidcUserRequestUtils() {}
|
||||
private OidcUserRequestUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
@@ -50,7 +52,8 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0 Provider's.
|
||||
* An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0
|
||||
* Provider's.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.0
|
||||
@@ -61,25 +64,30 @@ import org.springframework.util.StringUtils;
|
||||
* @see OidcUserInfo
|
||||
*/
|
||||
public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||
|
||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
private Set<String> accessibleScopes = new HashSet<>(Arrays.asList(
|
||||
OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
|
||||
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
|
||||
createDefaultClaimTypeConverters());
|
||||
|
||||
private Set<String> accessibleScopes = new HashSet<>(
|
||||
Arrays.asList(OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
|
||||
|
||||
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new DefaultOAuth2UserService();
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = (
|
||||
clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values
|
||||
* for an {@link OidcUserInfo}.
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link StandardClaimNames
|
||||
* claim name}
|
||||
* @since 5.2
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link StandardClaimNames claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
@@ -88,8 +96,9 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return (source) -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor,
|
||||
targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,26 +107,16 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
OidcUserInfo userInfo = null;
|
||||
if (this.shouldRetrieveUserInfo(userRequest)) {
|
||||
OAuth2User oauth2User = this.oauth2UserService.loadUser(userRequest);
|
||||
|
||||
Map<String, Object> claims;
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(userRequest.getClientRegistration());
|
||||
if (claimTypeConverter != null) {
|
||||
claims = claimTypeConverter.convert(oauth2User.getAttributes());
|
||||
} else {
|
||||
claims = DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes());
|
||||
}
|
||||
Map<String, Object> claims = getClaims(userRequest, oauth2User);
|
||||
userInfo = new OidcUserInfo(claims);
|
||||
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
|
||||
|
||||
// 1) The sub (subject) Claim MUST always be returned in the UserInfo Response
|
||||
if (userInfo.getSubject() == null) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
// 2) Due to the possibility of token substitution attacks (see Section 16.11),
|
||||
// 2) Due to the possibility of token substitution attacks (see Section
|
||||
// 16.11),
|
||||
// the UserInfo Response is not guaranteed to be about the End-User
|
||||
// identified by the sub (subject) element of the ID Token.
|
||||
// The sub Claim in the UserInfo Response MUST be verified to exactly match
|
||||
@@ -128,57 +127,63 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
|
||||
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));
|
||||
OAuth2AccessToken token = userRequest.getAccessToken();
|
||||
for (String authority : token.getScopes()) {
|
||||
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
|
||||
}
|
||||
return getUser(userRequest, userInfo, authorities);
|
||||
}
|
||||
|
||||
OidcUser user;
|
||||
|
||||
String userNameAttributeName = userRequest.getClientRegistration()
|
||||
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
|
||||
} else {
|
||||
user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
|
||||
private Map<String, Object> getClaims(OidcUserRequest userRequest, OAuth2User oauth2User) {
|
||||
Converter<Map<String, Object>, Map<String, Object>> converter = this.claimTypeConverterFactory
|
||||
.apply(userRequest.getClientRegistration());
|
||||
if (converter != null) {
|
||||
return converter.convert(oauth2User.getAttributes());
|
||||
}
|
||||
return DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes());
|
||||
}
|
||||
|
||||
return user;
|
||||
private OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo, Set<GrantedAuthority> authorities) {
|
||||
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
|
||||
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (StringUtils.hasText(userNameAttributeName)) {
|
||||
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
|
||||
}
|
||||
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
|
||||
}
|
||||
|
||||
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
|
||||
// Auto-disabled if UserInfo Endpoint URI is not provided
|
||||
if (StringUtils.isEmpty(userRequest.getClientRegistration().getProviderDetails()
|
||||
.getUserInfoEndpoint().getUri())) {
|
||||
|
||||
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
|
||||
if (StringUtils.isEmpty(providerDetails.getUserInfoEndpoint().getUri())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The Claims requested by the profile, email, address, and phone scope values
|
||||
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
|
||||
// when a response_type value is used that results in an Access Token being issued.
|
||||
// However, when no Access Token is issued, which is the case for the response_type=id_token,
|
||||
// when a response_type value is used that results in an Access Token being
|
||||
// issued.
|
||||
// However, when no Access Token is issued, which is the case for the
|
||||
// response_type=id_token,
|
||||
// the resulting Claims are returned in the ID Token.
|
||||
// The Authorization Code Grant Flow, which is response_type=code, results in an Access Token being issued.
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
|
||||
userRequest.getClientRegistration().getAuthorizationGrantType())) {
|
||||
|
||||
// Return true if there is at least one match between the authorized scope(s) and accessible scope(s)
|
||||
return this.accessibleScopes.isEmpty() ||
|
||||
CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes);
|
||||
// The Authorization Code Grant Flow, which is response_type=code, results in an
|
||||
// Access Token being issued.
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE
|
||||
.equals(userRequest.getClientRegistration().getAuthorizationGrantType())) {
|
||||
// Return true if there is at least one match between the authorized scope(s)
|
||||
// and accessible scope(s)
|
||||
return this.accessibleScopes.isEmpty()
|
||||
|| CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2UserService} used when requesting the user info resource.
|
||||
*
|
||||
* @param oauth2UserService the {@link OAuth2UserService} used when requesting the
|
||||
* user info resource.
|
||||
* @since 5.1
|
||||
* @param oauth2UserService the {@link OAuth2UserService} used when requesting the user info resource.
|
||||
*/
|
||||
public final void setOauth2UserService(OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService) {
|
||||
Assert.notNull(oauth2UserService, "oauth2UserService cannot be null");
|
||||
@@ -186,30 +191,34 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of
|
||||
* claim values for an {@link OidcUserInfo}. The default is {@link ClaimTypeConverter}
|
||||
* for all {@link ClientRegistration clients}.
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used
|
||||
* for type conversion of claim values for a specific {@link ClientRegistration
|
||||
* client}
|
||||
* @since 5.2
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
public final void setClaimTypeConverterFactory(
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scope(s) that allow access to the user info resource.
|
||||
* The default is {@link OidcScopes#PROFILE profile}, {@link OidcScopes#EMAIL email}, {@link OidcScopes#ADDRESS address} and {@link OidcScopes#PHONE phone}.
|
||||
* The scope(s) are checked against the "granted" scope(s) associated to the {@link OidcUserRequest#getAccessToken() access token}
|
||||
* to determine if the user info resource is accessible or not.
|
||||
* If there is at least one match, the user info resource will be requested, otherwise it will not.
|
||||
*
|
||||
* @since 5.2
|
||||
* Sets the scope(s) that allow access to the user info resource. The default is
|
||||
* {@link OidcScopes#PROFILE profile}, {@link OidcScopes#EMAIL email},
|
||||
* {@link OidcScopes#ADDRESS address} and {@link OidcScopes#PHONE phone}. The scope(s)
|
||||
* are checked against the "granted" scope(s) associated to the
|
||||
* {@link OidcUserRequest#getAccessToken() access token} to determine if the user info
|
||||
* resource is accessible or not. If there is at least one match, the user info
|
||||
* resource will be requested, otherwise it will not.
|
||||
* @param accessibleScopes the scope(s) that allow access to the user info resource
|
||||
* @since 5.2
|
||||
*/
|
||||
public final void setAccessibleScopes(Set<String> accessibleScopes) {
|
||||
Assert.notNull(accessibleScopes, "accessibleScopes cannot be null");
|
||||
this.accessibleScopes = accessibleScopes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user