Add ClientRegistrations.fromOidcConfiguration method
ClientRegistrations now provides the fromOidcConfiguration method to create a ClientRegistration.Builder from a map representation of an OpenID Provider Configuration Response. This is useful when the OpenID Provider Configuration is not available at a well-known location, or if custom validation is needed for the issuer location (e.g. if the issuer is only reachable via a back-channel URI that is different from the issuer value in the configuration). Fixes: gh-14633
This commit is contained in:
parent
1dd79c379b
commit
a3fd551fb5
|
@ -72,6 +72,46 @@ public final class ClientRegistrations {
|
|||
private ClientRegistrations() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ClientRegistration.Builder} using the provided map representation
|
||||
* of an <a href=
|
||||
* "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">OpenID
|
||||
* Provider Configuration Response</a> to initialize the
|
||||
* {@link ClientRegistration.Builder}.
|
||||
*
|
||||
* <p>
|
||||
* This is useful when the OpenID Provider Configuration is not available at a
|
||||
* well-known location, or if custom validation is needed for the issuer location
|
||||
* (e.g. if the issuer is only accessible from a back-channel URI that is different
|
||||
* from the issuer value in the configuration).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Example usage:
|
||||
* </p>
|
||||
* <pre>
|
||||
* RequestEntity<Void> request = RequestEntity.get(metadataEndpoint).build();
|
||||
* ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {};
|
||||
* Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
|
||||
* // Validate configuration.get("issuer") as per in the OIDC specification
|
||||
* ClientRegistration registration = ClientRegistrations.fromOidcConfiguration(configuration)
|
||||
* .clientId("client-id")
|
||||
* .clientSecret("client-secret")
|
||||
* .build();
|
||||
* </pre>
|
||||
* @param the OpenID Provider configuration map
|
||||
* @return the {@link ClientRegistration} built from the configuration
|
||||
*/
|
||||
public static ClientRegistration.Builder fromOidcConfiguration(Map<String, Object> configuration) {
|
||||
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
|
||||
ClientRegistration.Builder builder = withProviderConfiguration(metadata, metadata.getIssuer().getValue());
|
||||
builder.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
|
||||
if (metadata.getUserInfoEndpointURI() != null) {
|
||||
builder.userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString());
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ClientRegistration.Builder} using the provided <a href=
|
||||
* "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
|
||||
|
|
|
@ -455,6 +455,120 @@ public class ClientRegistrationsTests {
|
|||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationAllInformationThenSuccess() throws Exception {
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
ClientRegistration.ProviderDetails provider = registration.getProviderDetails();
|
||||
assertIssuerMetadata(registration, provider);
|
||||
assertThat(provider.getUserInfoEndpoint().getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo");
|
||||
}
|
||||
|
||||
private ClientRegistration.Builder registration(Map<String, Object> configuration) {
|
||||
this.issuer = "https://example.com";
|
||||
return ClientRegistrations.fromOidcConfiguration(configuration)
|
||||
.clientId("client-id")
|
||||
.clientSecret("client-secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationResponseMissingJwksUriThenThrowsIllegalArgumentException() throws Exception {
|
||||
this.response.remove("jwks_uri");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> registration(this.response).build())
|
||||
.withMessageContaining("The public JWK set URI must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationResponseMissingUserInfoUriThenSuccess() throws Exception {
|
||||
this.response.remove("userinfo_endpoint");
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
assertThat(registration.getProviderDetails().getUserInfoEndpoint().getUri()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationGrantTypesSupportedNullThenDefaulted() throws Exception {
|
||||
this.response.remove("grant_types_supported");
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationImplicitGrantTypeThenSuccess() throws Exception {
|
||||
this.response.put("grant_types_supported", Arrays.asList("implicit"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
// The authorization_code grant type is still the default
|
||||
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationResponseAuthorizationEndpointIsNullThenSuccess() throws Exception {
|
||||
this.response.put("grant_types_supported", Arrays.asList("urn:ietf:params:oauth:grant-type:jwt-bearer"));
|
||||
this.response.remove("authorization_endpoint");
|
||||
ClientRegistration registration = registration(this.response)
|
||||
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
|
||||
.build();
|
||||
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
|
||||
ClientRegistration.ProviderDetails provider = registration.getProviderDetails();
|
||||
assertThat(provider.getAuthorizationUri()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationTokenEndpointAuthMethodsNullThenDefaulted() throws Exception {
|
||||
this.response.remove("token_endpoint_auth_methods_supported");
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationClientSecretBasicAuthMethodThenMethodIsBasic() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("client_secret_basic"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationTokenEndpointAuthMethodsPostThenMethodIsPost() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("client_secret_post"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationClientSecretJwtAuthMethodThenMethodIsClientSecretBasic() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("client_secret_jwt"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
// The client_secret_basic auth method is still the default
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationPrivateKeyJwtAuthMethodThenMethodIsClientSecretBasic() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("private_key_jwt"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
// The client_secret_basic auth method is still the default
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationTokenEndpointAuthMethodsNoneThenMethodIsNone() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("none"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationTlsClientAuthMethodThenSuccess() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("tls_client_auth"));
|
||||
ClientRegistration registration = registration(this.response).build();
|
||||
// The client_secret_basic auth method is still the default
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
}
|
||||
|
||||
private ClientRegistration.Builder registration(String path) throws Exception {
|
||||
this.issuer = createIssuerFromServer(path);
|
||||
this.response.put("issuer", this.issuer);
|
||||
|
|
Loading…
Reference in New Issue