securityoauth-2.0keycloakkeycloak-rest-api

KeyCloak Java Service Account Create User


I am new to Keycloak and trying to wrap my head around how to properly register a user using the Keycloack admin client

The documentation doesn't have concrete examples and there a ton of screen shots missing that perpetuate ambiguity.

I have found two JAVA based examples which provide some insight on how the API calls should work: here and here but I keep encountering a jakarta.ws.rs.NotFoundException: HTTP 404 Not Found every time I try to create a user or view the realm representation. Any help on why this could be happening?

I have created a Client Credentials client and assigned the following roles to be able to manage users (create, delete, modify).

enter image description here

I am able to receive a token from the Token end-point using the Client Credentials flow in POSTMAN enter image description here

Below you will find the Java calls

private Keycloak getAdminKeycloak() {
        this.base = environment.getProperty(MyConstants.KEYCLOAK_TOKEN_END_POINT);
        this.realm = environment.getProperty(MyConstants.KEYCLOAK_REALM);
        this.username = environment.getProperty(MyConstants.KEYCLOAK_SERVER_API_CLIENTID);
        this.password = environment.getProperty(MyConstants.KEYCLOAK_SERVICE_API_SECRET);
        return KeycloakBuilder.builder().serverUrl(base)
                .realm(realm)
                .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                .clientId(username)
                .clientSecret(password).build();
    }

    @Override
    public ResponseEntity<String> registerUser(User user) {
        
        Keycloak keycloak = getAdminKeycloak();
        // set user representation
        UserRepresentation newuser = new UserRepresentation();
        newuser.setEmail(user.getEmail());
        newuser.setFirstName(user.getFirstName());
        newuser.setLastName(user.getLastName());
        newuser.setEnabled(true);
        // Get realm
        RealmResource realmResource = keycloak.realm(realm);
        System.out.print(realmResource.toRepresentation().toString());
        UsersResource usersResource = realmResource.users();
        ArrayList<UserRepresentation> users = (ArrayList<UserRepresentation>) usersResource.list();
        // create user
        Response response = usersResource.create(newuser);
        String userID = CreatedResponseUtil.getCreatedId(response);
        if (userID == null || userID.isEmpty()) {
            return new ResponseEntity<>(MyConstants.ERROR_OCCURED, HttpStatus.METHOD_FAILURE);
        }
        // set credentials
        CredentialRepresentation passwordCred = new CredentialRepresentation();
        passwordCred.setTemporary(false);
        passwordCred.setType(CredentialRepresentation.PASSWORD);
        passwordCred.setValue(user.getPassword());
        UserResource userResource = usersResource.get(userID);
        userResource.resetPassword(passwordCred);

        return new ResponseEntity<>(MyConstants.REGISTRATION_COMPLETE, HttpStatus.OK);
    }

Solution

  • Requirement

    Maven 3.9.9 and Java 17

    enter image description here More detail for keycloak docker launching in here

    Keycloak Using docker compose and version 23.0.7

    docker-compose.yml

    version: '3.6'
    
    services:
      keycloak_web:
        image: quay.io/keycloak/keycloak:23.0.7
        container_name: keycloak_web
        environment:
          KC_DB: postgres
          KC_DB_URL: jdbc:postgresql://keycloakdb:5432/keycloak
          KC_DB_USERNAME: keycloak
          KC_DB_PASSWORD: password
    
          KC_HOSTNAME: localhost
          KC_HOSTNAME_PORT: 8080
          KC_HOSTNAME_STRICT: false
          KC_HOSTNAME_STRICT_HTTPS: false
    
          KC_LOG_LEVEL: info
          KC_METRICS_ENABLED: true
          KC_HEALTH_ENABLED: true
          KEYCLOAK_ADMIN: admin
          KEYCLOAK_ADMIN_PASSWORD: admin
        command: start-dev
        depends_on:
          - keycloakdb
        ports:
          - 8080:8080
    
      keycloakdb:
        image: postgres:15
        volumes:
          - postgres_data:/var/lib/postgresql/data
        environment:
          POSTGRES_DB: keycloak
          POSTGRES_USER: keycloak
          POSTGRES_PASSWORD: password
    
    volumes:
      postgres_data:
    

    enter image description here

    Setting Client ID/ password and manager-user role

    enter image description here

    enter image description here

    enter image description here

    More detail got master token in here

    Test Client Token by curl

    curl -X POST "http://localhost:8080/realms/my-realm/protocol/openid-connect/token" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "client_id=my-client" \
      -d "client_secret=hzkBBj8MY8hDaqHblrSkKbaDQvUnhbyb" \
      -d "grant_type=client_credentials"
    

    enter image description here

    Java code

    Files tree

    C:\Users\benchvue\temp\6>tree /F
    Folder PATH listing
    Volume serial number is 8837-26D8
    C:.
    │   docker-compose.yml
    │   pom.xml
    │
    └───src
        └───main
            ├───java
            │   └───com
            │       └───example
            │               KeycloakUserRegistrationApplication.java
            │               KeycloakUserService.java
            │               User.java
            │
            └───resources
                    application.properties
    

    Main code

    KeycloakUserRegistrationApplication.java

    package com.example;
    
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.ResponseEntity;
    
    @SpringBootApplication
    public class KeycloakUserRegistrationApplication {
    
        public static void main(String[] args) {
            new SpringApplicationBuilder(KeycloakUserRegistrationApplication.class)
                    .properties("server.port=8081")
                    .run(args);
        }
    
        @Bean
        public CommandLineRunner run(KeycloakUserService keycloakUserService) {
            return args -> {
                User newUser = new User();
                newUser.setUsername("testuser");  // Setting the required username
                newUser.setEmail("testuser@example.com");
                newUser.setFirstName("John");
                newUser.setLastName("Kim");
                newUser.setPassword("password123");  // Example password, adjust as needed
    
                keycloakUserService.registerUser(newUser);
            };
        }
    }
    

    KeycloakUserService.java

    package com.example;
    
    import org.keycloak.OAuth2Constants;
    import org.keycloak.admin.client.Keycloak;
    import org.keycloak.admin.client.KeycloakBuilder;
    import org.keycloak.admin.client.resource.RealmResource;
    import org.keycloak.admin.client.resource.UsersResource;
    import org.keycloak.representations.AccessTokenResponse;
    import org.keycloak.representations.idm.CredentialRepresentation;
    import org.keycloak.representations.idm.UserRepresentation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.Environment;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    
    import javax.ws.rs.core.Response;
    
    @Service
    public class KeycloakUserService {
    
        @Autowired
        private Environment environment;
    
        private Keycloak getAdminKeycloak() {
            String serverUrl = environment.getProperty("keycloak.server-url");
            String masterRealm = environment.getProperty("keycloak.master-realm");
            String clientId = environment.getProperty("keycloak.master-client-id");
            String clientSecret = environment.getProperty("keycloak.master-client-secret");
            
            System.out.println("Server URL: " + serverUrl);
            System.out.println("Master Realm: " + masterRealm);
            System.out.println("Client ID: " + clientId);
            System.out.println("Client Secret: " + clientSecret);
    
            // Using the password grant type for admin client
            return KeycloakBuilder.builder()
                    .serverUrl(serverUrl)
                    .realm(masterRealm)
                    .grantType(OAuth2Constants.PASSWORD)
                    .clientId("admin-cli")
                    .username("admin")  // Ensure this matches the username in Keycloak
                    .password("admin")  // Ensure this matches the password in Keycloak
                    .build();
        }
    
        private Keycloak getClientKeycloak() {
            String serverUrl = environment.getProperty("keycloak.server-url");
            String targetRealm = environment.getProperty("keycloak.target-realm");
            String clientId = environment.getProperty("keycloak.client-id");
            String clientSecret = environment.getProperty("keycloak.client-secret");
    
            System.out.println("Target Realm Keycloak Config:");
            System.out.println("serverUrl: " + serverUrl);
            System.out.println("targetRealm: " + targetRealm);
            System.out.println("clientId: " + clientId);
            System.out.println("clientSecret: " + clientSecret);
    
            // Using client_credentials for non-admin clients
            Keycloak keycloak = KeycloakBuilder.builder()
                    .serverUrl(serverUrl)
                    .realm(targetRealm)
                    .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                    .clientId(clientId)
                    .clientSecret(clientSecret)
                    .build();
    
            try {
                AccessTokenResponse tokenResponse = keycloak.tokenManager().getAccessToken();
                System.out.println("Access Token for client: " + tokenResponse.getToken());
            } catch (Exception e) {
                System.out.println("Failed to retrieve client access token: " + e.getMessage());
            }
    
            return keycloak;
        }
    
        public ResponseEntity<String> registerUser(User user) {
            Keycloak keycloak = getAdminKeycloak();  // Using admin Keycloak instance
            String targetRealm = environment.getProperty("keycloak.target-realm");
        
            System.out.println("Using target realm: " + targetRealm);
            System.out.println("Creating user with email: " + user.getEmail());
        
            UserRepresentation newUser = new UserRepresentation();
            newUser.setUsername(user.getUsername());  // Ensure username is set here
            newUser.setEmail(user.getEmail());
            newUser.setFirstName(user.getFirstName());
            newUser.setLastName(user.getLastName());
            newUser.setEnabled(true);
        
            RealmResource realmResource = keycloak.realm(targetRealm);
            UsersResource usersResource = realmResource.users();
        
            Response response = usersResource.create(newUser);
            int status = response.getStatus();
            System.out.println("Response status: " + status);
        
            if (status == 201) {
                return new ResponseEntity<>("User registration completed successfully.", HttpStatus.CREATED);
            } else {
                String errorResponse = response.readEntity(String.class);
                System.out.println("Response: " + errorResponse);
                return new ResponseEntity<>("An error occurred while creating the user: " + errorResponse, HttpStatus.BAD_REQUEST);
            }
        }
        
    }
    

    application.properties

    # Keycloak server URL
    
    keycloak.server-url=http://localhost:8080
    keycloak.master-realm=master
    keycloak.master-client-id=admin
    keycloak.master-client-secret=admin
    keycloak.target-realm=my-realm
    keycloak.client-id=my-client
    keycloak.client-secret=hzkBBj8MY8hDaqHblrSkKbaDQvUnhbyb
    
    
    # Messages for registration status
    registration.complete=User registration completed successfully.
    error.occurred=An error occurred while creating the user.
    
    

    User.java

    package com.example;
    
    public class User {
        private String username;
        private String email;
        private String password;
        private String firstName;
        private String lastName;
    
        // Getter and setter for username
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        // Getter and setter for email
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        // Getter and setter for password
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        // Getter and setter for firstName
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        // Getter and setter for lastName
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    }
    

    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>keycloak-user-registration</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <properties>
            <java.version>17</java.version>
            <spring-boot.version>3.1.0</spring-boot.version>
            <keycloak.version>23.0.7</keycloak.version>
        </properties>
    
        <dependencies>
            <!-- Spring Boot Starter -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>
    
            <!-- Spring Boot Starter Web -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>
    
            <!-- Keycloak Admin Client (downgraded to 18.0.0) -->
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-admin-client</artifactId>
                <version>18.0.0</version>
            </dependency>
    
            <!-- Javax JAX-RS API (for javax.ws.rs.core) -->
            <dependency>
                <groupId>javax.ws.rs</groupId>
                <artifactId>javax.ws.rs-api</artifactId>
                <version>2.1.1</version>
            </dependency>
    
            <!-- Logback for logging compatibility -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.4.7</version>
            </dependency>
    
            <!-- Lombok (Optional for Getter/Setter) -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- Testing Dependencies -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <version>${spring-boot.version}</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.14.3</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-annotations</artifactId>
                <version>2.14.3</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>2.14.3</version>
            </dependency>
    
        </dependencies>
    
    
    
    
        <build>
            <plugins>
                <!-- Spring Boot Maven Plugin -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <mainClass>com.example.KeycloakUserRegistrationApplication</mainClass>
                    </configuration>
                </plugin>
    
                <!-- Compiler Plugin for Java 17 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    
    

    Compile and run it

    mvn clena compile
    mvn clean package
    java -jar target/keycloak-user-registration-1.0-SNAPSHOT.jar
    

    enter image description here

    enter image description here

    enter image description here

    Result -user created

    enter image description here

    Update using my-realm only

    KeycloakUserService.java

    package com.example;
    
    import org.keycloak.OAuth2Constants;
    import org.keycloak.admin.client.Keycloak;
    import org.keycloak.admin.client.KeycloakBuilder;
    import org.keycloak.admin.client.resource.RealmResource;
    import org.keycloak.admin.client.resource.UsersResource;
    import org.keycloak.representations.AccessTokenResponse;
    import org.keycloak.representations.idm.CredentialRepresentation;
    import org.keycloak.representations.idm.UserRepresentation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.Environment;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    
    import javax.ws.rs.core.Response;
    import org.keycloak.admin.client.resource.UserResource;
    
    
    @Service
    public class KeycloakUserService {
    
        @Autowired
        private Environment environment;
    
        private Keycloak getClientKeycloak() {
            String serverUrl = environment.getProperty("keycloak.server-url");
            String targetRealm = environment.getProperty("keycloak.target-realm");
            String clientId = environment.getProperty("keycloak.client-id");
            String clientSecret = environment.getProperty("keycloak.client-secret");
        
            System.out.println("Target Realm Keycloak Config:");
            System.out.println("serverUrl: " + serverUrl);
            System.out.println("targetRealm: " + targetRealm);
            System.out.println("clientId: " + clientId);
            System.out.println("clientSecret: " + clientSecret);
        
            // Building Keycloak instance using client credentials
            Keycloak keycloak = KeycloakBuilder.builder()
                    .serverUrl(serverUrl)
                    .realm(targetRealm) // Set to 'my-realm'
                    .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                    .clientId(clientId) // Set to 'my-client'
                    .clientSecret(clientSecret) // Set to 'my-client' secret
                    .build();
        
            try {
                AccessTokenResponse tokenResponse = keycloak.tokenManager().getAccessToken();
                System.out.println("Access Token for client: " + tokenResponse.getToken());
            } catch (Exception e) {
                System.out.println("Failed to retrieve client access token: " + e.getMessage());
            }
        
            return keycloak;
        }
        
        public ResponseEntity<String> registerUser(User user) {
            // Using the client Keycloak instance for user registration
            Keycloak keycloak = getClientKeycloak();
            
            System.out.println("Creating user with email: " + user.getEmail());
            
            // Set up the user representation for Keycloak
            UserRepresentation newUser = new UserRepresentation();
            newUser.setUsername(user.getUsername());
            newUser.setEmail(user.getEmail());
            newUser.setFirstName(user.getFirstName());
            newUser.setLastName(user.getLastName());
            newUser.setEnabled(true);
        
            // Access target realm
            RealmResource realmResource = keycloak.realm(environment.getProperty("keycloak.target-realm"));
            UsersResource usersResource = realmResource.users();
        
            // Create user in Keycloak
            Response response = usersResource.create(newUser);
            if (response.getStatus() != 201) {
                System.out.println("Response status: " + response.getStatus());
                System.out.println("Response: " + response.readEntity(String.class));
                return new ResponseEntity<>("An error occurred while creating the user", HttpStatus.BAD_REQUEST);
            }
        
            String userId = response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1");
            System.out.println("User created with ID: " + userId);
            response.close();
        
            // Set credentials
            CredentialRepresentation passwordCred = new CredentialRepresentation();
            passwordCred.setTemporary(false);
            passwordCred.setType(CredentialRepresentation.PASSWORD);
            passwordCred.setValue(user.getPassword());
        
            UserResource userResource = usersResource.get(userId);
            userResource.resetPassword(passwordCred);
        
            return new ResponseEntity<>("User registration completed successfully.", HttpStatus.OK);
        }
        
    }
    

    application.properties

    keycloak.server-url=http://localhost:8080
    keycloak.target-realm=my-realm
    keycloak.client-id=my-client
    keycloak.client-secret=hzkBBj8MY8hDaqHblrSkKbaDQvUnhbyb