How to integrate the existing local db user tables into keycloak UI ( 23.0.0 )

Hi , please find the problem below

Scenario: I am having existing users in my local db( mysql) , i want to integrate these user tables into keycloak.How can I do this. I have tried with custom spi but no luck the created custom id name is not displaying in keycloak.

Thanks in advance!

The User Storage SPI is your friend: Server Developer Guide

Hi,
Tried the same approach but for some reason I am not getting the id in the providers list
sharing the files for reference.

PropertyFileUserStorageProvider:

package com.csmart.dx.middleware.promotions;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;

import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SubjectCredentialManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.storage.ReadOnlyException;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.adapter.AbstractUserAdapter;
import org.keycloak.storage.user.UserLookupProvider;

public class PropertyFileUserStorageProvider
implements UserStorageProvider,
UserLookupProvider,
CredentialInputValidator,
CredentialInputUpdater {

protected KeycloakSession session;
protected Properties properties;
protected ComponentModel model;
// map of loaded users in this transaction
protected Map<String, UserModel> loadedUsers = new HashMap<>();

public PropertyFileUserStorageProvider(KeycloakSession session, ComponentModel model, Properties properties) {
    this.session = session;
    this.model = model;
    this.properties = properties;
}

@Override
public void close() {
	
}

@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
    if (input.getType().equals(PasswordCredentialModel.TYPE)) throw new ReadOnlyException("user is read only for this update");

    return false;
}

@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {

}

@Override
public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
    return Stream.empty();
}

@Override
public boolean supportsCredentialType(String credentialType) {
    return credentialType.equals(PasswordCredentialModel.TYPE);
}


@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
    String password = properties.getProperty(user.getUsername());
    return credentialType.equals(PasswordCredentialModel.TYPE) && password != null;
}

@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
    if (!supportsCredentialType(input.getType())) return false;

    String password = properties.getProperty(user.getUsername());
    if (password == null) return false;
    return password.equals(input.getChallengeResponse());
}

@Override
public UserModel getUserById(RealmModel realm, String id) {
    StorageId storageId = new StorageId(id);
    String username = storageId.getExternalId();
    return getUserByUsername(realm, username);
}

@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
    UserModel adapter = loadedUsers.get(username);
    if (adapter == null) {
        String password = properties.getProperty(username);
        if (password != null) {
            adapter = createAdapter(realm, username);
            loadedUsers.put(username, adapter);
        }
    }
    return adapter;
}

protected UserModel createAdapter(RealmModel realm, String username) {
    return new AbstractUserAdapter(session, realm, model) {
        @Override
        public String getUsername() {
            return username;
        }

		@Override
		public SubjectCredentialManager credentialManager() {
			return null;
		}
    };
}


@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
    return null;
}

}

PropertyFileUserStorageProviderFactory:

package com.csmart.dx.middleware.promotions;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.storage.UserStorageProviderFactory;

public class PropertyFileUserStorageProviderFactory implements UserStorageProviderFactory {

public static final String PROVIDER_NAME = "readonly-property-file";

@Override
public String getId() {
    return PROVIDER_NAME;
}

private static final Logger logger = Logger.getLogger(PropertyFileUserStorageProviderFactory.class);
protected Properties properties = new Properties();


@Override
public void init(Config.Scope config) {
    InputStream is = getClass().getClassLoader().getResourceAsStream("/users.properties");

    if (is == null) {
        logger.warn("Could not find users.properties in classpath");
    } else {
        try {
            properties.load(is);
        } catch (IOException ex) {
            logger.error("Failed to load users.properties file", ex);
        }
    }
}

@Override
public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) {
    return new PropertyFileUserStorageProvider(session, model, properties);
}

}

users.properties:

user1=password1
user2=password2

org.keycloak.storage.UserStorageProviderFactory:

com.csmart.dx.middleware.promotions.PropertyFileUserStorageProviderFactory

providing the folder structure for reference:
image

Can anyone help me with the above issue.

Can anyone help me with the above scenario - the custom spi which I have created and deployed is not displaying in the user federation list

How do you run your application?
You should copy JAR file to Keycloak providers in order to use them:
COPY /build/libs/{YOUR_JAR}*.jar /opt/keycloak/providers

Here is example of whole Dockerfile:

FROM quay.io/keycloak/keycloak:23.0.0 AS builder

WORKDIR /opt/keycloak
COPY /build/libs/{YOUR_JAR}*.jar /opt/keycloak/providers

FROM quay.io/keycloak/keycloak:23.0.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/

ENV KC_DB=postgres
ENV KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak
ENV KC_DB_USERNAME= {username}
ENV KC_DB_PASSWORD= {password}
ENV KC_LOG_LEVEL=INFO
ENV KC_HTTP_ENABLED=false
ENV KC_HTTP_PORT=8080
ENV KEYCLOAK_ADMIN=admin
ENV KEYCLOAK_ADMIN_PASSWORD=admin

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

CMD ["start-dev"]

am not using docker file to run the server
I have copied the jar into /providers folder then executed this command kc.bat build by going into my keycloak bin location
D:\test-demo\keycloak-23.0.0\keycloak-23.0.0\bin

This is the location where JAR should be copied, not just in bin folder.

That holds true for docker based deployments. When using a deployment method based on the published archives it is the providers folder in the directory.
So that should be correct.

2 things to think of

  1. start keycloak from the root of the unpacked dir (so bin/kc.bat)
  2. Does it log anything about picking up the SPI?

I have copied my jar in the /providers folder



the above were the information getting when I run kc.bat


when i start the server am getting the above information