Polish gh-1376

This commit is contained in:
Joe Grandja
2023-11-16 14:29:07 -05:00
parent ef6b1aceca
commit 74058e555c
5 changed files with 67 additions and 68 deletions

View File

@@ -23,32 +23,24 @@ To enable, add the following configuration:
include::{examples-dir}/main/java/sample/registration/SecurityConfig.java[] include::{examples-dir}/main/java/sample/registration/SecurityConfig.java[]
---- ----
<1> Enable the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint] with client registration endpoint authentication providers for providing custom metadata. Providing custom metadata is optional. <1> Enable the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint] with the default configuration.
<2> Optionally, customize the default ``AuthenticationProvider``'s to support custom client metadata parameters.
In order to accept custom client metadata when registering a client, a few additional implementation details In order to support custom client metadata parameters when registering a client, a few additional implementation details are required.
are necessary.
[NOTE] The following example shows a sample implementation of ``Converter``'s that support custom client metadata parameters (`logo_uri` and `contacts`) and are configured in `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider`.
====
The following example depicts custom metadata `logo_uri` (string type) and `contacts` (string array type)
====
Create a set of custom `Converter` classes in order to retain custom client claims. [[sample.CustomClientMetadataConfig]]
[[sample.CustomMetadataConfig]]
[source,java] [source,java]
---- ----
include::{examples-dir}/main/java/sample/registration/CustomMetadataConfig.java[] include::{examples-dir}/main/java/sample/registration/CustomClientMetadataConfig.java[]
---- ----
<1> Create a `Consumer<List<AuthenticationProvider>>` implementation. <1> Define a `Consumer<List<AuthenticationProvider>>` providing the ability to customize the default ``AuthenticationProvider``'s.
<2> Identify custom fields that should be accepted during client registration. <2> Define custom client metadata parameters that are supported for client registration.
<3> Filter for `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider` instances. <3> Configure `OidcClientRegistrationAuthenticationProvider.setRegisteredClientConverter()` with a `CustomRegisteredClientConverter`.
<4> Add a custom registered client `Converter` (implementation in #7) <4> Configure `OidcClientRegistrationAuthenticationProvider.setClientRegistrationConverter()` with a `CustomClientRegistrationConverter`.
<5> Add a custom client registration `Converter` to `OidcClientRegistrationAuthenticationProvider` (implementation in #8) <5> Configure `OidcClientConfigurationAuthenticationProvider.setClientRegistrationConverter()` with a `CustomClientRegistrationConverter`.
<6> Add a custom client registration `Converter` to `OidcClientConfigurationAuthenticationProvider` (implementation in #8)
<7> Custom registered client `Converter` implementation that adds custom claims to registered client settings.
<8> Custom client registration `Converter` implementation that modifies client registration claims with custom metadata.
[[configure-client-registrar]] [[configure-client-registrar]]
== Configure client registrar == Configure client registrar
@@ -114,8 +106,8 @@ After the client is registered, the access token is invalidated.
include::{examples-dir}/main/java/sample/registration/ClientRegistrar.java[] include::{examples-dir}/main/java/sample/registration/ClientRegistrar.java[]
---- ----
<1> A minimal representation of a client registration request. You may add additional client metadata parameters as per https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration Request]. This example request contains custom metadata fields `logo_uri` and `contacts`. <1> A minimal representation of a client registration request. You may add additional client metadata parameters as per https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration Request]. This example request contains custom client metadata parameters `logo_uri` and `contacts`.
<2> A minimal representation of a client registration response. You may add additional client metadata parameters as per https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[Client Registration Response]. This example response contains custom metadata fields `logo_uri` and `contacts`. <2> A minimal representation of a client registration response. You may add additional client metadata parameters as per https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[Client Registration Response]. This example response contains custom client metadata parameters `logo_uri` and `contacts`.
<3> Example demonstrating client registration and client retrieval. <3> Example demonstrating client registration and client retrieval.
<4> A sample client registration request object. <4> A sample client registration request object.
<5> Register the client using the "initial" access token and client registration request object. <5> Register the client using the "initial" access token and client registration request object.

View File

@@ -78,7 +78,7 @@ public class ClientRegistrar {
assert (clientRegistrationResponse.redirectUris().contains("https://client.example.org/callback2")); assert (clientRegistrationResponse.redirectUris().contains("https://client.example.org/callback2"));
assert (!clientRegistrationResponse.registrationAccessToken().isEmpty()); assert (!clientRegistrationResponse.registrationAccessToken().isEmpty());
assert (!clientRegistrationResponse.registrationClientUri().isEmpty()); assert (!clientRegistrationResponse.registrationClientUri().isEmpty());
assert (clientRegistrationResponse.logoUri().contentEquals("https://client.example.org/logo")); // <6> assert (clientRegistrationResponse.logoUri().contentEquals("https://client.example.org/logo"));
assert (clientRegistrationResponse.contacts().size() == 2); assert (clientRegistrationResponse.contacts().size() == 2);
assert (clientRegistrationResponse.contacts().contains("contact-1")); assert (clientRegistrationResponse.contacts().contains("contact-1"));
assert (clientRegistrationResponse.contacts().contains("contact-2")); assert (clientRegistrationResponse.contacts().contains("contact-2"));

View File

@@ -15,6 +15,13 @@
*/ */
package sample.registration; package sample.registration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -26,84 +33,84 @@ import org.springframework.security.oauth2.server.authorization.oidc.converter.R
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.HashMap; public class CustomClientMetadataConfig {
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CustomMetadataConfig { public static Consumer<List<AuthenticationProvider>> configureCustomClientMetadataConverters() { // <1>
public static Consumer<List<AuthenticationProvider>> registeredClientConverters() { List<String> customClientMetadata = List.of("logo_uri", "contacts"); // <2>
List<String> customClientMetadata = List.of("logo_uri", "contacts"); // <1>
return authenticationProviders -> // <2> return (authenticationProviders) -> {
{ CustomRegisteredClientConverter registeredClientConverter =
CustomRegisteredClientConverter registeredClientConverter = new CustomRegisteredClientConverter(customClientMetadata); new CustomRegisteredClientConverter(customClientMetadata);
CustomClientRegistrationConverter clientRegistrationConverter = new CustomClientRegistrationConverter(customClientMetadata); CustomClientRegistrationConverter clientRegistrationConverter =
new CustomClientRegistrationConverter(customClientMetadata);
authenticationProviders.forEach(authenticationProvider -> { authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { // <3> if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
provider.setRegisteredClientConverter(registeredClientConverter); // <4> provider.setRegisteredClientConverter(registeredClientConverter); // <3>
provider.setClientRegistrationConverter(clientRegistrationConverter); // <5> provider.setClientRegistrationConverter(clientRegistrationConverter); // <4>
} }
if (authenticationProvider instanceof OidcClientConfigurationAuthenticationProvider provider) { if (authenticationProvider instanceof OidcClientConfigurationAuthenticationProvider provider) {
provider.setClientRegistrationConverter(clientRegistrationConverter); // <6> provider.setClientRegistrationConverter(clientRegistrationConverter); // <5>
} }
}); });
}; };
} }
static class CustomRegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> { // <7> private static class CustomRegisteredClientConverter
private final List<String> customMetadata; implements Converter<OidcClientRegistration, RegisteredClient> {
private final List<String> customClientMetadata;
private final OidcClientRegistrationRegisteredClientConverter delegate; private final OidcClientRegistrationRegisteredClientConverter delegate;
CustomRegisteredClientConverter(List<String> customMetadata) { private CustomRegisteredClientConverter(List<String> customClientMetadata) {
this.customMetadata = customMetadata; this.customClientMetadata = customClientMetadata;
this.delegate = new OidcClientRegistrationRegisteredClientConverter(); this.delegate = new OidcClientRegistrationRegisteredClientConverter();
} }
@Override
public RegisteredClient convert(OidcClientRegistration clientRegistration) { public RegisteredClient convert(OidcClientRegistration clientRegistration) {
RegisteredClient convertedClient = delegate.convert(clientRegistration); RegisteredClient registeredClient = this.delegate.convert(clientRegistration);
ClientSettings.Builder clientSettingsBuilder = ClientSettings ClientSettings.Builder clientSettingsBuilder = ClientSettings.withSettings(
.withSettings(convertedClient.getClientSettings().getSettings()); registeredClient.getClientSettings().getSettings());
if (!CollectionUtils.isEmpty(this.customClientMetadata)) {
if (!CollectionUtils.isEmpty(this.customMetadata)) {
clientRegistration.getClaims().forEach((claim, value) -> { clientRegistration.getClaims().forEach((claim, value) -> {
if (this.customMetadata.contains(claim)) { if (this.customClientMetadata.contains(claim)) {
clientSettingsBuilder.setting(claim, value); clientSettingsBuilder.setting(claim, value);
} }
}); });
} }
return RegisteredClient.from(convertedClient).clientSettings(clientSettingsBuilder.build()).build(); return RegisteredClient.from(registeredClient)
.clientSettings(clientSettingsBuilder.build())
.build();
} }
} }
static class CustomClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> { // <8> private static class CustomClientRegistrationConverter
private final List<String> customMetadata; implements Converter<RegisteredClient, OidcClientRegistration> {
private final List<String> customClientMetadata;
private final RegisteredClientOidcClientRegistrationConverter delegate; private final RegisteredClientOidcClientRegistrationConverter delegate;
CustomClientRegistrationConverter(List<String> customMetadata) { private CustomClientRegistrationConverter(List<String> customClientMetadata) {
this.customMetadata = customMetadata; this.customClientMetadata = customClientMetadata;
this.delegate = new RegisteredClientOidcClientRegistrationConverter(); this.delegate = new RegisteredClientOidcClientRegistrationConverter();
} }
@Override
public OidcClientRegistration convert(RegisteredClient registeredClient) { public OidcClientRegistration convert(RegisteredClient registeredClient) {
var clientRegistration = delegate.convert(registeredClient); OidcClientRegistration clientRegistration = this.delegate.convert(registeredClient);
Map<String, Object> claims = new HashMap<>(clientRegistration.getClaims()); Map<String, Object> claims = new HashMap<>(clientRegistration.getClaims());
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(this.customClientMetadata)) {
ClientSettings clientSettings = registeredClient.getClientSettings(); ClientSettings clientSettings = registeredClient.getClientSettings();
claims.putAll(this.customClientMetadata.stream()
claims.putAll(customMetadata.stream() .filter(metadata -> clientSettings.getSetting(metadata) != null)
.filter(metadatum -> clientSettings.getSetting(metadatum) != null)
.collect(Collectors.toMap(Function.identity(), clientSettings::getSetting))); .collect(Collectors.toMap(Function.identity(), clientSettings::getSetting)));
} }
return OidcClientRegistration.withClaims(claims).build(); return OidcClientRegistration.withClaims(claims).build();
} }
} }
} }

View File

@@ -24,7 +24,7 @@ import org.springframework.security.oauth2.server.authorization.config.annotatio
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import static sample.registration.CustomMetadataConfig.registeredClientConverters; import static sample.registration.CustomClientMetadataConfig.configureCustomClientMetadataConverters;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@@ -33,13 +33,13 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(oidc -> oidc.clientRegistrationEndpoint(endpoint -> { .oidc(oidc -> oidc.clientRegistrationEndpoint(clientRegistrationEndpoint -> { // <1>
endpoint.authenticationProviders(registeredClientConverters()); // <1> clientRegistrationEndpoint
.authenticationProviders(configureCustomClientMetadataConverters()); // <2>
})); }));
http.oauth2ResourceServer(oauth2ResourceServer ->
http.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt(Customizer.withDefaults())); oauth2ResourceServer.jwt(Customizer.withDefaults()));
return http.build(); return http.build();
} }

View File

@@ -56,7 +56,7 @@ public class DynamicClientRegistrationTests {
private String port; private String port;
@Test @Test
public void dynamicallyRegisterClientWithCustomMetadata() throws Exception { public void dynamicallyRegisterClientWithCustomClientMetadata() throws Exception {
MockHttpServletResponse tokenResponse = this.mvc.perform(post("/oauth2/token") MockHttpServletResponse tokenResponse = this.mvc.perform(post("/oauth2/token")
.with(httpBasic("registrar-client", "secret")) .with(httpBasic("registrar-client", "secret"))
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())