Browse Source

Polish gh-1320

Joe Grandja 1 year ago
parent
commit
3eb951f59d

+ 45 - 59
docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc

@@ -3,60 +3,50 @@
 :index-link: ../how-to.html
 :index-link: ../how-to.html
 :docs-dir: ..
 :docs-dir: ..
 
 
-This guide shows how to configure OpenID Connect Dynamic Client Registration 1.0 in Spring Authorization Server and walks through an example of how to register a client.
-Spring Authorization Server implements https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration 1.0]
-specification, gaining the ability to dynamically register and retrieve OpenID clients.
+This guide shows how to configure OpenID Connect Dynamic Client Registration in Spring Authorization Server and walks through an example of how to register a client.
+Spring Authorization Server implements the https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration 1.0] specification, providing the capability to dynamically register and retrieve OpenID Connect clients.
 
 
-- xref:guides/how-to-dynamic-client-registration.adoc#enable[Enable Dynamic Client Registration]
-- xref:guides/how-to-dynamic-client-registration.adoc#configure-initial-client[Configure initial client]
-- xref:guides/how-to-dynamic-client-registration.adoc#obtain-initial-access-token[Obtain initial access token]
-- xref:guides/how-to-dynamic-client-registration.adoc#register-client[Register a client]
+* xref:guides/how-to-dynamic-client-registration.adoc#enable-dynamic-client-registration[Enable Dynamic Client Registration]
+* xref:guides/how-to-dynamic-client-registration.adoc#configure-client-registrar[Configure client registrar]
+* xref:guides/how-to-dynamic-client-registration.adoc#obtain-initial-access-token[Obtain initial access token]
+* xref:guides/how-to-dynamic-client-registration.adoc#register-client[Register a client]
 
 
-[[enable]]
+[[enable-dynamic-client-registration]]
 == Enable Dynamic Client Registration
 == Enable Dynamic Client Registration
 
 
 By default, dynamic client registration functionality is disabled in Spring Authorization Server.
 By default, dynamic client registration functionality is disabled in Spring Authorization Server.
 To enable, add the following configuration:
 To enable, add the following configuration:
 
 
-[[sample.dcrAuthServerConfig]]
+[[sample.SecurityConfig]]
 [source,java]
 [source,java]
 ----
 ----
-include::{examples-dir}/main/java/sample/dcr/DcrConfiguration.java[]
+include::{examples-dir}/main/java/sample/registration/SecurityConfig.java[]
 ----
 ----
 
 
-<1> Add a `SecurityFilterChain` `@Bean` that registers an `OAuth2AuthorizationServerConfigurer`
-<2> In the configurer, apply OIDC client registration endpoint customizer with default values.
-This enables dynamic client registration functionality.
+<1> Enable the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint] with the default configuration.
 
 
-Please refer to xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[Client Registration Endpoint docs] for in-depth configuration details.
+[[configure-client-registrar]]
+== Configure client registrar
 
 
-[[configure-initial-client]]
-== Configure initial client
+An existing client is used to register new clients with the authorization server.
+The client must be configured with scopes `client.create` and optionally `client.read` for registering clients and retrieving clients, respectively.
+The following listing shows an example client:
 
 
-An initial client is required in order to register new clients in the authorization server.
-The client must be configured with scopes `client.create` and optionally `client.read` for creating clients and reading clients, respectively.
-A programmatic example of such a client is below.
-
-[[sample.dcrRegisteredClientConfig]]
+[[sample.ClientConfig]]
 [source,java]
 [source,java]
 ----
 ----
-include::{examples-dir}/main/java/sample/dcr/RegisteredClientConfiguration.java[]
+include::{examples-dir}/main/java/sample/registration/ClientConfig.java[]
 ----
 ----
 
 
-<1> A `RegisteredClientRepository` `@Bean` is configured with a set of clients.
-<2> An initial client with client id `dcr-client` is configured.
-<3> `client_credentials` grant type is set to fetch access tokens directly.
-<4> `client.create` scope is configured for the client to ensure they are able to create clients.
-<5> `client.read` scope is configured for the client to ensure they are able to fetch and read clients.
-<6> The initial client is saved into the data store.
-
-After configuring the above, run the authorization server in your preferred environment.
+<1> `client_credentials` grant type is configured to obtain access tokens directly.
+<2> `client.create` scope is configured to allow the client to register a new client.
+<3> `client.read` scope is configured to allow the client to retrieve a registered client.
 
 
 [[obtain-initial-access-token]]
 [[obtain-initial-access-token]]
 == Obtain initial access token
 == Obtain initial access token
 
 
-An initial access token is required to be able to create client registration requests.
-The token request must contain a request for scope `client.create` only.
+An "initial" access token is required for the client registration request.
+The access token request *MUST* contain the `scope` parameter value `client.create` only.
 
 
 [source,httprequest]
 [source,httprequest]
 ----
 ----
@@ -69,18 +59,18 @@ grant_type=client_credentials&scope=client.create
 
 
 [WARNING]
 [WARNING]
 ====
 ====
-If you provide more than one scope in the request, you will not be able to register a client.
-The client creation request requires an access token with a single scope of `client.create`
+The client registration request requires an access token with a single scope of `client.create`.
+If the access token contains additional scope, the client registration request will be denied.
 ====
 ====
 
 
 [TIP]
 [TIP]
 ====
 ====
-To obtain encoded credentials for the above request, `base64` encode the client credentials in the format of
-`<clientId>:<clientSecret>`. Below is an encoding operation for the example in this guide.
+To obtain encoded credentials for the above request, `base64` encode the client credentials in the format of `<clientId>:<clientSecret>`.
+Below is an encoding operation for the example in this guide.
 
 
 [source,console]
 [source,console]
 ----
 ----
-echo -n "initial-app:secret" | base64
+echo -n "registrar-client:secret" | base64
 ----
 ----
 ====
 ====
 
 
@@ -90,30 +80,26 @@ echo -n "initial-app:secret" | base64
 With an access token obtained from the previous step, a client can now be dynamically registered.
 With an access token obtained from the previous step, a client can now be dynamically registered.
 
 
 [NOTE]
 [NOTE]
-The access token can only be used once. After a single registration request, the access token is invalidated.
+The "initial" access token can only be used once.
+After the client is registered, the access token is invalidated.
 
 
-[[sample.dcrClientRegistration]]
+[[sample.ClientRegistrar]]
 [source,java]
 [source,java]
 ----
 ----
-include::{examples-dir}/main/java/sample/dcr/DcrClient.java[]
+include::{examples-dir}/main/java/sample/registration/ClientRegistrar.java[]
 ----
 ----
 
 
-<1> A minimal client registration request object.
-You may add additional fields as per https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[OpenID Connect Dynamic Client Registration 1.0 spec - Client Registration Request].
-<2> A minimal client registration response object.
-You may add additional response fields as per https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[OpenID Connect Dynamic Client Registration 1.0 spec - Client Registration Response].
-<3> A sample client registration request object which will be used to register a sample client.
-<4> Example dynamic client registration procedure, demonstrating dynamic registration and client retrieval.
-<5> Register a client using sample request from step 2, using initial access token from previous step.
-Skip to step 10 for implementation.
-<6> After registration, assert on the fields that should be populated in the response upon successful registration.
-<7> Extract `registration_access_token` and `registration_client_uri` fields, for use in retrieval of the newly registered client.
-<8> Retrieve client. Skip to step 11 for implementation.
-<9> After client retrieval, assert on the fields that should be populated in the response.
-<10> Sample client registration procedure using Spring WebFlux's `WebClient`.
-Note that the `WebClient` must have `baseUrl` of the authorization server configured.
-<11> Sample client retrieval procedure using Spring WebFlux's `WebClient`.
-Note that the `WebClient` must have `baseUrl` of the authorization server configured.
-
-The retrieve client response should contain the same information about the client as seen when the client was first
-registered, except for `registration_access_token` field.
+<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].
+<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].
+<3> Example demonstrating client registration and client retrieval.
+<4> A sample client registration request object.
+<5> Register the client using the "initial" access token and client registration request object.
+<6> After successful registration, assert on the client metadata parameters that should be populated in the response.
+<7> Extract `registration_access_token` and `registration_client_uri` response parameters, for use in retrieval of the newly registered client.
+<8> Retrieve the client using the `registration_access_token` and `registration_client_uri`.
+<9> After client retrieval, assert on the client metadata parameters that should be populated in the response.
+<10> Sample https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration Request] using `WebClient`.
+<11> Sample https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read Request] using `WebClient`.
+
+[NOTE]
+The https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse[Client Read Response] should contain the same client metadata parameters as the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[Client Registration Response], except the `registration_access_token` parameter.

+ 0 - 96
docs/src/main/java/sample/dcr/DcrConfiguration.java

@@ -1,96 +0,0 @@
-/*
- * 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.dcr;
-
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.RSAKey;
-import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.SecurityContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
-import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
-import org.springframework.security.web.SecurityFilterChain;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Collections;
-import java.util.UUID;
-
-@Configuration
-@EnableWebSecurity
-public class DcrConfiguration {
-	@Bean // <1>
-	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
-		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
-		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
-				.oidc(oidc -> oidc.clientRegistrationEndpoint(Customizer.withDefaults())); // <2>
-		http.oauth2ResourceServer(oauth2ResourceServer ->
-				oauth2ResourceServer.jwt(Customizer.withDefaults()));
-
-		return http.build();
-	}
-	// @fold:on
-
-	@Bean
-	public UserDetailsService userDetailsService() {
-		// This example uses client credentials grant type - no need for any users.
-		return new InMemoryUserDetailsManager(Collections.emptyList());
-	}
-
-	@Bean
-	public JWKSource<SecurityContext> jwkSource() {
-		// @formatter:off
-		KeyPair keyPair;
-		try {
-			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
-			keyPairGenerator.initialize(2048);
-			keyPair = keyPairGenerator.generateKeyPair();
-		} catch (Exception ex) {
-			throw new IllegalStateException(ex);
-		}
-		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
-		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
-		RSAKey rsaKey = new RSAKey.Builder(publicKey)
-				.privateKey(privateKey)
-				.keyID(UUID.randomUUID().toString())
-				.build();
-		// @formatter:on
-		JWKSet jwkSet = new JWKSet(rsaKey);
-		return new ImmutableJWKSet<>(jwkSet);
-	}
-
-	@Bean
-	public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
-		return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
-	}
-
-	@Bean
-	public AuthorizationServerSettings authorizationServerSettings() {
-		return AuthorizationServerSettings.builder().build();
-	}
-	// @fold:off
-}

+ 13 - 11
docs/src/main/java/sample/dcr/RegisteredClientConfiguration.java → docs/src/main/java/sample/registration/ClientConfig.java

@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-package sample.dcr;
+package sample.registration;
+
+import java.util.UUID;
 
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
@@ -23,21 +25,21 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryR
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 
 
-import java.util.UUID;
-
 @Configuration
 @Configuration
-public class RegisteredClientConfiguration {
-	@Bean // <1>
+public class ClientConfig {
+
+	@Bean
 	public RegisteredClientRepository registeredClientRepository() {
 	public RegisteredClientRepository registeredClientRepository() {
-		RegisteredClient initialClient = RegisteredClient.withId(UUID.randomUUID().toString())
-				.clientId("dcr-client") // <2>
+		RegisteredClient registrarClient = RegisteredClient.withId(UUID.randomUUID().toString())
+				.clientId("registrar-client")
 				.clientSecret("{noop}secret")
 				.clientSecret("{noop}secret")
 				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
 				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
-				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // <3>
-				.scope("client.create") // <4>
-				.scope("client.read") // <5>
+				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)	// <1>
+				.scope("client.create")	// <2>
+				.scope("client.read")	// <3>
 				.build();
 				.build();
 
 
-		return new InMemoryRegisteredClientRepository(initialClient); // <6>
+		return new InMemoryRegisteredClientRepository(registrarClient);
 	}
 	}
+
 }
 }

+ 31 - 27
docs/src/main/java/sample/dcr/DcrClient.java → docs/src/main/java/sample/registration/ClientRegistrar.java

@@ -13,56 +13,58 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-package sample.dcr;
+package sample.registration;
+
+import java.util.List;
+import java.util.Objects;
 
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import reactor.core.publisher.Mono;
+
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.web.reactive.function.client.WebClient;
 import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
 
 
-import java.util.List;
-import java.util.Objects;
-
-public class DcrClient {
+public class ClientRegistrar {
 	// @fold:on
 	// @fold:on
 	private final WebClient webClient;
 	private final WebClient webClient;
 
 
-	public DcrClient(final WebClient webClient) {
+	public ClientRegistrar(WebClient webClient) {
 		this.webClient = webClient;
 		this.webClient = webClient;
 	}
 	}
 	// @fold:off
 	// @fold:off
 
 
-	public record DcrRequest( // <1>
+	public record ClientRegistrationRequest(	// <1>
 			@JsonProperty("client_name") String clientName,
 			@JsonProperty("client_name") String clientName,
 			@JsonProperty("grant_types") List<String> grantTypes,
 			@JsonProperty("grant_types") List<String> grantTypes,
 			@JsonProperty("redirect_uris") List<String> redirectUris,
 			@JsonProperty("redirect_uris") List<String> redirectUris,
 			String scope) {
 			String scope) {
 	}
 	}
 
 
-	public record DcrResponse( // <2>
+	public record ClientRegistrationResponse(	// <2>
 			@JsonProperty("registration_access_token") String registrationAccessToken,
 			@JsonProperty("registration_access_token") String registrationAccessToken,
 			@JsonProperty("registration_client_uri") String registrationClientUri,
 			@JsonProperty("registration_client_uri") String registrationClientUri,
 			@JsonProperty("client_name") String clientName,
 			@JsonProperty("client_name") String clientName,
+			@JsonProperty("client_id") String clientId,
 			@JsonProperty("client_secret") String clientSecret,
 			@JsonProperty("client_secret") String clientSecret,
 			@JsonProperty("grant_types") List<String> grantTypes,
 			@JsonProperty("grant_types") List<String> grantTypes,
 			@JsonProperty("redirect_uris") List<String> redirectUris,
 			@JsonProperty("redirect_uris") List<String> redirectUris,
 			String scope) {
 			String scope) {
 	}
 	}
 
 
-	public static final DcrRequest SAMPLE_CLIENT_REGISTRATION_REQUEST = new DcrRequest( // <3>
-			"client-1",
-			List.of(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
-			List.of("https://client.example.org/callback", "https://client.example.org/callback2"),
-			"openid email profile"
-	);
+	public void exampleRegistration(String initialAccessToken) {	// <3>
+		ClientRegistrationRequest clientRegistrationRequest = new ClientRegistrationRequest(	// <4>
+				"client-1",
+				List.of(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
+				List.of("https://client.example.org/callback", "https://client.example.org/callback2"),
+				"openid email profile"
+		);
 
 
-	public void exampleRegistration(String initialAccessToken) { // <4>
-		DcrResponse clientRegistrationResponse =
-				this.registerClient(initialAccessToken, SAMPLE_CLIENT_REGISTRATION_REQUEST); // <5>
+		ClientRegistrationResponse clientRegistrationResponse =
+				registerClient(initialAccessToken, clientRegistrationRequest);	// <5>
 
 
-		assert (clientRegistrationResponse.clientName().contentEquals("client-1")); // <6>
+		assert (clientRegistrationResponse.clientName().contentEquals("client-1"));	// <6>
 		assert (!Objects.isNull(clientRegistrationResponse.clientSecret()));
 		assert (!Objects.isNull(clientRegistrationResponse.clientSecret()));
 		assert (clientRegistrationResponse.scope().contentEquals("openid profile email"));
 		assert (clientRegistrationResponse.scope().contentEquals("openid profile email"));
 		assert (clientRegistrationResponse.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()));
 		assert (clientRegistrationResponse.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()));
@@ -71,12 +73,13 @@ public class DcrClient {
 		assert (!clientRegistrationResponse.registrationAccessToken().isEmpty());
 		assert (!clientRegistrationResponse.registrationAccessToken().isEmpty());
 		assert (!clientRegistrationResponse.registrationClientUri().isEmpty());
 		assert (!clientRegistrationResponse.registrationClientUri().isEmpty());
 
 
-		String registrationAccessToken = clientRegistrationResponse.registrationAccessToken(); // <7>
+		String registrationAccessToken = clientRegistrationResponse.registrationAccessToken();	// <7>
 		String registrationClientUri = clientRegistrationResponse.registrationClientUri();
 		String registrationClientUri = clientRegistrationResponse.registrationClientUri();
 
 
-		DcrResponse retrievedClient = this.retrieveClient(registrationAccessToken, registrationClientUri); // <8>
+		ClientRegistrationResponse retrievedClient = retrieveClient(registrationAccessToken, registrationClientUri);	// <8>
 
 
-		assert (retrievedClient.clientName().contentEquals("client-1")); // <9>
+		assert (retrievedClient.clientName().contentEquals("client-1"));	// <9>
+		assert (!Objects.isNull(retrievedClient.clientId()));
 		assert (!Objects.isNull(retrievedClient.clientSecret()));
 		assert (!Objects.isNull(retrievedClient.clientSecret()));
 		assert (retrievedClient.scope().contentEquals("openid profile email"));
 		assert (retrievedClient.scope().contentEquals("openid profile email"));
 		assert (retrievedClient.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()));
 		assert (retrievedClient.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()));
@@ -86,26 +89,27 @@ public class DcrClient {
 		assert (!retrievedClient.registrationClientUri().isEmpty());
 		assert (!retrievedClient.registrationClientUri().isEmpty());
 	}
 	}
 
 
-	public DcrResponse registerClient(String initialAccessToken, DcrRequest request) { // <10>
+	public ClientRegistrationResponse registerClient(String initialAccessToken, ClientRegistrationRequest request) {	// <10>
 		return this.webClient
 		return this.webClient
 				.post()
 				.post()
 				.uri("/connect/register")
 				.uri("/connect/register")
 				.contentType(MediaType.APPLICATION_JSON)
 				.contentType(MediaType.APPLICATION_JSON)
 				.accept(MediaType.APPLICATION_JSON)
 				.accept(MediaType.APPLICATION_JSON)
 				.header(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(initialAccessToken))
 				.header(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(initialAccessToken))
-				.body(Mono.just(request), DcrRequest.class)
+				.body(Mono.just(request), ClientRegistrationRequest.class)
 				.retrieve()
 				.retrieve()
-				.bodyToMono(DcrResponse.class)
+				.bodyToMono(ClientRegistrationResponse.class)
 				.block();
 				.block();
 	}
 	}
 
 
-	public DcrResponse retrieveClient(String registrationAccessToken, String registrationClientUri) { // <11>
+	public ClientRegistrationResponse retrieveClient(String registrationAccessToken, String registrationClientUri) {	// <11>
 		return this.webClient
 		return this.webClient
 				.get()
 				.get()
 				.uri(registrationClientUri)
 				.uri(registrationClientUri)
 				.header(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(registrationAccessToken))
 				.header(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(registrationAccessToken))
 				.retrieve()
 				.retrieve()
-				.bodyToMono(DcrResponse.class)
+				.bodyToMono(ClientRegistrationResponse.class)
 				.block();
 				.block();
 	}
 	}
+
 }
 }

+ 42 - 0
docs/src/main/java/sample/registration/SecurityConfig.java

@@ -0,0 +1,42 @@
+/*
+ * 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.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+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()));
+
+		return http.build();
+	}
+
+}

+ 16 - 13
docs/src/test/java/sample/dcr/DynamicClientRegistrationTests.java → docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java

@@ -13,19 +13,22 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
-package sample.dcr;
+package sample.registration;
 
 
 import com.jayway.jsonpath.JsonPath;
 import com.jayway.jsonpath.JsonPath;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.web.server.LocalServerPort;
 import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.web.reactive.function.client.WebClient;
 import org.springframework.web.reactive.function.client.WebClient;
 
 
@@ -34,9 +37,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 
-
 /**
 /**
- * Tests for Dynamic Client Registration how-to guide
+ * Tests for Dynamic Client Registration how-to guide.
  *
  *
  * @author Dmitriy Dubson
  * @author Dmitriy Dubson
  */
  */
@@ -54,12 +56,12 @@ public class DynamicClientRegistrationTests {
 	private String port;
 	private String port;
 
 
 	@Test
 	@Test
-	public void dynamicallyRegisterAClient() throws Exception {
-		String tokenRequestBody = "scope=client.create&grant_type=client_credentials" ;
+	public void dynamicallyRegisterClient() throws Exception {
 		MockHttpServletResponse tokenResponse = this.mvc.perform(post("/oauth2/token")
 		MockHttpServletResponse tokenResponse = this.mvc.perform(post("/oauth2/token")
-						.with(httpBasic("dcr-client", "secret"))
-						.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
-						.content(tokenRequestBody))
+						.with(httpBasic("registrar-client", "secret"))
+						.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
+						.param(OAuth2ParameterNames.SCOPE, "client.create")
+						.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE))
 				.andExpect(status().isOk())
 				.andExpect(status().isOk())
 				.andExpect(jsonPath("$.access_token").isNotEmpty())
 				.andExpect(jsonPath("$.access_token").isNotEmpty())
 				.andReturn()
 				.andReturn()
@@ -67,15 +69,16 @@ public class DynamicClientRegistrationTests {
 
 
 		String initialAccessToken = JsonPath.parse(tokenResponse.getContentAsString()).read("$.access_token");
 		String initialAccessToken = JsonPath.parse(tokenResponse.getContentAsString()).read("$.access_token");
 
 
-		WebClient webClient = WebClient.builder().baseUrl("http://127.0.0.1:%s".formatted(port)).build();
-		DcrClient dcrClient = new DcrClient(webClient);
+		WebClient webClient = WebClient.builder().baseUrl("http://127.0.0.1:%s".formatted(this.port)).build();
+		ClientRegistrar clientRegistrar = new ClientRegistrar(webClient);
 
 
-		dcrClient.exampleRegistration(initialAccessToken);
+		clientRegistrar.exampleRegistration(initialAccessToken);
 	}
 	}
 
 
 	@EnableAutoConfiguration
 	@EnableAutoConfiguration
 	@EnableWebSecurity
 	@EnableWebSecurity
-	@Import({DcrConfiguration.class, RegisteredClientConfiguration.class})
+	@ComponentScan
 	static class AuthorizationServerConfig {
 	static class AuthorizationServerConfig {
 	}
 	}
+
 }
 }