Multi-tenant Quarkus with KeyCloak? - java

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

#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.

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.

Related

Making a Spring Boot Web/MVC server application fail with a human-friendly message if no profile is selected

I have a Spring Boot 2.7 Web/MVC server application with profiles for different environments (DEV/QA/PROD). I have a common application.properties with shared configuration and environment specific configuration, for example, JDBC URLs in separate application-<environment>.properties on the classpath.
If the application is started without a profile being selected, it fails with a cryptic error message complaining about a jdbcUrl property missing and not being able to initialize the context - all this within a huge stack trace. This is self-explanatory for the developer, but not trivial for an operations person.
What would be the Spring way of checking if exactly one profile is selected and displaying a non-stacktrace, human (operator) friendly error message?
I am looking for something similar to what is shown if the server port is already in use.
I would like to avoid hacks and find the Spring-endorsed solution for this.
it fails with a cryptic error message complaining about a jdbcUrl property missing and not being able to initialize the context - all this within a huge stack trace
The more fundamental problem seems to be that you're fighting the built-in Boot facilities that provide assistance to humans out of the box. Instead of using #Value("${jdbcUrl}") in your code, you should inject a javax.sql.DataSource, and Boot will automatically configure one when spring.datasource.url is set. If it's not present, you'll get an error that says "no bean of DataSource is available", and the auto-config report will explain why.
You can use a postprocessor for a value . If it is not present, then log your human friendly error error message and quit the application.
e.g
#Bean
public BeanPostProcessor bpp(Environment environment) {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(environment.getActiveProfiles()[0].equalsIgnoreCase(environment.getDefaultProfiles()[0])){
// Your Error message goes here
System.exit(1);
}
return bean;
}
};
}

quarkus custom bean annotation "creating" proxies even with Singleton scope

I have a java library that I want to use inside a quarkus application, this library has a custom annotation that I want to use for bean loading so I wrote a quarkus extension and tried the BeanDefiningAnnotationBuildItem approach.
Extract from the extension deployment module:
static DotName OP_RULE_ANNOTATION = DotName.createSimple(OpRule.class.getName());
static DotName SINGLETON = DotName.createSimple(Singleton.class.getName());
#BuildStep
void additionalBeanAnnotations(BuildProducer<BeanDefiningAnnotationBuildItem> beanDefiningAnnotations) {
beanDefiningAnnotations.produce(new BeanDefiningAnnotationBuildItem(OP_RULE_ANNOTATION, SINGLETON, false));
}
The annotation is correctly detected and beans are loaded but when I try to inject them they are wrapped by a proxy and it looks like the Singleton scope is ignored.
Does anyone have any hint on how I can debug this?
update:
While adding tests to the extension I also added a test to ensure that those classes would have not been wrapped by a Proxy and it passed, I do not have any special config on the application (that I know of)
The issue was caused by dev-mode monitoring, disabling it with this property solves the issue:
quarkus.arc.dev-mode.monitoring-enabled
https://quarkus.io/guides/all-config#quarkus-arc_quarkus.arc.dev-mode.monitoring-enabled

Wait for database connection in Spring Boot without an exception

I want to create a microservice with Spring Boot. For persistence i use a mariadb database. To wait for the database which is running in a docker container, i implemented the following code like shown here:
#Bean
public DatabaseStartupValidator databaseStartupValidator(DataSource dataSource) {
var dsv = new DatabaseStartupValidator();
dsv.setDataSource(dataSource);
dsv.setTimeout(60);
dsv.setInterval(7);
dsv.setValidationQuery(DatabaseDriver.MYSQL.getValidationQuery());
return dsv;
}
The code is working very well, my application is now waiting for the database connection. But i get an exception at startup of the application:
java.sql.SQLNonTransientConnectionException: Could not connect to Host ....
...
...
...
In the next line i get an information, that it will wait for the database:
021-04-07 21:29:40.816 INFO 16569 --- [ main] o.s.j.support.DatabaseStartupValidator : Database has not started up yet - retrying in 7 seconds (timeout in 57.65 seconds)
After that the application is starting as expected. So i think everything is working fine, but what i have to do to suppress the Exception? In the linked article it should work without an exception. Do i have to implement the "dependsOnPostProcessor" function? Which dependency i have to use? Sorry, possible a dumb question, i am new to spring boot.
to get rid of that exception you can state the below directive in your application.properties file:
logging.level.com.zaxxer.hikari=OFF
Keep in mind that if the application will not be able to get in contact with the db your spring crashes after a while due to that exception. In addition the above directive prevent you to see any logging activity related to Hikari.
In summary you hide the appearance of the exception until it is possible before the application dies due to timeout.
hoping I clarified a bit the case
Yes indeed you need to add the "depends-on" for the beans that rely on the data source. Note the following part of the documentation:
To be referenced via "depends-on" from beans that depend on database startup, like a Hibernate SessionFactory or custom data access objects that access a DataSource directly.
If I understand it well, this means that beans such as an EntityManagerFactory which rely on the database will now have to go through the DatabaseStartupValidator bean and wait for the DB startup. I don't know what caused your exception, but usually there is an EntityManagerFactory involved, so try adding the DependsOn on this object at least.
This is how the linked article is doing it:
#Bean
public static BeanFactoryPostProcessor dependsOnPostProcessor() {
return bf -> {
// Let beans that need the database depend on the DatabaseStartupValidator
// like the JPA EntityManagerFactory or Flyway
String[] flyway = bf.getBeanNamesForType(Flyway.class);
Stream.of(flyway)
.map(bf::getBeanDefinition)
.forEach(it -> it.setDependsOn("databaseStartupValidator"));
String[] jpa = bf.getBeanNamesForType(EntityManagerFactory.class);
Stream.of(jpa)
.map(bf::getBeanDefinition)
.forEach(it -> it.setDependsOn("databaseStartupValidator"));
};
}
You may not necessarily have Flyway configured, but the main thing to note is the dependency itself is referenced by the bean name databaseStartupValidator which is the name of the method that creates the bean.

Registering Hystrix Concurrency Strategy fails after migrating to spring-boot-2.0

Trying to register hystrix concurrency strategy fails after migrating to Spring-boot-2.0 with spring actuator enabled with java.lang.IllegalStateException stating that "Another strategy was already registered" . This is although I have not used registerConcurrencyStrategy anywhere else in my code.
I'd like to register concurrency strategy to carry-forward Log MDC context so that I'm able to log both within and outside the Hystrix wrapped method equally well, which includes thread-locals. And this used to work perfectly in spring-boot-1.5
After having migrated to spring-boot 2.0 (from 1.5), the HystrixPlugins.getInstance().registerConcurrencyStrategy(this); fails with IllegalStateException
As per https://github.com/Netflix/Hystrix/issues/1057, this issue can come if either (a) Any other code flow would have registered its own or default ConcurrencyStrategy before this is invoked (b) any call would have come via Hystrix before this is invoked
Since the above invocation is within the constructor of a class which is annotated with #Component, this should get invoked ideally before any method call happens (except initialization of other beans, including their constructors).
We even tried moving this code inside the SpringBoot Application Class's main method before invoking the SpringApplication.run(MyApplication.class, args); but that also didn't work
#Component
public class ContextCopyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private static final String EVENT = "HystrixConcurrencyStrategy";
private static final String ACTION = "ContextCopy";
public ContextCopyHystrixConcurrencyStrategy(Logger logger, LoggerUtil defaultLoggerUtil) {
try {
HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
} catch (IllegalStateException e) {
defaultLoggerUtil.logEvents(logger, Level.WARN, e.getMessage(), EVENT, ACTION, "", "Race condition! Could not register strategy. HystrixConcurrencyStrategy is already initialized.");
}
Expected: My registering should have happened before any other code and registering should have been successful
Actual: My registering fails with IllegalStateException
How do I make sure that my registering happens well before any other registering (which is not present in my code, but may be inside some of the libraries that I may be transitively using)
By default, Spring boot 2 accuator registers Hystrix Metric Binder beans which reset already set HystrixConcurrencyStrategy and sets HystrixConcurrencyStrategyDefault.
So, disabling that bean by
management.metrics.binders.hystrix.enabled=false
would help not resetting your custom HystrixConcurrencyStrategy
We took a close look at my maven .m2 directory classes and looked for registerConcurrencyStrategy inside all the classes in all the jars. And we found that
io.micrometer.core.instrument.binder.hystrix
was internally registering the HystrixConcurrencyStrategy with the default one.
And upon further research we found that setting the following property in application.properties:
management.metrics.binders.hystrix.enabled=false disabled the Hystrix Metrics Binder (I'm actually not sure what it does though!) and then things worked
I was using spring-boot-starter-parent v2.5.3 with Spring Cloud version 2020.0.3.
I had to manually include version for spring-cloud-starter-netflix-hystrix. I was getting "Another strategy was already registered" exception when starting my microservice. I included the
management.metrics.binders.hystrix.enabled=false
in the application.properties file and this issue got resolved.

camel proxy complains about wrong type

I configured a spring boot based application to use remoting with activemq/jms. The listening part is running fine but I have problems implementing the sending part.
For the sender I went back to "classic" camel and spring because I found more working examples for this but still receive an error:
org.springframework.beans.factory.BeanNotOfRequiredTypeException:
Bean named 'myProxy' must be of type [foo.bar.YouNameIt],
but was actually of type [com.sun.proxy.$Proxy83]
This is the way I try to load the proxy definition:
ApplicationContext context = new ClassPathXmlApplicationContext("config/spring.xml");
YouNameIt youNameIt = context.getBean("myProxy", YouNameIt.class);
And this is the entry in the spring.xml:
<camel:proxy id="myProxy"
serviceInterface="foo.bar.IYouNameIt"
serviceUrl="activemq:queue:site12345" />
What am I doing wrong ?
Access the interface IYouNameIt and not the implementation class YouNameIt:
IYouNameIt youNameIt = context.getBean("myProxy", IYouNameIt.class);
See here for a full Spring remoting example.

Categories

Resources