如何在Keycloak User Storage SPI/Quarkus中配置动态Schema名称
实现多Schema的Keycloak JPA用户存储提供程序
要实现基于不同PostgreSQL Schema的多个用户存储实例,核心是让每个实例动态绑定指定的Schema,而非硬编码配置。以下是具体实现步骤:
1. 扩展用户存储配置项
首先在用户存储工厂类中添加Schema名称的配置项,这样在Keycloak控制台创建实例时可以手动指定Schema:
public class CustomJpaUserStorageProviderFactory implements UserStorageProviderFactory<CustomJpaUserStorageProvider> { // 缓存不同Schema对应的EntityManagerFactory,避免重复创建 private final Map<String, EntityManagerFactory> emfCache = new ConcurrentHashMap<>(); // 基础JPA配置(驱动、URL、账号密码等,可从Keycloak全局配置读取) private final Properties baseJpaProps = new Properties(); @Override public void init(Config.Scope config) { // 初始化基础数据库连接配置 baseJpaProps.put("javax.persistence.jdbc.driver", "org.postgresql.Driver"); baseJpaProps.put("javax.persistence.jdbc.url", config.get("jdbcUrl")); baseJpaProps.put("javax.persistence.jdbc.user", config.get("dbUser")); baseJpaProps.put("javax.persistence.jdbc.password", config.get("dbPassword")); baseJpaProps.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); } @Override public List<ProviderConfigProperty> getConfigProperties() { List<ProviderConfigProperty> props = new ArrayList<>(); // 添加Schema名称配置项 props.add(new ProviderConfigProperty( "schemaName", "PostgreSQL Schema名称", "当前用户存储实例对应的PostgreSQL Schema", ProviderConfigProperty.STRING_TYPE, null )); // 可添加其他必要配置(如JDBC地址、账号等) return props; } // 其他必要方法(如getId())省略 }
2. 动态创建Schema专属的EntityManagerFactory
每个用户存储实例需要独立的EntityManagerFactory,绑定指定的Schema:
@Override public CustomJpaUserStorageProvider create(KeycloakSession session, ComponentModel model) { String targetSchema = model.getConfig().getFirst("schemaName"); // 缓存中不存在则创建新的EntityManagerFactory EntityManagerFactory emf = emfCache.computeIfAbsent(targetSchema, schema -> { Properties schemaProps = new Properties(baseJpaProps); // 设置Hibernate默认Schema schemaProps.put("hibernate.default_schema", schema); return Persistence.createEntityManagerFactory("custom-jpa-user-storage", schemaProps); }); // 创建Provider实例并传入专属EntityManager return new CustomJpaUserStorageProvider(session, model, emf.createEntityManager()); } @Override public void close() { // 关闭所有缓存的EntityManagerFactory emfCache.values().forEach(EntityManagerFactory::close); }
3. 调整实体类映射
移除实体类中硬编码的Schema配置,让Hibernate使用EntityManagerFactory指定的默认Schema:
@Entity @Table(name = "users") // 不指定schema,由EntityManagerFactory的default_schema控制 public class UserEntity { @Id @Column(name = "id") private String id; @Column(name = "username") private String username; // 其他字段、Getter/Setter省略 }
4. 在Provider中使用专属EntityManager
在用户存储实现类中,使用绑定了指定Schema的EntityManager进行数据库操作:
public class CustomJpaUserStorageProvider implements UserStorageProvider, UserLookupProvider { private final EntityManager em; private final KeycloakSession session; private final ComponentModel model; public CustomJpaUserStorageProvider(KeycloakSession session, ComponentModel model, EntityManager em) { this.session = session; this.model = model; this.em = em; } @Override public UserModel getUserByUsername(String username, RealmModel realm) { UserEntity user = em.createQuery( "SELECT u FROM UserEntity u WHERE u.username = :username", UserEntity.class) .setParameter("username", username) .getSingleResultOrNull(); // 转换为Keycloak UserModel的逻辑(参考官方示例) return user != null ? mapToUserModel(user, realm) : null; } @Override public void close() { if (em.isOpen()) { em.close(); } } // 其他必要方法(如getUserById)省略 }
最终效果
在Keycloak控制台创建用户存储实例时,分别输入不同的Schema名称,即可生成两个独立的用户存储实例,各自对应PostgreSQL中的指定Schema进行数据查询。
内容的提问来源于stack exchange,提问作者Matt
相关产品推荐
相关产品推荐

