You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Keycloak 20.0.1自定义UserStorageProvider创建EntityManagerFactory遇空指针

问题

我们正在将Keycloak 13.0.1迁移至Keycloak 20.0.1,应用将用户存储在外部PostgreSQL数据库中,通过自定义UserStorageProvider暴露给Keycloak。在Keycloak 20中,无法获取可用的EntityManagerFactory或EntityManager来支撑自定义UserStorageProvider。

相关代码

CustomUserStorageProviderFactory.java

public class CustomUserStorageProviderFactory implements UserStorageProviderFactory<CustomUserStorageProvider> {

  private static final String PROVIDER_ID = "CUSTOM_ID";

  @Override
  public CustomUserStorageProvider create(KeycloakSession session, ComponentModel model) {
    try {
      log.info("Intializing user federation provider.");
      EntityManagerFactory factory = EntityManagerFactoryHelper.getFactory();
      CustomUserStorageProvider provider = new CustomUserStorageProvider(session, model, factory.createEntityManager());
      return provider;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

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

}

EntityManagerFactoryHelper.java

public class EntityManagerFactoryHelper {

  private static String PERSISTENCE_UNIT_NAME = "customunit";
  private static EntityManagerFactory factory;

  static {

    try {
      factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }

  }

  public static EntityManagerFactory getFactory() {
    return factory;
  }

}

persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">
    <persistence-unit name="customunit">
        <class>my.custom.Class</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://database_url:5432/database_name" />
            <property name="javax.persistence.jdbc.user" value="user" />
            <property name="javax.persistence.jdbc.password" value="password" />
            <property name="javax.persistence.provider" value="org.hibernate.jpa.HibernatePersistenceProvider" />
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />

            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="none" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

异常信息

自定义提供者代码打包为JAR放置在providers目录下,初始化CustomUserStorageProviderFactory时抛出如下异常:

java.lang.NullPointerException
    at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(FastBootHibernatePersistenceProvider.java:172)
    at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:66)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
    at my.package.EntityManagerFactoryHelper.<clinit>(EntityManagerFactoryHelper.java:14)
    at my.package.CustomUserStorageProviderFactory.create(CustomUserStorageProviderFactory.java:22)
    at my.package.CustomUserStorageProviderFactory.create(CustomUserStorageProviderFactory.java:10)

空指针源于FastBootHibernatePersistenceProvider中以下代码,此处RecordedState为null:

RecordedState recordedState = PersistenceUnitsHolder.popRecordedState(persistenceUnitName);

if (recordedState.isReactive()) {
    throw new IllegalStateException(
            "Attempting to boot a blocking Hibernate ORM instance on a reactive RecordedState");
}

开启TRACE日志后,Keycloak启动时JPA启动线程执行此代码无异常,能正常连接数据库、检查并注册实体。仅在初始化自定义提供者获取EntityManagerFactory时出现错误。

解决方案

Keycloak 20基于Quarkus重构,原有的JPA初始化逻辑不再适配,需调整为Quarkus兼容的方式:

  1. 移除静态块初始化逻辑
    静态块在Quarkus类加载阶段执行,此时JPA上下文尚未完全初始化,导致无法获取RecordedState。直接通过Keycloak上下文获取EntityManager。

  2. 通过KeycloakSession获取EntityManager
    Keycloak 20的KeycloakSession已集成Quarkus上下文,可直接获取EntityManager:

    @Override
    public CustomUserStorageProvider create(KeycloakSession session, ComponentModel model) {
        try {
            log.info("Intializing user federation provider.");
            // 获取绑定到指定persistence unit的EntityManager
            EntityManager em = session.getEntityManagerFactory("customunit").createEntityManager();
            CustomUserStorageProvider provider = new CustomUserStorageProvider(session, model, em);
            return provider;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
  3. 配置Quarkus数据源与Persistence Unit绑定
    在conf/quarkus.properties中添加自定义数据源配置:

    # 自定义数据源配置
    quarkus.datasource.customdb.db-kind=postgresql
    quarkus.datasource.customdb.jdbc.url=jdbc:postgresql://database_url:5432/database_name
    quarkus.datasource.customdb.username=user
    quarkus.datasource.customdb.password=password
    # 绑定persistence unit到自定义数据源
    quarkus.hibernate-orm.customunit.datasource=customdb
    quarkus.hibernate-orm.customunit.packages=my.custom
    

    同时修改persistence.xml,移除JDBC相关配置,适配Quarkus规范:

    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_2.xsd"
                 version="2.2">
        <persistence-unit name="customunit">
            <class>my.custom.Class</class>
            <exclude-unlisted-classes>true</exclude-unlisted-classes>
            <properties>
                <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
                <property name="hibernate.hbm2ddl.auto" value="none" />
                <property name="hibernate.show_sql" value="true" />
                <property name="hibernate.format_sql" value="true" />
            </properties>
        </persistence-unit>
    </persistence>
    
  4. 精简依赖包
    自定义提供者JAR无需打包Hibernate或PostgreSQL驱动,Keycloak 20已内置这些依赖,避免版本冲突。


内容的提问来源于stack exchange,提问作者ziggy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 21:05:17