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?