Add customize client metadata example in dynamic client registration how-to

Closes gh-1044
This commit is contained in:
Dmitriy Dubson
2023-11-09 11:06:43 -05:00
committed by Joe Grandja
parent 7f1cde0117
commit ef6b1aceca
5 changed files with 160 additions and 7 deletions

View File

@@ -39,6 +39,8 @@ public class ClientRegistrar {
@JsonProperty("client_name") String clientName,
@JsonProperty("grant_types") List<String> grantTypes,
@JsonProperty("redirect_uris") List<String> redirectUris,
@JsonProperty("logo_uri") String logoUri,
List<String> contacts,
String scope) {
}
@@ -50,6 +52,8 @@ public class ClientRegistrar {
@JsonProperty("client_secret") String clientSecret,
@JsonProperty("grant_types") List<String> grantTypes,
@JsonProperty("redirect_uris") List<String> redirectUris,
@JsonProperty("logo_uri") String logoUri,
List<String> contacts,
String scope) {
}
@@ -58,6 +62,8 @@ public class ClientRegistrar {
"client-1",
List.of(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
List.of("https://client.example.org/callback", "https://client.example.org/callback2"),
"https://client.example.org/logo",
List.of("contact-1", "contact-2"),
"openid email profile"
);
@@ -72,6 +78,10 @@ public class ClientRegistrar {
assert (clientRegistrationResponse.redirectUris().contains("https://client.example.org/callback2"));
assert (!clientRegistrationResponse.registrationAccessToken().isEmpty());
assert (!clientRegistrationResponse.registrationClientUri().isEmpty());
assert (clientRegistrationResponse.logoUri().contentEquals("https://client.example.org/logo")); // <6>
assert (clientRegistrationResponse.contacts().size() == 2);
assert (clientRegistrationResponse.contacts().contains("contact-1"));
assert (clientRegistrationResponse.contacts().contains("contact-2"));
String registrationAccessToken = clientRegistrationResponse.registrationAccessToken(); // <7>
String registrationClientUri = clientRegistrationResponse.registrationClientUri();
@@ -85,6 +95,10 @@ public class ClientRegistrar {
assert (retrievedClient.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()));
assert (retrievedClient.redirectUris().contains("https://client.example.org/callback"));
assert (retrievedClient.redirectUris().contains("https://client.example.org/callback2"));
assert (retrievedClient.logoUri().contentEquals("https://client.example.org/logo"));
assert (retrievedClient.contacts().size() == 2);
assert (retrievedClient.contacts().contains("contact-1"));
assert (retrievedClient.contacts().contains("contact-2"));
assert (Objects.isNull(retrievedClient.registrationAccessToken()));
assert (!retrievedClient.registrationClientUri().isEmpty());
}

View File

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

View File

@@ -24,6 +24,8 @@ 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.web.SecurityFilterChain;
import static sample.registration.CustomMetadataConfig.registeredClientConverters;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@@ -31,10 +33,13 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(oidc -> oidc.clientRegistrationEndpoint(Customizer.withDefaults())); // <1>
http.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.jwt(Customizer.withDefaults()));
.oidc(oidc -> oidc.clientRegistrationEndpoint(endpoint -> {
endpoint.authenticationProviders(registeredClientConverters()); // <1>
}));
http.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt(Customizer.withDefaults()));
return http.build();
}

View File

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