eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

Course – LSS – NPI (cat=Spring Security)
announcement - icon

If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:

>> LEARN SPRING SECURITY

1. Introduction

The Spring Authorization Server comes with a range of sensible defaults that allow us to use it with almost no configuration. This makes it a great choice for using with client applications in test scenarios and when we want to have full control of the user’s login experience.

One feature, although available, is not enabled by default: Dynamic Client Registration.

In this tutorial, we’ll show how to enable and use it from a client application.

2. Why Use Dynamic Registration?

When an OAuth2-based application client or, in OIDC parlance, a relying party (RP) starts an authentication flow, it sends the authorization server its own client identifier to the Identity Provider.

This identifier, in general, is issued to the client using an out-of-band process, which will then add it to the configuration and be used when needed.

For instance, when using popular Identity Provider solutions such as Azure’s EntraID or Auth0, we can use the admin console or APIs to provision a new client. In the process, we’ll need to inform the application name, authorized callback URLs, supported scopes, etc.

Once we’ve supplied the required information, we’ll end up with a new client identifier and, for the so-called “secret” clients, a client secret. We then add these to the application’s configuration, and we are ready to deploy it.

Now, this process works fine when we have a small set of applications, or when we always use a single Identity Provider. For more complex scenarios, though, the registration process needs to be dynamic, and this is where the OpenID Connect Dynamic Client Registration specification comes into play.

For a real-world case, a good example is the UK’s OpenBanking standard, which uses dynamic client registration as one of its core protocols.

3. How Does Dynamic Registration Work?

The OpenID Connect standard uses a single registration URL that clients use to register themselves. This is done with a POST request with a JSON object that has the client metadata required to perform the registration.

Importantly, access to the registration endpoint requires authentication, usually a Bearer token. This, of course, begs the question: how does a wannabe client get a token for this operation?

Unfortunately, the answer is unclear. On one hand, the spec says that the endpoint is a protected resource and, as such, requires some form of authentication. On the other hand, it also mentions the possibility of an open registration endpoint.

For the Spring Authorization Server, the registration requires a bearer token with the client.create scope. To create this token, we use the regular OAuth2’s token endpoint and basic credentials.

This is the resulting sequence for a successful registration:

dynamic registration flow

Once the client completes a successful registration, it can use the returned client id and secret to execute any standard authorization flows.

4. Implementing Dynamic Registration

Now that we understand the required steps let’s create a test scenario using two Spring Boot applications. One will host the Spring Authorization Server, and the other will be a simple WebMVC application that uses the Spring Security Outh2 login starter module.

Instead of using the regular static configuration for clients, the latter will use the dynamic registration endpoint to acquire a client identifier and secret at startup time.

Let’s start with the server.

5. Authorization Server Implementation

We’ll start by adding the required maven dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
    <version>1.3.1</version>
</dependency>

The latest version is available on Maven Central.

For a regular Spring Authorization Server application, this dependency would be all we needed. However, for security reasons, dynamic registration is not enabled by default. Also, as of this writing, there’s no way to enable it just using configuration properties.

This means we must add some code – finally.

5.1. Enabling Dynamic Registration

The OAuth2AuthorizationServerConfigurer is the doorway to configure all aspects of the Authorization Server, including the registration endpoint. This configuration should be done as part of the creation of a SecurityFilterChain bean:

@Configuration
@EnableConfigurationProperties(SecurityConfig.RegistrationProperties.class)
public class SecurityConfig {
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
          .oidc(oidc -> {
              oidc.clientRegistrationEndpoint(Customizer.withDefaults());
          });

        http.exceptionHandling((exceptions) -> exceptions
          .defaultAuthenticationEntryPointFor(
            new LoginUrlAuthenticationEntryPoint("/login"),
            new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
          )
        );

        http.oauth2ResourceServer((resourceServer) -> resourceServer
            .jwt(Customizer.withDefaults()));

        return http.build();
    }

    // ... other beans omitted
}

Here, we use the server’s configurer oidc() method to get access to the OidcConfigurer instance. This sub-configurer has methods that allow us to control the endpoints related to the OpenID Connect standard. To enable the registration endpoint, we use the clientRegististrationEndpoint() method with the default configuration. This will enable registration at the /connect/register path, using bearer token authorization. Further configuration options include:

  • Defining custom authentication
  • Custom processing of the received registration data
  • Custom processing of the response sent to the client

Now, since we’re providing a custom SecurityFilterChain, Spring Boot’s auto-configuration will step back, leaving us responsible for adding some extra bits to the configuration.

In particular, we need to add the logic to setup form login authentication:

@Bean
@Order(2)
SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
    return http.authorizeHttpRequests(r -> r.anyRequest().authenticated())
      .formLogin(Customizer.withDefaults())
      .build();
}

5.2. Registration Client Configuration

As mentioned above, the registration mechanism itself requires the client to send a bearer token. Spring Authorization Server solves this chicken-and-egg problem by requiring clients to use a client credentials flow to generate this token.

The required scope for this token request is client.create and the client must use one of the supported authentication schemes supported by the server. Here, we’ll use Basic credentials, but, in a real-world scenario, we can use other methods.

This registration client is, from the Authorization Server’s point of view, just another client. As such we’ll create it using the RegisteredClient fluent API:

@Bean
public RegisteredClientRepository registeredClientRepository(RegistrationProperties props) {
    RegisteredClient registrarClient = RegisteredClient.withId(UUID.randomUUID().toString())
      .clientId(props.getRegistrarClientId())
      .clientSecret(props.getRegistrarClientSecret())
      .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
      .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
      .clientSettings(ClientSettings.builder()
        .requireProofKey(false)
        .requireAuthorizationConsent(false)
        .build())
      .scope("client.create")
      .scope("client.read")
      .build();

    RegisteredClientRepository delegate = new  InMemoryRegisteredClientRepository(registrarClient);
    return new CustomRegisteredClientRepository(delegate);
}

We’ve used a @ConfigurationProperties class to allow configuring the client ID and secret properties using Spring’s standard Environment mechanism.

This bootstrap registration will be the only one created at startup time. We’ll add it to our custom RegisteredClientRepository before returning it.

5.3. Custom RegisteredClientRepository

Spring Authorization Server uses the configured RegisteredClientRepository implementation to store all registered clients in the server. Out-of-the-box, it comes with memory and JDBC-based implementations, which cover the basic use cases.

Those implementations, however, do not offer any capabilities in terms of customizing the registration before it is saved. In our case, we’d like to modify the default ClientProperties settings so no consent or PKCE will be needed when authorizing a user.

Our implementation delegates most methods to the actual repository passed at construction time. The important exception is the save() method:

@Override
public void save(RegisteredClient registeredClient) {
    Set<String> scopes = ( registeredClient.getScopes() == null || registeredClient.getScopes().isEmpty())?
      Set.of("openid","email","profile"):
      registeredClient.getScopes();

    // Disable PKCE & Consent
    RegisteredClient modifiedClient = RegisteredClient.from(registeredClient)
      .scopes(s -> s.addAll(scopes))
      .clientSettings(ClientSettings
        .withSettings(registeredClient.getClientSettings().getSettings())
        .requireAuthorizationConsent(false)
        .requireProofKey(false)
        .build())
      .build();

    delegate.save(modifiedClient);
}

Here, we create a new RegisteredClient based on the received one, changing the ClientSettings as needed. This new registration is then passed to the backend where it will be stored until needed.

This concludes the server implementation. Now, let’s move on to the client side

6. Dynamic Registration Client Implementation

Our client will also be a standard Spring Web MVC application, with a single page displaying current user information. Spring Security, or, more specifically, its OAuth2 Login module, will handle all security aspects.

Let’s start with the required Maven dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>3.3.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>3.3.2</version>
</dependency>

The latest versions of these dependencies are available on Maven Central:

6.1. Security Configuration

By default, SpringBoot ‘s auto-configuration mechanism uses information from the available PropertySources to collect the required data to create one or more ClientRegistration instances, which are then stored in a memory-based ClientRegistrationRepository.

For instance, given this application.yaml:

spring:
  security:
    oauth2:
      client:
        provider:
          spring-auth-server:
            issuer-uri: http://localhost:8080
        registration:
          test-client:
            provider: spring-auth-server
            client-name: test-client
            client-id: xxxxx
            client-secret: yyyy
            authorization-grant-type:
              - authorization_code
              - refresh_token
              - client_credentials
            scope:
              - openid
              - email
              - profile

Spring will create a ClientRegistration named test-client and pass it to the repository.

Later, when there’s a need to start an authentication flow, the OAuth2 engine queries this repository and recovers the registration by its registration identifier – test-client, in our case.

The key point here is that the authorization server should already know the ClientRegistration returned at this point. This implies that to support dynamic clients, we must implement an alternative repository and expose it as a @Bean.

By doing so, Spring Boot’s auto-configuration will automatically use it instead of the default one.

6.2. Dynamic Client Registration Repository

As expected, our implementation must implement the ClientRegistration interface, which contains just a single method: findByRegistrationId(). This raises a question: How does the OAuth2 engine know which registrations are available? After all, it can list them on the default login page.

As it turns out, Spring Security expects the repository to also implement Iterable<ClientRegistration> so it can enumerate the available clients:

public class DynamicClientRegistrationRepository implements ClientRegistrationRepository, Iterable<ClientRegistration> {
    private final RegistrationDetails registrationDetails;
    private final Map<String, ClientRegistration> staticClients;
    private final RegistrationRestTemplate registrationClient;
    private final Map<String, ClientRegistration> registrations = new HashMap<>();

    // ... implementation omitted
}

Our class requires a few inputs to work:

  • a RegistrationDetails record with all parameters required to perform the dynamic registration
  • a Map of clients that will be dynamically registered
  • a RestTemplate used to access the authorization server

Notice that, for this example, we assume that all clients will be registered on the same Authorization Server.

Another important design decision is to define when the dynamic registration will take place. Here, we’ll take a simplistic approach and expose a public doRegistrations() method that will register all known clients and save the returned client identifier and secret for later use:

public void doRegistrations() {
    staticClients.forEach((key, value) -> findByRegistrationId(key));
}

The implementation calls findByRegistrationId() for each static client passed to the constructor. This method checks if there’s a valid registration for the given identifier and, in case it is missing, triggers the actual registration process.

6.3. Dynamic Registration

The doRegistration() function is where the real action happens:

private ClientRegistration doRegistration(String registrationId) {
    String token = createRegistrationToken();
    var staticRegistration = staticClients.get(registrationId);

    var body = Map.of(
      "client_name", staticRegistration.getClientName(),
      "grant_types", List.of(staticRegistration.getAuthorizationGrantType()),
      "scope", String.join(" ", staticRegistration.getScopes()),
      "redirect_uris", List.of(resolveCallbackUri(staticRegistration)));

    var headers = new HttpHeaders();
    headers.setBearerAuth(token);
    headers.setContentType(MediaType.APPLICATION_JSON);

    var request = new RequestEntity<>(
      body,
      headers,
      HttpMethod.POST,
      registrationDetails.registrationEndpoint());

    var response = registrationClient.exchange(request, ObjectNode.class);
    // ... error handling omitted
    return createClientRegistration(staticRegistration, response.getBody());
}

Firstly, we must get a registration token that we need to call the registration endpoint. Notice that we must get a new token for every registration attempt since, as described in Spring Authorization’s Server documentation, we can only use this token once.

Next, we build the registration payload using data from the static registration object, add the required authorization and content-type headers, and send the request to the registration endpoint.

Finally, we use the response data to create the final ClientRegistration that will be saved in the repository’s cache and returned to the OAuth2 engine.

6.4. Registering the Dynamic Repository @Bean

To complete our client, the last required step is to expose our DynamicClientRegistrationRepository as a @Bean. Let’s create a @Configuration class for that:

@Bean
ClientRegistrationRepository dynamicClientRegistrationRepository( DynamicClientRegistrationRepository.RegistrationRestTemplate restTemplate) {
    var registrationDetails = new DynamicClientRegistrationRepository.RegistrationDetails(
      registrationProperties.getRegistrationEndpoint(),
      registrationProperties.getRegistrationUsername(),
      registrationProperties.getRegistrationPassword(),
      registrationProperties.getRegistrationScopes(),
      registrationProperties.getGrantTypes(),
      registrationProperties.getRedirectUris(),
      registrationProperties.getTokenEndpoint());

    Map<String,ClientRegistration> staticClients = (new OAuth2ClientPropertiesMapper(clientProperties)).asClientRegistrations();
    var repo =  new DynamicClientRegistrationRepository(registrationDetails, staticClients, restTemplate);
    repo.doRegistrations();
    return repo;
}

The @Bean-annotated dynamicClientRegistrationRepository() method creates the repository by first populating the RegistrationDetails record from available properties.

Secondly, it creates the staticClient map leveraging the OAuth2ClientPropertiesMapper class available in SpringBoot’s auto-configuration module. This approach allows us to quickly switch from static to dynamic clients and back with minimal effort, as the configuration structure is the same for both.

7. Testing

Finally, let’s do some integration testing. Firstly, we start the server application, which is configured to listen on port 8080:

[ server ] $ mvn spring-boot:run
... lots of messages omitted
[           main] c.b.s.s.a.AuthorizationServerApplication : Started AuthorizationServerApplication in 2.222 seconds (process running for 2.454)
[           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
[           main] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC

Next, it’s time to start the client in another shell:

[client] $ mvn spring-boot:run
// ... lots of messages omitted
[  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
[  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8090 (http) with context path ''
[  restartedMain] d.c.DynamicRegistrationClientApplication : Started DynamicRegistrationClientApplication in 2.063 seconds (process running for 2.425)

Both applications run with the debug property set, so they produce quite a lot of log messages. In particular, we can see a call to the authorization server’s /connect/register endpoint:

[nio-8080-exec-3] o.s.security.web.FilterChainProxy        : Securing POST /connect/register
// ... lots of messages omitted
[nio-8080-exec-3] ClientRegistrationAuthenticationProvider : Retrieved authorization with initial access token
[nio-8080-exec-3] ClientRegistrationAuthenticationProvider : Validated client registration request parameters
[nio-8080-exec-3] s.s.a.r.CustomRegisteredClientRepository : Saving registered client: id=30OTlhO1Fb7UF110YdXULEDbFva4Uc8hPBGMfi60Wik, name=test-client

On the client side, we can see a message with the registration identifier (test-client) and the corresponding client_id:

[  restartedMain] s.d.c.c.OAuth2DynamicClientConfiguration : Creating a dynamic client registration repository
[  restartedMain] .c.s.DynamicClientRegistrationRepository : findByRegistrationId: test-client
[  restartedMain] .c.s.DynamicClientRegistrationRepository : doRegistration: registrationId=test-client
[  restartedMain] .c.s.DynamicClientRegistrationRepository : creating ClientRegistration: registrationId=test-client, client_id=30OTlhO1Fb7UF110YdXULEDbFva4Uc8hPBGMfi60Wik

If we open a browser and point it to http://localhost:8090, we’ll be redirected to the login page. Notice that the URL in the address bar changed to http://localhost:8080, which shows us that this page came from the authorization server.

The test credentials are user1/password. Once we put them in the form and send it, we’ll return to the client’s home page. Since we’re now authenticated, we’ll see a page containing some details extracted from the authorization token.

8. Conclusion

In this tutorial, we’ve shown how to enable the Spring Authorization Server’s Dynamic Registration feature and use it from a Spring Security-based client application.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LSS – NPI (cat=Security/Spring Security)
announcement - icon

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)