Problem while deploying custom SPI (FreeMarkerLoginFormsProvider)

Viewed 3468

Story

I'm using keycloak 6.0.1. I have one realm with two types of users: Admins and regular users. I need a theme that can distinguish between them since the logins are different (3 fields vs 2 fields). Therefore I want to extend the provider FreeMarkerLoginFormsProvider to pass through a custom parameter. I'm using keycloak deployer to deploy my SPIs.

Problem

Keycloak unfortunately throws an exception while deployment:

09:55:32,170 ERROR [org.jboss.as.controller.management-operation] (DeploymentScanner-threads - 1) WFLYCTL0013: Operation ("full-replace-deployment") failed - address: ([]) - failure description: {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"spi-1.0.0.jar\".POST_MODULE" => "WFLYSRV0153: Failed to process phase POST_MODULE of deployment \"spi-1.0.0.jar\" Caused by: java.lang.NoClassDefFoundError: org/keycloak/theme/FreeMarkerUtil Caused by: java.lang.ClassNotFoundException: org.keycloak.theme.FreeMarkerUtil from [Module \"deployment.spi-1.0.0.jar\" from Service Module Loader]"}} 09:55:32,165 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-3) MSC000001: Failed to start service jboss.deployment.unit."spi-1.0.0.jar".POST_MODULE: org.jboss.msc.service.StartException in service jboss.deployment.unit."spi-1.0.0.jar".POST_MODULE: WFLYSRV0153: Failed to process phase POST_MODULE of deployment "spi-1.0.0.jar" at org.jboss.as.server@8.0.0.Final//org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:183) at org.jboss.msc@1.4.5.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1738) at org.jboss.msc@1.4.5.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.execute(ServiceControllerImpl.java:1700) at org.jboss.msc@1.4.5.Final//org.jboss.msc.service.ServiceControllerImpl$ControllerTask.run(ServiceControllerImpl.java:1558) at org.jboss.threads@2.3.3.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads@2.3.3.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982) at org.jboss.threads@2.3.3.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486) at org.jboss.threads@2.3.3.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377) at java.base/java.lang.Thread.run(Thread.java:834) Caused by: java.lang.NoClassDefFoundError: org/keycloak/theme/FreeMarkerUtil at deployment.spi-1.0.0.jar//com.example.keycloak.spi.CustomFreeMarkerLoginFormsProviderFactory.init(CustomFreeMarkerLoginFormsProviderFactory.java:23) at org.keycloak.keycloak-services@6.0.1//org.keycloak.services.DefaultKeycloakSessionFactory.loadFactories(DefaultKeycloakSessionFactory.java:217) at org.keycloak.keycloak-services@6.0.1//org.keycloak.services.DefaultKeycloakSessionFactory.deploy(DefaultKeycloakSessionFactory.java:115) at org.keycloak.keycloak-services@6.0.1//org.keycloak.provider.ProviderManagerRegistry.deploy(ProviderManagerRegistry.java:42) at org.keycloak.keycloak-wildfly-server-subsystem@6.0.1//org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor.deploy(KeycloakProviderDeploymentProcessor.java:55) at org.jboss.as.server@8.0.0.Final//org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:176) ... 8 more Caused by: java.lang.ClassNotFoundException: org.keycloak.theme.FreeMarkerUtil from [Module "deployment.spi-1.0.0.jar" from Service Module Loader] at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255) at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410) at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398) at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116) ... 14 more

I've also tried the module approach but then I get a NullPointerException:

09:23:09,394 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-1) Uncaught server error: java.lang.NullPointerException at org.keycloak.keycloak-services@6.0.1//org.keycloak.protocol.AuthorizationEndpointBase.createAuthenticationSession(AuthorizationEndpointBase.java:193) at org.keycloak.keycloak-services@6.0.1//org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint.process(AuthorizationEndpoint.java:141) at org.keycloak.keycloak-services@6.0.1//org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint.buildGet(AuthorizationEndpoint.java:108) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.jboss.resteasy.resteasy-jaxrs@3.6.3.Final//org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:139)...

I've been following the official docs. My other custom Pbkdf2PasswordHashProvider actually works just fine. Am I missing something here?

Source

import org.keycloak.forms.login.freemarker.FreeMarkerLoginFormsProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.theme.FreeMarkerUtil;

public class CustomFreeMarkerLoginFormsProvider extends FreeMarkerLoginFormsProvider {

    public CustomFreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
        super(session, freeMarker);
        setAttribute("isAdmin", uriInfo.getPath().contains("admin"));
    }
}
3 Answers

I had a similar situation with the FreeMarkerAccountProvider/FreeMarkerAccountProviderFactory and was receiving java.lang.NoClassDefFoundError: org/keycloak/theme/FreeMarkerUtil.

This solved the problem Keycloak plugin using keycloak-services.

As I am registring my Provider with the Keycloak Deployer. It seems that it doesn't hadle all the dependencies, so I've created a jboss-deployment-structure.xml file with my dependencies under src/main/resources/META-INF. Example here jboss-deployment- structure.xml.

I also had to disabled the existing Provider and activate mine instead. I did this by adding the following to my standalone.xlm in the <subsystem xmlns="urn:jboss:domain:keycloak-server:1.1"> tag :

<spi name="account">
  <provider name="freemarker" enabled="false"/>
  <provider name="myfreemarker" enabled="true"/>
</spi>

I may find a solution for you (I had a similar problem and works for me now), tells me if it's work for you as well. I think you have a CustomFreeMarkerLoginFormsProviderFactory in your code which comes with your CustomFreeMarkerLoginFormsProvider.

public class CustomFreeMarkerLoginFormsFactory extends FreeMarkerLoginFormsProviderFactory {

private FreeMarkerUtil freeMarker;

@Override
public LoginFormsProvider create(KeycloakSession session) {
    return new CustomFreeMarkerLoginFormsProvider(session, freeMarker);
}

@Override
public void init(Config.Scope config) {
    freeMarker = new FreeMarkerUtil();
}

@Override
public void postInit(KeycloakSessionFactory factory) {}

@Override
public void close() {
    freeMarker = null;
}

@Override
public String getId() {
    return "customfreemarker";
}

}

Then you need to activate your provider in the standalone-configuration.cli like bellow:

embed-server --server-config=standalone.xml
/subsystem=keycloak-server/spi=login:add()
/subsystem=keycloak-server/spi=login/provider=freemarker:add(enabled="false")
/subsystem=keycloak-server/spi=login/provider=customfreemarker:add(enabled="true")

It seems that you don't need to activate your custom factory for the login provider. I keep the default id "freemarker" and it is taken into consideration instead of the default factory. Only the login provider factory works like this, don't know why.

When i change the function with the same function as original class FreeMarkerLoginFormsProviderFactory like bellow it works (and you don't need lines in standalone.xml any more):

@Override
public String getId() {
    return "freemarker";
}

I think it is a Keycloak issue/imporvement ?? because it works well with other providers like you said. But i did not find anything about it in keycloak issues. The problem apears between v4.4.0.Final and 4.5.0.Final. Keep me informed of your progress.

check the modules.xml of your module if it includes

<module name="org.keycloak.keycloak-services"/>
Related