Browse Source

Polish gh-1320

pull/1345/head
Joe Grandja 2 years ago
parent
commit
3eb951f59d
  1. 104
      docs/modules/ROOT/pages/guides/how-to-dynamic-client-registration.adoc
  2. 96
      docs/src/main/java/sample/dcr/DcrConfiguration.java
  3. 24
      docs/src/main/java/sample/registration/ClientConfig.java
  4. 42
      docs/src/main/java/sample/registration/ClientRegistrar.java
  5. 42
      docs/src/main/java/sample/registration/SecurityConfig.java
  6. 29
      docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java

104
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. 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 https://openid.net/specs/openid-connect-registration-1_0.html[OpenID Connect Dynamic Client Registration 1.0] 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.
specification, gaining the ability to dynamically register and retrieve OpenID clients.
- xref:guides/how-to-dynamic-client-registration.adoc#enable[Enable Dynamic Client Registration] * 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-initial-client[Configure initial client] * 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#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#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` <1> Enable the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint] with the default configuration.
<2> In the configurer, apply OIDC client registration endpoint customizer with default values.
This enables dynamic client registration functionality.
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]] An existing client is used to register new clients with the authorization server.
== Configure initial client 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. [[sample.ClientConfig]]
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]]
[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. <1> `client_credentials` grant type is configured to obtain access tokens directly.
<2> An initial client with client id `dcr-client` is configured. <2> `client.create` scope is configured to allow the client to register a new client.
<3> `client_credentials` grant type is set to fetch access tokens directly. <3> `client.read` scope is configured to allow the client to retrieve a registered client.
<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.
[[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. An "initial" access token is required for the client registration request.
The token request must contain a request for scope `client.create` only. 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 registration request requires an access token with a single scope of `client.create`.
The client creation 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 To obtain encoded credentials for the above request, `base64` encode the client credentials in the format of `<clientId>:<clientSecret>`.
`<clientId>:<clientSecret>`. Below is an encoding operation for the example in this guide. 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. <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].
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 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].
<2> A minimal client registration response object. <3> Example demonstrating client registration and client retrieval.
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]. <4> A sample client registration request object.
<3> A sample client registration request object which will be used to register a sample client. <5> Register the client using the "initial" access token and client registration request object.
<4> Example dynamic client registration procedure, demonstrating dynamic registration and client retrieval. <6> After successful registration, assert on the client metadata parameters that should be populated in the response.
<5> Register a client using sample request from step 2, using initial access token from previous step. <7> Extract `registration_access_token` and `registration_client_uri` response parameters, for use in retrieval of the newly registered client.
Skip to step 10 for implementation. <8> Retrieve the client using the `registration_access_token` and `registration_client_uri`.
<6> After registration, assert on the fields that should be populated in the response upon successful registration. <9> After client retrieval, assert on the client metadata parameters that should be populated in the response.
<7> Extract `registration_access_token` and `registration_client_uri` fields, for use in retrieval of the newly registered client. <10> Sample https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration Request] using `WebClient`.
<8> Retrieve client. Skip to step 11 for implementation. <11> Sample https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read Request] using `WebClient`.
<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]
Note that the `WebClient` must have `baseUrl` of the authorization server configured. 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.
<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.

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
}

24
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 { public class ClientConfig {
@Bean // <1>
@Bean
public RegisteredClientRepository registeredClientRepository() { public RegisteredClientRepository registeredClientRepository() {
RegisteredClient initialClient = RegisteredClient.withId(UUID.randomUUID().toString()) RegisteredClient registrarClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("dcr-client") // <2> .clientId("registrar-client")
.clientSecret("{noop}secret") .clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // <3> .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // <1>
.scope("client.create") // <4> .scope("client.create") // <2>
.scope("client.read") // <5> .scope("client.read") // <3>
.build(); .build();
return new InMemoryRegisteredClientRepository(initialClient); // <6> return new InMemoryRegisteredClientRepository(registrarClient);
} }
} }

42
docs/src/main/java/sample/dcr/DcrClient.java → docs/src/main/java/sample/registration/ClientRegistrar.java

@ -13,54 +13,56 @@
* 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; public class ClientRegistrar {
import java.util.Objects;
public class DcrClient {
// @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> public void exampleRegistration(String initialAccessToken) { // <3>
ClientRegistrationRequest clientRegistrationRequest = new ClientRegistrationRequest( // <4>
"client-1", "client-1",
List.of(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), List.of(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
List.of("https://client.example.org/callback", "https://client.example.org/callback2"), List.of("https://client.example.org/callback", "https://client.example.org/callback2"),
"openid email profile" "openid email profile"
); );
public void exampleRegistration(String initialAccessToken) { // <4> ClientRegistrationResponse clientRegistrationResponse =
DcrResponse clientRegistrationResponse = registerClient(initialAccessToken, clientRegistrationRequest); // <5>
this.registerClient(initialAccessToken, SAMPLE_CLIENT_REGISTRATION_REQUEST); // <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()));
@ -74,9 +76,10 @@ public class DcrClient {
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
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();
}
}

29
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 { public void dynamicallyRegisterClient() throws Exception {
String tokenRequestBody = "scope=client.create&grant_type=client_credentials" ;
MockHttpServletResponse tokenResponse = this.mvc.perform(post("/oauth2/token") MockHttpServletResponse tokenResponse = this.mvc.perform(post("/oauth2/token")
.with(httpBasic("dcr-client", "secret")) .with(httpBasic("registrar-client", "secret"))
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.content(tokenRequestBody)) .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(); WebClient webClient = WebClient.builder().baseUrl("http://127.0.0.1:%s".formatted(this.port)).build();
DcrClient dcrClient = new DcrClient(webClient); 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 {
} }
} }
Loading…
Cancel
Save