Jelajahi Sumber

Polish gh-143

Joe Grandja 4 tahun lalu
induk
melakukan
ab591dc39d
15 mengubah file dengan 494 tambahan dan 519 penghapusan
  1. 18 12
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 0 79
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToSetStringConverter2.java
  3. 90 91
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcProviderConfiguration.java
  4. 17 17
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcProviderMetadataClaimAccessor.java
  5. 5 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcProviderMetadataClaimNames.java
  6. 67 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/http/converter/HttpMessageConverters.java
  7. 35 36
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java
  8. 30 30
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ProviderSettings.java
  9. 19 13
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OidcProviderConfigurationEndpointFilter.java
  10. 14 21
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java
  11. 0 76
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/converter/ObjectToSetStringConverter2Test.java
  12. 116 64
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/OidcProviderConfigurationTests.java
  13. 16 18
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java
  14. 23 36
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ProviderSettingsTests.java
  15. 44 21
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OidcProviderConfigurationEndpointFilterTests.java

+ 18 - 12
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -54,9 +54,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
-import java.net.MalformedURLException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -66,12 +64,16 @@ import java.util.Map;
  * An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support.
  *
  * @author Joe Grandja
+ * @author Daniel Garnier-Moiroux
  * @since 0.0.1
  * @see AbstractHttpConfigurer
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see OAuth2AuthorizationEndpointFilter
  * @see OAuth2TokenEndpointFilter
+ * @see OAuth2TokenRevocationEndpointFilter
+ * @see JwkSetEndpointFilter
+ * @see OidcProviderConfigurationEndpointFilter
  * @see OAuth2ClientAuthenticationFilter
  */
 public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
@@ -147,15 +149,17 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	 * @return a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints
 	 */
 	public List<RequestMatcher> getEndpointMatchers() {
-		// TODO: use ProviderSettings instead
+		// TODO Initialize matchers using URI's from ProviderSettings
 		return Arrays.asList(this.authorizationEndpointMatcher, this.tokenEndpointMatcher,
-				this.tokenRevocationEndpointMatcher, this.jwkSetEndpointMatcher, this.oidcProviderConfigurationEndpointMatcher);
+				this.tokenRevocationEndpointMatcher, this.jwkSetEndpointMatcher,
+				this.oidcProviderConfigurationEndpointMatcher);
 	}
 
 	@Override
 	public void init(B builder) {
 		ProviderSettings providerSettings = getProviderSettings(builder);
 		validateProviderSettings(providerSettings);
+
 		OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
 				new OAuth2ClientAuthenticationProvider(
 						getRegisteredClientRepository(builder),
@@ -208,14 +212,16 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 	@Override
 	public void configure(B builder) {
-		if (getProviderSettings(builder).issuer() != null) {
-			OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = new OidcProviderConfigurationEndpointFilter(getProviderSettings(builder));
+		ProviderSettings providerSettings = getProviderSettings(builder);
+		if (providerSettings.issuer() != null) {
+			OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
+					new OidcProviderConfigurationEndpointFilter(providerSettings);
 			builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
 		}
 
 		JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(
 				getKeySource(builder),
-				getProviderSettings(builder).jwkSetEndpoint());
+				providerSettings.jwksEndpoint());
 		builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
 
 		AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
@@ -230,20 +236,20 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 				new OAuth2AuthorizationEndpointFilter(
 						getRegisteredClientRepository(builder),
 						getAuthorizationService(builder),
-						getProviderSettings(builder).authorizationEndpoint());
+						providerSettings.authorizationEndpoint());
 		builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
 
 		OAuth2TokenEndpointFilter tokenEndpointFilter =
 				new OAuth2TokenEndpointFilter(
 						authenticationManager,
 						getAuthorizationService(builder),
-						getProviderSettings(builder).tokenEndpoint());
+						providerSettings.tokenEndpoint());
 		builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
 
 		OAuth2TokenRevocationEndpointFilter tokenRevocationEndpointFilter =
 				new OAuth2TokenRevocationEndpointFilter(
 						authenticationManager,
-						getProviderSettings(builder).tokenRevocationEndpoint());
+						providerSettings.tokenRevocationEndpoint());
 		builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), OAuth2TokenEndpointFilter.class);
 	}
 
@@ -323,8 +329,8 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		if (providerSettings.issuer() != null) {
 			try {
 				new URI(providerSettings.issuer()).toURL();
-			} catch (MalformedURLException | URISyntaxException e) {
-				throw new IllegalArgumentException("issuer must be a valid URL");
+			} catch (Exception ex) {
+				throw new IllegalArgumentException("issuer must be a valid URL", ex);
 			}
 		}
 	}

+ 0 - 79
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToSetStringConverter2.java

@@ -1,79 +0,0 @@
-/*
- * Copyright 2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.security.oauth2.core.converter;
-
-import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.core.convert.converter.ConditionalGenericConverter;
-import org.springframework.core.convert.converter.GenericConverter;
-import org.springframework.util.ClassUtils;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * TODO
- * This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
- *
- * @author Daniel Garnier-Moiroux
- * @since 0.1.0
- * @see <a target="_blank" href="https://github.com/spring-projects/spring-security/pull/9146">Issue gh-9146</a>
- */
-final public class ObjectToSetStringConverter2 implements ConditionalGenericConverter {
-
-	@Override
-	public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
-		Set<GenericConverter.ConvertiblePair> convertibleTypes = new LinkedHashSet<>();
-		convertibleTypes.add(new GenericConverter.ConvertiblePair(Object.class, Set.class));
-		return convertibleTypes;
-	}
-
-	@Override
-	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
-		if (targetType.getElementTypeDescriptor() == null
-				|| targetType.getElementTypeDescriptor().getType().equals(String.class) || sourceType == null
-				|| ClassUtils.isAssignable(sourceType.getType(), targetType.getElementTypeDescriptor().getType())) {
-			return true;
-		}
-		return false;
-	}
-
-	@Override
-	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
-		if (source == null) {
-			return null;
-		}
-		if (source instanceof Set) {
-			Set<?> sourceList = (Set<?>) source;
-			for (Object entry: sourceList) {
-				if (entry instanceof String) {
-					return source;
-				}
-			}
-		}
-		if (source instanceof Collection) {
-			Collection<String> results = new LinkedHashSet<>();
-			for (Object object : ((Collection<?>) source)) {
-				if (object != null) {
-					results.add(object.toString());
-				}
-			}
-			return results;
-		}
-		return Collections.singleton(source.toString());
-	}
-}

+ 90 - 91
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcProviderConfiguration.java

@@ -23,9 +23,9 @@ import java.net.URI;
 import java.net.URL;
 import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -39,13 +39,11 @@ import java.util.function.Consumer;
  * @see OidcProviderMetadataClaimAccessor
  * @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
  */
-public class OidcProviderConfiguration implements OidcProviderMetadataClaimAccessor, Serializable {
+public final class OidcProviderConfiguration implements OidcProviderMetadataClaimAccessor, Serializable {
 	private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
-
 	private final Map<String, Object> claims;
 
 	private OidcProviderConfiguration(Map<String, Object> claims) {
-		Assert.notEmpty(claims, "claims cannot be empty");
 		this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
 	}
 
@@ -60,7 +58,7 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 	}
 
 	/**
-	 * Constructs a new empty {@link Builder}.
+	 * Constructs a new {@link Builder} with empty claims.
 	 *
 	 * @return the {@link Builder}
 	 */
@@ -68,7 +66,6 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 		return new Builder();
 	}
 
-
 	/**
 	 * Constructs a new {@link Builder} with the provided claims.
 	 *
@@ -82,13 +79,8 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 
 	/**
 	 * Helps configure an {@link OidcProviderConfiguration}
-	 *
-	 * @author Daniel Garnier-Moiroux
-	 * @since 0.1.0
-	 * @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">OpenID Connect Discovery 1.0</a>
-	 * for required claims
 	 */
-	public static final class Builder {
+	public static class Builder {
 		private final Map<String, Object> claims = new LinkedHashMap<>();
 
 		private Builder() {
@@ -97,7 +89,7 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 		/**
 		 * Use this {@code issuer} in the resulting {@link OidcProviderConfiguration}, REQUIRED.
 		 *
-		 * @param issuer the issuer URI
+		 * @param issuer the URL of the OpenID Provider's Issuer Identifier
 		 * @return the {@link Builder} for further configuration
 		 */
 		public Builder issuer(String issuer) {
@@ -125,135 +117,135 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 		}
 
 		/**
-		 * Use this {@code jwks_uri} in the resulting {@link OidcProviderConfiguration}, REQUIRED.
+		 * Add this Authentication Method to the collection of {@code token_endpoint_auth_methods_supported}
+		 * in the resulting {@link OidcProviderConfiguration}, OPTIONAL.
 		 *
-		 * @param jwksUri the URL of the OpenID Provider's JSON Web Key Set document
+		 * @param authenticationMethod the OAuth 2.0 Authentication Method supported by the Token endpoint
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder jwksUri(String jwksUri) {
-			return claim(OidcProviderMetadataClaimNames.JWKS_URI, jwksUri);
+		public Builder tokenEndpointAuthenticationMethod(String authenticationMethod) {
+			addClaimToClaimList(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
+			return this;
 		}
 
 		/**
-		 * Add this Response Type to the collection of {@code response_types_supported} in the resulting
-		 * {@link OidcProviderConfiguration}, REQUIRED.
+		 * A {@code Consumer} of the Token Endpoint Authentication Method(s) allowing the ability to add, replace, or remove.
 		 *
-		 * @param responseType the OAuth 2.0 {@code response_type} values that the OpenID Provider supports
+		 * @param authenticationMethodsConsumer a {@code Consumer} of the Token Endpoint Authentication Method(s)
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder responseType(String responseType) {
-			addClaimToClaimSet(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType);
+		public Builder tokenEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
+			acceptClaimValues(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
 			return this;
 		}
 
 		/**
-		 * A {@code Consumer} of the Response Type(s) allowing the ability to add, replace, or remove.
+		 * Use this {@code jwks_uri} in the resulting {@link OidcProviderConfiguration}, REQUIRED.
 		 *
-		 * @param responseTypesConsumer a {@code Consumer} of the Response Type(s)
+		 * @param jwksUri the URL of the OpenID Provider's JSON Web Key Set document
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder responseTypes(Consumer<Set<String>> responseTypesConsumer) {
-			applyToClaim(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseTypesConsumer);
-			return this;
+		public Builder jwksUri(String jwksUri) {
+			return claim(OidcProviderMetadataClaimNames.JWKS_URI, jwksUri);
 		}
 
 		/**
-		 * Add this Subject Type to the collection of {@code subject_types_supported} in the resulting
+		 * Add this Response Type to the collection of {@code response_types_supported} in the resulting
 		 * {@link OidcProviderConfiguration}, REQUIRED.
 		 *
-		 * @param subjectType the Subject Identifiers that the OpenID Provider supports
+		 * @param responseType the OAuth 2.0 {@code response_type} value that the OpenID Provider supports
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder subjectType(String subjectType) {
-			addClaimToClaimSet(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectType);
+		public Builder responseType(String responseType) {
+			addClaimToClaimList(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType);
 			return this;
 		}
 
 		/**
-		 * A {@code Consumer} of the Subject Types(s) allowing the ability to add, replace, or remove.
+		 * A {@code Consumer} of the Response Type(s) allowing the ability to add, replace, or remove.
 		 *
-		 * @param subjectTypesConsumer a {@code Consumer} of the Subject Types(s)
+		 * @param responseTypesConsumer a {@code Consumer} of the Response Type(s)
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder subjectTypes(Consumer<Set<String>> subjectTypesConsumer) {
-			applyToClaim(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectTypesConsumer);
+		public Builder responseTypes(Consumer<List<String>> responseTypesConsumer) {
+			acceptClaimValues(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseTypesConsumer);
 			return this;
 		}
 
 		/**
-		 * Add this Scope to the collection of {@code scopes_supported} in the resulting
-		 * {@link OidcProviderConfiguration}, RECOMMENDED.
+		 * Add this Grant Type to the collection of {@code grant_types_supported} in the resulting
+		 * {@link OidcProviderConfiguration}, OPTIONAL.
 		 *
-		 * @param scope the OAuth 2.0 {@code scopes} values that the OpenID Provider supports
+		 * @param grantType the OAuth 2.0 {@code grant_type} value that the OpenID Provider supports
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder scope(String scope) {
-			addClaimToClaimSet(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, scope);
+		public Builder grantType(String grantType) {
+			addClaimToClaimList(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType);
 			return this;
 		}
 
 		/**
-		 * A {@code Consumer} of the Scopes(s) allowing the ability to add, replace, or remove.
+		 * A {@code Consumer} of the Grant Type(s) allowing the ability to add, replace, or remove.
 		 *
-		 * @param scopesConsumer a {@code Consumer} of the Scopes(s)
+		 * @param grantTypesConsumer a {@code Consumer} of the Grant Type(s)
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder scopes(Consumer<Set<String>> scopesConsumer) {
-			applyToClaim(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer);
+		public Builder grantTypes(Consumer<List<String>> grantTypesConsumer) {
+			acceptClaimValues(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer);
 			return this;
 		}
 
 		/**
-		 * Add this Grant Type to the collection of {@code grant_types_supported} in the resulting
-		 * {@link OidcProviderConfiguration}, OPTIONAL.
+		 * Add this Subject Type to the collection of {@code subject_types_supported} in the resulting
+		 * {@link OidcProviderConfiguration}, REQUIRED.
 		 *
-		 * @param grantType the OAuth 2.0 {@code grant_type} values that the OpenID Provider supports
+		 * @param subjectType the Subject Type that the OpenID Provider supports
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder grantType(String grantType) {
-			addClaimToClaimSet(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType);
+		public Builder subjectType(String subjectType) {
+			addClaimToClaimList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectType);
 			return this;
 		}
 
 		/**
-		 * A {@code Consumer} of the Grant Type(s) allowing the ability to add, replace, or remove.
+		 * A {@code Consumer} of the Subject Types(s) allowing the ability to add, replace, or remove.
 		 *
-		 * @param grantTypesConsumer a {@code Consumer} of the Grant Type(s)
+		 * @param subjectTypesConsumer a {@code Consumer} of the Subject Types(s)
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder grantTypes(Consumer<Set<String>> grantTypesConsumer) {
-			applyToClaim(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer);
+		public Builder subjectTypes(Consumer<List<String>> subjectTypesConsumer) {
+			acceptClaimValues(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectTypesConsumer);
 			return this;
 		}
 
 		/**
-		 * Add this Authentication Method to the collection of {@code token_endpoint_auth_methods_supported}
-		 * in the resulting {@link OidcProviderConfiguration}, OPTIONAL.
+		 * Add this Scope to the collection of {@code scopes_supported} in the resulting
+		 * {@link OidcProviderConfiguration}, RECOMMENDED.
 		 *
-		 * @param authenticationMethod the OAuth 2.0 Authentication Method supported by the Token endpoint
+		 * @param scope the OAuth 2.0 {@code scope} value that the OpenID Provider supports
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder tokenEndpointAuthenticationMethod(String authenticationMethod) {
-			addClaimToClaimSet(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
+		public Builder scope(String scope) {
+			addClaimToClaimList(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, scope);
 			return this;
 		}
 
 		/**
-		 * A {@code Consumer} of the Token Endpoint Authentication Method(s) allowing the ability to add, replace, or remove.
+		 * A {@code Consumer} of the Scopes(s) allowing the ability to add, replace, or remove.
 		 *
-		 * @param authenticationMethodsConsumer a {@code Consumer} of the Token Endpoint Authentication Method(s)
+		 * @param scopesConsumer a {@code Consumer} of the Scopes(s)
 		 * @return the {@link Builder} for further configuration
 		 */
-		public Builder tokenEndpointAuthenticationMethods(Consumer<Set<String>> authenticationMethodsConsumer) {
-			applyToClaim(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
+		public Builder scopes(Consumer<List<String>> scopesConsumer) {
+			acceptClaimValues(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer);
 			return this;
 		}
 
 		/**
-		 * Use this claim in the resulting {@link OidcProviderConfiguration}
+		 * Use this claim in the resulting {@link OidcProviderConfiguration}.
 		 *
-		 * @param name The claim name
-		 * @param value The claim value
+		 * @param name the claim name
+		 * @param value the claim value
 		 * @return the {@link Builder} for further configuration
 		 */
 		public Builder claim(String name, Object value) {
@@ -267,7 +259,7 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 		 * Provides access to every {@link #claim(String, Object)} declared so far with
 		 * the possibility to add, replace, or remove.
 		 *
-		 * @param claimsConsumer the consumer
+		 * @param claimsConsumer a {@code Consumer} of the claims
 		 * @return the {@link Builder} for further configurations
 		 */
 		public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
@@ -276,15 +268,13 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 		}
 
 		/**
-		 * Validate the claims and build the {@link OidcProviderConfiguration}. The following claims are REQUIRED:
-		 * - issuer
-		 * - authorization_endpoint
-		 * - token_endpoint
-		 * - jwks_uri
-		 * - response_types_supported
-		 * - subject_types_supported
+		 * Validate the claims and build the {@link OidcProviderConfiguration}.
+		 * <p>
+		 * The following claims are REQUIRED:
+		 * {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint}, {@code jwks_uri},
+		 * {@code response_types_supported} and {@code subject_types_supported}.
 		 *
-		 * @return The constructed {@link OidcProviderConfiguration}
+		 * @return the {@link OidcProviderConfiguration}
 		 */
 		public OidcProviderConfiguration build() {
 			validateClaims();
@@ -298,34 +288,43 @@ public class OidcProviderConfiguration implements OidcProviderMetadataClaimAcces
 			validateURL(this.claims.get(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint must be a valid URL");
 			Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint cannot be null");
 			validateURL(this.claims.get(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint must be a valid URL");
-			Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.JWKS_URI), "jwkSetUri cannot be null");
-			validateURL(this.claims.get(OidcProviderMetadataClaimNames.JWKS_URI), "jwkSetUri must be a valid URL");
-			Assert.notEmpty((Set<?>) this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
-			Assert.notEmpty((Set<?>) this.claims.get(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be empty");
+			Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.JWKS_URI), "jwksUri cannot be null");
+			validateURL(this.claims.get(OidcProviderMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL");
+			Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be null");
+			Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes must be of type List");
+			Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be empty");
+			Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
+			Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
+			Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
 		}
 
-		private void validateURL(Object url, String errorMessage) {
-			if (url.getClass().isAssignableFrom(URL.class)) return;
+		private static void validateURL(Object url, String errorMessage) {
+			if (URL.class.isAssignableFrom(url.getClass())) {
+				return;
+			}
 
 			try {
 				new URI(url.toString()).toURL();
-			} catch (Exception e) {
-				throw new IllegalArgumentException(errorMessage);
+			} catch (Exception ex) {
+				throw new IllegalArgumentException(errorMessage, ex);
 			}
-
 		}
 
 		@SuppressWarnings("unchecked")
-		private void addClaimToClaimSet(String name, String value) {
-			this.claims.putIfAbsent(name, new LinkedHashSet<String>());
-			((Set<String>) this.claims.get(name)).add(value);
+		private void addClaimToClaimList(String name, String value) {
+			Assert.hasText(name, "name cannot be empty");
+			Assert.notNull(value, "value cannot be null");
+			this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
+			((List<String>) this.claims.get(name)).add(value);
 		}
 
 		@SuppressWarnings("unchecked")
-		private void applyToClaim(String name, Consumer<Set<String>> consumer) {
-			this.claims.putIfAbsent(name, new LinkedHashSet<String>());
-			Set<String> values = (Set<String>) this.claims.get(name);
-			consumer.accept(values);
+		private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
+			Assert.hasText(name, "name cannot be empty");
+			Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
+			this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
+			List<String> values = (List<String>) this.claims.get(name);
+			valuesConsumer.accept(values);
 		}
 	}
 }

+ 17 - 17
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcProviderMetadataClaimAccessor.java

@@ -35,30 +35,30 @@ import java.util.List;
 public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
 
 	/**
-	 * Returns the URL the OpenID Provider asserts as its Issuer Identifier {@code (issuer)}.
+	 * Returns the {@code URL} the OpenID Provider asserts as its Issuer Identifier {@code (issuer)}.
 	 *
-	 * @return the URL the OpenID Provider asserts as its Issuer Identifier
+	 * @return the {@code URL} the OpenID Provider asserts as its Issuer Identifier
 	 */
 	default URL getIssuer() {
-		return this.getClaimAsURL(OidcProviderMetadataClaimNames.ISSUER);
+		return getClaimAsURL(OidcProviderMetadataClaimNames.ISSUER);
 	}
 
 	/**
-	 * Returns the URL of the OAuth 2.0 Authorization Endpoint {@code (authorization_endpoint)}.
+	 * Returns the {@code URL} of the OAuth 2.0 Authorization Endpoint {@code (authorization_endpoint)}.
 	 *
-	 * @return the URL of the OAuth 2.0 Authorization Endpoint
+	 * @return the {@code URL} of the OAuth 2.0 Authorization Endpoint
 	 */
 	default URL getAuthorizationEndpoint() {
-		return this.getClaimAsURL(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT);
+		return getClaimAsURL(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT);
 	}
 
 	/**
-	 * Returns the URL of the OAuth 2.0 Token Endpoint {@code (token_endpoint)}.
+	 * Returns the {@code URL} of the OAuth 2.0 Token Endpoint {@code (token_endpoint)}.
 	 *
-	 * @return the URL of the OAuth 2.0 Token Endpoint
+	 * @return the {@code URL} of the OAuth 2.0 Token Endpoint
 	 */
 	default URL getTokenEndpoint() {
-		return this.getClaimAsURL(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT);
+		return getClaimAsURL(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT);
 	}
 
 	/**
@@ -67,16 +67,16 @@ public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
 	 * @return the client authentication methods supported by the OAuth 2.0 Token Endpoint
 	 */
 	default List<String> getTokenEndpointAuthenticationMethods() {
-		return this.getClaimAsStringList(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED);
+		return getClaimAsStringList(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED);
 	}
 
 	/**
-	 * Returns the URL of the JSON Web Key Set {@code (jwks_uri)}.
+	 * Returns the {@code URL} of the JSON Web Key Set {@code (jwks_uri)}.
 	 *
-	 * @return the URL of the JSON Web Key Set
+	 * @return the {@code URL} of the JSON Web Key Set
 	 */
 	default URL getJwksUri() {
-		return this.getClaimAsURL(OidcProviderMetadataClaimNames.JWKS_URI);
+		return getClaimAsURL(OidcProviderMetadataClaimNames.JWKS_URI);
 	}
 
 	/**
@@ -85,7 +85,7 @@ public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
 	 * @return the OAuth 2.0 {@code response_type} values supported
 	 */
 	default List<String> getResponseTypes() {
-		return this.getClaimAsStringList(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED);
+		return getClaimAsStringList(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED);
 	}
 
 	/**
@@ -94,7 +94,7 @@ public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
 	 * @return the OAuth 2.0 {@code grant_type} values supported
 	 */
 	default List<String> getGrantTypes() {
-		return this.getClaimAsStringList(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED);
+		return getClaimAsStringList(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED);
 	}
 
 	/**
@@ -103,7 +103,7 @@ public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
 	 * @return the Subject Identifier types supported
 	 */
 	default List<String> getSubjectTypes() {
-		return this.getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED);
+		return getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED);
 	}
 
 	/**
@@ -112,7 +112,7 @@ public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
 	 * @return the OAuth 2.0 {@code scope} values supported
 	 */
 	default List<String> getScopes() {
-		return this.getClaimAsStringList(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED);
+		return getClaimAsStringList(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED);
 	}
 
 }

+ 5 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcProviderMetadataClaimNames.java

@@ -16,7 +16,7 @@
 package org.springframework.security.oauth2.core.oidc;
 
 /**
- * The names of the "claims" defined by the OpenID Connect Discovery 1.0 that can be returned
+ * The names of the "claims" defined by OpenID Connect Discovery 1.0 that can be returned
  * in the OpenID Provider Configuration Response.
  *
  * @author Daniel Garnier-Moiroux
@@ -26,17 +26,17 @@ package org.springframework.security.oauth2.core.oidc;
 public interface OidcProviderMetadataClaimNames {
 
 	/**
-	 * {@code issuer} - the URL the OpenID Provider asserts as its Issuer Identifier
+	 * {@code issuer} - the {@code URL} the OpenID Provider asserts as its Issuer Identifier
 	 */
 	String ISSUER = "issuer";
 
 	/**
-	 * {@code authorization_endpoint} - the URL of the OAuth 2.0 Authorization Endpoint
+	 * {@code authorization_endpoint} - the {@code URL} of the OAuth 2.0 Authorization Endpoint
 	 */
 	String AUTHORIZATION_ENDPOINT = "authorization_endpoint";
 
 	/**
-	 * {@code token_endpoint} - the URL of the OAuth 2.0 Token Endpoint
+	 * {@code token_endpoint} - the {@code URL} of the OAuth 2.0 Token Endpoint
 	 */
 	String TOKEN_ENDPOINT = "token_endpoint";
 
@@ -46,7 +46,7 @@ public interface OidcProviderMetadataClaimNames {
 	String TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED = "token_endpoint_auth_methods_supported";
 
 	/**
-	 * {@code jwks_uri} - the URL of the JSON Web Key Set
+	 * {@code jwks_uri} - the {@code URL} of the JSON Web Key Set
 	 */
 	String JWKS_URI = "jwks_uri";
 

+ 67 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/http/converter/HttpMessageConverters.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2018 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.core.oidc.http.converter;
+
+import org.springframework.http.converter.GenericHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.GsonHttpMessageConverter;
+import org.springframework.http.converter.json.JsonbHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.util.ClassUtils;
+
+/**
+ * TODO
+ * This class is a straight copy from Spring Security.
+ * It should be consolidated when merging this codebase into Spring Security.
+ *
+ * Utility methods for {@link HttpMessageConverter}'s.
+ *
+ * @author Joe Grandja
+ * @since 5.1
+ */
+final class HttpMessageConverters {
+
+	private static final boolean jackson2Present;
+
+	private static final boolean gsonPresent;
+
+	private static final boolean jsonbPresent;
+
+	static {
+		ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
+		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
+				&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
+		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
+		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
+	}
+
+	private HttpMessageConverters() {
+	}
+
+	static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
+		if (jackson2Present) {
+			return new MappingJackson2HttpMessageConverter();
+		}
+		if (gsonPresent) {
+			return new GsonHttpMessageConverter();
+		}
+		if (jsonbPresent) {
+			return new JsonbHttpMessageConverter();
+		}
+		return null;
+	}
+
+}

+ 35 - 36
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/http/converter/OidcProviderConfigurationHttpMessageConverter.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.core.http.converter;
+package org.springframework.security.oauth2.core.oidc.http.converter;
 
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.core.convert.TypeDescriptor;
@@ -28,20 +28,17 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.http.converter.HttpMessageNotWritableException;
 import org.springframework.security.oauth2.core.converter.ClaimConversionService;
 import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
-import org.springframework.security.oauth2.core.converter.ObjectToSetStringConverter2;
 import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
 import org.springframework.security.oauth2.core.oidc.OidcProviderMetadataClaimNames;
 import org.springframework.util.Assert;
 
-import java.io.IOException;
 import java.net.URL;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
-
 
 /**
- * A {@link HttpMessageConverter} for an {@link OidcProviderConfiguration OpenID Provider Configuration Metadata}.
+ * A {@link HttpMessageConverter} for an {@link OidcProviderConfiguration OpenID Provider Configuration Response}.
  *
  * @author Daniel Garnier-Moiroux
  * @since 0.1.0
@@ -50,9 +47,9 @@ import java.util.Set;
  */
 public class OidcProviderConfigurationHttpMessageConverter
 		extends AbstractHttpMessageConverter<OidcProviderConfiguration> {
+
 	private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
-			new ParameterizedTypeReference<Map<String, Object>>() {
-			};
+			new ParameterizedTypeReference<Map<String, Object>>() {};
 
 	private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
 
@@ -73,7 +70,8 @@ public class OidcProviderConfigurationHttpMessageConverter
 	protected OidcProviderConfiguration readInternal(Class<? extends OidcProviderConfiguration> clazz, HttpInputMessage inputMessage)
 			throws HttpMessageNotReadableException {
 		try {
-			Map<String, Object> providerConfigurationParameters = (Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
+			Map<String, Object> providerConfigurationParameters =
+					(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
 			return this.providerConfigurationConverter.convert(providerConfigurationParameters);
 		} catch (Exception ex) {
 			throw new HttpMessageNotReadableException(
@@ -82,7 +80,8 @@ public class OidcProviderConfigurationHttpMessageConverter
 	}
 
 	@Override
-	protected void writeInternal(OidcProviderConfiguration providerConfiguration, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
+	protected void writeInternal(OidcProviderConfiguration providerConfiguration, HttpOutputMessage outputMessage)
+			throws HttpMessageNotWritableException {
 		try {
 			Map<String, Object> providerConfigurationResponseParameters =
 					this.providerConfigurationParametersConverter.convert(providerConfiguration);
@@ -98,6 +97,18 @@ public class OidcProviderConfigurationHttpMessageConverter
 		}
 	}
 
+	/**
+	 * Sets the {@link Converter} used for converting the OpenID Provider Configuration parameters
+	 * to an {@link OidcProviderConfiguration}.
+	 *
+	 * @param providerConfigurationConverter the {@link Converter} used for converting to an
+	 * {@link OidcProviderConfiguration}
+	 */
+	public final void setProviderConfigurationConverter(Converter<Map<String, Object>, OidcProviderConfiguration> providerConfigurationConverter) {
+		Assert.notNull(providerConfigurationConverter, "providerConfigurationConverter cannot be null");
+		this.providerConfigurationConverter = providerConfigurationConverter;
+	}
+
 	/**
 	 * Sets the {@link Converter} used for converting the {@link OidcProviderConfiguration} to a
 	 * {@code Map} representation of the OpenID Provider Configuration.
@@ -111,18 +122,6 @@ public class OidcProviderConfigurationHttpMessageConverter
 		this.providerConfigurationParametersConverter = providerConfigurationParametersConverter;
 	}
 
-	/**
-	 * Sets the {@link Converter} used for converting the OpenID Provider Configuration parameters
-	 * to an {@link OidcProviderConfiguration}.
-	 *
-	 * @param providerConfigurationConverter the {@link Converter} used for converting to an
-	 * {@link OidcProviderConfiguration}
-	 */
-	public final void setProviderConfigurationConverter(Converter<Map<String, Object>, OidcProviderConfiguration> providerConfigurationConverter) {
-		Assert.notNull(providerConfigurationConverter, "providerConfigurationConverter cannot be null");
-		this.providerConfigurationConverter = providerConfigurationConverter;
-	}
-
 	private static final class OidcProviderConfigurationConverter implements Converter<Map<String, Object>, OidcProviderConfiguration> {
 		private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
 		private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
@@ -130,22 +129,22 @@ public class OidcProviderConfigurationHttpMessageConverter
 		private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
 		private final ClaimTypeConverter claimTypeConverter;
 
-		OidcProviderConfigurationConverter() {
-			CLAIM_CONVERSION_SERVICE.addConverter(new ObjectToSetStringConverter2());
-			Map<String, Converter<Object, ?>> claimNameToConverter = new HashMap<>();
-			Converter<Object, ?> setStringConverter = getConverter(TypeDescriptor.collection(Set.class, STRING_TYPE_DESCRIPTOR));
+		private OidcProviderConfigurationConverter() {
+			Converter<Object, ?> collectionStringConverter = getConverter(
+					TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
 			Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
 
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.ISSUER, urlConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.JWKS_URI, urlConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, setStringConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, setStringConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, setStringConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, setStringConverter);
-			claimNameToConverter.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, setStringConverter);
-			this.claimTypeConverter = new ClaimTypeConverter(claimNameToConverter);
+			Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
+			claimConverters.put(OidcProviderMetadataClaimNames.ISSUER, urlConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.JWKS_URI, urlConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, collectionStringConverter);
+			claimConverters.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
+			this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
 		}
 
 		@Override

+ 30 - 30
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/ProviderSettings.java

@@ -15,57 +15,61 @@
  */
 package org.springframework.security.oauth2.server.authorization.config;
 
-
-import org.springframework.util.Assert;
-
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * A facility for OpenID Connect Provider Configuration settings.
+ * A facility for provider configuration settings.
  *
  * @author Daniel Garnier-Moiroux
  * @since 0.1.0
  * @see Settings
- * @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">OpenID Connect Discovery 1.0</a>
  */
 public class ProviderSettings extends Settings {
 	private static final String PROVIDER_SETTING_BASE = "setting.provider.";
 	public static final String ISSUER = PROVIDER_SETTING_BASE.concat("issuer");
 	public static final String AUTHORIZATION_ENDPOINT = PROVIDER_SETTING_BASE.concat("authorization-endpoint");
 	public static final String TOKEN_ENDPOINT = PROVIDER_SETTING_BASE.concat("token-endpoint");
-	public static final String JWK_SET_ENDPOINT = PROVIDER_SETTING_BASE.concat("jwk-set-endpoint");
+	public static final String JWKS_ENDPOINT = PROVIDER_SETTING_BASE.concat("jwks-endpoint");
 	public static final String TOKEN_REVOCATION_ENDPOINT = PROVIDER_SETTING_BASE.concat("token-revocation-endpoint");
 
 	/**
 	 * Constructs a {@code ProviderSettings}.
 	 */
 	public ProviderSettings() {
-		super(defaultSettings());
+		this(defaultSettings());
+	}
+
+	/**
+	 * Constructs a {@code ProviderSettings} using the provided parameters.
+	 *
+	 * @param settings the initial settings
+	 */
+	public ProviderSettings(Map<String, Object> settings) {
+		super(settings);
 	}
 
 	/**
-	 * Returns the URL for the OpenID Issuer.
+	 * Returns the URL of the Provider's Issuer Identifier
 	 *
-	 * @return the URL for the OpenID Issuer
+	 * @return the URL of the Provider's Issuer Identifier
 	 */
 	public String issuer() {
 		return setting(ISSUER);
 	}
 
 	/**
-	 * Sets the URL the Provider uses as its Issuer Identity.
+	 * Sets the URL the Provider uses as its Issuer Identifier.
 	 *
-	 * @param issuer the URL the Provider uses as its Issuer Identity.
+	 * @param issuer the URL the Provider uses as its Issuer Identifier.
 	 * @return the {@link ProviderSettings} for further configuration
 	 */
 	public ProviderSettings issuer(String issuer) {
-		Assert.notNull(issuer, "issuer cannot be null");
 		return setting(ISSUER, issuer);
 	}
 
 	/**
-	 * Returns the provider's OAuth 2.0 Authorization endpoint. The default is {@code /oauth2/authorize}.
+	 * Returns the Provider's OAuth 2.0 Authorization endpoint. The default is {@code /oauth2/authorize}.
 	 *
 	 * @return the Authorization endpoint
 	 */
@@ -74,18 +78,17 @@ public class ProviderSettings extends Settings {
 	}
 
 	/**
-	 * Sets the provider's OAuth 2.0 Authorization endpoint.
+	 * Sets the Provider's OAuth 2.0 Authorization endpoint.
 	 *
 	 * @param authorizationEndpoint the Authorization endpoint
 	 * @return the {@link ProviderSettings} for further configuration
 	 */
 	public ProviderSettings authorizationEndpoint(String authorizationEndpoint) {
-		Assert.hasText(authorizationEndpoint, "authorizationEndpoint cannot be empty");
 		return setting(AUTHORIZATION_ENDPOINT, authorizationEndpoint);
 	}
 
 	/**
-	 * Returns the provider's OAuth 2.0 Token endpoint. The default is {@code /oauth2/token}.
+	 * Returns the Provider's OAuth 2.0 Token endpoint. The default is {@code /oauth2/token}.
 	 *
 	 * @return the Token endpoint
 	 */
@@ -94,38 +97,36 @@ public class ProviderSettings extends Settings {
 	}
 
 	/**
-	 * Sets the provider's OAuth 2.0 Token endpoint.
+	 * Sets the Provider's OAuth 2.0 Token endpoint.
 	 *
 	 * @param tokenEndpoint the Token endpoint
 	 * @return the {@link ProviderSettings} for further configuration
 	 */
 	public ProviderSettings tokenEndpoint(String tokenEndpoint) {
-		Assert.hasText(tokenEndpoint, "tokenEndpoint cannot be empty");
 		return setting(TOKEN_ENDPOINT, tokenEndpoint);
 	}
 
 	/**
-	 * Returns the provider's JWK Set endpoint. The default is {@code /oauth2/jwks}.
+	 * Returns the Provider's JWK Set endpoint. The default is {@code /oauth2/jwks}.
 	 *
 	 * @return the JWK Set endpoint
 	 */
-	public String jwkSetEndpoint() {
-		return setting(JWK_SET_ENDPOINT);
+	public String jwksEndpoint() {
+		return setting(JWKS_ENDPOINT);
 	}
 
 	/**
-	 * Sets the provider's OAuth 2.0 JWK Set endpoint.
+	 * Sets the Provider's JWK Set endpoint.
 	 *
-	 * @param jwkSetEndpoint the JWK Set endpoint
+	 * @param jwksEndpoint the JWK Set endpoint
 	 * @return the {@link ProviderSettings} for further configuration
 	 */
-	public ProviderSettings jwkSetEndpoint(String jwkSetEndpoint) {
-		Assert.hasText(jwkSetEndpoint, "jwkSetEndpoint cannot be empty");
-		return setting(JWK_SET_ENDPOINT, jwkSetEndpoint);
+	public ProviderSettings jwksEndpoint(String jwksEndpoint) {
+		return setting(JWKS_ENDPOINT, jwksEndpoint);
 	}
 
 	/**
-	 * Returns the provider's Token Revocation endpoint. The default is {@code /oauth2/revoke}.
+	 * Returns the Provider's OAuth 2.0 Token Revocation endpoint. The default is {@code /oauth2/revoke}.
 	 *
 	 * @return the Token Revocation endpoint
 	 */
@@ -134,13 +135,12 @@ public class ProviderSettings extends Settings {
 	}
 
 	/**
-	 * Sets the provider's OAuth 2.0 Token Revocation endpoint.
+	 * Sets the Provider's OAuth 2.0 Token Revocation endpoint.
 	 *
 	 * @param tokenRevocationEndpoint the Token Revocation endpoint
 	 * @return the {@link ProviderSettings} for further configuration
 	 */
 	public ProviderSettings tokenRevocationEndpoint(String tokenRevocationEndpoint) {
-		Assert.hasText(tokenRevocationEndpoint, "tokenRevocationEndpoint cannot be empty");
 		return setting(TOKEN_REVOCATION_ENDPOINT, tokenRevocationEndpoint);
 	}
 
@@ -148,7 +148,7 @@ public class ProviderSettings extends Settings {
 		Map<String, Object> settings = new HashMap<>();
 		settings.put(AUTHORIZATION_ENDPOINT, "/oauth2/authorize");
 		settings.put(TOKEN_ENDPOINT, "/oauth2/token");
-		settings.put(JWK_SET_ENDPOINT, "/oauth2/jwks");
+		settings.put(JWKS_ENDPOINT, "/oauth2/jwks");
 		settings.put(TOKEN_REVOCATION_ENDPOINT, "/oauth2/revoke");
 		return settings;
 	}

+ 19 - 13
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OidcProviderConfigurationEndpointFilter.java

@@ -20,9 +20,9 @@ import org.springframework.http.MediaType;
 import org.springframework.http.server.ServletServerHttpResponse;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
-import org.springframework.security.oauth2.core.http.converter.OidcProviderConfigurationHttpMessageConverter;
 import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.core.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -37,12 +37,13 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
 /**
- * A {@code Filter} that processes OpenID Provider Configuration Request.
+ * A {@code Filter} that processes OpenID Provider Configuration Requests.
  *
  * @author Daniel Garnier-Moiroux
  * @since 0.1.0
+ * @see OidcProviderConfiguration
  * @see ProviderSettings
- * @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig">OpenID Connect Discovery 1.0</a>
+ * @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">4.1. OpenID Provider Configuration Request</a>
  */
 public class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilter {
 	/**
@@ -50,22 +51,24 @@ public class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilte
 	 */
 	public static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration";
 
-	private final RequestMatcher requestMatcher;
 	private final ProviderSettings providerSettings;
-	private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter = new OidcProviderConfigurationHttpMessageConverter();
+	private final RequestMatcher requestMatcher;
+	private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter =
+			new OidcProviderConfigurationHttpMessageConverter();
 
 	public OidcProviderConfigurationEndpointFilter(ProviderSettings providerSettings) {
 		Assert.notNull(providerSettings, "providerSettings cannot be null");
+		this.providerSettings = providerSettings;
 		this.requestMatcher = new AntPathRequestMatcher(
 				DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI,
 				HttpMethod.GET.name()
 		);
-		this.providerSettings = providerSettings;
 	}
 
 	@Override
 	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 			throws ServletException, IOException {
+
 		if (!this.requestMatcher.matches(request)) {
 			filterChain.doFilter(request, response);
 			return;
@@ -75,20 +78,23 @@ public class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilte
 				.issuer(this.providerSettings.issuer())
 				.authorizationEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.authorizationEndpoint()))
 				.tokenEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.tokenEndpoint()))
-				.jwksUri(asUrl(this.providerSettings.issuer(), this.providerSettings.jwkSetEndpoint()))
-				.subjectType("public")
+				.tokenEndpointAuthenticationMethod("client_secret_basic")	// TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_BASIC in Spring Security 5.5.0
+				.tokenEndpointAuthenticationMethod("client_secret_post")	// TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_POST in Spring Security 5.5.0
+				.jwksUri(asUrl(this.providerSettings.issuer(), this.providerSettings.jwksEndpoint()))
 				.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
-				.scope(OidcScopes.OPENID)
 				.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
 				.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
-				.tokenEndpointAuthenticationMethod("client_secret_basic") // TODO: move this ClientAuthenticationMethod
+				.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
+				.subjectType("public")
+				.scope(OidcScopes.OPENID)
 				.build();
 
-		ServletServerHttpResponse resp = new ServletServerHttpResponse(response);
-		this.providerConfigurationHttpMessageConverter.write(providerConfiguration, MediaType.APPLICATION_JSON, resp);
+		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+		this.providerConfigurationHttpMessageConverter.write(
+				providerConfiguration, MediaType.APPLICATION_JSON, httpResponse);
 	}
 
-	private String asUrl(String issuer, String endpoint) {
+	private static String asUrl(String issuer, String endpoint) {
 		return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
 	}
 }

+ 14 - 21
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java

@@ -24,13 +24,11 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
 import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
-import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -40,7 +38,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 /**
- * Integration tests for the OpenID Connect.
+ * Integration tests for OpenID Connect 1.0.
  *
  * @author Daniel Garnier-Moiroux
  */
@@ -54,17 +52,17 @@ public class OidcTests {
 	private MockMvc mvc;
 
 	@Test
-	public void requestWhenIssuerSetAndOpenIDProviderConfigurationRequestThenReturnProviderConfigurationResponse() throws Exception {
+	public void requestWhenConfigurationRequestAndIssuerSetThenReturnConfigurationResponse() throws Exception {
 		this.spring.register(AuthorizationServerConfigurationWithIssuer.class).autowire();
 
-		this.mvc.perform(MockMvcRequestBuilders.get(OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
+		this.mvc.perform(get(OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
 				.andExpect(status().is2xxSuccessful())
 				.andExpect(jsonPath("issuer").value(issuerUrl))
 				.andReturn();
 	}
 
 	@Test
-	public void requestWhenIssuerNotSetAndOpenIDProviderConfigurationRequestThenRedirectsToLogin() throws Exception {
+	public void requestWhenConfigurationRequestAndIssuerNotSetThenRedirectToLogin() throws Exception {
 		this.spring.register(AuthorizationServerConfiguration.class).autowire();
 
 		MvcResult mvcResult = this.mvc.perform(get(OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
@@ -74,16 +72,16 @@ public class OidcTests {
 	}
 
 	@Test
-	public void requestWhenIssuerNotValidUrlThenThrowException() {
+	public void loadContextWhenIssuerNotValidUrlThenThrowException() {
 		assertThatThrownBy(
-				() -> this.spring.register(AuthorizationServerConfigurationWithInvalidUrlIssuer.class).autowire()
+				() -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUrl.class).autowire()
 		);
 	}
 
 	@Test
-	public void requestWhenIssuerNotValidUriThenThrowException() {
+	public void loadContextWhenIssuerNotValidUriThenThrowException() {
 		assertThatThrownBy(
-				() -> this.spring.register(AuthorizationServerConfigurationWithInvalidUriIssuer.class).autowire()
+				() -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUri.class).autowire()
 		);
 	}
 
@@ -98,20 +96,15 @@ public class OidcTests {
 
 		@Bean
 		CryptoKeySource keySource() {
-			return new StaticKeyGeneratingCryptoKeySource();
-		}
-
-		@Bean
-		ProviderSettings providerSettings() {
-			return new ProviderSettings();
+			return mock(CryptoKeySource.class);
 		}
 	}
 
 	@EnableWebSecurity
 	@Import(OAuth2AuthorizationServerConfiguration.class)
 	static class AuthorizationServerConfigurationWithIssuer extends AuthorizationServerConfiguration {
+
 		@Bean
-		@Override
 		ProviderSettings providerSettings() {
 			return new ProviderSettings().issuer(issuerUrl);
 		}
@@ -119,9 +112,9 @@ public class OidcTests {
 
 	@EnableWebSecurity
 	@Import(OAuth2AuthorizationServerConfiguration.class)
-	static class AuthorizationServerConfigurationWithInvalidUrlIssuer extends AuthorizationServerConfiguration {
+	static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration {
+
 		@Bean
-		@Override
 		ProviderSettings providerSettings() {
 			return new ProviderSettings().issuer("urn:example");
 		}
@@ -129,9 +122,9 @@ public class OidcTests {
 
 	@EnableWebSecurity
 	@Import(OAuth2AuthorizationServerConfiguration.class)
-	static class AuthorizationServerConfigurationWithInvalidUriIssuer extends AuthorizationServerConfiguration {
+	static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration {
+
 		@Bean
-		@Override
 		ProviderSettings providerSettings() {
 			return new ProviderSettings().issuer("https://not a valid uri");
 		}

+ 0 - 76
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/converter/ObjectToSetStringConverter2Test.java

@@ -1,76 +0,0 @@
-/*
- * Copyright 2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.security.oauth2.core.converter;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * TODO
- * This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
- * These tests will probably be folded into tests for {@link ClaimConversionService}.
- *
- * Tests for {@link ObjectToSetStringConverter2}.
- *
- * @author Daniel Garnier-Moiroux
- */
-public class ObjectToSetStringConverter2Test {
-	@Test
-	@SuppressWarnings("unchecked")
-	public void convertFromNullThenReturnNull() {
-		ObjectToSetStringConverter2 converter = new ObjectToSetStringConverter2();
-		Set<String> result = (Set<String>) converter.convert(null, null, null);
-		assertThat(result).isNull();
-	}
-
-	@Test
-	@SuppressWarnings("unchecked")
-	public void convertFromStringThenReturnSet() {
-		ObjectToSetStringConverter2 converter = new ObjectToSetStringConverter2();
-		Set<String> result = (Set<String>) converter.convert("Hello", null, null);
-		assertThat(result).containsExactly("Hello");
-	}
-
-	@Test
-	@SuppressWarnings("unchecked")
-	public void convertFromSetThenReturnSet() {
-		ObjectToSetStringConverter2 converter = new ObjectToSetStringConverter2();
-		Set<String> result = (Set<String>) converter.convert(new HashSet<>(Arrays.asList("Hello", "world")), null, null);
-		assertThat(result).containsExactlyInAnyOrder("Hello", "world");
-	}
-
-	@Test
-	@SuppressWarnings("unchecked")
-	public void convertFromCollectionThenReturnSet() {
-		ObjectToSetStringConverter2 converter = new ObjectToSetStringConverter2();
-		Set<String> result = (Set<String>) converter.convert(Arrays.asList("Hello", "world"), null, null);
-		assertThat(result).containsExactlyInAnyOrder("Hello", "world");
-	}
-
-	@Test
-	@SuppressWarnings("unchecked")
-	public void convertFromEmptyCollectionThenReturnEmptySet() {
-		ObjectToSetStringConverter2 converter = new ObjectToSetStringConverter2();
-		Set<String> result = (Set<String>) converter.convert(Collections.emptyList(), null, null);
-		assertThat(result).isEmpty();
-	}
-}

+ 116 - 64
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/OidcProviderConfigurationTests.java

@@ -17,11 +17,12 @@ package org.springframework.security.oauth2.core.oidc;
 
 import org.junit.Test;
 
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -54,7 +55,7 @@ public class OidcProviderConfigurationTests {
 				.grantType("authorization_code")
 				.grantType("client_credentials")
 				.subjectType("public")
-				.tokenEndpointAuthenticationMethod("basic")
+				.tokenEndpointAuthenticationMethod("client_secret_basic")
 				.claim("a-claim", "a-value")
 				.build();
 
@@ -66,8 +67,8 @@ public class OidcProviderConfigurationTests {
 		assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
 		assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
 		assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
-		assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("basic");
-		assertThat(providerConfiguration.getClaimAsString("a-claim")).isEqualTo("a-value");
+		assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
+		assertThat(providerConfiguration.<String>getClaim("a-claim")).isEqualTo("a-value");
 	}
 
 	@Test
@@ -94,15 +95,15 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void buildFromClaimsThenCreated() {
-		HashMap<String, Object> claims = new HashMap<>();
+	public void buildWhenClaimsProvidedThenCreated() {
+		Map<String, Object> claims = new HashMap<>();
 		claims.put(OidcProviderMetadataClaimNames.ISSUER, "https://example.com/issuer1");
 		claims.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, "https://example.com/issuer1/oauth2/authorize");
 		claims.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, "https://example.com/issuer1/oauth2/token");
 		claims.put(OidcProviderMetadataClaimNames.JWKS_URI, "https://example.com/issuer1/oauth2/jwks");
-		claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singleton("openid"));
-		claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singleton("code"));
-		claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singleton("public"));
+		claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
+		claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
+		claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public"));
 		claims.put("some-claim", "some-value");
 
 		OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
@@ -116,19 +117,19 @@ public class OidcProviderConfigurationTests {
 		assertThat(providerConfiguration.getGrantTypes()).isNull();
 		assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
 		assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
-		assertThat(providerConfiguration.getClaimAsString("some-claim")).isEqualTo("some-value");
+		assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
 	}
 
 	@Test
-	public void buildFromClaimsWhenUsingUrlsThenCreated() {
-		HashMap<String, Object> claims = new HashMap<>();
+	public void buildWhenClaimsProvidedWithUrlsThenCreated() {
+		Map<String, Object> claims = new HashMap<>();
 		claims.put(OidcProviderMetadataClaimNames.ISSUER, url("https://example.com/issuer1"));
 		claims.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, url("https://example.com/issuer1/oauth2/authorize"));
 		claims.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, url("https://example.com/issuer1/oauth2/token"));
 		claims.put(OidcProviderMetadataClaimNames.JWKS_URI, url("https://example.com/issuer1/oauth2/jwks"));
-		claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singleton("openid"));
-		claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singleton("code"));
-		claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singleton("public"));
+		claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
+		claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
+		claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public"));
 		claims.put("some-claim", "some-value");
 
 		OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
@@ -142,45 +143,46 @@ public class OidcProviderConfigurationTests {
 		assertThat(providerConfiguration.getGrantTypes()).isNull();
 		assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
 		assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
-		assertThat(providerConfiguration.getClaimAsString("some-claim")).isEqualTo("some-value");
+		assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
 	}
 
 	@Test
-	public void withClaimsWhenNullThenThrowsException() {
+	public void withClaimsWhenNullThenThrowIllegalArgumentException() {
 		assertThatThrownBy(() -> OidcProviderConfiguration.withClaims(null))
 				.isInstanceOf(IllegalArgumentException.class);
 	}
 
 	@Test
-	public void  withClaimsWhenMissingRequiredClaimsThenThrowsException() {
+	public void withClaimsWhenMissingRequiredClaimsThenThrowIllegalArgumentException() {
 		assertThatThrownBy(() -> OidcProviderConfiguration.withClaims(Collections.emptyMap()))
-				.isInstanceOf(IllegalArgumentException.class);
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("claims cannot be empty");
 	}
 
 	@Test
 	public void buildWhenCalledTwiceThenGeneratesTwoConfigurations() {
-		OidcProviderConfiguration first = minimalConfigurationBuilder
+		OidcProviderConfiguration first = this.minimalConfigurationBuilder
 				.grantType("client_credentials")
 				.build();
 
-		OidcProviderConfiguration second = minimalConfigurationBuilder
+		OidcProviderConfiguration second = this.minimalConfigurationBuilder
 				.claims((claims) ->
 						{
-							LinkedHashSet<String> newGrantTypes = new LinkedHashSet<>();
+							Set<String> newGrantTypes = new LinkedHashSet<>();
 							newGrantTypes.add("authorization_code");
-							newGrantTypes.add("implicit");
+							newGrantTypes.add("custom_grant");
 							claims.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, newGrantTypes);
 						}
 				)
 				.build();
 
 		assertThat(first.getGrantTypes()).containsExactly("client_credentials");
-		assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "implicit");
+		assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "custom_grant");
 	}
 
 	@Test
-	public void buildWhenMissingIssuerThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenMissingIssuerThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.ISSUER));
 
 		assertThatThrownBy(builder::build)
@@ -189,18 +191,18 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void buildWhenIssuerIsNotAnUrlThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenIssuerNotUrlThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.ISSUER, "not an url"));
 
 		assertThatThrownBy(builder::build)
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessageStartingWith("issuer must be a valid URL");
+				.hasMessage("issuer must be a valid URL");
 	}
 
 	@Test
-	public void buildWhenMissingAuthorizationEndpointThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenMissingAuthorizationEndpointThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT));
 
 		assertThatThrownBy(builder::build)
@@ -209,8 +211,8 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void buildWhenAuthorizationEndpointIsNotAnUrlThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenAuthorizationEndpointNotUrlThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, "not an url"));
 
 		assertThatThrownBy(builder::build)
@@ -219,8 +221,8 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void buildWhenMissingTokenEndpointThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenMissingTokenEndpointThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT));
 
 		assertThatThrownBy(builder::build)
@@ -229,8 +231,8 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void buildWhenTokenEndpointIsNotAnUrlThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenTokenEndpointNotUrlThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, "not an url"));
 
 		assertThatThrownBy(builder::build)
@@ -239,48 +241,100 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void buildWhenMissingJwksUriThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenMissingJwksUriThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.JWKS_URI));
 
 		assertThatThrownBy(builder::build)
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("jwkSetUri cannot be null");
+				.hasMessage("jwksUri cannot be null");
 	}
 
 	@Test
-	public void buildWheJwksUriIsNotAnUrlThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenJwksUriNotUrlThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.JWKS_URI, "not an url"));
 
 		assertThatThrownBy(builder::build)
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessageStartingWith("jwkSetUri must be a valid URL");
+				.hasMessageStartingWith("jwksUri must be a valid URL");
 	}
 
 	@Test
-	public void buildWhenMissingResponseTypesThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenMissingResponseTypesThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED));
 
 		assertThatThrownBy(builder::build)
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("responseTypes cannot be empty");
+				.hasMessage("responseTypes cannot be null");
+	}
+
+	@Test
+	public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
+				.claims((claims) -> {
+					claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED);
+					claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, "code");
+				});
+
+		assertThatThrownBy(builder::build)
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessageContaining("responseTypes must be of type List");
+	}
+
+	@Test
+	public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
+				.claims((claims) -> {
+					claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED);
+					claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.emptyList());
+				});
+
+		assertThatThrownBy(builder::build)
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessageContaining("responseTypes cannot be empty");
 	}
 
 	@Test
-	public void buildWhenMissingSubjectTypesThenThrowsException() {
-		OidcProviderConfiguration.Builder builder = minimalConfigurationBuilder
+	public void buildWhenMissingSubjectTypesThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
 				.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED));
 
 		assertThatThrownBy(builder::build)
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("subjectTypes cannot be empty");
+				.hasMessage("subjectTypes cannot be null");
+	}
+
+	@Test
+	public void buildWhenSubjectTypesNotListThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
+				.claims((claims) -> {
+					claims.remove(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED);
+					claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, "public");
+				});
+
+		assertThatThrownBy(builder::build)
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessageContaining("subjectTypes must be of type List");
+	}
+
+	@Test
+	public void buildWhenSubjectTypesEmptyListThenThrowIllegalArgumentException() {
+		OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
+				.claims((claims) -> {
+					claims.remove(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED);
+					claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.emptyList());
+				});
+
+		assertThatThrownBy(builder::build)
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessageContaining("subjectTypes cannot be empty");
 	}
 
 	@Test
 	public void responseTypesWhenAddingOrRemovingThenCorrectValues() {
-		OidcProviderConfiguration configuration = minimalConfigurationBuilder
+		OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
 				.responseType("should-be-removed")
 				.responseTypes(responseTypes -> {
 					responseTypes.clear();
@@ -293,7 +347,7 @@ public class OidcProviderConfigurationTests {
 
 	@Test
 	public void responseTypesWhenNotPresentAndAddingThenCorrectValues() {
-		OidcProviderConfiguration configuration = minimalConfigurationBuilder
+		OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
 				.claims(claims -> claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED))
 				.responseTypes(responseTypes -> responseTypes.add("some-response-type"))
 				.build();
@@ -303,7 +357,7 @@ public class OidcProviderConfigurationTests {
 
 	@Test
 	public void subjectTypesWhenAddingOrRemovingThenCorrectValues() {
-		OidcProviderConfiguration configuration = minimalConfigurationBuilder
+		OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
 				.subjectType("should-be-removed")
 				.subjectTypes(subjectTypes -> {
 					subjectTypes.clear();
@@ -316,7 +370,7 @@ public class OidcProviderConfigurationTests {
 
 	@Test
 	public void scopesWhenAddingOrRemovingThenCorrectValues() {
-		OidcProviderConfiguration configuration = minimalConfigurationBuilder
+		OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
 				.scope("should-be-removed")
 				.scopes(scopes -> {
 					scopes.clear();
@@ -329,7 +383,7 @@ public class OidcProviderConfigurationTests {
 
 	@Test
 	public void grantTypesWhenAddingOrRemovingThenCorrectValues() {
-		OidcProviderConfiguration configuration = minimalConfigurationBuilder
+		OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
 				.grantType("should-be-removed")
 				.grantTypes(grantTypes -> {
 					grantTypes.clear();
@@ -342,7 +396,7 @@ public class OidcProviderConfigurationTests {
 
 	@Test
 	public void tokenEndpointAuthenticationMethodsWhenAddingOrRemovingThenCorrectValues() {
-		OidcProviderConfiguration configuration = minimalConfigurationBuilder
+		OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
 				.tokenEndpointAuthenticationMethod("should-be-removed")
 				.tokenEndpointAuthenticationMethods(authMethods -> {
 					authMethods.clear();
@@ -355,24 +409,22 @@ public class OidcProviderConfigurationTests {
 
 	@Test
 	public void claimWhenNameIsNullThenThrowIllegalArgumentException() {
-		OidcProviderConfiguration.Builder builder = OidcProviderConfiguration.withClaims();
-		assertThatThrownBy(() -> builder.claim(null, "value"))
+		assertThatThrownBy(() -> OidcProviderConfiguration.withClaims().claim(null, "value"))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("name cannot be empty");
 	}
 
 	@Test
 	public void claimWhenValueIsNullThenThrowIllegalArgumentException() {
-		OidcProviderConfiguration.Builder builder = OidcProviderConfiguration.withClaims();
-		assertThatThrownBy(() -> builder.claim("claim-name", null))
+		assertThatThrownBy(() -> OidcProviderConfiguration.withClaims().claim("claim-name", null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("value cannot be null");
 	}
 
 	@Test
-	public void claimsWhenRemovingAClaimThenIsNotPresent() {
+	public void claimsWhenRemovingClaimThenNotPresent() {
 		OidcProviderConfiguration configuration =
-				minimalConfigurationBuilder
+				this.minimalConfigurationBuilder
 						.grantType("some-grant-type")
 						.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED))
 						.build();
@@ -380,9 +432,9 @@ public class OidcProviderConfigurationTests {
 	}
 
 	@Test
-	public void claimsWhenAddingAClaimThenIsPresent() {
+	public void claimsWhenAddingClaimThenPresent() {
 		OidcProviderConfiguration configuration =
-				minimalConfigurationBuilder
+				this.minimalConfigurationBuilder
 						.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, "authorization_code"))
 						.build();
 		assertThat(configuration.getGrantTypes()).containsExactly("authorization_code");
@@ -391,7 +443,7 @@ public class OidcProviderConfigurationTests {
 	private static URL url(String urlString) {
 		try {
 			return new URL(urlString);
-		} catch (MalformedURLException e) {
+		} catch (Exception ex) {
 			throw new IllegalArgumentException("urlString must be a valid URL and valid URI");
 		}
 	}

+ 16 - 18
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java → oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java

@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.core.http.converter;
+package org.springframework.security.oauth2.core.oidc.http.converter;
 
 import org.junit.Test;
 import org.springframework.core.convert.converter.Converter;
@@ -24,7 +24,6 @@ import org.springframework.mock.http.MockHttpOutputMessage;
 import org.springframework.mock.http.client.MockClientHttpResponse;
 import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
 
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Map;
@@ -48,17 +47,17 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 	}
 
 	@Test
-	public void setProviderConfigurationParametersConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
+	public void setProviderConfigurationParametersConverterWhenNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setProviderConfigurationParametersConverter(null));
 	}
 
 	@Test
-	public void setProviderConfigurationConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
+	public void setProviderConfigurationConverterWhenNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setProviderConfigurationConverter(null));
 	}
 
 	@Test
-	public void readInternalWhenSuccessfulProviderConfigurationOnlyRequiredParametersThenReadOidcProviderConfiguration() throws Exception {
+	public void readInternalWhenRequiredParametersThenSuccess() throws Exception {
 		// @formatter:off
 		String providerConfigurationResponse = "{\n"
 				+ "		\"issuer\": \"https://example.com/issuer1\",\n"
@@ -85,7 +84,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 	}
 
 	@Test
-	public void readInternalWhenSuccessfulProviderConfigurationThenReadOidcProviderConfiguration() throws Exception {
+	public void readInternalWhenValidParametersThenSuccess() throws Exception {
 		// @formatter:off
 		String providerConfigurationResponse = "{\n"
 				+ "		\"issuer\": \"https://example.com/issuer1\",\n"
@@ -96,7 +95,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 				+ "		\"response_types_supported\": [\"code\"],\n"
 				+ "		\"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n"
 				+ "		\"subject_types_supported\": [\"public\"],\n"
-				+ "		\"token_endpoint_auth_methods_supported\": [\"basic\"],\n"
+				+ "		\"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
 				+ "		\"custom_claim\": \"value\",\n"
 				+ "		\"custom_collection_claim\": [\"value1\", \"value2\"]\n"
 				+ "}\n";
@@ -113,8 +112,8 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 		assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
 		assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
 		assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
-		assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("basic");
-		assertThat(providerConfiguration.getClaimAsString("custom_claim")).isEqualTo("value");
+		assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
+		assertThat(providerConfiguration.<String>getClaim("custom_claim")).isEqualTo("value");
 		assertThat(providerConfiguration.getClaimAsStringList("custom_collection_claim")).containsExactlyInAnyOrder("value1", "value2");
 	}
 
@@ -144,7 +143,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 	}
 
 	@Test
-	public void writeInternalWhenOidcProviderConfigurationThenWriteTokenResponse() throws Exception {
+	public void writeInternalWhenProviderConfigurationThenSuccess() {
 		OidcProviderConfiguration providerConfiguration =
 				OidcProviderConfiguration.withClaims()
 						.issuer("https://example.com/issuer1")
@@ -156,7 +155,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 						.grantType("authorization_code")
 						.grantType("client_credentials")
 						.subjectType("public")
-						.tokenEndpointAuthenticationMethod("basic")
+						.tokenEndpointAuthenticationMethod("client_secret_basic")
 						.claim("custom_claim", "value")
 						.claim("custom_collection_claim", Arrays.asList("value1", "value2"))
 						.build();
@@ -173,14 +172,13 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 		assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
 		assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]");
 		assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
-		assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"basic\"]");
+		assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
 		assertThat(providerConfigurationResponse).contains("\"custom_claim\":\"value\"");
 		assertThat(providerConfigurationResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]");
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
-	public void writeInternalWhenWriteFailsThenThrowsException() throws MalformedURLException {
+	public void writeInternalWhenWriteFailsThenThrowsException() {
 		String errorMessage = "this is not a valid converter";
 		Converter<OidcProviderConfiguration, Map<String, Object>> failingConverter =
 				source -> {
@@ -190,10 +188,10 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
 
 		OidcProviderConfiguration providerConfiguration =
 				OidcProviderConfiguration.withClaims()
-						.issuer("https://example.com")
-						.authorizationEndpoint("https://example.com")
-						.tokenEndpoint("https://example.com")
-						.jwksUri("https://example.com")
+						.issuer("https://example.com/issuer1")
+						.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
+						.tokenEndpoint("https://example.com/issuer1/oauth2/token")
+						.jwksUri("https://example.com/issuer1/oauth2/jwks")
 						.responseType("code")
 						.subjectType("public")
 						.build();

+ 23 - 36
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/ProviderSettingsTests.java

@@ -17,8 +17,6 @@ package org.springframework.security.oauth2.server.authorization.config;
 
 import org.junit.Test;
 
-import java.net.MalformedURLException;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -28,41 +26,42 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
  * @author Daniel Garnier-Moiroux
  */
 public class ProviderSettingsTests {
+
 	@Test
-	public void constructorWhenDefaultThenDefaultsAreSetAndIssuerIsNotSet() {
+	public void constructorWhenDefaultThenDefaultsAreSet() {
 		ProviderSettings providerSettings = new ProviderSettings();
 
 		assertThat(providerSettings.issuer()).isNull();
 		assertThat(providerSettings.authorizationEndpoint()).isEqualTo("/oauth2/authorize");
 		assertThat(providerSettings.tokenEndpoint()).isEqualTo("/oauth2/token");
-		assertThat(providerSettings.jwkSetEndpoint()).isEqualTo("/oauth2/jwks");
+		assertThat(providerSettings.jwksEndpoint()).isEqualTo("/oauth2/jwks");
 		assertThat(providerSettings.tokenRevocationEndpoint()).isEqualTo("/oauth2/revoke");
 	}
 
 	@Test
-	public void settingsWhenProvidedThenSet() throws MalformedURLException {
-		String authorizationEndpoint = "/my-endpoints/authorize";
-		String tokenEndpoint = "/my-endpoints/token";
-		String jwksEndpoint = "/my-endpoints/jwks";
-		String tokenRevocationEndpoint = "/my-endpoints/revoke";
-		String issuer = "https://example.com/9000";
+	public void settingsWhenProvidedThenSet() {
+		String authorizationEndpoint = "/oauth2/v1/authorize";
+		String tokenEndpoint = "/oauth2/v1/token";
+		String jwksEndpoint = "/oauth2/v1/jwks";
+		String tokenRevocationEndpoint = "/oauth2/v1/revoke";
+		String issuer = "https://example.com:9000";
 
 		ProviderSettings providerSettings = new ProviderSettings()
 				.issuer(issuer)
 				.authorizationEndpoint(authorizationEndpoint)
 				.tokenEndpoint(tokenEndpoint)
-				.jwkSetEndpoint(jwksEndpoint)
+				.jwksEndpoint(jwksEndpoint)
 				.tokenRevocationEndpoint(tokenRevocationEndpoint);
 
 		assertThat(providerSettings.issuer()).isEqualTo(issuer);
 		assertThat(providerSettings.authorizationEndpoint()).isEqualTo(authorizationEndpoint);
 		assertThat(providerSettings.tokenEndpoint()).isEqualTo(tokenEndpoint);
-		assertThat(providerSettings.jwkSetEndpoint()).isEqualTo(jwksEndpoint);
+		assertThat(providerSettings.jwksEndpoint()).isEqualTo(jwksEndpoint);
 		assertThat(providerSettings.tokenRevocationEndpoint()).isEqualTo(tokenRevocationEndpoint);
 	}
 
 	@Test
-	public void settingWhenCalledThenReturnTokenSettings() {
+	public void settingWhenCustomThenReturnAllSettings() {
 		ProviderSettings providerSettings = new ProviderSettings()
 				.setting("name1", "value1")
 				.settings(settings -> settings.put("name2", "value2"));
@@ -73,54 +72,42 @@ public class ProviderSettingsTests {
 	}
 
 	@Test
-	public void issuerWhenNullThenThrowsIllegalArgumentException() {
+	public void issuerWhenNullThenThrowIllegalArgumentException() {
 		ProviderSettings settings = new ProviderSettings();
 		assertThatThrownBy(() -> settings.issuer(null))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("issuer cannot be null");
+				.hasMessage("value cannot be null");
 	}
 
 	@Test
-	public void authorizationEndpointWhenNullThenThrowsIllegalArgumentException() {
+	public void authorizationEndpointWhenNullThenThrowIllegalArgumentException() {
 		ProviderSettings settings = new ProviderSettings();
 		assertThatThrownBy(() -> settings.authorizationEndpoint(null))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("authorizationEndpoint cannot be empty");
-		assertThatThrownBy(() -> settings.authorizationEndpoint(""))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("authorizationEndpoint cannot be empty");
+				.hasMessage("value cannot be null");
 	}
 
 	@Test
-	public void tokenEndpointWhenNullThenThrowsIllegalArgumentException() {
+	public void tokenEndpointWhenNullThenThrowIllegalArgumentException() {
 		ProviderSettings settings = new ProviderSettings();
 		assertThatThrownBy(() -> settings.tokenEndpoint(null))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("tokenEndpoint cannot be empty");
-		assertThatThrownBy(() -> settings.tokenEndpoint(""))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("tokenEndpoint cannot be empty");
+				.hasMessage("value cannot be null");
 	}
 
 	@Test
-	public void tokenRevocationEndpointWhenNullThenThrowsIllegalArgumentException() {
+	public void tokenRevocationEndpointWhenNullThenThrowIllegalArgumentException() {
 		ProviderSettings settings = new ProviderSettings();
 		assertThatThrownBy(() -> settings.tokenRevocationEndpoint(null))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("tokenRevocationEndpoint cannot be empty");
-		assertThatThrownBy(() -> settings.tokenRevocationEndpoint(""))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("tokenRevocationEndpoint cannot be empty");
+				.hasMessage("value cannot be null");
 	}
 
 	@Test
-	public void jwkSetEndpointWhenNullThenThrowsIllegalArgumentException() {
+	public void jwksEndpointWhenNullThenThrowIllegalArgumentException() {
 		ProviderSettings settings = new ProviderSettings();
-		assertThatThrownBy(() -> settings.jwkSetEndpoint(null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("jwkSetEndpoint cannot be empty");
-		assertThatThrownBy(() -> settings.jwkSetEndpoint(""))
+		assertThatThrownBy(() -> settings.jwksEndpoint(null))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("jwkSetEndpoint cannot be empty");
+				.hasMessage("value cannot be null");
 	}
 }

+ 44 - 21
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OidcProviderConfigurationEndpointFilterTests.java

@@ -38,6 +38,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
  * @author Daniel Garnier-Moiroux
  */
 public class OidcProviderConfigurationEndpointFilterTests {
+
 	@Test
 	public void constructorWhenProviderSettingsNullThenThrowIllegalArgumentException() {
 		assertThatThrownBy(() -> new OidcProviderConfigurationEndpointFilter(null))
@@ -46,8 +47,10 @@ public class OidcProviderConfigurationEndpointFilterTests {
 	}
 
 	@Test
-	public void doFilterWhenRequestDoesNotMatchThenNotProcessed() throws Exception {
-		OidcProviderConfigurationEndpointFilter filter = new OidcProviderConfigurationEndpointFilter(new ProviderSettings());
+	public void doFilterWhenNotConfigurationRequestThenNotProcessed() throws Exception {
+		OidcProviderConfigurationEndpointFilter filter =
+				new OidcProviderConfigurationEndpointFilter(new ProviderSettings());
+
 		String requestUri = "/path";
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
@@ -60,20 +63,38 @@ public class OidcProviderConfigurationEndpointFilterTests {
 	}
 
 	@Test
-	public void doFilterWhenSuccessThenConfigurationResponse() throws Exception {
-		String authorizationEndpoint = "/my-endpoints/authorize";
-		String tokenEndpoint = "/my-endpoints/token";
-		String jwksEndpoint = "/my-endpoints/jwks";
+	public void doFilterWhenConfigurationRequestPostThenNotProcessed() throws Exception {
+		OidcProviderConfigurationEndpointFilter filter =
+				new OidcProviderConfigurationEndpointFilter(new ProviderSettings());
+
+		String requestUri = OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI;
+		MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
+		request.setServletPath(requestUri);
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		FilterChain filterChain = mock(FilterChain.class);
+
+		filter.doFilter(request, response, filterChain);
+
+		verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+	}
+
+	@Test
+	public void doFilterWhenConfigurationRequestThenConfigurationResponse() throws Exception {
+		String authorizationEndpoint = "/oauth2/v1/authorize";
+		String tokenEndpoint = "/oauth2/v1/token";
+		String jwksEndpoint = "/oauth2/v1/jwks";
 
 		ProviderSettings providerSettings = new ProviderSettings()
 				.issuer("https://example.com/issuer1")
 				.authorizationEndpoint(authorizationEndpoint)
 				.tokenEndpoint(tokenEndpoint)
-				.jwkSetEndpoint(jwksEndpoint);
-		OidcProviderConfigurationEndpointFilter filter = new OidcProviderConfigurationEndpointFilter(providerSettings);
+				.jwksEndpoint(jwksEndpoint);
+		OidcProviderConfigurationEndpointFilter filter =
+				new OidcProviderConfigurationEndpointFilter(providerSettings);
 
-		MockHttpServletRequest request = new MockHttpServletRequest("GET", org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI);
-		request.setServletPath(org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI);
+		String requestUri = OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI;
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+		request.setServletPath(requestUri);
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
 
@@ -84,29 +105,31 @@ public class OidcProviderConfigurationEndpointFilterTests {
 		assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
 		String providerConfigurationResponse = response.getContentAsString();
 		assertThat(providerConfigurationResponse).contains("\"issuer\":\"https://example.com/issuer1\"");
-		assertThat(providerConfigurationResponse).contains("\"authorization_endpoint\":\"https://example.com/issuer1/my-endpoints/authorize\"");
-		assertThat(providerConfigurationResponse).contains("\"token_endpoint\":\"https://example.com/issuer1/my-endpoints/token\"");
-		assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/my-endpoints/jwks\"");
+		assertThat(providerConfigurationResponse).contains("\"authorization_endpoint\":\"https://example.com/issuer1/oauth2/v1/authorize\"");
+		assertThat(providerConfigurationResponse).contains("\"token_endpoint\":\"https://example.com/issuer1/oauth2/v1/token\"");
+		assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/v1/jwks\"");
 		assertThat(providerConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]");
 		assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
-		assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]");
+		assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
 		assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
-		assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
+		assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
 	}
 
-
 	@Test
-	public void  doFilterWhenProviderSettingsWithInvalidIssuerThenThrowIllegalArgumentException() {
+	public void doFilterWhenProviderSettingsWithInvalidIssuerThenThrowIllegalArgumentException() {
 		ProviderSettings providerSettings = new ProviderSettings()
 				.issuer("https://this is an invalid URL");
-		OidcProviderConfigurationEndpointFilter filter = new OidcProviderConfigurationEndpointFilter(providerSettings);
+		OidcProviderConfigurationEndpointFilter filter =
+				new OidcProviderConfigurationEndpointFilter(providerSettings);
 
-		MockHttpServletRequest request = new MockHttpServletRequest("GET", org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI);
-		request.setServletPath(org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI);
+		String requestUri = OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI;
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+		request.setServletPath(requestUri);
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
 
 		assertThatThrownBy(() -> filter.doFilter(request, response, filterChain))
-				.isInstanceOf(IllegalArgumentException.class);
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("issuer must be a valid URL");
 	}
 }