mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Asynchronous server initialization
Closes #47187 Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com> Signed-off-by: Pedro Ruivo <pruivo@users.noreply.github.com> Co-authored-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com> Co-authored-by: Steven Hawkins <shawkins@redhat.com>
This commit is contained in:
@@ -77,6 +77,13 @@ For more information about brokering, see link:{developerguide_link}#_identity_b
|
||||
Notable changes may include internal behavior changes that prevent common misconfigurations, bugs that are fixed, or changes to simplify running {project_name}.
|
||||
It also lists significant changes to internal APIs.
|
||||
|
||||
=== Endpoints are opened while Keycloak is initializing
|
||||
|
||||
By default, {project_name} now opens its HTTP(S) and Management ports while initialization is still in progress.
|
||||
If you use a proxy or load balancer, configure an HTTP health check with the path `/health/ready` to ensure traffic is only routed to the server once it is fully ready.
|
||||
|
||||
If this behavior is not desired or an HTTP health check is not possible, start {project_name} with `--server-async-bootstrap=false` to revert to the previous behavior where ports are opened only after initialization completes.
|
||||
|
||||
=== Dev Mode defaults to localhost
|
||||
|
||||
When running the server in dev mode on a platform other than Windows Subsystem For Linux, the `http-host` setting will default to localhost.
|
||||
|
||||
@@ -34,12 +34,16 @@ These endpoints respond with HTTP status `200 OK` on success or `503 Service Una
|
||||
}
|
||||
----
|
||||
|
||||
.Successful response for endpoints with information on the database connection:
|
||||
.Successful response for endpoints with additional per-check information:
|
||||
[source, json]
|
||||
----
|
||||
{
|
||||
"status": "UP",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Keycloak Initialized",
|
||||
"status": "UP"
|
||||
},
|
||||
{
|
||||
"name": "Graceful Shutdown",
|
||||
"status": "UP"
|
||||
@@ -128,6 +132,10 @@ The table below shows the available checks.
|
||||
|Will start to return "DOWN" once the pre-shutdown phase started.
|
||||
|No
|
||||
|
||||
|Keycloak Initialized
|
||||
|Returns the status of the server initialization.
|
||||
|No
|
||||
|
||||
|===
|
||||
|
||||
For some checks, you'll need to also enable metrics as indicated by the *Requires Metrics* column. To enable metrics
|
||||
|
||||
@@ -52,6 +52,20 @@ By default, there is no limit set.
|
||||
Set the option `http-max-queued-requests` to limit the number of queued requests to a given threshold matching your environment.
|
||||
Any request that exceeds this limit would return with an immediate `503 Server not Available` response.
|
||||
|
||||
== Server bootstrap behavior
|
||||
|
||||
Server initialization, such as database migrations, may take a significant amount of time to complete.
|
||||
By default, {project_name} opens its HTTP(S) and Management endpoints while initialization is still in progress in the background.
|
||||
This allows the startup and liveness probes to report UP early, preventing orchestrators like Kubernetes from killing the container during a long-running migration, while the readiness probe reports DOWN until initialization completes.
|
||||
|
||||
If you run {project_name} behind a proxy or load balancer, configure an HTTP health check with the path `/health/ready` to ensure traffic is routed only to instances that have completed initialization.
|
||||
|
||||
If an HTTP health check is not possible, or you prefer the server to accept connections only after initialization completes, start {project_name} with the following option:
|
||||
|
||||
<@kc.start parameters="--server-async-bootstrap=false"/>
|
||||
|
||||
With this setting, {project_name} opens its endpoints only after the bootstrap is complete and the server is ready to handle requests.
|
||||
|
||||
== Production grade database
|
||||
The database used by {project_name} is crucial for the overall performance, availability, reliability and integrity of {project_name}. For details on how to configure a supported database, see <@links.server id="db"/>.
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public enum OptionCategory {
|
||||
EXPORT("Export", 130, ConfigSupportLevel.SUPPORTED),
|
||||
IMPORT("Import", 140, ConfigSupportLevel.SUPPORTED),
|
||||
OPENAPI("OpenAPI configuration", 150, ConfigSupportLevel.SUPPORTED),
|
||||
SERVER("Server configuration", 160, ConfigSupportLevel.SUPPORTED),
|
||||
BOOTSTRAP_ADMIN("Bootstrap Admin", 998, ConfigSupportLevel.SUPPORTED),
|
||||
GENERAL("General", 999, ConfigSupportLevel.SUPPORTED);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.config;
|
||||
|
||||
public final class ServerOptions {
|
||||
|
||||
private ServerOptions() {}
|
||||
|
||||
public static final Option<Boolean> SERVER_ASYNC_BOOTSTRAP = new OptionBuilder<>("server-async-bootstrap", Boolean.class)
|
||||
.category(OptionCategory.SERVER)
|
||||
.defaultValue(Boolean.TRUE)
|
||||
.description("If true, endpoints are opened while the bootstrap runs in the background. If false, endpoints are opened after bootstrap completes, ensuring the server is ready to handle requests.")
|
||||
.build();
|
||||
}
|
||||
+7
@@ -96,6 +96,7 @@ import org.keycloak.quarkus.runtime.configuration.mappers.WildcardPropertyMapper
|
||||
import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakHandlerChainCustomizer;
|
||||
import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakTracingCustomizer;
|
||||
import org.keycloak.quarkus.runtime.logging.ClearMappedDiagnosticContextFilter;
|
||||
import org.keycloak.quarkus.runtime.services.health.BoostrapReadyHealthCheck;
|
||||
import org.keycloak.quarkus.runtime.services.health.KeycloakClusterReadyHealthCheck;
|
||||
import org.keycloak.quarkus.runtime.services.health.KeycloakReadyHealthCheck;
|
||||
import org.keycloak.quarkus.runtime.storage.database.jpa.NamedJpaConnectionProviderFactory;
|
||||
@@ -838,6 +839,7 @@ class KeycloakProcessor {
|
||||
if (isHealthDisabled()) {
|
||||
disableReadyHealthCheck(removeBeans, index);
|
||||
disableClusterHealthCheck(removeBeans, index);
|
||||
disableBootstrapReadyHealthCheck(removeBeans, index);
|
||||
return;
|
||||
}
|
||||
if (isMetricsDisabled()) {
|
||||
@@ -860,6 +862,11 @@ class KeycloakProcessor {
|
||||
removeBeans.produce(new BuildTimeConditionBuildItem(disabledBean.asClass(), false));
|
||||
}
|
||||
|
||||
private static void disableBootstrapReadyHealthCheck(BuildProducer<BuildTimeConditionBuildItem> removeBeans, CombinedIndexBuildItem index) {
|
||||
ClassInfo disabledBean = index.getIndex().getClassByName(DotName.createSimple(BoostrapReadyHealthCheck.class.getName()));
|
||||
removeBeans.produce(new BuildTimeConditionBuildItem(disabledBean.asClass(), false));
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void disableMdcContextFilter(BuildProducer<BuildTimeConditionBuildItem> removeBeans, CombinedIndexBuildItem index) {
|
||||
if (!Configuration.isTrue(LoggingOptions.LOG_MDC_ENABLED)) {
|
||||
|
||||
+2
-1
@@ -32,8 +32,9 @@ public class MetricsEnabledProfile implements QuarkusTestProfile {
|
||||
"kc.health-enabled","true",
|
||||
"kc.metrics-enabled", "true",
|
||||
"kc.cache", "local",
|
||||
"kc.server-async-bootstrap", "false",
|
||||
"quarkus.micrometer.export.prometheus.path", "/prom/metrics",
|
||||
"quarkus.class-loading.removed-artifacts", "io.quarkus:quarkus-jdbc-oracle,io.quarkus:quarkus-jdbc-oracle-deployment"); // config works a bit odd in unit tests, so this is to ensure we exclude Oracle to avoid ClassNotFound ex
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -54,7 +54,8 @@ public final class PropertyMappers {
|
||||
new FeaturePropertyMappers(), new ImportPropertyMappers(), new ManagementPropertyMappers(),
|
||||
new MetricsPropertyMappers(), new OpenApiPropertyMappers(), new LoggingPropertyMappers(), new ProxyPropertyMappers(),
|
||||
new VaultPropertyMappers(), new TracingPropertyMappers(), new TransactionPropertyMappers(),
|
||||
new SecurityPropertyMappers(), new TruststorePropertyMappers(), new TelemetryPropertyMappers());
|
||||
new SecurityPropertyMappers(), new TruststorePropertyMappers(), new TelemetryPropertyMappers(),
|
||||
new ServerPropertyMappers());
|
||||
}
|
||||
|
||||
public static List<PropertyMapperGrouping> getPropertyMapperGroupings() {
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.config.ServerOptions;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
public final class ServerPropertyMappers implements PropertyMapperGrouping {
|
||||
@Override
|
||||
public List<? extends PropertyMapper<?>> getPropertyMappers() {
|
||||
return List.of(
|
||||
fromOption(ServerOptions.SERVER_ASYNC_BOOTSTRAP)
|
||||
.paramLabel("enabled")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
+22
-7
@@ -21,9 +21,9 @@ import jakarta.enterprise.event.Observes;
|
||||
import jakarta.ws.rs.ApplicationPath;
|
||||
|
||||
import org.keycloak.config.BootstrapAdminOptions;
|
||||
import org.keycloak.config.ServerOptions;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||
@@ -41,9 +41,12 @@ import io.quarkus.runtime.StartupEvent;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import static org.keycloak.common.util.Environment.isDevMode;
|
||||
import static org.keycloak.common.util.Environment.isNonServerMode;
|
||||
|
||||
@ApplicationPath("/")
|
||||
@Blocking
|
||||
public class QuarkusKeycloakApplication extends KeycloakApplication {
|
||||
public class QuarkusKeycloakApplication extends KeycloakApplication<QuarkusKeycloakSessionFactory> {
|
||||
|
||||
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
|
||||
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
|
||||
@@ -72,10 +75,13 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSessionFactory createSessionFactory() {
|
||||
QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance();
|
||||
instance.init();
|
||||
return instance;
|
||||
public QuarkusKeycloakSessionFactory createSessionFactory() {
|
||||
return QuarkusKeycloakSessionFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initKeycloakSessionFactory(QuarkusKeycloakSessionFactory quarkusKeycloakSessionFactory) {
|
||||
quarkusKeycloakSessionFactory.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,7 +111,16 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTransactionTimeout(KeycloakSessionFactory sessionFactory) {
|
||||
protected boolean supportsAsyncInitialization() {
|
||||
var asyncBootstrap = Configuration.getOptionalKcValue(ServerOptions.SERVER_ASYNC_BOOTSTRAP)
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(Boolean.TRUE);
|
||||
// skip async bootstrap in dev and non-server mode
|
||||
return !isDevMode() && !isNonServerMode() && asyncBootstrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTransactionTimeout(QuarkusKeycloakSessionFactory sessionFactory) {
|
||||
return ((QuarkusJpaConnectionProviderFactory) sessionFactory.getProviderFactory(JpaConnectionProvider.class)).getMigrationTransactionTimeout();
|
||||
}
|
||||
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package org.keycloak.quarkus.runtime.services;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
|
||||
|
||||
/**
|
||||
* Pre-matching request filter that returns a 503 Service Unavailable response while the server bootstrap is in progress.
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class BootstrapFilter {
|
||||
|
||||
private final long startup;
|
||||
private boolean ready;
|
||||
|
||||
public BootstrapFilter() {
|
||||
startup = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@ServerRequestFilter(priority = 1, preMatching = true)
|
||||
public Response filter(ContainerRequestContext ignored) {
|
||||
if (ready) {
|
||||
// JVM branch prediction may optimize this code and saves on reading a static volatile field
|
||||
return null;
|
||||
}
|
||||
if (KeycloakApplication.isBootstrapCompleted()) {
|
||||
// Return null to continue the request chain normally
|
||||
ready = true;
|
||||
return null;
|
||||
}
|
||||
// Implement a back-off to wait as long as the current start-up took, but then retry at least once per minute
|
||||
long retry = Math.min(Math.max((System.currentTimeMillis() - startup) / 1000, 1), 60);
|
||||
// Return 503 Service Unavailable
|
||||
return Response
|
||||
.status(Response.Status.SERVICE_UNAVAILABLE)
|
||||
.type(MediaType.TEXT_PLAIN)
|
||||
.entity("Boostrap in progress. Retry in " + retry + " seconds.")
|
||||
.header(HttpHeaders.RETRY_AFTER, retry)
|
||||
.header("Refresh", retry)
|
||||
.build();
|
||||
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.quarkus.runtime.services.health;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
import io.smallrye.health.api.AsyncHealthCheck;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import org.eclipse.microprofile.health.HealthCheckResponse;
|
||||
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
|
||||
import org.eclipse.microprofile.health.Readiness;
|
||||
|
||||
/**
|
||||
* Readiness health check that reports DOWN while the server bootstrap is in progress and UP once initialization completes.
|
||||
*/
|
||||
@Readiness
|
||||
@ApplicationScoped
|
||||
public class BoostrapReadyHealthCheck implements AsyncHealthCheck {
|
||||
|
||||
private static final HealthCheckResponse UP = builder().up().build();
|
||||
private boolean bootstrapCompleted;
|
||||
|
||||
@Override
|
||||
public Uni<HealthCheckResponse> call() {
|
||||
// JVM branch prediction may optimize this code and saves on reading a static volatile field
|
||||
if (bootstrapCompleted) {
|
||||
return ready();
|
||||
}
|
||||
if (KeycloakApplication.isBootstrapCompleted()) {
|
||||
bootstrapCompleted = true;
|
||||
return ready();
|
||||
}
|
||||
return Uni.createFrom().item(builder().down().build());
|
||||
}
|
||||
|
||||
private Uni<HealthCheckResponse> ready() {
|
||||
return Uni.createFrom().item(UP);
|
||||
}
|
||||
|
||||
private static HealthCheckResponseBuilder builder() {
|
||||
return HealthCheckResponse.named("Keycloak Initialized");
|
||||
}
|
||||
}
|
||||
+5
-8
@@ -19,10 +19,7 @@ package org.keycloak.quarkus.runtime.services.health;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
|
||||
|
||||
import io.smallrye.health.api.AsyncHealthCheck;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
@@ -34,15 +31,15 @@ import static org.keycloak.quarkus.runtime.services.health.KeycloakReadyHealthCh
|
||||
public class KeycloakClusterReadyHealthCheck implements AsyncHealthCheck {
|
||||
|
||||
private final AtomicReference<Instant> failingSince = new AtomicReference<>();
|
||||
private final InfinispanConnectionProviderFactory factory;
|
||||
|
||||
public KeycloakClusterReadyHealthCheck(InfinispanConnectionProviderFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uni<HealthCheckResponse> call() {
|
||||
var builder = HealthCheckResponse.named("Keycloak cluster health check").up();
|
||||
if (InfinispanUtils.isRemoteInfinispan()) {
|
||||
return Uni.createFrom().item(builder.build());
|
||||
}
|
||||
var sessionFactory = QuarkusKeycloakSessionFactory.getInstance();
|
||||
InfinispanConnectionProviderFactory factory = (InfinispanConnectionProviderFactory) sessionFactory.getProviderFactory(InfinispanConnectionProvider.class);
|
||||
if (factory.isClusterHealthy()) {
|
||||
failingSince.set(null);
|
||||
} else {
|
||||
|
||||
+22
-6
@@ -22,7 +22,7 @@ import jakarta.enterprise.inject.Produces;
|
||||
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
import io.smallrye.health.api.AsyncHealthCheck;
|
||||
import org.eclipse.microprofile.health.Readiness;
|
||||
@@ -30,16 +30,32 @@ import org.eclipse.microprofile.health.Readiness;
|
||||
@ApplicationScoped
|
||||
public class KeycloakClusterReadyHealthCheckProducer {
|
||||
|
||||
private AsyncHealthCheck instance;
|
||||
private boolean ready;
|
||||
|
||||
@Produces
|
||||
@Readiness
|
||||
@Dependent
|
||||
public AsyncHealthCheck createHealthCheck() {
|
||||
var sessionFactory = QuarkusKeycloakSessionFactory.getInstance();
|
||||
InfinispanConnectionProviderFactory factory = (InfinispanConnectionProviderFactory) sessionFactory.getProviderFactory(InfinispanConnectionProvider.class);
|
||||
if (factory.isClusterHealthSupported()) {
|
||||
return new KeycloakClusterReadyHealthCheck();
|
||||
} else {
|
||||
if (ready) {
|
||||
// JVM branch prediction may optimize this code and saves on reading a static volatile field
|
||||
return instance;
|
||||
}
|
||||
if (!KeycloakApplication.isBootstrapCompleted()) {
|
||||
return null;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (ready) {
|
||||
return instance;
|
||||
}
|
||||
var sessionFactory = KeycloakApplication.getSessionFactory();
|
||||
var factory = (InfinispanConnectionProviderFactory) sessionFactory.getProviderFactory(InfinispanConnectionProvider.class);
|
||||
if (factory.isClusterHealthSupported()) {
|
||||
instance = new KeycloakClusterReadyHealthCheck(factory);
|
||||
}
|
||||
ready = true;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public class HealthDistTest {
|
||||
.statusCode(200);
|
||||
when().get("/health/ready").then()
|
||||
.statusCode(200)
|
||||
.body("checks.size()", equalTo(3));
|
||||
.body("checks.size()", equalTo(4));
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
+8
-1
@@ -269,4 +269,11 @@ Truststore:
|
||||
in a container environment. Default: true.
|
||||
--truststore-paths <truststore-paths>
|
||||
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||
directories containing those files that will be used as a system truststore.
|
||||
directories containing those files that will be used as a system truststore.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
+8
-1
@@ -271,4 +271,11 @@ Truststore:
|
||||
in a container environment. Default: true.
|
||||
--truststore-paths <truststore-paths>
|
||||
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||
directories containing those files that will be used as a system truststore.
|
||||
directories containing those files that will be used as a system truststore.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
+7
@@ -279,6 +279,13 @@ Export:
|
||||
Set the number of users per file. It is used only if 'users' is set to
|
||||
'different_files'. Default: 50.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -569,6 +569,13 @@ OpenAPI configuration:
|
||||
available at '/openapi/ui'. Default: false. Available only when OpenAPI
|
||||
Endpoint is enabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -274,6 +274,13 @@ Import:
|
||||
Set if existing data should be overwritten. If set to false, data will be
|
||||
ignored. Default: true.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -564,6 +564,13 @@ OpenAPI configuration:
|
||||
available at '/openapi/ui'. Default: false. Available only when OpenAPI
|
||||
Endpoint is enabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -520,6 +520,13 @@ Security:
|
||||
feature is enabled. Possible values are: non-strict, strict. Default:
|
||||
disabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -930,6 +930,13 @@ OpenAPI configuration:
|
||||
available at '/openapi/ui'. Default: false. Available only when OpenAPI
|
||||
Endpoint is enabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -568,6 +568,13 @@ Security:
|
||||
feature is enabled. Possible values are: non-strict, strict. Default:
|
||||
disabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -931,6 +931,13 @@ OpenAPI configuration:
|
||||
available at '/openapi/ui'. Default: false. Available only when OpenAPI
|
||||
Endpoint is enabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -486,6 +486,13 @@ Truststore:
|
||||
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||
directories containing those files that will be used as a system truststore.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -819,6 +819,13 @@ Truststore:
|
||||
List of pkcs12 (p12, pfx, or pkcs12 file extensions), PEM files, or
|
||||
directories containing those files that will be used as a system truststore.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -567,6 +567,13 @@ Security:
|
||||
feature is enabled. Possible values are: non-strict, strict. Default:
|
||||
disabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -930,6 +930,13 @@ OpenAPI configuration:
|
||||
available at '/openapi/ui'. Default: false. Available only when OpenAPI
|
||||
Endpoint is enabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -565,6 +565,13 @@ Security:
|
||||
feature is enabled. Possible values are: non-strict, strict. Default:
|
||||
disabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+7
@@ -928,6 +928,13 @@ OpenAPI configuration:
|
||||
available at '/openapi/ui'. Default: false. Available only when OpenAPI
|
||||
Endpoint is enabled.
|
||||
|
||||
Server configuration:
|
||||
|
||||
--server-async-bootstrap <enabled>
|
||||
If true, endpoints are opened while the bootstrap runs in the background. If
|
||||
false, endpoints are opened after bootstrap completes, ensuring the server
|
||||
is ready to handle requests. Default: true.
|
||||
|
||||
Bootstrap Admin:
|
||||
|
||||
--bootstrap-admin-client-id <client id>
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ public class KeycloakDistributionDecorator implements KeycloakDistribution {
|
||||
List<String> args = new ArrayList<>(rawArgs);
|
||||
args.addAll(List.of(config.defaultOptions()));
|
||||
setEnvVar("KC_SHUTDOWN_DELAY", "0s");
|
||||
setEnvVar("KC_SERVER_ASYNC_BOOTSTRAP", "false");
|
||||
return delegate.run(new ServerOptions(storageConfig, databaseConfig, args));
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@
|
||||
package org.keycloak.services.managers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
@@ -52,7 +53,7 @@ public class DefaultBruteForceProtectorFactory implements BruteForceProtectorFac
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
protector.shutdown();
|
||||
Optional.ofNullable(protector).ifPresent(DefaultBruteForceProtector::shutdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.ws.rs.core.Application;
|
||||
@@ -40,13 +43,15 @@ import org.jboss.logging.Logger;
|
||||
* @version $Revision: 1 $
|
||||
*
|
||||
*/
|
||||
public abstract class KeycloakApplication extends Application {
|
||||
public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> extends Application {
|
||||
|
||||
private static final String KC_TMPDIR = "kc.io.tmpdir";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
|
||||
|
||||
private static KeycloakSessionFactory sessionFactory;
|
||||
private static volatile KeycloakSessionFactory sessionFactory;
|
||||
// Set to true when bootstrap is completed. It never changes back to false.
|
||||
private static volatile boolean bootstrapCompleted = false;
|
||||
|
||||
public KeycloakApplication() {
|
||||
try {
|
||||
@@ -81,10 +86,33 @@ public abstract class KeycloakApplication extends Application {
|
||||
protected void startup() {
|
||||
Profile.getInstance().logUnsupportedFeatures();
|
||||
CryptoIntegration.init(KeycloakApplication.class.getClassLoader());
|
||||
KeycloakApplication.sessionFactory = createSessionFactory();
|
||||
var ksf = createSessionFactory();
|
||||
sessionFactory = ksf;
|
||||
|
||||
setTransactionTimeout();
|
||||
var exportImportManager = KeycloakModelUtils.runJobInTransactionWithResult(sessionFactory, session -> {
|
||||
if (supportsAsyncInitialization()) {
|
||||
final var executor = Executors.newSingleThreadExecutor();
|
||||
CompletableFuture.runAsync(() -> runBootstrap(ksf), executor)
|
||||
.exceptionally(throwable -> {
|
||||
exit(throwable);
|
||||
return null;
|
||||
})
|
||||
.thenRun(executor::shutdown);
|
||||
return;
|
||||
}
|
||||
|
||||
runBootstrap(ksf);
|
||||
}
|
||||
|
||||
protected boolean supportsAsyncInitialization() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void runBootstrap(KSF keycloakSessionFactory) {
|
||||
var startTime = System.nanoTime();
|
||||
|
||||
initKeycloakSessionFactory(keycloakSessionFactory);
|
||||
setTransactionTimeout(keycloakSessionFactory);
|
||||
var exportImportManager = KeycloakModelUtils.runJobInTransactionWithResult(keycloakSessionFactory, session -> {
|
||||
DBLockManager dbLockManager = new DBLockManager(session);
|
||||
dbLockManager.checkForcedUnlock();
|
||||
DBLockProvider dbLock = dbLockManager.getDBLock();
|
||||
@@ -101,11 +129,15 @@ public abstract class KeycloakApplication extends Application {
|
||||
exportImportManager.runExport();
|
||||
}
|
||||
|
||||
resetTransactionTimeout();
|
||||
sessionFactory.publish(new PostMigrationEvent(sessionFactory));
|
||||
resetTransactionTimeout(keycloakSessionFactory);
|
||||
bootstrapCompleted = true;
|
||||
keycloakSessionFactory.publish(new PostMigrationEvent(keycloakSessionFactory));
|
||||
|
||||
var duration = Duration.ofNanos(System.nanoTime() - startTime);
|
||||
logger.infof("Bootstrap completed in %f seconds", (double) duration.toMillis() / 1000);
|
||||
}
|
||||
|
||||
protected int getTransactionTimeout(KeycloakSessionFactory sessionFactory) {
|
||||
protected int getTransactionTimeout(KSF sessionFactory) {
|
||||
return Math.toIntExact(TimeUnit.MINUTES.toSeconds(5));
|
||||
}
|
||||
|
||||
@@ -144,24 +176,30 @@ public abstract class KeycloakApplication extends Application {
|
||||
|
||||
protected abstract void initAndStart();
|
||||
|
||||
protected abstract KeycloakSessionFactory createSessionFactory();
|
||||
protected abstract KSF createSessionFactory();
|
||||
|
||||
protected abstract void initKeycloakSessionFactory(KSF ksf);
|
||||
|
||||
public static KeycloakSessionFactory getSessionFactory() {
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
private void setTransactionTimeout() {
|
||||
public static boolean isBootstrapCompleted() {
|
||||
return bootstrapCompleted;
|
||||
}
|
||||
|
||||
private void setTransactionTimeout(KSF keycloakSessionFactory) {
|
||||
try {
|
||||
var transactionTimeoutSeconds = getTransactionTimeout(sessionFactory);
|
||||
KeycloakModelUtils.setTransactionLimit(sessionFactory, transactionTimeoutSeconds);
|
||||
var transactionTimeoutSeconds = getTransactionTimeout(keycloakSessionFactory);
|
||||
KeycloakModelUtils.setTransactionLimit(keycloakSessionFactory, transactionTimeoutSeconds);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to set the transaction timeout, using the default value");
|
||||
}
|
||||
}
|
||||
|
||||
private void resetTransactionTimeout() {
|
||||
private void resetTransactionTimeout(KSF keycloakSessionFactory) {
|
||||
try {
|
||||
KeycloakModelUtils.setTransactionLimit(sessionFactory, 0);
|
||||
KeycloakModelUtils.setTransactionLimit(keycloakSessionFactory, 0);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to reset the transaction timeout");
|
||||
}
|
||||
|
||||
+8
-6
@@ -26,7 +26,6 @@ import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
||||
import org.keycloak.common.util.MultiSiteUtils;
|
||||
import org.keycloak.exportimport.ExportImportManager;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.services.error.KcUnrecognizedPropertyExceptionHandler;
|
||||
import org.keycloak.services.error.KeycloakErrorHandler;
|
||||
@@ -42,7 +41,7 @@ import org.keycloak.services.resources.WelcomeResource;
|
||||
import org.keycloak.services.resources.admin.AdminRoot;
|
||||
import org.keycloak.services.util.ObjectMapperResolver;
|
||||
|
||||
public class ResteasyKeycloakApplication extends KeycloakApplication {
|
||||
public class ResteasyKeycloakApplication extends KeycloakApplication<ResteasyKeycloakSessionFactory> {
|
||||
|
||||
protected Set<Object> singletons = new HashSet<>();
|
||||
protected Set<Class<?>> classes = new HashSet<>();
|
||||
@@ -91,10 +90,13 @@ public class ResteasyKeycloakApplication extends KeycloakApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeycloakSessionFactory createSessionFactory() {
|
||||
ResteasyKeycloakSessionFactory factory = new ResteasyKeycloakSessionFactory();
|
||||
factory.init();
|
||||
return factory;
|
||||
protected ResteasyKeycloakSessionFactory createSessionFactory() {
|
||||
return new ResteasyKeycloakSessionFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initKeycloakSessionFactory(ResteasyKeycloakSessionFactory resteasyKeycloakSessionFactory) {
|
||||
resteasyKeycloakSessionFactory.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ public class ClusteredKeycloakServer implements KeycloakServer {
|
||||
} catch (TimeoutException e) {
|
||||
throw new RuntimeException("Expected %d cluster members".formatted(numServers), e);
|
||||
}
|
||||
ReadinessProbe.waitUntilReady(this::getManagementBaseUrl, numServers);
|
||||
}
|
||||
|
||||
private void startContainersWithMixedImage(KeycloakServerConfigBuilder configBuilder, String[] imagePeServer, CountdownLatchLoggingConsumer clusterLatch) {
|
||||
|
||||
+1
@@ -97,6 +97,7 @@ public class DistributionKeycloakServer implements KeycloakServer {
|
||||
OutputHandler outputHandler = startKeycloak(args);
|
||||
|
||||
waitForStart(outputHandler);
|
||||
ReadinessProbe.waitUntilReady(this);
|
||||
|
||||
if (!Environment.isWindows()) {
|
||||
FileUtils.writeToFile(getPidFile(), ProcessUtils.getKeycloakPid(keycloakProcess));
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ public class EmbeddedKeycloakServer implements KeycloakServer {
|
||||
}
|
||||
|
||||
keycloak = builder.start(keycloakServerConfigBuilder.toArgs());
|
||||
ReadinessProbe.waitUntilReady(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package org.keycloak.testframework.server;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/**
|
||||
* Polls the server's endpoint until it reports ready, supporting both HTTP and HTTPS connections.
|
||||
*/
|
||||
public final class ReadinessProbe {
|
||||
|
||||
private static final long STARTUP_TIMEOUT_MILLIS = Duration.ofMinutes(5).toMillis();
|
||||
private static final int CONNECTION_TIMEOUT_MILLIS = Math.toIntExact(Duration.ofSeconds(5).toMillis());
|
||||
private static final long POLL_INTERVAL_MILLIS = Duration.ofMillis(500).toMillis();
|
||||
|
||||
private ReadinessProbe() {
|
||||
}
|
||||
|
||||
public static void waitUntilReady(KeycloakServer server) {
|
||||
waitUntilReady(index -> server.getBaseUrl(), 1);
|
||||
}
|
||||
|
||||
public static void waitUntilReady(IntFunction<String> baseUrlFunction, int clusterSize) {
|
||||
var deadline = System.currentTimeMillis() + STARTUP_TIMEOUT_MILLIS;
|
||||
var sslContext = createTrustAllSslContext();
|
||||
for (int i = 0; i < clusterSize; i++) {
|
||||
// can't use /health/ready has it is not enabled in most tests
|
||||
var url = baseUrlFunction.apply(i) + "/realms/master";
|
||||
waitUntilReady(url, sslContext, deadline);
|
||||
}
|
||||
}
|
||||
|
||||
private static void waitUntilReady(String url, SSLContext sslContext, long deadline) {
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) URI.create(url).toURL().openConnection();
|
||||
if (connection instanceof HttpsURLConnection https) {
|
||||
https.setSSLSocketFactory(sslContext.getSocketFactory());
|
||||
https.setHostnameVerifier((hostname, session) -> true);
|
||||
}
|
||||
connection.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS);
|
||||
connection.setReadTimeout(CONNECTION_TIMEOUT_MILLIS);
|
||||
connection.setRequestMethod("GET");
|
||||
|
||||
try {
|
||||
if (connection.getResponseCode() == 200) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// server not yet available, retry
|
||||
}
|
||||
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(POLL_INTERVAL_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Interrupted while waiting for server readiness", e);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Server did not become ready within " + TimeUnit.MILLISECONDS.toSeconds(STARTUP_TIMEOUT_MILLIS) + " seconds: " + url);
|
||||
}
|
||||
|
||||
private static SSLContext createTrustAllSslContext() {
|
||||
try {
|
||||
TrustManager[] trustAll = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
}
|
||||
};
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(null, trustAll, null);
|
||||
return ctx;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create trust-all SSLContext", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -30,6 +30,7 @@ public class RemoteKeycloakServer implements KeycloakServer {
|
||||
}
|
||||
waitForStartup();
|
||||
}
|
||||
ReadinessProbe.waitUntilReady(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user