All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.security.oauth2.client.registration.ClientRegistration Maven / Gradle / Ivy

There is a newer version: 6.3.3
Show newest version
/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.oauth2.client.registration;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.log.LogMessage;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * A representation of a client registration with an OAuth 2.0 or OpenID Connect 1.0
 * Provider.
 *
 * @author Joe Grandja
 * @author Michael Sosa
 * @since 5.0
 * @see Section 2
 * Client Registration
 */
public final class ClientRegistration implements Serializable {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private String registrationId;

	private String clientId;

	private String clientSecret;

	private ClientAuthenticationMethod clientAuthenticationMethod;

	private AuthorizationGrantType authorizationGrantType;

	private String redirectUri;

	private Set scopes = Collections.emptySet();

	private ProviderDetails providerDetails = new ProviderDetails();

	private String clientName;

	private ClientRegistration() {
	}

	/**
	 * Returns the identifier for the registration.
	 * @return the identifier for the registration
	 */
	public String getRegistrationId() {
		return this.registrationId;
	}

	/**
	 * Returns the client identifier.
	 * @return the client identifier
	 */
	public String getClientId() {
		return this.clientId;
	}

	/**
	 * Returns the client secret.
	 * @return the client secret
	 */
	public String getClientSecret() {
		return this.clientSecret;
	}

	/**
	 * Returns the {@link ClientAuthenticationMethod authentication method} used when
	 * authenticating the client with the authorization server.
	 * @return the {@link ClientAuthenticationMethod}
	 */
	public ClientAuthenticationMethod getClientAuthenticationMethod() {
		return this.clientAuthenticationMethod;
	}

	/**
	 * Returns the {@link AuthorizationGrantType authorization grant type} used for the
	 * client.
	 * @return the {@link AuthorizationGrantType}
	 */
	public AuthorizationGrantType getAuthorizationGrantType() {
		return this.authorizationGrantType;
	}

	/**
	 * Returns the uri (or uri template) for the redirection endpoint.
	 *
	 * 
* The supported uri template variables are: {baseScheme}, {baseHost}, {basePort}, * {basePath} and {registrationId}. * *
* NOTE: {baseUrl} is also supported, which is the same as * {baseScheme}://{baseHost}{basePort}{basePath}. * *
* Configuring uri template variables is especially useful when the client is running * behind a Proxy Server. This ensures that the X-Forwarded-* headers are used when * expanding the redirect-uri. * @return the uri (or uri template) for the redirection endpoint * @since 5.4 */ public String getRedirectUri() { return this.redirectUri; } /** * Returns the scope(s) used for the client. * @return the {@code Set} of scope(s) */ public Set getScopes() { return this.scopes; } /** * Returns the details of the provider. * @return the {@link ProviderDetails} */ public ProviderDetails getProviderDetails() { return this.providerDetails; } /** * Returns the logical name of the client or registration. * @return the client or registration name */ public String getClientName() { return this.clientName; } @Override public String toString() { // @formatter:off return "ClientRegistration{" + "registrationId='" + this.registrationId + '\'' + ", clientId='" + this.clientId + '\'' + ", clientSecret='" + this.clientSecret + '\'' + ", clientAuthenticationMethod=" + this.clientAuthenticationMethod + ", authorizationGrantType=" + this.authorizationGrantType + ", redirectUri='" + this.redirectUri + '\'' + ", scopes=" + this.scopes + ", providerDetails=" + this.providerDetails + ", clientName='" + this.clientName + '\'' + '}'; // @formatter:on } /** * Returns a new {@link Builder}, initialized with the provided registration * identifier. * @param registrationId the identifier for the registration * @return the {@link Builder} */ public static Builder withRegistrationId(String registrationId) { Assert.hasText(registrationId, "registrationId cannot be empty"); return new Builder(registrationId); } /** * Returns a new {@link Builder}, initialized with the provided * {@link ClientRegistration}. * @param clientRegistration the {@link ClientRegistration} to copy from * @return the {@link Builder} */ public static Builder withClientRegistration(ClientRegistration clientRegistration) { Assert.notNull(clientRegistration, "clientRegistration cannot be null"); return new Builder(clientRegistration); } /** * Details of the Provider. */ public class ProviderDetails implements Serializable { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private String authorizationUri; private String tokenUri; private UserInfoEndpoint userInfoEndpoint = new UserInfoEndpoint(); private String jwkSetUri; private String issuerUri; private Map configurationMetadata = Collections.emptyMap(); ProviderDetails() { } /** * Returns the uri for the authorization endpoint. * @return the uri for the authorization endpoint */ public String getAuthorizationUri() { return this.authorizationUri; } /** * Returns the uri for the token endpoint. * @return the uri for the token endpoint */ public String getTokenUri() { return this.tokenUri; } /** * Returns the details of the {@link UserInfoEndpoint UserInfo Endpoint}. * @return the {@link UserInfoEndpoint} */ public UserInfoEndpoint getUserInfoEndpoint() { return this.userInfoEndpoint; } /** * Returns the uri for the JSON Web Key (JWK) Set endpoint. * @return the uri for the JSON Web Key (JWK) Set endpoint */ public String getJwkSetUri() { return this.jwkSetUri; } /** * Returns the issuer identifier uri for the OpenID Connect 1.0 provider or the * OAuth 2.0 Authorization Server. * @return the issuer identifier uri for the OpenID Connect 1.0 provider or the * OAuth 2.0 Authorization Server * @since 5.4 */ public String getIssuerUri() { return this.issuerUri; } /** * Returns a {@code Map} of the metadata describing the provider's configuration. * @return a {@code Map} of the metadata describing the provider's configuration * @since 5.1 */ public Map getConfigurationMetadata() { return this.configurationMetadata; } /** * Details of the UserInfo Endpoint. */ public class UserInfoEndpoint implements Serializable { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private String uri; private AuthenticationMethod authenticationMethod = AuthenticationMethod.HEADER; private String userNameAttributeName; UserInfoEndpoint() { } /** * Returns the uri for the user info endpoint. * @return the uri for the user info endpoint */ public String getUri() { return this.uri; } /** * Returns the authentication method for the user info endpoint. * @return the {@link AuthenticationMethod} for the user info endpoint. * @since 5.1 */ public AuthenticationMethod getAuthenticationMethod() { return this.authenticationMethod; } /** * Returns the attribute name used to access the user's name from the user * info response. * @return the attribute name used to access the user's name from the user * info response */ public String getUserNameAttributeName() { return this.userNameAttributeName; } } } /** * A builder for {@link ClientRegistration}. */ public static final class Builder implements Serializable { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private static final Log logger = LogFactory.getLog(Builder.class); private static final List AUTHORIZATION_GRANT_TYPES = Arrays.asList( AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS, AuthorizationGrantType.REFRESH_TOKEN, AuthorizationGrantType.PASSWORD); private String registrationId; private String clientId; private String clientSecret; private ClientAuthenticationMethod clientAuthenticationMethod; private AuthorizationGrantType authorizationGrantType; private String redirectUri; private Set scopes; private String authorizationUri; private String tokenUri; private String userInfoUri; private AuthenticationMethod userInfoAuthenticationMethod = AuthenticationMethod.HEADER; private String userNameAttributeName; private String jwkSetUri; private String issuerUri; private Map configurationMetadata = Collections.emptyMap(); private String clientName; private Builder(String registrationId) { this.registrationId = registrationId; } private Builder(ClientRegistration clientRegistration) { this.registrationId = clientRegistration.registrationId; this.clientId = clientRegistration.clientId; this.clientSecret = clientRegistration.clientSecret; this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod; this.authorizationGrantType = clientRegistration.authorizationGrantType; this.redirectUri = clientRegistration.redirectUri; this.scopes = (clientRegistration.scopes != null) ? new HashSet<>(clientRegistration.scopes) : null; this.authorizationUri = clientRegistration.providerDetails.authorizationUri; this.tokenUri = clientRegistration.providerDetails.tokenUri; this.userInfoUri = clientRegistration.providerDetails.userInfoEndpoint.uri; this.userInfoAuthenticationMethod = clientRegistration.providerDetails.userInfoEndpoint.authenticationMethod; this.userNameAttributeName = clientRegistration.providerDetails.userInfoEndpoint.userNameAttributeName; this.jwkSetUri = clientRegistration.providerDetails.jwkSetUri; this.issuerUri = clientRegistration.providerDetails.issuerUri; Map configurationMetadata = clientRegistration.providerDetails.configurationMetadata; if (configurationMetadata != Collections.EMPTY_MAP) { this.configurationMetadata = new HashMap<>(configurationMetadata); } this.clientName = clientRegistration.clientName; } /** * Sets the registration id. * @param registrationId the registration id * @return the {@link Builder} */ public Builder registrationId(String registrationId) { this.registrationId = registrationId; return this; } /** * Sets the client identifier. * @param clientId the client identifier * @return the {@link Builder} */ public Builder clientId(String clientId) { this.clientId = clientId; return this; } /** * Sets the client secret. * @param clientSecret the client secret * @return the {@link Builder} */ public Builder clientSecret(String clientSecret) { this.clientSecret = clientSecret; return this; } /** * Sets the {@link ClientAuthenticationMethod authentication method} used when * authenticating the client with the authorization server. * @param clientAuthenticationMethod the authentication method used for the client * @return the {@link Builder} */ public Builder clientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) { this.clientAuthenticationMethod = clientAuthenticationMethod; return this; } /** * Sets the {@link AuthorizationGrantType authorization grant type} used for the * client. * @param authorizationGrantType the authorization grant type used for the client * @return the {@link Builder} */ public Builder authorizationGrantType(AuthorizationGrantType authorizationGrantType) { this.authorizationGrantType = authorizationGrantType; return this; } /** * Sets the uri (or uri template) for the redirection endpoint. * *
* The supported uri template variables are: {baseScheme}, {baseHost}, {basePort}, * {basePath} and {registrationId}. * *
* NOTE: {baseUrl} is also supported, which is the same as * {baseScheme}://{baseHost}{basePort}{basePath}. * *
* Configuring uri template variables is especially useful when the client is * running behind a Proxy Server. This ensures that the X-Forwarded-* headers are * used when expanding the redirect-uri. * @param redirectUri the uri (or uri template) for the redirection endpoint * @return the {@link Builder} * @since 5.4 */ public Builder redirectUri(String redirectUri) { this.redirectUri = redirectUri; return this; } /** * Sets the scope(s) used for the client. * @param scope the scope(s) used for the client * @return the {@link Builder} */ public Builder scope(String... scope) { if (scope != null && scope.length > 0) { this.scopes = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(scope))); } return this; } /** * Sets the scope(s) used for the client. * @param scope the scope(s) used for the client * @return the {@link Builder} */ public Builder scope(Collection scope) { if (scope != null && !scope.isEmpty()) { this.scopes = Collections.unmodifiableSet(new LinkedHashSet<>(scope)); } return this; } /** * Sets the uri for the authorization endpoint. * @param authorizationUri the uri for the authorization endpoint * @return the {@link Builder} */ public Builder authorizationUri(String authorizationUri) { this.authorizationUri = authorizationUri; return this; } /** * Sets the uri for the token endpoint. * @param tokenUri the uri for the token endpoint * @return the {@link Builder} */ public Builder tokenUri(String tokenUri) { this.tokenUri = tokenUri; return this; } /** * Sets the uri for the user info endpoint. * @param userInfoUri the uri for the user info endpoint * @return the {@link Builder} */ public Builder userInfoUri(String userInfoUri) { this.userInfoUri = userInfoUri; return this; } /** * Sets the authentication method for the user info endpoint. * @param userInfoAuthenticationMethod the authentication method for the user info * endpoint * @return the {@link Builder} * @since 5.1 */ public Builder userInfoAuthenticationMethod(AuthenticationMethod userInfoAuthenticationMethod) { this.userInfoAuthenticationMethod = userInfoAuthenticationMethod; return this; } /** * Sets the attribute name used to access the user's name from the user info * response. * @param userNameAttributeName the attribute name used to access the user's name * from the user info response * @return the {@link Builder} */ public Builder userNameAttributeName(String userNameAttributeName) { this.userNameAttributeName = userNameAttributeName; return this; } /** * Sets the uri for the JSON Web Key (JWK) Set endpoint. * @param jwkSetUri the uri for the JSON Web Key (JWK) Set endpoint * @return the {@link Builder} */ public Builder jwkSetUri(String jwkSetUri) { this.jwkSetUri = jwkSetUri; return this; } /** * Sets the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth * 2.0 Authorization Server. * @param issuerUri the issuer identifier uri for the OpenID Connect 1.0 provider * or the OAuth 2.0 Authorization Server * @return the {@link Builder} * @since 5.4 */ public Builder issuerUri(String issuerUri) { this.issuerUri = issuerUri; return this; } /** * Sets the metadata describing the provider's configuration. * @param configurationMetadata the metadata describing the provider's * configuration * @return the {@link Builder} * @since 5.1 */ public Builder providerConfigurationMetadata(Map configurationMetadata) { if (configurationMetadata != null) { this.configurationMetadata = new LinkedHashMap<>(configurationMetadata); } return this; } /** * Sets the logical name of the client or registration. * @param clientName the client or registration name * @return the {@link Builder} */ public Builder clientName(String clientName) { this.clientName = clientName; return this; } /** * Builds a new {@link ClientRegistration}. * @return a {@link ClientRegistration} */ public ClientRegistration build() { Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null"); if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(this.authorizationGrantType)) { this.validateClientCredentialsGrantType(); } else if (AuthorizationGrantType.PASSWORD.equals(this.authorizationGrantType)) { this.validatePasswordGrantType(); } else if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)) { this.validateAuthorizationCodeGrantType(); } this.validateAuthorizationGrantTypes(); this.validateScopes(); return this.create(); } private ClientRegistration create() { ClientRegistration clientRegistration = new ClientRegistration(); clientRegistration.registrationId = this.registrationId; clientRegistration.clientId = this.clientId; clientRegistration.clientSecret = StringUtils.hasText(this.clientSecret) ? this.clientSecret : ""; clientRegistration.clientAuthenticationMethod = (this.clientAuthenticationMethod != null) ? this.clientAuthenticationMethod : deduceClientAuthenticationMethod(clientRegistration); clientRegistration.authorizationGrantType = this.authorizationGrantType; clientRegistration.redirectUri = this.redirectUri; clientRegistration.scopes = this.scopes; clientRegistration.providerDetails = createProviderDetails(clientRegistration); clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName : this.registrationId; return clientRegistration; } private ClientAuthenticationMethod deduceClientAuthenticationMethod(ClientRegistration clientRegistration) { if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType) && !StringUtils.hasText(this.clientSecret)) { return ClientAuthenticationMethod.NONE; } return ClientAuthenticationMethod.CLIENT_SECRET_BASIC; } private ProviderDetails createProviderDetails(ClientRegistration clientRegistration) { ProviderDetails providerDetails = clientRegistration.new ProviderDetails(); providerDetails.authorizationUri = this.authorizationUri; providerDetails.tokenUri = this.tokenUri; providerDetails.userInfoEndpoint.uri = this.userInfoUri; providerDetails.userInfoEndpoint.authenticationMethod = this.userInfoAuthenticationMethod; providerDetails.userInfoEndpoint.userNameAttributeName = this.userNameAttributeName; providerDetails.jwkSetUri = this.jwkSetUri; providerDetails.issuerUri = this.issuerUri; providerDetails.configurationMetadata = Collections.unmodifiableMap(this.configurationMetadata); return providerDetails; } private void validateAuthorizationCodeGrantType() { Assert.isTrue(AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType), () -> "authorizationGrantType must be " + AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); Assert.hasText(this.registrationId, "registrationId cannot be empty"); Assert.hasText(this.clientId, "clientId cannot be empty"); Assert.hasText(this.redirectUri, "redirectUri cannot be empty"); Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty"); Assert.hasText(this.tokenUri, "tokenUri cannot be empty"); } private void validateClientCredentialsGrantType() { Assert.isTrue(AuthorizationGrantType.CLIENT_CREDENTIALS.equals(this.authorizationGrantType), () -> "authorizationGrantType must be " + AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); Assert.hasText(this.registrationId, "registrationId cannot be empty"); Assert.hasText(this.clientId, "clientId cannot be empty"); Assert.hasText(this.tokenUri, "tokenUri cannot be empty"); } private void validatePasswordGrantType() { Assert.isTrue(AuthorizationGrantType.PASSWORD.equals(this.authorizationGrantType), () -> "authorizationGrantType must be " + AuthorizationGrantType.PASSWORD.getValue()); Assert.hasText(this.registrationId, "registrationId cannot be empty"); Assert.hasText(this.clientId, "clientId cannot be empty"); Assert.hasText(this.tokenUri, "tokenUri cannot be empty"); } private void validateAuthorizationGrantTypes() { for (AuthorizationGrantType authorizationGrantType : AUTHORIZATION_GRANT_TYPES) { if (authorizationGrantType.getValue().equalsIgnoreCase(this.authorizationGrantType.getValue()) && !authorizationGrantType.equals(this.authorizationGrantType)) { logger.warn(LogMessage.format( "AuthorizationGrantType: %s does not match the pre-defined constant %s and won't match a valid OAuth2AuthorizedClientProvider", this.authorizationGrantType, authorizationGrantType)); } } } private void validateScopes() { if (this.scopes == null) { return; } for (String scope : this.scopes) { Assert.isTrue(validateScope(scope), "scope \"" + scope + "\" contains invalid characters"); } } private static boolean validateScope(String scope) { return scope == null || scope.chars().allMatch((c) -> withinTheRangeOf(c, 0x21, 0x21) || withinTheRangeOf(c, 0x23, 0x5B) || withinTheRangeOf(c, 0x5D, 0x7E)); } private static boolean withinTheRangeOf(int c, int min, int max) { return c >= min && c <= max; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy