0
votes

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;
    }

}
2

2 Answers

2
votes

It's possible in Spring-Boot, almost the same way like in example you posted. Only starting with version 4.6.0-Final tho, they added class KeycloakSpringBootConfigResolverWrapper that actually checks if there is any KeycloakConfigResolver already. In the previous version it was just putting it's own resolver. Now all you have to do is register a custom KeycloakConfigResolver bean and it works.

I see there is very similiar class in Quarkus as in keycloak-spring-boot-adapters - https://github.com/quarkusio/quarkus/blob/master/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusKeycloakConfigResolver.java. Code is pretty obvious, no need to explain it.

Since I'm not familiar with Quarkus, I can't be 100% sure it's gonna work, but the field is annotated with Inject, so it suggests that you can just provide your own resolver, the same way as possible in Spring-Boot and in the example you posted.

2
votes

@Shadov, is right. You need a KeycloakConfigResolver.

With Quarkus, you just need to create a class that implements KeycloakConfigResolver. Similar to this.

I'll update the guide with some reference about this.