java – How to make two keycloak modules (factory providers) communicates between them?

I try to develop two modules of Keycloak (two authenticators in Keycloak terminology) and I want to make them communicate between them.

Before describing the error I have, let me introduce the way I build my modules.

Basically, a Keycloak module is developed by implementing two interfaces called AuthenticatorFactory and Authenticator defined by Keycloak.

For my first module, I have implemented those two interfaces in two files called moduleA_Factory.java and moduleA.java.

For my second module, I have implemented those two interfaces in two files called moduleB_Factory.java and moduleB.java.

Once Keycloak starts, it will instantiate the classes defined in moduleA_Factory.java and moduleB_Factory.java. I used maven to create this project.Here are the contents of those files:

TREE:

.
├── pom.xml
└── src
    └── main
        ├── java
        │   ├── moduleA_Factory.java
        │   ├── moduleA.java
        │   ├── moduleB_Factory.java
        │   └── moduleB.java
        └── resources
            └── META-INF
                └── services
                    └── org.keycloak.authentication.AuthenticatorFactory

pom.xml:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>mygroup</groupId>
    <artifactId>modules</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <version>12.0.4</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
            <version>12.0.4</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <version>12.0.4</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
    </build>
</project>

org.keycloak.authentication.AuthenticatorFactory:

keycloak.mymodules.moduleA_Factory
keycloak.mymodules.moduleB_Factory

moduleA_Factory.java:

package keycloak.mymodules;

import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;

import java.util.Collections;
import java.util.List;

import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;

public class moduleA_Factory implements AuthenticatorFactory {
    
    public static final String ID = "moduleA";
    private static final moduleA AUTHENTICATOR_INSTANCE = new moduleA();

    @Override
    public Authenticator create(KeycloakSession keycloakSession) {
        return AUTHENTICATOR_INSTANCE;
    }

    @Override
    public String getDisplayType() {
        return "my module A";
    }

    @Override
    public boolean isConfigurable() {
        return true;
    }

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return new AuthenticationExecutionModel.Requirement[] { AuthenticationExecutionModel.Requirement.REQUIRED };
    }

    @Override
    public boolean isUserSetupAllowed() {
        return false;
    }

    @Override
    public String getHelpText() {
        return "some help text";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        ProviderConfigProperty name = new ProviderConfigProperty();

        name.setType(STRING_TYPE);
        name.setLabel("label");
        name.setHelpText("some description");

        return Collections.singletonList(name);
    }

    @Override
    public String getReferenceCategory() {
        return null;
    }

    @Override
    public void init(Config.Scope scope) {
    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
    }

    @Override
    public void close() {
    }

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

moduleA.java:

package keycloak.mymodules;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.*;

public class moduleA implements Authenticator {
    
    @Override
    public void authenticate(AuthenticationFlowContext context) {
        context.success();
    }

    @Override
    public void action(AuthenticationFlowContext context) {
    }

    @Override
    public boolean requiresUser() {
        return true;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return true;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
    }

    @Override
    public void close() {
    }
}

moduleB_Factory.java:

package keycloak.mymodules;

import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;

import java.util.Collections;
import java.util.List;

import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;

public class moduleB_Factory implements AuthenticatorFactory {
    
    public static final String ID = "moduleB";
    private static final moduleB AUTHENTICATOR_INSTANCE = new moduleB();

    @Override
    public Authenticator create(KeycloakSession keycloakSession) {
        return AUTHENTICATOR_INSTANCE;
    }

    @Override
    public String getDisplayType() {
        return "my module B";
    }

    @Override
    public boolean isConfigurable() {
        return true;
    }

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return new AuthenticationExecutionModel.Requirement[] { AuthenticationExecutionModel.Requirement.REQUIRED };
    }

    @Override
    public boolean isUserSetupAllowed() {
        return false;
    }

    @Override
    public String getHelpText() {
        return "some help text";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        ProviderConfigProperty name = new ProviderConfigProperty();

        name.setType(STRING_TYPE);
        name.setLabel("label");
        name.setHelpText("some description");

        return Collections.singletonList(name);
    }

    @Override
    public String getReferenceCategory() {
        return null;
    }

    @Override
    public void init(Config.Scope scope) {
    }

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
    }

    @Override
    public void close() {
    }

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

moduleB.java:

package keycloak.mymodules;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.*;

public class moduleB implements Authenticator {
    
    @Override
    public void authenticate(AuthenticationFlowContext context) {
        context.success();
    }

    @Override
    public void action(AuthenticationFlowContext context) {
    }

    @Override
    public boolean requiresUser() {
        return true;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return true;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
    }

    @Override
    public void close() {
    }
}

Before going further, I noted that the class AuthenticatorFactory inherits from the class ProviderFactory<Authenticator> provided by Keycloak.

If I understand well, when Keycloak has instantiated all modules, the function postInit of my classes moduleA_Factory and moduleB_Factory are called (https://www.keycloak.org/docs-api/13.0/javadocs/org/keycloak/provider/ProviderFactory.html)

This function takes a class KeycloakSessionFactory as argument. Moreover, according to https://www.keycloak.org/docs-api/12.0/javadocs/org/keycloak/models/KeycloakSessionFactory.html, this class KeycloakSessionFactory provides the function <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) which, I guess, can be used to get the instantiated modules. So, in moduleA_Factory.java I have implemented the postInit function with:

@Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
        moduleB_Factory v = keycloakSessionFactory.getProviderFactory(moduleB_Factory.class);
}

However, this gives me an error:

[ERROR] /home/seb/keycloak_test/src/main/java/moduleA_Factory.java:[73,55] no suitable method found for getProviderFactory(java.lang.Class<keycloak.mymodules.moduleB_Factory>)
    method org.keycloak.models.KeycloakSessionFactory.<T>getProviderFactory(java.lang.Class<T>) is not applicable
      (inference variable T has incompatible bounds
        equality constraints: keycloak.mymodules.moduleB_Factory
        lower bounds: org.keycloak.provider.Provider)
    method org.keycloak.models.KeycloakSessionFactory.<T>getProviderFactory(java.lang.Class<T>,java.lang.String) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))

Do you know why I have this error?

Leave a Comment