I am writing a suite of services using the Quarkus framework. The services are designed to be multitenant, and are supposed to be protected using KeyCloak. Each tenant will have a separate KeyCloak security realm, with its own set of users, groups, roles, etc.
I’ve found the Quarkus guide to KeyCloak protection, explaining how to configure JAX-RS for authorization using KeyCloak. However, this guide assumes only 1 KeyCloak realm. I also found this example showing how to deploy a WAR file to Wildfly that loads one of multiple KeyCloak realm configuration files depending on the specified realm.
However, it’s not clear if this code can translate over to Quarkus.
Is it possible to dynamically load the KeyCloak configuration in Quarkus this way? Is there a better way to implement multi-tenant security for these Quarkus services?
UPDATE: Based on Pedro and Shadov's suggestions below, I added a really simple KeycloakConfigResolver implementation and marked it as @ApplicationScoped. However, when I attempted to launch Quarkus, I get the following exception and never see my custom KeycloakConfigResolver being called:
17:53:55,340 INFO [io.qua.dep.QuarkusAugmentor] Beginning quarkus augmentation
17:53:55,758 INFO [org.jbo.threads] JBoss Threads version 3.0.0.Beta4
17:53:56,888 INFO [org.hib.Version] HHH000412: Hibernate Core {5.4.3.Final}
17:53:57,812 INFO [io.qua.dep.QuarkusAugmentor] Quarkus augmentation completed in 2472ms
17:53:57,967 ERROR [io.qua.dev.DevModeMain] Failed to start quarkus: java.lang.ExceptionInInitializerError
at java.base/java.lang.J9VMInternals.ensureError(J9VMInternals.java:193)
at java.base/java.lang.J9VMInternals.recordInitializationFailure(J9VMInternals.java:182)
at java.base/java.lang.J9VMInternals.newInstanceImpl(Native Method)
at java.base/java.lang.Class.newInstance(Class.java:2082)
at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:117)
at io.quarkus.dev.DevModeMain.doStart(DevModeMain.java:166)
at io.quarkus.dev.DevModeMain.main(DevModeMain.java:88)
Caused by: java.lang.RuntimeException: Failed to start quarkus
at io.quarkus.runner.ApplicationImpl1.<clinit>(ApplicationImpl1.zig:333)
... 5 more
Caused by: java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
at [Source: UNKNOWN; line: 1, column: 0]
at org.keycloak.adapters.KeycloakDeploymentBuilder.loadAdapterConfig(KeycloakDeploymentBuilder.java:198)
at org.keycloak.adapters.KeycloakDeploymentBuilder.build(KeycloakDeploymentBuilder.java:187)
at io.quarkus.keycloak.KeycloakTemplate.createKeycloakDeploymentContext(KeycloakTemplate.java:36)
at io.quarkus.deployment.steps.KeycloakAdapterProcessor$configureAdapter5.deploy_0(KeycloakAdapterProcessor$configureAdapter5.zig:47)
at io.quarkus.deployment.steps.KeycloakAdapterProcessor$configureAdapter5.deploy(KeycloakAdapterProcessor$configureAdapter5.zig:106)
at io.quarkus.runner.ApplicationImpl1.<clinit>(ApplicationImpl1.zig:207)
... 5 more
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
at [Source: UNKNOWN; line: 1, column: 0]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4145)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4000)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3070)
at org.keycloak.adapters.KeycloakDeploymentBuilder.loadAdapterConfig(KeycloakDeploymentBuilder.java:196)
... 10 more
17:53:57,968 ERROR [io.qua.dev.DevModeMain] Failed to start Quarkus, attempting to start hot replacement endpoint to recover
17:53:58,003 INFO [org.xnio] XNIO version 3.7.2.Final
17:53:58,017 INFO [org.xni.nio] XNIO NIO Implementation Version 3.7.2.Final
My custom KeycloakConfigResolver is empty, save for some logging statements. I never see my resolve method being called or any of the logging statements. Here's what the implementation looks like:
@ApplicationScoped
public class MultiTenantKeycloakConfigResolver implements KeycloakConfigResolver {
/**
* Logger for this class
*/
private static final Logger logger = LoggerFactory.getLogger(MultiTenantKeycloakConfigResolver.class);
/*
* (non-Javadoc)
*
* @see
* org.keycloak.adapters.KeycloakConfigResolver#resolve(org.keycloak.adapters.
* spi.HttpFacade.Request)
*/
@Override
public KeycloakDeployment resolve(Request facade) {
if (logger.isDebugEnabled()) {
logger.debug("resolve(Request) - start"); //$NON-NLS-1$
}
if (logger.isInfoEnabled()) {
logger.info("resolve(Request) - HERE!!!"); //$NON-NLS-1$
}
// TODO Implement method
if (logger.isDebugEnabled()) {
logger.debug("resolve(Request) - end"); //$NON-NLS-1$
}
return null;
}
}