1. Overview

This tutorial is about configuring a backend with OAuth2 using Spring Boot and Keycloak.

We’ll use Keycloak as an OpenID Provider. We can think of it as a user-service in charge of authentication and user data (roles, profiles, contact info, etc.). It is one of the most complete OpenID Connect (OIDC) implementations with features like:

  • Single Sign-On (SSO) and single sign-out (Back-Channel Logout)
  • Identity brokering, social login & user federation
  • UIs for server administration & user account management
  • An admin REST API to control everything programmatically

After reviewing configuration options for OAuth2 in Spring Security, we’ll configure two different Spring Boot applications:

  • A stateful client with oauth2Login
  • A stateless oauth2ResourceServer

2. Keycloak Quickstart with Docker

In this section, we start a Keycloak server with a pre-configured realm. We’ll see how to create such a realm in section 6.

2.1. Docker Compose File

The easiest way to sandbox an authorization server on a developer’s desktop is by pulling a Keycloak Docker image. To configure it, we’ll use a Docker compose file:

services:
  keycloak:
    container_name: baeldung-keycloak.openid-provider
    image: quay.io/keycloak/keycloak:25.0.1
    command:
    - start-dev
    - --import-realm
    ports:
    - 8080:8080
    volumes:
    - ./keycloak/:/opt/keycloak/data/import/
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      KC_HTTP_PORT: 8080
      KC_HOSTNAME_URL: http://localhost:8080
      KC_HOSTNAME_ADMIN_URL: http://localhost:8080
      KC_HOSTNAME_STRICT_BACKCHANNEL: true
      KC_HTTP_RELATIVE_PATH: /
      KC_HTTP_ENABLED: true
      KC_HEALTH_ENABLED: true
      KC_METRICS_ENABLED: true
    extra_hosts:
    - "host.docker.internal:host-gateway"
    healthcheck:
      test: ['CMD-SHELL', '[ -f /tmp/HealthCheck.java ] || echo "public class HealthCheck { public static void main(String[] args) throws java.lang.Throwable { System.exit(java.net.HttpURLConnection.HTTP_OK == ((java.net.HttpURLConnection)new java.net.URL(args[0]).openConnection()).getResponseCode() ? 0 : 1); } }" > /tmp/HealthCheck.java && java /tmp/HealthCheck.java http://localhost:8080/auth/health/live']
      interval: 5s
      timeout: 5s
      retries: 20

2.2. Using the Companion Project Realm

The companion project contains a JSON file with the definition for some Keycloak objects we’ll need:

  • A baeldung-keycloak realm
  • A baeldung-keycloak-confidential client with secret as secret
  • A mapper to add realm roles to access and ID tokens issued to the baeldung-keycloak-confidential client (by default, only access tokens include realm roles)
  • A NICE role defined at the realm level (Keycloak also supports role definition at the client level)
  • Two users: brice (with the NICE role) and igor (without the NICE role). Both have secret as secret

The —import-realm argument in the compose file instructs Keycloak to load objects from the JSON files in its data/import/ folder.

Given the volume defined in the compose file, we should copy the keycloak/baeldung-keycloak-realm.json file from the companion project to a ./keycloak/ folder relative to the compose file.

2.3. Starting the Docker Container

To ease the initial configuration, the Docker compose file above uses a KEYCLOAK_ADMIN_PASSWORD environment variable that we should set before starting the container:

export KEYCLOAK_ADMIN_PASSWORD=admin
docker compose up -d

After running these commands, Keycloak starts. We know that start-up is complete once we see a line containing Keycloak 25.0.1 […] started.

We can now use the Keycloak admin console by browsing to http://localhost:8080, using admin/admin as credentials.

3. Spring Security for OAuth2

There is usually a lot of confusion around OAuth2 and the different options that Spring Security offers to configure applications.

3.1. OAuth2 Actors

Let’s first go through the three key participants of OAuth2.

Authorization servers are responsible for resource owners’ authentication and issuing tokens to clients – in this tutorial, we’re using Keycloak for this

Clients drive flows to get tokens from the authorization server, store tokens, and authorize requests to resource servers with valid tokens. When resource owners are users, clients use authorization-code flow (or, when the user device has limited input capabilities, the device flow) to log them in against the authorization server and get tokens to send requests on their behalf to resource servers.

In this tutorial, our clients are a Spring application with oauth2Login and Postman.

Resource servers provide secured access to REST resources. They check tokens’ validity (issuer, expiration, audience, etc.) and control client access (client scopes, resource owner roles, relations between the resource owner and the accessed resource, etc.). We’ll configure a REST API as a resource server.

It’s worth noting from the above that choosing a flow to get tokens from an authorization server is the responsibility of OAuth2 clients. Consequently, logging users in and out is never a resource server concern.

3.2. oauth2Login

Spring Security oauth2Login configures authorization-code and refresh-token flows. It also configures an authorized client repository to store tokens (in session by default), and an authorized client manager for us to access it – refreshing tokens when required before returning them.

Requests to a Spring client with oauth2Login are authorized with a session cookie. This is why protection against CSRF attacks should always be enabled in a Security(Web)FilterChain bean with oauth2Login.

The type of Authentication in the security context after a request is successfully authorized is OAuth2AuthenticationToken.

If the provider is auto-configured using OpenID, the OAuth2AuthenticationToken principal is an OidcUser built from the ID token, otherwise, an extra call to the userinfo endpoint is required to set an OAuth2User as principal.

Spring Security authorities are mapped using a GrantedAuthoritiesMapper or a custom OAuth2UserService.

3.3. oauth2ResouceServer

Spring Security oauth2ResouceServer configures Bearer token security. It offers a choice between introspection (aka opaque token) and JWT decoding.

In the case of resource servers, the user state is held by the token claims and sessions can be disabled. This brings two great benefits:

  • CSRF protection can be disabled – the CSRF attack involves sessions, not used here
  • Resource servers are easy to scale – no matter to what instance a request is routed, the state for the client and the resource owner comes with the claims

The type of Authentication in the security context after a request is successfully authorized can be customized with an authentication converter. But details of the default authentication type, of how it is converted from the token, and of how the authorities it contains are resolved depend on the resource server kind:

  • With a JWT decoder, the default authentication type is JwtAuthenticationToken and authorities are mapped with a JWT authentication converter using access token claims
  • With access tokens introspection, the default authentication type is BearerTokenAuthentication and authorities are resolved with a Custom introspector using the introspection endpoint response.

3.4. Choosing Between oauth2Login and oauth2ResourceServer

We can deduce from the above that oauth2Login and oauth2ResourceServer serve different purposes. Spring Security provides a different Spring Boot starter for each because the two shouldn’t stand in the same Security(Web)FilterChain bean:

  • oauth2Login authorization is based on sessions and oauth2ResourceServer is based on Bearer tokens
  • Because it is session-based, oauth2Login requires protection against CSRF. But because of their stateless nature, resource servers don’t need it
  • Authorities mapping and the type of Authentication in the security context differ in oauth2Login, oauth2ResourceServer with JWT decoder, and oauth2ResourceServer with introspection

Because of stateless applications’ scalability, we usually prefer configuring REST endpoints with oauth2ResourceServer. But this requires that what sends (or routes) requests to REST APIs can fetch tokens from an authorization server, store them in some state (a session or whatever), refresh them when they expire, and authorize requests with an access token.

Many REST clients can do that (programmatic ones like Spring’s RestClient & WebClient, or those with a UI like Postman), but browsers can’t do it without the help of a framework like Angular, React, or Vue (and this is now discouraged for security reasons).

The main use cases for oauth2Login are Server-side rendered UIs, and Spring Cloud Gateway used as an OAuth2 Backend For Frontend. But because of their stateful nature, we’ll have to use something like Spring Session or a smart proxy when scaling horizontally for high availability or load-balancing.

3.5. Combining Heterogeneous Requests Authorization Mechanisms

To authorize requests from different heterogeneous consumers, we might need to configure an application with several of oauth2Login, oauth2ResourceServer, x509, formLogin, Basic auth, etc. This could happen for instance on an OAuth2 BFF where the frontend requests are authorized with oauth2Login, and actuator endpoints with oauth2ResourceServer or Basic.

In such cases, different Security(Web)FilterChain beans should be used for each request authorization mechanism, all decorated with a distinct @Order and all but the last one in order with a securityMatcher defining which request it should be applied to. Without a securityMatcher, a filter chain serves as the default for all requests that haven’t matched so far.

4. Thymeleaf with Login

Our first use-case for OAuth2 with Spring Boot and Keycloak is a Thymeleaf application that authenticates users with an OpenID Provider. It is simple, yet, demonstrates Role Based Access Control (RBAC) with Keycloak in a stateful application.

It’s worth noting that:

  • In systems with a single-page or mobile application, we’d use an OAuth2 BFF with a similar OAuth2 client configuration.
  • Our Thymeleaf app is an OAuth2 client because it uses oauth2Login and the ID token to build user authentication, but it doesn’t use the access token (it does not send requests to a resource server). As seen before, requests between the browser and our Spring backend are authorized with a session cookie.
  • To scale horizontally, we’d need to share the session across instances (Spring Session) or a smart proxy rooting all requests from the same user-agent to the same instance.

4.1. Dependencies

Our most important dependency to enable user login with OAuth2 is spring-boot-starter-oauth2-client. Of course, as we create a servlet application rendering Thymeleaf templates, we also need spring-boot-starter-web and spring-boot-starter-thymeleaf.

Let’s add them to the pom.xml:

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

This is enough for runtime, but if we want to mock authentication to test access control in a Spring OAuth 2 application, we also need spring-security-test with test scope.

4.2. Provider & Registration Configuration

As Keycloak exposes its OpenID configuration at ${issuer-uri}/.well-known/openid-configuration, and given that Spring Security can auto-configure a provider from its OpenId configuration, defining its issuer-uri is enough:

spring.security.oauth2.client.provider.baeldung-keycloak.issuer-uri=http://localhost:8080/realms/baeldung-keycloak

Let’s now configure a client registration using the provider above:

spring.security.oauth2.client.registration.keycloak.provider=baeldung-keycloak
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.client-id=baeldung-keycloak-confidential
spring.security.oauth2.client.registration.keycloak.client-secret=secret
spring.security.oauth2.client.registration.keycloak.scope=openid

The value we specify in client-id matches the confidential client declared in the Keycloak admin console.

We can get the value for client-secret from the Credentials tab. With the imported realm, the secret is secret.

4.3. Mapping Keycloak Realm Roles to Spring Security Authorities

There are several ways to map authorities in a filter chain with oauth2Login, but the easiest is probably to expose a GrantedAuthoritiesMapper bean, and that’s what we’ll do here.

Let’s start by defining a bean responsible for extracting authorities from a Keycloak claim set, which could be an ID or access token payload, as well as a userinfo or introspection response body:

interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<GrantedAuthority>> {}

@Bean
AuthoritiesConverter realmRolesAuthoritiesConverter() {
  return claims -> {
    var realmAccess = Optional.ofNullable((Map<String, Object>) claims.get("realm_access"));
    var roles = realmAccess.flatMap(map -> Optional.ofNullable((List<String>) map.get("roles")));
    return roles.map(List::stream)
      .orElse(Stream.empty())
      .map(SimpleGrantedAuthority::new)
      .map(GrantedAuthority.class::cast)
      .toList();
  };
}

Because of generics type erasure in the JVM and because there could be many Converter beans with different inputs and outputs in an application context, this AuthoritiesConverter interface can be a useful tip for the bean factory when it searches for a Converter<Map<String, Object>, Collection> bean.

As we auto-configured our OIDC provider using its issuer-uri, what we get as input in the GrantedAuthoritiesMapper are OidcUserAuthority instances:

@Bean
GrantedAuthoritiesMapper authenticationConverter(
  Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter) {
    return (authorities) -> authorities.stream()
      .filter(authority -> authority instanceof OidcUserAuthority)
      .map(OidcUserAuthority.class::cast)
      .map(OidcUserAuthority::getIdToken)
      .map(OidcIdToken::getClaims)
      .map(authoritiesConverter::convert)
      .flatMap(roles -> roles.stream())
      .collect(Collectors.toSet());
}

It’s worth noting how we injected and used the authorities converter bean defined above.

4.4. Putting the SecurityFilterChain Bean Together

Here is the complete SecurityFilterChain we’ll use:

@Bean
SecurityFilterChain clientSecurityFilterChain(
      HttpSecurity http,
      ClientRegistrationRepository clientRegistrationRepository) throws Exception {
  http.oauth2Login(Customizer.withDefaults());
  http.logout((logout) -> {
    var logoutSuccessHandler =
        new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
    logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/");
    logout.logoutSuccessHandler(logoutSuccessHandler);
  });

  http.authorizeHttpRequests(requests -> {
    requests.requestMatchers("/", "/favicon.ico").permitAll();
    requests.requestMatchers("/nice").hasAuthority("NICE");
    requests.anyRequest().denyAll();
  });

  return http.build();
}

Let’s break it down to understand the key parts.

The oauth2Login() method adds OAuth2LoginAuthenticationFilter to the filter chain. This filter intercepts requests and applies the needed logic to handle authorization code & refresh token flows. It also stores tokens in session.

To understand how OAuth2 logouts work, we should remember that a user has a minimum of two independent sessions: one on the authorization server (Keycloak in our case), and one on each client with oauth2Login. For a complete logout, all sessions must be terminated.

The OpenID standard defines different ways to achieve that, but we’ll focus on RP-Initiated Logout which we configure using an OidcClientInitiatedLogoutSuccessHandler.

In this flow, the user agent (user’s browser) first logs out from the Relying Party (our Spring application with oaut2Login) to close its session. The RP answers with a redirection to the OpenID Provider (OP) containing the ID token linked to the session to close and a post-logout URL (the Thymeleaf index in our case). The OP then closes its session and redirects the user agent to the provided URL.

To conclude, we define access control rules:

  • For users to log in from the index, it must be accessible to anonymous requests.
  • The /nice path is accessible only to authenticated users granted with the NICE authority.

4.5. Thymeleaf Web Pages

We’re using Thymeleaf for our two web pages:

  • index.html displays a login or logout button depending on the user’s state. It also contains a button to navigate to /nice which, for a decent user experience, we only display to authenticated users with the NICE authority.
  • nice.html contains only static content.

Our index.html has conditional logic that’s able to use values from the user’s session:

<div class="container">
  <h1 class="form-signin-heading">Baeldung: Keycloak &amp; Spring Boot</h1>
  <p>Welcome to a Thymeleaf UI served by Spring Boot and secured using Keycloak!</p>
  <div th:if="${!isAuthenticated}">
    <a href="/oauth2/authorization/keycloak"><button type="button"
        class="btn btn-lg btn-primary btn-block">Login</button></a>
  </div>
  <div th:if="${isAuthenticated}">
    <p>Hi <span th:utext="${name}">..!..</span>!</p>
    <a href="/logout"><button type="button" class="btn btn-lg btn-primary">Logout</button></a>
    <a th:if="${isNice}" href="/nice">
      <button type="button" class="btn btn-lg btn-primary">Browse to NICE users page</button></a>
  </div>
</div>

4.6. Controller

The UiController builds the index page model (user name, as well as isAuthenticated and isNice flags), and resolves Thymeleaf templates:

@Controller
public class UiController {
  @GetMapping("/")
  public String getIndex(Model model, Authentication auth) {
    model.addAttribute("name",
        auth instanceof OAuth2AuthenticationToken oauth && oauth.getPrincipal() instanceof OidcUser oidc
        ? oidc.getPreferredUsername()
        : "");
    model.addAttribute("isAuthenticated",
        auth != null && auth.isAuthenticated());
    model.addAttribute("isNice", 
        auth != null && auth.getAuthorities().stream().anyMatch(authority -> {
          return Objects.equals("NICE", authority.getAuthority());
        }));
    return "index.html";
  }
  
  @GetMapping("/nice")
  public String getNice(Model model, Authentication auth) {
    return "nice.html";
  }
}

4.7. Trying the Thymeleaf Application with oauth2Login

We can now start the application using our favorite IDE. Alternatively, from the maven parent context, we’d run:

sh ./mvnw -pl spring-boot-mvc-client spring-boot:run

By pointing a browser to http://localhost:8081/, we should see a page with a login button.

The button to navigate to the page reserved for NICE users should be visible only when logged in as brice (and not as igor).

On the next login attempt after an RP-Initiated Logout, we should have to input credentials.

If we remove the logout section from Java conf, the Keycloak session won’t end when logging out from the Spring application and a new login attempt will complete silently. Keycloak would not display login forms as, from its point of view, we’re still logged in.

5. REST API with a JWT Decoder

Now let’s continue our introduction to OAuth2 with Spring Boot and Keycloak with a stateless REST API expecting Bearer access tokens in the JWT format.

5.1. Dependencies

This time, our most important dependency is spring-boot-starter-oauth2-resource-server. As we create a servlet application, we’ll need spring-boot-starter-web too:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

This is enough for runtime, but if we want to mock authentication during tests, we also need spring-security-test with test scope.

5.2. Configuring the JWT Decoder

To validate access tokens on resource servers we have a choice of a JWT decoder or introspection. The first requires the authorization server to issue access tokens in the JWT format. However, it is much more efficient as introspection requires a call from the resource server to the authorization server for each request. This introduces latency and might overload the authorization server.

Keycloak access tokens are JWTs and, with Spring Boot, a single property is enough to configure a resource server with a JWT decoder:

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/baeldung-keycloak

5.3. Mapping Keycloak Realm Roles to Spring Security Authorities

In the case of a resource server with a JWT decoder, we should configure the authentication converter.

We can reuse the implementation of the previous authorities converter bean here:

@Bean
JwtAuthenticationConverter authenticationConverter(
      Converter<Map<String, Object>, Collection<GrantedAuthority>> authoritiesConverter) {
  var authenticationConverter = new JwtAuthenticationConverter();
  authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
    return authoritiesConverter.convert(jwt.getClaims());
  });
  return authenticationConverter;
}

It’s worth noting that, this time, we pass access token claims instead of ID token ones.

5.4. Putting the SecurityFilterChain Bean Together

We are now ready to define a resource server security filter chain:

@Bean
SecurityFilterChain resourceServerSecurityFilterChain(
      HttpSecurity http,
      Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
  http.oauth2ResourceServer(resourceServer -> {
    resourceServer.jwt(jwtDecoder -> {
      jwtDecoder.jwtAuthenticationConverter(authenticationConverter);
    });
  });

  http.sessionManagement(sessions -> {
    sessions.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
  }).csrf(csrf -> {
    csrf.disable();
  });

  http.authorizeHttpRequests(requests -> {
    requests.requestMatchers("/me").authenticated();
    requests.anyRequest().denyAll();
  });

  return http.build();
}

In this code:

  • The first block enables resource server configuration with a JWT decoder and a custom authentication converter to turn Keycloak roles into Spring Security authorities.
  • We then completely disable sessions and protection against CSRF.
  • Last, we define some access control: only requests with a valid Bearer token can access the /me endpoint, and we forbid access to any other resource, whatever the request authorization.

5.5. REST Controller

To demo Keycloak roles mapping in a resource server, we’ll expose an endpoint reflecting some info from the JwtAuthenticationToken in the security context:

@RestController
public class MeController {
  @GetMapping("/me")
  public UserInfoDto getGretting(JwtAuthenticationToken auth) {
    return new UserInfoDto(
      auth.getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME),
      auth.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());
  }

  public static record UserInfoDto(String name, List roles) {}
}

It is safe to cast the auth to JwtAuthenticationToken because access is limited to isAuthenticated() and because JwtAuthenticationToken is what our authentication converter returns*.* If the route was permitAll(), we should also handle AnonymousAuthenticationToken in case of unauthorized requests – those without a Bearer token.

5.6. Trying the REST API

Now we’re ready to try our REST API live. Like we did for the Thymeleaf app, we can run it using our favorite IDE or, from the maven parent context, execute:

sh ./mvnw -pl spring-boot-resource-server spring-boot:run

Then, to get an access token from Keycloak with Postman, we should open the Authorization tab of the collection or request, select OAuth2, and fill the form with the values we already set in Keycloak (redirect URI) and Spring properties, or that we get from the OpenID configuration:

postman token config

Last, we send a GET request to http://localhost:8082/me. The response should be a JSON payload with our Keycloak login and realm roles:

postman get me

6. Creating a Keycloak Realm

In this tutorial, we sandboxed the Keycloak objects in a custom realm that we imported when creating the Docker container. This can be super useful when onboarding new developers in a team or experimenting with alternate configurations.

Let’s detail how these objects were initially created.

6.1. Realm

Let’s navigate to the upper left corner to click the Create Realm button:

create realm button

On the next screen, we name the realm baeldung-keycloak:

create realm form

After clicking the Create button, we’re redirected to its details.

All the operations in the next sections will be boxed into this baeldung-keycloak realm.

6.2. Confidential Client to Authenticate Users

Now we’ll navigate to the Clients page where we can create a new client named baeldung-keycloak-confidential:

create client 1

In the next screen, we’ll ensure that Client authentication is enabled – this makes the client “confidential” – and that only Standard flow is checked, which activates the authorization code and refresh token flows:

create client 2

Last, we need to allow redirection URIs and origins for our client:

create client 3

As our Spring Boot client application with oauth2Login is configured to run on port 8081, and with keycloak as registration-id, we set:

6.3. Configuring Which JSON Payloads Include Realm Roles

To ensure that Keycloak roles are added to the various payloads we use in Spring applications, we:

Navigate to the Client scopes tab in client details:

client scopes 1

Click baeldung-keycloak-confidential-dedicated scope to access its configuration details:

client scope baeldung keycloak confidential dedicated

Click the Add predefined mapper button, and select the realm roles mapper:

client scope add predefined mapper

When editing it, we can choose which JSON payloads include realm roles:

  • access tokens: for resource servers with JWT decoder
  • introspection: for resource servers with token introspection (opaque token in Spring Security wording)
  • ID tokens: for clients with oauth2Login and OpenID (using the issuer-uri to auto-configure the provider)
  • userinfo: for clients with oauth2Login but without OpenID (issuer-uri left blank and other provider properties set in Spring conf)

Because we implement some Role Based Access Control (RBAC) in both an OpenID client with oauth2Login (a Thymeleaf application) and a resource server with a JWT decoder, we should ensure that realm roles are added to access and ID tokens.

client scope configure realm roles mapper

6.4. Realm Role

Within Keycloak, we may define roles, and assign them to users, for the all realm or on a per-client basis. In this tutorial, we focus on realm roles.

Let’s navigate to the Realm roles page:

realm roles

There we create the NICE role:

realm role create

6.5. Users and Realm Roles Assignment

Let’s go to the Users page to add two users (one named brice and granted with the NICE role, and a second one named igor and granted with no realm role):

users

We first create a new user named brice:

user create

After we click the Create button, we can see user details. We should browse to the credential tab to set its password:

user credentials

Finally, we navigate to the Role Mappings tab to assign the NICE role (mind the Filter by realm roles drop-down):

user roles

We may repeat the instruction in this section for igor, skipping the role assignment.

6.6. Exporting Realms

After we complete the realm configuration, we can export it with the following commands (in Docker desktop, use the Exec tab for the running container):

cd /opt/keycloak/bin/
sh ./kc.sh export --dir /tmp/keycloak/ --users realm_file

We can then collect the JSON file for each realm from the Files tab.

7. Conclusion

In this article, we configured a backend with OAuth2 using Spring Boot and Keycloak.

In addition to a minimal Keycloak setup with Docker, we saw how to import and export realms.

Also, after reviewing the different configuration options for OAuth2 in Spring applications, we configured Spring applications as stateful OAuth2 clients with oauth2Login or stateless OAuth2 resource servers.

As usual, the source code for this article is available over on GitHub.