Моя основная цель состоит в том, чтобы всякий раз, когда я использую SPI пользовательского хранилища для загрузки пользователи на вкладке «Пользователи» на портале keycloak. Я хочу видеть ввод пароля на вкладке учетных данных. Как показано на изображении, он пуст.
Я попробовал написать реализацию для UserFedereatedStorageProvider, а также создал для него фабрику и снова создал файл с именем org.keycloak.storage.federated. .UserFederatedStorageProvider имеет полное имя класса. Но я ничего не вижу на этой вкладке.
Я развернул Keycloak - Внутреннюю базу данных Keycloak - Внешнюю базу данных Keycloak на докере, и все они работают очень хорошо, не выдавая никаких ошибок при запуске. Я перехожу на вкладку учетные данные. Консоль Keycloak работает хорошо.
Проверьте снимки экрана:


Если вам нужна дополнительная информация, пожалуйста, проверьте мой репозиторий на GitHub.
URL-адрес репозитория GitHub: https://github.com/CaptainEddy/keycloak-maven-repo.git
Заранее спасибо!
Ниже это мой код,
CustomerUserFederatedUserCredentialStore:
package com.cob.portal;
import java.util.List;
import java.util.stream.Stream;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
public class CustomerUserFederatedUserCredentialStore implements UserFederatedStorageProvider{
private ExternalCredentialStore credentialStore = new ExternalCredentialStore();
private final KeycloakSession session;
// Simulating an external store where credentials are fetched from (e.g., LDAP)
private ExternalCredentialStore externalStore;
public CustomerUserFederatedUserCredentialStore(KeycloakSession session,
ExternalCredentialStore externalCredentialStore) {
this.session = session;
this.externalStore = new ExternalCredentialStore(); // Initialize the external store simulation
}
@Override
public void close() {
// Cleanup resources if needed, like closing connections to external systems
}
@Override
public void updateCredential(RealmModel realm, String userId, CredentialModel cred) {
// Update the credential in the external storage system (e.g., LDAP)
// Here we simulate this by updating it in our mock external store
if (cred.getType().equals(CredentialModel.PASSWORD)) {
externalStore.updatePassword(userId, cred.getValue());
}
}
@Override
public CredentialModel createCredential(RealmModel realm, String userId, CredentialModel cred) {
String hashedPassword = null;
// Check if the credential is a password
if (cred.getType().equals(CredentialModel.PASSWORD)) {
// Hash the password using the external store (e.g., LDAP, AD)
hashedPassword = externalStore.createPassword(userId, cred.getValue(), session);
// Set the hashed password in the credential model
cred.setValue(hashedPassword);
}
// Return the credential model after storing it
return cred;
}
@Override
public boolean removeStoredCredential(RealmModel realm, String userId, String id) {
// Remove the credential from external storage (e.g., LDAP)
boolean success = externalStore.removePassword(userId);
// Optionally, remove the credential from Keycloak's database if needed
return success;
}
@Override
public CredentialModel getStoredCredentialById(RealmModel realm, String userId, String id) {
// Retrieve the credential from the external storage (e.g., LDAP)
String password = externalStore.getPassword(userId);
if (password != null) {
CredentialModel credential = new CredentialModel();
credential.setId(id);
credential.setType(CredentialModel.PASSWORD);
credential.setValue(password);
return credential;
}
return null;
}
@Override
public Stream getStoredCredentialsStream(RealmModel realm, String userId) {
// Fetch credentials (like passwords) from the external store and return as
// stream
String password = externalStore.getPassword(userId);
if (password != null) {
CredentialModel credential = new CredentialModel();
credential.setId(KeycloakModelUtils.generateId());
credential.setType(CredentialModel.PASSWORD);
credential.setValue(password);
return Stream.of(credential);
}
return Stream.empty();
}
@Override
public Stream getStoredCredentialsByTypeStream(RealmModel realm, String userId, String type) {
// Filter credentials by type, for example, only returning passwords
if (CredentialModel.PASSWORD.equals(type)) {
String password = externalStore.getPassword(userId);
if (password != null) {
CredentialModel credential = new CredentialModel();
credential.setId(KeycloakModelUtils.generateId());
credential.setType(CredentialModel.PASSWORD);
credential.setValue(password);
return Stream.of(credential);
}
}
return Stream.empty();
}
@Override
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, String userId, String name, String type) {
// For federated users, retrieve credential by name and type (e.g., password)
if (CredentialModel.PASSWORD.equals(type)) {
String password = externalStore.getPassword(userId);
if (password != null) {
CredentialModel credential = new CredentialModel();
credential.setId(name); // In practice, this would probably be a real ID
credential.setType(CredentialModel.PASSWORD);
credential.setValue(password);
return credential;
}
}
return null;
}
@Override
public void setSingleAttribute(RealmModel realm, String userId, String name, String value) {
// TODO Auto-generated method stub
}
@Override
public void setAttribute(RealmModel realm, String userId, String name, List values) {
// TODO Auto-generated method stub
}
@Override
public void removeAttribute(RealmModel realm, String userId, String name) {
// TODO Auto-generated method stub
}
@Override
public MultivaluedHashMap getAttributes(RealmModel realm, String userId) {
// TODO Auto-generated method stub
return null;
}
@Override
public Stream getUsersByUserAttributeStream(RealmModel realm, String name, String value) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
// TODO Auto-generated method stub
return null;
}
@Override
public void addFederatedIdentity(RealmModel realm, String userId, FederatedIdentityModel socialLink) {
// TODO Auto-generated method stub
}
@Override
public boolean removeFederatedIdentity(RealmModel realm, String userId, String socialProvider) {
// TODO Auto-generated method stub
return false;
}
@Override
public void preRemove(RealmModel realm, IdentityProviderModel provider) {
// TODO Auto-generated method stub
}
@Override
public void updateFederatedIdentity(RealmModel realm, String userId,
FederatedIdentityModel federatedIdentityModel) {
// TODO Auto-generated method stub
}
@Override
public Stream getFederatedIdentitiesStream(String userId, RealmModel realm) {
// TODO Auto-generated method stub
return null;
}
@Override
public FederatedIdentityModel getFederatedIdentity(String userId, String socialProvider, RealmModel realm) {
// TODO Auto-generated method stub
return null;
}
@Override
public void addConsent(RealmModel realm, String userId, UserConsentModel consent) {
// TODO Auto-generated method stub
}
@Override
public UserConsentModel getConsentByClient(RealmModel realm, String userId, String clientInternalId) {
// TODO Auto-generated method stub
return null;
}
@Override
public Stream getConsentsStream(RealmModel realm, String userId) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) {
// TODO Auto-generated method stub
}
@Override
public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) {
// TODO Auto-generated method stub
return false;
}
@Override
public void setNotBeforeForUser(RealmModel realm, String userId, int notBefore) {
// TODO Auto-generated method stub
}
@Override
public int getNotBeforeOfUser(RealmModel realm, String userId) {
// TODO Auto-generated method stub
return 0;
}
@Override
public Stream getGroupsStream(RealmModel realm, String userId) {
// TODO Auto-generated method stub
return null;
}
@Override
public void joinGroup(RealmModel realm, String userId, GroupModel group) {
// TODO Auto-generated method stub
}
@Override
public void leaveGroup(RealmModel realm, String userId, GroupModel group) {
// TODO Auto-generated method stub
}
@Override
public Stream getMembershipStream(RealmModel realm, GroupModel group, Integer firstResult, Integer max) {
// TODO Auto-generated method stub
return null;
}
@Override
public Stream getRequiredActionsStream(RealmModel realm, String userId) {
// TODO Auto-generated method stub
return null;
}
@Override
public void addRequiredAction(RealmModel realm, String userId, String action) {
// TODO Auto-generated method stub
}
@Override
public void removeRequiredAction(RealmModel realm, String userId, String action) {
// TODO Auto-generated method stub
}
@Override
public void grantRole(RealmModel realm, String userId, RoleModel role) {
// TODO Auto-generated method stub
}
@Override
public Stream getRoleMappingsStream(RealmModel realm, String userId) {
// TODO Auto-generated method stub
return null;
}
@Override
public void deleteRoleMapping(RealmModel realm, String userId, RoleModel role) {
// TODO Auto-generated method stub
}
@Override
public Stream getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer max) {
// TODO Auto-generated method stub
return null;
}
@Override
public Stream getStoredUsersStream(RealmModel realm, Integer first, Integer max) {
// TODO Auto-generated method stub
return null;
}
@Override
public int getStoredUsersCount(RealmModel realm) {
// TODO Auto-generated method stub
return 0;
}
@Override
public void preRemove(RealmModel realm) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(ProtocolMapperModel protocolMapper) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(ClientScopeModel clientScope) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(RealmModel realm, UserModel user) {
// TODO Auto-generated method stub
}
@Override
public void preRemove(RealmModel realm, ComponentModel model) {
// TODO Auto-generated method stub
}
}
ExternalCredentialStore:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.credential.PasswordCredentialModel;
public class ExternalCredentialStore {
public final String url = "jdbc:postgresql://postgresuser:5433/appdev?currentSchema=public";
public final String dbUsername = "admin";
public final String dbPassword = "admin";
public final String SQLQueryToGetUserPasswordByUserId = "SELECT password FROM users WHERE id = ?";
public final String SQLQueryToUpdatePasswordById = "UPDATE users SET password = ? WHERE id = ?";
public final String SQLQueryToDeletePasswordById = "UPDATE users SET password = ? WHERE id = ?";
// Simulate the process of getting the password for a federated user
public String getPassword(String userId) {
String password = null;
// JDBC Connection setup to external database
try (Connection conn = (Connection) DriverManager.getConnection(url, dbUsername, dbPassword);
PreparedStatement stmt = conn.prepareStatement(SQLQueryToGetUserPasswordByUserId)) {
// Set the username parameter
stmt.setString(1, userId);
// Execute the query and retrieve the password
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
password = rs.getString("password"); // Assuming the column name is 'password'
System.out.println("From fetch Method - Password: " + password);
}
}
} catch (Exception e) {
e.printStackTrace(); // Handle exception properly
throw new RuntimeException("Error fetching password from external database", e);
}
return password;
}
// Simulate creating a password for the federated user
public String createPassword(String userId, String password, KeycloakSession session) {
System.out.println("Creating password for user: " + userId);
// Use the PBKDF2 algorithm to hash the password
PasswordCredentialModel hashedCredential = new PBKDF2PasswordHashProvider(session)
.encodedCredential(password, 10000);
// Set the hashed password in the credential
hashedCredential.setValue(hashedCredential.getPasswordSecretData().getValue());
return hashedCredential.getValue();
}
// Simulate updating a password for the federated user
public void updatePassword(String userId, String password) {
System.out.println("Updating password for user: " + userId);
// JDBC Connection setup to external database
try (Connection conn = (Connection) DriverManager.getConnection(url, dbUsername, dbPassword);
PreparedStatement stmt = conn.prepareStatement(SQLQueryToUpdatePasswordById)) {
// Set the username parameter
stmt.setString(1, password);
stmt.setString(2, userId);
// Execute the query and retrieve the password
try (ResultSet rs = stmt.executeQuery()) {
if (rs.rowUpdated()) {
password = rs.getString("password"); // Assuming the column name is 'password'
System.out.println("From fetch Method - Password: " + password);
}
}
} catch (Exception e) {
e.printStackTrace(); // Handle exception properly
throw new RuntimeException("Error fetching password from external database", e);
}
}
// Simulate removing the password from the federated user
public boolean removePassword(String userId) {
// This would remove the user's password from the external store
System.out.println("Removing password for user: " + userId);
try (Connection conn = (Connection) DriverManager.getConnection(url, dbUsername, dbPassword);
PreparedStatement stmt = conn.prepareStatement(SQLQueryToDeletePasswordById)) {
// Set the username parameter
stmt.setString(1, "");
stmt.setString(2, userId);
// Execute the query and retrieve the password
try (ResultSet rs = stmt.executeQuery()) {
if (rs.rowUpdated()) {
System.out.println("Made Pwd Blank");
return true;
}
}
} catch (Exception e) {
e.printStackTrace(); // Handle exception properly
throw new RuntimeException("Error fetching password from external database", e);
}
return false;
}
}
PBKDF2PasswordHashProvider:
package com.cob.portal;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.models.credential.dto.PasswordCredentialData;
import org.keycloak.models.credential.dto.PasswordSecretData;
public class PBKDF2PasswordHashProvider implements PasswordHashProvider {
private static final int SALT_SIZE = 16; // Salt size in bytes
private static final int PBKDF2_ITERATIONS = 10000; // Number of iterations for PBKDF2
private static final int KEY_LENGTH = 256; // Length of the hashed password key
private KeycloakSession session = null;
public PBKDF2PasswordHashProvider(KeycloakSession session) {
super();
this.session = session;
}
@Override
public void close() {
// Nothing to close
}
@Override
public boolean policyCheck(PasswordPolicy policy, PasswordCredentialModel credential) {
// You can implement your own password policy checks here
// For example, check password length, character types, etc.
return true;
}
@Override
public PasswordCredentialModel encodedCredential(String rawPassword, int iterations) {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_SIZE];
random.nextBytes(salt);
char[] passwordChars = rawPassword.toCharArray();
try {
PBEKeySpec spec = new PBEKeySpec(passwordChars, salt, iterations, KEY_LENGTH);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = skf.generateSecret(spec).getEncoded();
String encodedHash = Base64.getEncoder().encodeToString(hash);
PasswordCredentialData data = new PasswordCredentialData(PBKDF2_ITERATIONS, "pbkdf2");
PasswordSecretData secretData = new PasswordSecretData(null, Base64.getEncoder().encodeToString(salt),
null);
return PasswordCredentialModel.createFromValues(data.getAlgorithm(), salt, data.getHashIterations(),
encodedHash);
} catch (Exception e) {
throw new RuntimeException("Error creating password hash", e);
} finally {
// Clear password characters from memory
Arrays.fill(passwordChars, (char) 0);
}
}
@Override
public boolean verify(String rawPassword, PasswordCredentialModel credential) {
byte[] salt = credential.getPasswordSecretData().getSalt();
int iterations = credential.getPasswordCredentialData().getHashIterations();
String encodedHash = credential.getPasswordSecretData().getValue();
char[] passwordChars = rawPassword.toCharArray();
try {
PBEKeySpec spec = new PBEKeySpec(passwordChars, salt, iterations, KEY_LENGTH);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] testHash = skf.generateSecret(spec).getEncoded();
String testEncodedHash = Base64.getEncoder().encodeToString(testHash);
return encodedHash.equals(testEncodedHash);
} catch (Exception e) {
throw new RuntimeException("Error verifying password", e);
} finally {
Arrays.fill(passwordChars, (char) 0);
}
}
}
Подробнее здесь: https://stackoverflow.com/questions/793 ... l-db-to-ke
Мобильная версия