diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle
index fc56abf254..e59f23fb09 100644
--- a/config/spring-security-config.gradle
+++ b/config/spring-security-config.gradle
@@ -34,6 +34,7 @@ dependencies {
testCompile apachedsDependencies
testCompile powerMock2Dependencies
testCompile spockDependencies
+ testCompile 'com.squareup.okhttp3:mockwebserver'
testCompile 'ch.qos.logback:logback-classic'
testCompile 'io.projectreactor.ipc:reactor-netty'
testCompile 'javax.annotation:jsr250-api:1.0'
diff --git a/config/src/main/java/org/springframework/security/config/oauth2/client/OidcConfigurationProvider.java b/config/src/main/java/org/springframework/security/config/oauth2/client/OidcConfigurationProvider.java
new file mode 100644
index 0000000000..77984669c5
--- /dev/null
+++ b/config/src/main/java/org/springframework/security/config/oauth2/client/OidcConfigurationProvider.java
@@ -0,0 +1,118 @@
+/*
+ * 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
+ *
+ * http://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.config.oauth2.client;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
+import org.springframework.web.client.RestTemplate;
+
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+/**
+ * Allows creating a {@link ClientRegistration.Builder} from an
+ * OpenID Provider Configuration.
+ *
+ * @author Rob Winch
+ * @since 5.1
+ */
+public final class OidcConfigurationProvider {
+
+ /**
+ * Given the Issuer creates a
+ * {@link ClientRegistration.Builder} by making an
+ * OpenID Provider
+ * Configuration Request and using the values in the
+ * OpenID
+ * Provider Configuration Response to initialize the {@link ClientRegistration.Builder}.
+ *
+ *
+ * For example if the issuer provided is "https://example.com", then an "OpenID Provider Configuration Request" will
+ * be made to "https://example.com/.well-known/openid-configuration". The result is expected to be an "OpenID
+ * Provider Configuration Response".
+ *
+ *
+ *
+ * Example usage:
+ *
+ *
+ * ClientRegistration registration = OidcConfigurationProvider.issuer("https://example.com")
+ * .clientId("client-id")
+ * .clientSecret("client-secret")
+ * .build();
+ *
+ * @param issuer the Issuer
+ * @return a {@link ClientRegistration.Builder} that was initialized by the OpenID Provider Configuration.
+ */
+ public static ClientRegistration.Builder issuer(String issuer) {
+ RestTemplate rest = new RestTemplate();
+ String openidConfiguration = rest.getForObject(issuer + "/.well-known/openid-configuration", String.class);
+ OIDCProviderMetadata metadata = parse(openidConfiguration);
+ String name = URI.create(issuer).getHost();
+ List metadataAuthMethods = metadata.getTokenEndpointAuthMethods();
+ // if null, the default includes client_secret_basic
+ if (metadataAuthMethods != null && !metadataAuthMethods.contains(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
+ throw new IllegalArgumentException("Only ClientAuthenticationMethod.BASIC is supported. The issuer \"" + issuer + "\" returned a configuration of " + metadataAuthMethods);
+ }
+ List grantTypes = metadata.getGrantTypes();
+ // If null, the default includes authorization_code
+ if (grantTypes != null && !grantTypes.contains(GrantType.AUTHORIZATION_CODE)) {
+ throw new IllegalArgumentException("Only AuthorizationGrantType.AUTHORIZATION_CODE is supported. The issuer \"" + issuer + "\" returned a configuration of " + grantTypes);
+ }
+ List scopes = getScopes(metadata);
+ return ClientRegistration.withRegistrationId(name)
+ .userNameAttributeName(IdTokenClaimNames.SUB)
+ .scope(scopes)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
+ .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
+ .authorizationUri(metadata.getAuthorizationEndpointURI().toASCIIString())
+ .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
+ .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString())
+ .tokenUri(metadata.getTokenEndpointURI().toASCIIString())
+ .clientName(issuer);
+ }
+
+ private static List getScopes(OIDCProviderMetadata metadata) {
+ Scope scope = metadata.getScopes();
+ if (scope == null) {
+ // If null, default to "openid" which must be supported
+ return Arrays.asList("openid");
+ } else {
+ return scope.toStringList();
+ }
+ }
+
+ private static OIDCProviderMetadata parse(String body) {
+ try {
+ return OIDCProviderMetadata.parse(body);
+ }
+ catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private OidcConfigurationProvider() {}
+}
diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/OidcConfigurationProviderTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/OidcConfigurationProviderTests.java
new file mode 100644
index 0000000000..bd967c58ed
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/oauth2/client/OidcConfigurationProviderTests.java
@@ -0,0 +1,209 @@
+/*
+ * 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
+ *
+ * http://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.config.oauth2.client;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.*;
+
+/**
+ * @author Rob Winch
+ * @since 5.1
+ */
+public class OidcConfigurationProviderTests {
+
+ /**
+ * Contains all optional parameters that are found in ClientRegistration
+ */
+ private static final String DEFAULT_RESPONSE =
+ "{\n"
+ + " \"authorization_endpoint\": \"https://example.com/o/oauth2/v2/auth\", \n"
+ + " \"claims_supported\": [\n"
+ + " \"aud\", \n"
+ + " \"email\", \n"
+ + " \"email_verified\", \n"
+ + " \"exp\", \n"
+ + " \"family_name\", \n"
+ + " \"given_name\", \n"
+ + " \"iat\", \n"
+ + " \"iss\", \n"
+ + " \"locale\", \n"
+ + " \"name\", \n"
+ + " \"picture\", \n"
+ + " \"sub\"\n"
+ + " ], \n"
+ + " \"code_challenge_methods_supported\": [\n"
+ + " \"plain\", \n"
+ + " \"S256\"\n"
+ + " ], \n"
+ + " \"id_token_signing_alg_values_supported\": [\n"
+ + " \"RS256\"\n"
+ + " ], \n"
+ + " \"issuer\": \"https://example.com\", \n"
+ + " \"jwks_uri\": \"https://example.com/oauth2/v3/certs\", \n"
+ + " \"response_types_supported\": [\n"
+ + " \"code\", \n"
+ + " \"token\", \n"
+ + " \"id_token\", \n"
+ + " \"code token\", \n"
+ + " \"code id_token\", \n"
+ + " \"token id_token\", \n"
+ + " \"code token id_token\", \n"
+ + " \"none\"\n"
+ + " ], \n"
+ + " \"revocation_endpoint\": \"https://example.com/o/oauth2/revoke\", \n"
+ + " \"scopes_supported\": [\n"
+ + " \"openid\", \n"
+ + " \"email\", \n"
+ + " \"profile\"\n"
+ + " ], \n"
+ + " \"subject_types_supported\": [\n"
+ + " \"public\"\n"
+ + " ], \n"
+ + " \"grant_types_supported\" : [\"authorization_code\"], \n"
+ + " \"token_endpoint\": \"https://example.com/oauth2/v4/token\", \n"
+ + " \"token_endpoint_auth_methods_supported\": [\n"
+ + " \"client_secret_post\", \n"
+ + " \"client_secret_basic\"\n"
+ + " ], \n"
+ + " \"userinfo_endpoint\": \"https://example.com/oauth2/v3/userinfo\"\n"
+ + "}";
+
+ private MockWebServer server;
+
+ private ObjectMapper mapper = new ObjectMapper();
+
+ private Map response;
+
+ private String issuer;
+
+ @Before
+ public void setup() throws Exception {
+ this.server = new MockWebServer();
+ this.server.start();
+ this.response = this.mapper.readValue(DEFAULT_RESPONSE, new TypeReference