Keycloak自定义注册流触发KC-SERVICES0013空指针错误求助
我在Keycloak 24.0.1中基于一份指南实现双表单自定义注册流,使用两个表单及自定义模板。第一个表单的认证器工厂代码如下,第二个表单仅修改PROVIDER_ID、getDisplayType()方法,以及render()方法返回form.createForm("register2.ftl");。
已完成操作
- 构建应用并将JAR文件复制到
providers目录 - 将自定义
.ftl模板文件添加到themes目录 - 执行命令
bin/kc.bat start-dev --debug暴露调试端口并设置断点
第一个表单认证器工厂代码
public class FirstCustomFormAuthenticatorFactory implements FormAuthenticatorFactory, FormAuthenticator { private static final String PROVIDER_ID = "register-form1"; private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { AuthenticationExecutionModel.Requirement.REQUIRED }; @Override public String getDisplayType() { return "Registration Form 1"; } @Override public String getReferenceCategory() { return ""; } @Override public boolean isConfigurable() { return false; } @Override public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { return REQUIREMENT_CHOICES; } @Override public boolean isUserSetupAllowed() { return false; } @Override public String getHelpText() { return ""; } @Override public List<ProviderConfigProperty> getConfigProperties() { return List.of(); } @Override public FormAuthenticator create(KeycloakSession keycloakSession) { return this; } @Override public void init(Config.Scope scope) { } @Override public void postInit(KeycloakSessionFactory keycloakSessionFactory) { } @Override public void close() { } @Override public String getId() { return PROVIDER_ID; } @Override public Response render(FormContext context, LoginFormsProvider form) { RegisterBean rb = new RegisterBean(new MultivaluedHashMap<>(), context.getSession()); form.setAttribute("register", rb); return form.createForm("register.ftl"); } }
问题复现与报错
Keycloak服务器启动无报错,执行以下操作后出现异常:
- 访问
http://localhost:8080/admin/myRealm/console - 填写首个注册表单的必要凭证并提交
报错信息:
KC-SERVICES0013: Failed authentication: java.lang.NullPointerException: Cannot invoke "org.keycloak.authentication.FormAuthenticator.render(org.keycloak.authentication.FormContext, org.keycloak.forms.login.LoginFormsProvider)" because "this.formAuthenticator" is null at org.keycloak.authentication.FormAuthenticationFlow.renderForm(FormAuthenticationFlow.java:307) at org.keycloak.authentication.FormAuthenticationFlow.processFlow(FormAuthenticationFlow.java:285) at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:377) at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:246) at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:133) at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:987) at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:364) at org.keycloak.services.resources.LoginActionsService.processRegistration(LoginActionsService.java:724) at org.keycloak.services.resources.LoginActionsService.registerRequest(LoginActionsService.java:780) at org.keycloak.services.resources.LoginActionsService.processRegister(LoginActionsService.java:758) at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$processRegister_707378c0de357d574c0a2e1d6056afe7606ab5ff.invoke(Unknown Source) at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29) at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141) at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147) at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582) at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538) at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29) at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:857)
排查建议
验证Provider注册:
确认JAR包中包含META-INF/services/org.keycloak.authentication.FormAuthenticatorFactory文件,文件内容为你的工厂类全限定名(例如com.yourpackage.FirstCustomFormAuthenticatorFactory)。Keycloak通过这个文件发现并加载自定义扩展,缺失的话会导致工厂无法被识别。修正
create方法实现:
当前create方法返回工厂自身(this),但Keycloak的FormAuthenticatorFactory应为每个请求创建独立的FormAuthenticator实例。建议拆分工厂类和认证器类:单独实现FirstCustomFormAuthenticator类实现FormAuthenticator接口,然后在工厂的create方法中返回该类的新实例,避免单例带来的初始化问题。补全FormAuthenticator的所有方法:
当前代码仅实现了render方法,但FormAuthenticator包含多个必须实现的方法(如action、requiresUser等)。未实现这些方法会导致Keycloak无法正确初始化认证器实例,添加以下实现(可根据业务逻辑调整):@Override public void action(FormContext context) { // 处理表单提交,验证输入后调用context.success()进入下一个流节点 context.success(); } @Override public boolean requiresUser() { return false; } @Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; } @Override public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { }检查注册流配置:
在Keycloak管理控制台中,确认自定义注册流的每个执行节点都正确关联了对应的Provider ID(register-form1和第二个表单的ID),节点类型选择“Form”,且配置无误。确认扩展加载状态:
查看Keycloak启动日志,搜索你的Provider ID或工厂类名,确认是否有Registered provider之类的加载成功日志。如果没有,说明JAR未被正确加载,检查文件权限、路径是否正确,或执行bin/kc.bat build重新构建服务器配置。调试验证:
在工厂类的create方法和认证器的render、action方法设置断点,确认这些方法是否被调用。如果create未被调用,说明工厂未注册;如果render未被调用,说明流配置存在问题。
内容的提问来源于stack exchange,提问作者Alex_Pap

