Browse Source

Add issuerUri to ClientRegistration.providerDetails

- Add "issuerUri" attribute to ClientRegistration.providerDetails for OpenID Connect Discovery 1.0 or OAuth 2.0 Authorization Server Metadata.
- Validate OidcIdToken "iss" claim against the OpenID Provider "issuerUri" value.
- Update documentation for client registration: it includes issuer-uri property now.

Fixes gh-8326
Thomas Vitale 5 years ago
parent
commit
78fa859798

+ 1 - 0
config/src/main/java/org/springframework/security/config/oauth2/client/CommonOAuth2Provider.java

@@ -42,6 +42,7 @@ public enum CommonOAuth2Provider {
 			builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
 			builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");
 			builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
+			builder.issuerUri("https://accounts.google.com");
 			builder.userNameAttributeName(IdTokenClaimNames.SUB);
 			builder.clientName("Google");
 			return builder;

+ 2 - 0
config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java

@@ -165,6 +165,7 @@ public class ClientRegistrationsBeanDefinitionParserTests {
 				.isEqualTo(AuthenticationMethod.HEADER);
 		assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub");
 		assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
+		assertThat(googleProviderDetails.getIssuerUri()).isEqualTo(serverUrl);
 	}
 
 	@Test
@@ -195,6 +196,7 @@ public class ClientRegistrationsBeanDefinitionParserTests {
 				.isEqualTo(AuthenticationMethod.HEADER);
 		assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub");
 		assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
+		assertThat(googleProviderDetails.getIssuerUri()).isEqualTo("https://accounts.google.com");
 
 		ClientRegistration githubRegistration = clientRegistrationRepository.findByRegistrationId("github-login");
 		assertThat(githubRegistration).isNotNull();

+ 3 - 1
docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-login.adoc

@@ -137,9 +137,11 @@ The following table outlines the mapping of the Spring Boot 2.x OAuth Client pro
 |`spring.security.oauth2.client.provider._[providerId]_.user-info-authentication-method`
 |`providerDetails.userInfoEndpoint.authenticationMethod`
 
-
 |`spring.security.oauth2.client.provider._[providerId]_.user-name-attribute`
 |`providerDetails.userInfoEndpoint.userNameAttributeName`
+
+|`spring.security.oauth2.client.provider._[providerId]_.issuer-uri`
+|`providerDetails.issuerUri`
 |===
 
 [TIP]

+ 1 - 2
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java

@@ -69,8 +69,7 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
 
 		// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
 		// MUST exactly match the value of the iss (issuer) Claim.
-		String metadataIssuer = (String) this.clientRegistration.getProviderDetails().getConfigurationMetadata()
-				.get("issuer");
+		String metadataIssuer = this.clientRegistration.getProviderDetails().getIssuerUri();
 
 		if (metadataIssuer != null && !Objects.equals(metadataIssuer, idToken.getIssuer().toExternalForm())) {
 			invalidClaims.put(IdTokenClaimNames.ISS, idToken.getIssuer());

+ 25 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java

@@ -163,6 +163,7 @@ public final class ClientRegistration implements Serializable {
 		private String tokenUri;
 		private UserInfoEndpoint userInfoEndpoint = new UserInfoEndpoint();
 		private String jwkSetUri;
+		private String issuerUri;
 		private Map<String, Object> configurationMetadata = Collections.emptyMap();
 
 		private ProviderDetails() {
@@ -204,6 +205,16 @@ public final class ClientRegistration implements Serializable {
 			return this.jwkSetUri;
 		}
 
+		/**
+		 * Returns the uri for the OpenID Provider Issuer.
+		 *
+		 * @since 5.4
+		 * @return the uri for the OpenID Provider Issuer
+		 */
+		public String getIssuerUri() {
+			return this.issuerUri;
+		}
+
 		/**
 		 * Returns a {@code Map} of the metadata describing the provider's configuration.
 		 *
@@ -296,6 +307,7 @@ public final class ClientRegistration implements Serializable {
 		private AuthenticationMethod userInfoAuthenticationMethod = AuthenticationMethod.HEADER;
 		private String userNameAttributeName;
 		private String jwkSetUri;
+		private String issuerUri;
 		private Map<String, Object> configurationMetadata = Collections.emptyMap();
 		private String clientName;
 
@@ -317,6 +329,7 @@ public final class ClientRegistration implements Serializable {
 			this.userInfoAuthenticationMethod = clientRegistration.providerDetails.userInfoEndpoint.authenticationMethod;
 			this.userNameAttributeName = clientRegistration.providerDetails.userInfoEndpoint.userNameAttributeName;
 			this.jwkSetUri = clientRegistration.providerDetails.jwkSetUri;
+			this.issuerUri = clientRegistration.providerDetails.issuerUri;
 			Map<String, Object> configurationMetadata = clientRegistration.providerDetails.configurationMetadata;
 			if (configurationMetadata != EMPTY_MAP) {
 				this.configurationMetadata = new HashMap<>(configurationMetadata);
@@ -486,6 +499,17 @@ public final class ClientRegistration implements Serializable {
 			return this;
 		}
 
+		/**
+		 * Sets the uri for the OpenID Provider Issuer.
+		 *
+		 * @param issuerUri the uri for the OpenID Provider Issuer
+		 * @return the {@link Builder}
+		 */
+		public Builder issuerUri(String issuerUri) {
+			this.issuerUri = issuerUri;
+			return this;
+		}
+
 		/**
 		 * Sets the metadata describing the provider's configuration.
 		 *
@@ -554,6 +578,7 @@ public final class ClientRegistration implements Serializable {
 			providerDetails.userInfoEndpoint.authenticationMethod = this.userInfoAuthenticationMethod;
 			providerDetails.userInfoEndpoint.userNameAttributeName = this.userNameAttributeName;
 			providerDetails.jwkSetUri = this.jwkSetUri;
+			providerDetails.issuerUri = this.issuerUri;
 			providerDetails.configurationMetadata = Collections.unmodifiableMap(this.configurationMetadata);
 			clientRegistration.providerDetails = providerDetails;
 

+ 1 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java

@@ -248,6 +248,7 @@ public final class ClientRegistrations {
 				.authorizationUri(metadata.getAuthorizationEndpointURI().toASCIIString())
 				.providerConfigurationMetadata(configurationMetadata)
 				.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
+				.issuerUri(issuer)
 				.clientName(issuer);
 	}
 

+ 4 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java

@@ -139,6 +139,8 @@ public class OAuth2AuthorizedClientMixinTests {
 				.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
 		assertThat(clientRegistration.getProviderDetails().getJwkSetUri())
 				.isEqualTo(expectedClientRegistration.getProviderDetails().getJwkSetUri());
+		assertThat(clientRegistration.getProviderDetails().getIssuerUri())
+				.isEqualTo(expectedClientRegistration.getProviderDetails().getIssuerUri());
 		assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata())
 				.containsExactlyEntriesOf(clientRegistration.getProviderDetails().getConfigurationMetadata());
 		assertThat(clientRegistration.getClientName())
@@ -203,6 +205,7 @@ public class OAuth2AuthorizedClientMixinTests {
 				.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod());
 		assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).isNull();
 		assertThat(clientRegistration.getProviderDetails().getJwkSetUri()).isNull();
+		assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNull();
 		assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isEmpty();
 		assertThat(clientRegistration.getClientName())
 				.isEqualTo(clientRegistration.getRegistrationId());
@@ -276,6 +279,7 @@ public class OAuth2AuthorizedClientMixinTests {
 				"        \"userNameAttributeName\": " + (userInfoEndpoint.getUserNameAttributeName() != null ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" +
 				"      },\n" +
 				"      \"jwkSetUri\": " + (providerDetails.getJwkSetUri() != null ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" +
+				"      \"issuerUri\": " + (providerDetails.getIssuerUri() != null ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" +
 				"      \"configurationMetadata\": {\n" +
 				"        " + configurationMetadata + "\n" +
 				"      }\n" +

+ 2 - 6
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidatorTests.java

@@ -98,9 +98,7 @@ public class OidcIdTokenValidatorTests {
 		 * When the issuer is set in the provider metadata, and it does not match the issuer in the ID Token,
 		 * the validation must fail
 		 */
-		Map<String, Object> configurationMetadata = new HashMap<>();
-		configurationMetadata.put("issuer", "https://issuer.somethingelse.com");
-		this.registration = this.registration.providerConfigurationMetadata(configurationMetadata);
+		this.registration = this.registration.issuerUri("https://issuer.somethingelse.com");
 
 		assertThat(this.validateIdToken())
 				.hasSize(1)
@@ -114,9 +112,7 @@ public class OidcIdTokenValidatorTests {
 		 * When the issuer is set in the provider metadata, and it does match the issuer in the ID Token,
 		 * the validation must succeed
 		 */
-		Map<String, Object> configurationMetadata = new HashMap<>();
-		configurationMetadata.put("issuer", "https://issuer.example.com");
-		this.registration = this.registration.providerConfigurationMetadata(configurationMetadata);
+		this.registration = this.registration.issuerUri("https://issuer.example.com");
 
 		assertThat(this.validateIdToken()).isEmpty();
 	}

+ 1 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java

@@ -162,6 +162,7 @@ public class ClientRegistrationsTest {
 		assertThat(provider.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth");
 		assertThat(provider.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token");
 		assertThat(provider.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
+		assertThat(provider.getIssuerUri()).isEqualTo(this.issuer);
 		assertThat(provider.getConfigurationMetadata()).containsKeys("authorization_endpoint", "claims_supported",
 				"code_challenge_methods_supported", "id_token_signing_alg_values_supported", "issuer", "jwks_uri",
 				"response_types_supported", "revocation_endpoint", "scopes_supported", "subject_types_supported",