From 3eb951f59d2562fe88aad8e7904e46591ffadfbf Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 13 Sep 2023 19:38:41 -0400 Subject: [PATCH] Polish gh-1320 --- .../how-to-dynamic-client-registration.adoc | 104 ++++++++---------- .../java/sample/dcr/DcrConfiguration.java | 96 ---------------- .../ClientConfig.java} | 24 ++-- .../ClientRegistrar.java} | 58 +++++----- .../sample/registration/SecurityConfig.java | 42 +++++++ .../DynamicClientRegistrationTests.java | 29 ++--- 6 files changed, 147 insertions(+), 206 deletions(-) delete mode 100644 docs/src/main/java/sample/dcr/DcrConfiguration.java rename docs/src/main/java/sample/{dcr/RegisteredClientConfiguration.java => registration/ClientConfig.java} (80%) rename docs/src/main/java/sample/{dcr/DcrClient.java => registration/ClientRegistrar.java} (71%) create mode 100644 docs/src/main/java/sample/registration/SecurityConfig.java rename docs/src/test/java/sample/{dcr => registration}/DynamicClientRegistrationTests.java (75%) diff --git a/docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc b/docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc index f4a4cd13..85fc06a3 100644 --- a/docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc +++ b/docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc @@ -3,60 +3,50 @@ :index-link: ../how-to.html :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 By default, dynamic client registration functionality is disabled in Spring Authorization Server. To enable, add the following configuration: -[[sample.dcrAuthServerConfig]] +[[sample.SecurityConfig]] [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] ---- -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 -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] ---- @@ -69,18 +59,18 @@ grant_type=client_credentials&scope=client.create [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] ==== -To obtain encoded credentials for the above request, `base64` encode the client credentials in the format of -`:`. 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 `:`. +Below is an encoding operation for the example in this guide. [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. [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] ---- -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. diff --git a/docs/src/main/java/sample/dcr/DcrConfiguration.java b/docs/src/main/java/sample/dcr/DcrConfiguration.java deleted file mode 100644 index 2f37878f..00000000 --- a/docs/src/main/java/sample/dcr/DcrConfiguration.java +++ /dev/null @@ -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 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 jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - public AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().build(); - } - // @fold:off -} diff --git a/docs/src/main/java/sample/dcr/RegisteredClientConfiguration.java b/docs/src/main/java/sample/registration/ClientConfig.java similarity index 80% rename from docs/src/main/java/sample/dcr/RegisteredClientConfiguration.java rename to docs/src/main/java/sample/registration/ClientConfig.java index c973fdf0..d6f81a1b 100644 --- a/docs/src/main/java/sample/dcr/RegisteredClientConfiguration.java +++ b/docs/src/main/java/sample/registration/ClientConfig.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * 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.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.RegisteredClientRepository; -import java.util.UUID; - @Configuration -public class RegisteredClientConfiguration { - @Bean // <1> +public class ClientConfig { + + @Bean 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") .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(); - return new InMemoryRegisteredClientRepository(initialClient); // <6> + return new InMemoryRegisteredClientRepository(registrarClient); } + } diff --git a/docs/src/main/java/sample/dcr/DcrClient.java b/docs/src/main/java/sample/registration/ClientRegistrar.java similarity index 71% rename from docs/src/main/java/sample/dcr/DcrClient.java rename to docs/src/main/java/sample/registration/ClientRegistrar.java index 9f999332..f199a435 100644 --- a/docs/src/main/java/sample/dcr/DcrClient.java +++ b/docs/src/main/java/sample/registration/ClientRegistrar.java @@ -13,56 +13,58 @@ * See the License for the specific language governing permissions and * 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 reactor.core.publisher.Mono; + import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.core.AuthorizationGrantType; 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 private final WebClient webClient; - public DcrClient(final WebClient webClient) { + public ClientRegistrar(WebClient webClient) { this.webClient = webClient; } // @fold:off - public record DcrRequest( // <1> + public record ClientRegistrationRequest( // <1> @JsonProperty("client_name") String clientName, @JsonProperty("grant_types") List grantTypes, @JsonProperty("redirect_uris") List redirectUris, String scope) { } - public record DcrResponse( // <2> + public record ClientRegistrationResponse( // <2> @JsonProperty("registration_access_token") String registrationAccessToken, @JsonProperty("registration_client_uri") String registrationClientUri, @JsonProperty("client_name") String clientName, + @JsonProperty("client_id") String clientId, @JsonProperty("client_secret") String clientSecret, @JsonProperty("grant_types") List grantTypes, @JsonProperty("redirect_uris") List redirectUris, 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 (clientRegistrationResponse.scope().contentEquals("openid profile email")); assert (clientRegistrationResponse.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())); @@ -71,12 +73,13 @@ public class DcrClient { assert (!clientRegistrationResponse.registrationAccessToken().isEmpty()); assert (!clientRegistrationResponse.registrationClientUri().isEmpty()); - String registrationAccessToken = clientRegistrationResponse.registrationAccessToken(); // <7> + String registrationAccessToken = clientRegistrationResponse.registrationAccessToken(); // <7> 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 (retrievedClient.scope().contentEquals("openid profile email")); assert (retrievedClient.grantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())); @@ -86,26 +89,27 @@ public class DcrClient { assert (!retrievedClient.registrationClientUri().isEmpty()); } - public DcrResponse registerClient(String initialAccessToken, DcrRequest request) { // <10> + public ClientRegistrationResponse registerClient(String initialAccessToken, ClientRegistrationRequest request) { // <10> return this.webClient .post() .uri("/connect/register") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(initialAccessToken)) - .body(Mono.just(request), DcrRequest.class) + .body(Mono.just(request), ClientRegistrationRequest.class) .retrieve() - .bodyToMono(DcrResponse.class) + .bodyToMono(ClientRegistrationResponse.class) .block(); } - public DcrResponse retrieveClient(String registrationAccessToken, String registrationClientUri) { // <11> + public ClientRegistrationResponse retrieveClient(String registrationAccessToken, String registrationClientUri) { // <11> return this.webClient .get() .uri(registrationClientUri) .header(HttpHeaders.AUTHORIZATION, "Bearer %s".formatted(registrationAccessToken)) .retrieve() - .bodyToMono(DcrResponse.class) + .bodyToMono(ClientRegistrationResponse.class) .block(); } + } diff --git a/docs/src/main/java/sample/registration/SecurityConfig.java b/docs/src/main/java/sample/registration/SecurityConfig.java new file mode 100644 index 00000000..b55be7e7 --- /dev/null +++ b/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(); + } + +} diff --git a/docs/src/test/java/sample/dcr/DynamicClientRegistrationTests.java b/docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java similarity index 75% rename from docs/src/test/java/sample/dcr/DynamicClientRegistrationTests.java rename to docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java index e0c47a9d..41874e75 100644 --- a/docs/src/test/java/sample/dcr/DynamicClientRegistrationTests.java +++ b/docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java @@ -13,19 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package sample.dcr; +package sample.registration; import com.jayway.jsonpath.JsonPath; import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; 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.mock.web.MockHttpServletResponse; 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.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.status; - /** - * Tests for Dynamic Client Registration how-to guide + * Tests for Dynamic Client Registration how-to guide. * * @author Dmitriy Dubson */ @@ -54,12 +56,12 @@ public class DynamicClientRegistrationTests { private String port; @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") - .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(jsonPath("$.access_token").isNotEmpty()) .andReturn() @@ -67,15 +69,16 @@ public class DynamicClientRegistrationTests { 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 @EnableWebSecurity - @Import({DcrConfiguration.class, RegisteredClientConfiguration.class}) + @ComponentScan static class AuthorizationServerConfig { } + }