Redirect to relative-path from the root path (#32868)
Closes #32863 Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
9e42e8013d
commit
84564f080a
8 changed files with 97 additions and 9 deletions
|
@ -124,6 +124,15 @@ The support is in preview mode, and we would be happy to obtain any feedback.
|
||||||
|
|
||||||
For more information, see the link:{tracingguide_link}[{tracingguide_name}] guide.
|
For more information, see the link:{tracingguide_link}[{tracingguide_name}] guide.
|
||||||
|
|
||||||
|
= Automatic redirect from root to relative path
|
||||||
|
|
||||||
|
User is automatically redirected to the path where {project_name} is hosted when the `http-relative-path` property is specified.
|
||||||
|
It means when the relative path is set to `/auth`, and the user access `localhost:8080/`, the page is redirected to `localhost:8080/auth`.
|
||||||
|
|
||||||
|
The same applies to the management interface when the `http-management-relative-path` or `http-relative-path` property is specified.
|
||||||
|
|
||||||
|
It improves user experience as users no longer need to set the relative path to the URL explicitly.
|
||||||
|
|
||||||
= Removal of legacy cookies
|
= Removal of legacy cookies
|
||||||
|
|
||||||
Keycloak no longer sends `_LEGACY` cookies, which where introduced as a work-around to older browsers not supporting
|
Keycloak no longer sends `_LEGACY` cookies, which where introduced as a work-around to older browsers not supporting
|
||||||
|
|
|
@ -36,6 +36,15 @@ to perform any cleanup or custom processing when a realm, and their groups, are
|
||||||
The `GroupProvider` interface is also updated with a new `preRemove(RealmModel)` method to force implementations to properly
|
The `GroupProvider` interface is also updated with a new `preRemove(RealmModel)` method to force implementations to properly
|
||||||
handle the removal of groups when a realm is removed.
|
handle the removal of groups when a realm is removed.
|
||||||
|
|
||||||
|
= Automatic redirect from root to relative path
|
||||||
|
|
||||||
|
User is automatically redirected to the path where {project_name} is hosted when the `http-relative-path` property is specified.
|
||||||
|
It means when the relative path is set to `/auth`, and the user access `localhost:8080/`, the page is redirected to `localhost:8080/auth`.
|
||||||
|
|
||||||
|
The same applies to the management interface when the `http-management-relative-path` or `http-relative-path` property is specified.
|
||||||
|
|
||||||
|
It improves user experience as users no longer need to set the relative path to the URL explicitly.
|
||||||
|
|
||||||
= Operator scheduling defaults
|
= Operator scheduling defaults
|
||||||
|
|
||||||
Keycloak Pods will now have default affinities to prevent multiple instances from the same CR from being deployed on the same node, and all Pods from the same CR will prefer to be in the same zone to prevent stretch cache clusters.
|
Keycloak Pods will now have default affinities to prevent multiple instances from the same CR from being deployed on the same node, and all Pods from the same CR will prefer to be in the same zone to prevent stretch cache clusters.
|
||||||
|
|
|
@ -62,6 +62,9 @@ By default, the new Quarkus distribution removes `/auth` from the context-path.
|
||||||
bin/kc.[sh|bat] start-dev --http-relative-path /auth
|
bin/kc.[sh|bat] start-dev --http-relative-path /auth
|
||||||
----
|
----
|
||||||
|
|
||||||
|
When the relative path is specified, it is still possible to be redirected from the root to the relative path.
|
||||||
|
Specifically, when the user access `localhost:8080/`, the page is redirected to the `localhost:8080/auth`.
|
||||||
|
|
||||||
== Migrating custom providers
|
== Migrating custom providers
|
||||||
|
|
||||||
Similarly to the WildFly distribution custom providers are deployed to Keycloak by copying them to a deployment directory. In the new distribution you should copy your providers to the `providers` directory instead of `standalone/deployments`, which no longer exists. Additional dependencies are also copied to the `providers` directory.
|
Similarly to the WildFly distribution custom providers are deployed to Keycloak by copying them to a deployment directory. In the new distribution you should copy your providers to the `providers` directory instead of `standalone/deployments`, which no longer exists. Additional dependencies are also copied to the `providers` directory.
|
||||||
|
|
|
@ -16,19 +16,25 @@ The most significant advantage might be seen in Kubernetes environments as the s
|
||||||
The management interface is turned on when something is exposed on it.
|
The management interface is turned on when something is exposed on it.
|
||||||
Management endpoints such as `/metrics` and `/health` are exposed on the default management port `9000` when metrics and health are enabled.
|
Management endpoints such as `/metrics` and `/health` are exposed on the default management port `9000` when metrics and health are enabled.
|
||||||
The management interface provides a set of options and is fully configurable.
|
The management interface provides a set of options and is fully configurable.
|
||||||
In order to change the port for the management interface, you can use the {project_name} option `http-management-port`.
|
|
||||||
|
|
||||||
NOTE: If management interface properties are not explicitly set, their values are automatically inherited from the default HTTP server.
|
NOTE: If management interface properties are not explicitly set, their values are automatically inherited from the default HTTP server.
|
||||||
|
|
||||||
|
=== Port
|
||||||
|
In order to change the port for the management interface, you can use the {project_name} option `http-management-port`.
|
||||||
|
|
||||||
|
=== Relative path
|
||||||
You can change the relative path of the management interface, as the prefix path for the management endpoints can be different.
|
You can change the relative path of the management interface, as the prefix path for the management endpoints can be different.
|
||||||
You can achieve it via the {project_name} option `http-management-relative-path`.
|
You can achieve it via the {project_name} option `http-management-relative-path`.
|
||||||
|
|
||||||
For instance, if you set the CLI option `--http-management-relative-path=/management`, the metrics, and health endpoints will be accessed on the `/management/metrics` and `/management/health` paths.
|
For instance, if you set the CLI option `--http-management-relative-path=/management`, the metrics, and health endpoints will be accessed on the `/management/metrics` and `/management/health` paths.
|
||||||
|
|
||||||
|
User is automatically *redirected* to the path where {project_name} is hosted when the relative path is specified.
|
||||||
|
It means when the relative path is set to `/management`, and the user access `localhost:9000/`, the page is redirected to `localhost:9000/management`.
|
||||||
|
|
||||||
NOTE: If you do not explicitly set the value for it, the value from the `http-relative-path` property is used. For instance,
|
NOTE: If you do not explicitly set the value for it, the value from the `http-relative-path` property is used. For instance,
|
||||||
if you set the CLI option `--http-relative-path=/auth`, these endpoints are accessible on the `/auth/metrics` and `/auth/health` paths.
|
if you set the CLI option `--http-relative-path=/auth`, these endpoints are accessible on the `/auth/metrics` and `/auth/health` paths.
|
||||||
|
|
||||||
== TLS support
|
=== TLS support
|
||||||
|
|
||||||
When the TLS is set for the default {project_name} server, the management interface will be accessible through HTTPS as well.
|
When the TLS is set for the default {project_name} server, the management interface will be accessible through HTTPS as well.
|
||||||
The management interface can run only either on HTTP or HTTPS, not both as for the main server.
|
The management interface can run only either on HTTP or HTTPS, not both as for the main server.
|
||||||
|
@ -36,7 +42,7 @@ The management interface can run only either on HTTP or HTTPS, not both as for t
|
||||||
Specific {project_name} management interface options with the prefix `https-management-*` were provided for setting different TLS parameters for the management HTTP server. Their function is similar to their counterparts for the main HTTP server, for details see <@links.server id="enabletls" />.
|
Specific {project_name} management interface options with the prefix `https-management-*` were provided for setting different TLS parameters for the management HTTP server. Their function is similar to their counterparts for the main HTTP server, for details see <@links.server id="enabletls" />.
|
||||||
When these options are not explicitly set, the TLS parameters are inherited from the default HTTP server.
|
When these options are not explicitly set, the TLS parameters are inherited from the default HTTP server.
|
||||||
|
|
||||||
== Disable Management interface
|
=== Disable Management interface
|
||||||
|
|
||||||
The management interface is automatically turned off when nothing is exposed on it.
|
The management interface is automatically turned off when nothing is exposed on it.
|
||||||
Currently, only health checks and metrics are exposed on the management interface regardless.
|
Currently, only health checks and metrics are exposed on the management interface regardless.
|
||||||
|
|
|
@ -25,7 +25,6 @@ import io.quarkus.agroal.spi.JdbcDriverBuildItem;
|
||||||
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
|
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
|
||||||
import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
|
import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
|
||||||
import io.quarkus.bootstrap.logging.InitialConfigurator;
|
import io.quarkus.bootstrap.logging.InitialConfigurator;
|
||||||
import io.quarkus.builder.item.EmptyBuildItem;
|
|
||||||
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem;
|
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem;
|
||||||
import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig;
|
import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig;
|
||||||
import io.quarkus.deployment.IsDevelopment;
|
import io.quarkus.deployment.IsDevelopment;
|
||||||
|
@ -48,6 +47,7 @@ import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem;
|
||||||
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
|
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
|
||||||
import io.quarkus.runtime.LaunchMode;
|
import io.quarkus.runtime.LaunchMode;
|
||||||
import io.quarkus.runtime.configuration.ConfigurationException;
|
import io.quarkus.runtime.configuration.ConfigurationException;
|
||||||
|
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
|
||||||
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
|
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
|
||||||
import io.quarkus.vertx.http.deployment.RouteBuildItem;
|
import io.quarkus.vertx.http.deployment.RouteBuildItem;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
|
@ -77,6 +77,7 @@ import org.keycloak.common.util.MultiSiteUtils;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.config.DatabaseOptions;
|
import org.keycloak.config.DatabaseOptions;
|
||||||
import org.keycloak.config.HealthOptions;
|
import org.keycloak.config.HealthOptions;
|
||||||
|
import org.keycloak.config.HttpOptions;
|
||||||
import org.keycloak.config.ManagementOptions;
|
import org.keycloak.config.ManagementOptions;
|
||||||
import org.keycloak.config.MetricsOptions;
|
import org.keycloak.config.MetricsOptions;
|
||||||
import org.keycloak.config.SecurityOptions;
|
import org.keycloak.config.SecurityOptions;
|
||||||
|
@ -123,6 +124,7 @@ import org.keycloak.theme.ThemeResourceSpi;
|
||||||
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
||||||
import org.keycloak.userprofile.config.UPConfigUtils;
|
import org.keycloak.userprofile.config.UPConfigUtils;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.utils.StringUtil;
|
||||||
import org.keycloak.vault.FilesKeystoreVaultProviderFactory;
|
import org.keycloak.vault.FilesKeystoreVaultProviderFactory;
|
||||||
import org.keycloak.vault.FilesPlainTextVaultProviderFactory;
|
import org.keycloak.vault.FilesPlainTextVaultProviderFactory;
|
||||||
|
|
||||||
|
@ -234,17 +236,43 @@ class KeycloakProcessor {
|
||||||
recorder.configureProfile(profile.getName(), profile.getFeatures());
|
recorder.configureProfile(profile.getName(), profile.getFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Record(ExecutionTime.STATIC_INIT)
|
||||||
|
@BuildStep
|
||||||
|
@Consume(ConfigBuildItem.class)
|
||||||
|
void configureRedirectForRootPath(BuildProducer<RouteBuildItem> routes,
|
||||||
|
HttpRootPathBuildItem httpRootPathBuildItem,
|
||||||
|
KeycloakRecorder recorder) {
|
||||||
|
Configuration.getOptionalKcValue(HttpOptions.HTTP_RELATIVE_PATH)
|
||||||
|
.filter(StringUtil::isNotBlank)
|
||||||
|
.filter(f -> !f.equals("/"))
|
||||||
|
.ifPresent(relativePath ->
|
||||||
|
routes.produce(httpRootPathBuildItem.routeBuilder()
|
||||||
|
.route("/")
|
||||||
|
.handler(recorder.getRedirectHandler(relativePath))
|
||||||
|
.build())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Record(ExecutionTime.STATIC_INIT)
|
@Record(ExecutionTime.STATIC_INIT)
|
||||||
@BuildStep(onlyIf = IsManagementEnabled.class)
|
@BuildStep(onlyIf = IsManagementEnabled.class)
|
||||||
@Consume(ConfigBuildItem.class)
|
@Consume(ConfigBuildItem.class)
|
||||||
void configureManagementInterface(BuildProducer<RouteBuildItem> routes,
|
void configureManagementInterface(BuildProducer<RouteBuildItem> routes,
|
||||||
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
|
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
|
||||||
KeycloakRecorder recorder) {
|
KeycloakRecorder recorder) {
|
||||||
final var path = Configuration.getOptionalKcValue(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH.getKey()).orElse("/");
|
final var relativePath = Configuration.getOptionalKcValue(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH).orElse("/");
|
||||||
|
|
||||||
|
if (StringUtil.isNotBlank(relativePath) && !relativePath.equals("/")) {
|
||||||
|
// redirect from / to the relativePath
|
||||||
|
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
|
||||||
|
.management()
|
||||||
|
.route("/")
|
||||||
|
.handler(recorder.getRedirectHandler(relativePath))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
|
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
|
||||||
.management()
|
.management()
|
||||||
.route(path)
|
.route(relativePath)
|
||||||
.handler(recorder.getManagementHandler())
|
.handler(recorder.getManagementHandler())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
import static io.restassured.RestAssured.given;
|
import static io.restassured.RestAssured.given;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
|
||||||
class KeycloakPathConfigurationTest {
|
class KeycloakPathConfigurationTest {
|
||||||
|
|
||||||
|
@ -98,11 +99,16 @@ class KeycloakPathConfigurationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRootUnavailable() {
|
void testRootRedirect() {
|
||||||
|
given().basePath("/").redirects().follow(false)
|
||||||
|
.when().get("")
|
||||||
|
.then()
|
||||||
|
.statusCode(302)
|
||||||
|
.header("Location", is("/auth"));
|
||||||
|
|
||||||
given().basePath("/")
|
given().basePath("/")
|
||||||
.when().get("")
|
.when().get("")
|
||||||
.then()
|
.then()
|
||||||
// application root is configured to /auth, so we expect 404 on /
|
.statusCode(200);
|
||||||
.statusCode(404);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,11 @@ public class KeycloakRecorder {
|
||||||
Profile.init(profileName, features);
|
Profile.init(profileName, features);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default handler for redirecting to specific path
|
||||||
|
public Handler<RoutingContext> getRedirectHandler(String redirectPath) {
|
||||||
|
return routingContext -> routingContext.redirect(redirectPath);
|
||||||
|
}
|
||||||
|
|
||||||
// default handler for the management interface
|
// default handler for the management interface
|
||||||
public Handler<RoutingContext> getManagementHandler() {
|
public Handler<RoutingContext> getManagementHandler() {
|
||||||
return routingContext -> routingContext.response().end("Keycloak Management Interface");
|
return routingContext -> routingContext.response().end("Keycloak Management Interface");
|
||||||
|
|
|
@ -29,7 +29,9 @@ import org.keycloak.it.utils.KeycloakDistribution;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
import static io.restassured.RestAssured.when;
|
import static io.restassured.RestAssured.when;
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
@ -120,10 +122,30 @@ public class ManagementDistTest {
|
||||||
assertRelativePath(result, "/management");
|
assertRelativePath(result, "/management");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Launch({"start-dev", "--http-relative-path=/auth", "--http-management-relative-path=/management"})
|
||||||
|
void testManagementRootRedirects(LaunchResult result, KeycloakDistribution distribution) {
|
||||||
|
assertRelativePath(result, "/management");
|
||||||
|
|
||||||
|
distribution.setRequestPort(8080);
|
||||||
|
|
||||||
|
given().redirects().follow(false).when().get("/").then().statusCode(302).header("Location", is("/auth"));
|
||||||
|
when().get("/").then().statusCode(200).body(containsString("Welcome to Keycloak"));
|
||||||
|
when().get("/auth").then().statusCode(200).body(containsString("Welcome to Keycloak"));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertRelativePath(LaunchResult result, String relativePath) {
|
private void assertRelativePath(LaunchResult result, String relativePath) {
|
||||||
CLIResult cliResult = (CLIResult) result;
|
CLIResult cliResult = (CLIResult) result;
|
||||||
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9000");
|
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9000");
|
||||||
|
|
||||||
|
given().redirects().follow(false).when().get("/").then()
|
||||||
|
.statusCode(302)
|
||||||
|
.and()
|
||||||
|
.header("Location", is(relativePath));
|
||||||
|
when().get("/").then()
|
||||||
|
.statusCode(200)
|
||||||
|
.and()
|
||||||
|
.body(is("Keycloak Management Interface"));
|
||||||
when().get(relativePath).then()
|
when().get(relativePath).then()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.and()
|
.and()
|
||||||
|
|
Loading…
Reference in a new issue