diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index 8b362b9429ed1..f2aca372ad0b2 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -95,8 +95,9 @@
- quay.io/keycloak/keycloak:16.1.0
-
+ 16.1.0
+ quay.io/keycloak/keycloak:${keycloak.version}
+
6.0.3
2.27.2
diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc
index 43496dff798ba..ffa95d1e26aa9 100644
--- a/docs/src/main/asciidoc/security-openid-connect-client.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc
@@ -635,6 +635,31 @@ quarkus.oidc-client.credentials.jwt.subject=${apple.subject}
quarkus.oidc-client.credentials.jwt.issuer=${apple.issuer}
----
+==== Mutual TLS
+
+Some OpenID Connect Providers may require that a client is authenticated as part of the `Mutual TLS` (`MTLS`) authentication process.
+
+`quarkus-oidc-client` can be configured as follows to support `MTLS`:
+
+[source,properties]
+----
+quarkus.oidc.tls.verification=certificate-validation
+
+# Keystore configuration
+quarkus.oidc.client.tls.key-store-file=client-keystore.jks
+quarkus.oidc.client.tls.key-store-password=${key-store-password}
+
+# Add more keystore properties if needed:
+#quarkus.oidc.client.tls.key-store-alias=keyAlias
+#quarkus.oidc.client.tls.key-store-alias-password=keyAliasPassword
+
+# Truststore configuration
+quarkus.oidc.client.tls.trust-store-file=client-truststore.jks
+quarkus.oidc.client.tls.trust-store-password=${trust-store-password}
+# Add more truststore properties if needed:
+#quarkus.oidc.client.tls.trust-store-alias=certAlias
+----
+
[[integration-testing-oidc-client]]
=== Testing
diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc
index 711b65586749d..92b8adf89be69 100644
--- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc
@@ -978,7 +978,6 @@ quarkus.oidc.credentials.jwt.subject=custom-subject
quarkus.oidc.credentials.jwt.issuer=custom-issuer
----
-
==== Apple POST JWT
Apple OpenID Connect Provider uses a `client_secret_post` method where a secret is a JWT produced with a `private_key_jwt` authentication method but with Apple account specific issuer and subject claims.
@@ -999,6 +998,31 @@ quarkus.oidc.credentials.jwt.subject=${apple.subject}
quarkus.oidc.credentials.jwt.issuer=${apple.issuer}
----
+==== Mutual TLS
+
+Some OpenID Connect Providers may require that a client is authenticated as part of the `Mutual TLS` (`MTLS`) authentication process.
+
+`quarkus-oidc` can be configured as follows to support `MTLS`:
+
+[source,properties]
+----
+quarkus.oidc.tls.verification=certificate-validation
+
+# Keystore configuration
+quarkus.oidc.tls.key-store-file=client-keystore.jks
+quarkus.oidc.tls.key-store-password=${key-store-password}
+
+# Add more keystore properties if needed:
+#quarkus.oidc.tls.key-store-alias=keyAlias
+#quarkus.oidc.tls.key-store-alias-password=keyAliasPassword
+
+# Truststore configuration
+quarkus.oidc.tls.trust-store-file=client-truststore.jks
+quarkus.oidc.tls.trust-store-password=${trust-store-password}
+# Add more truststore properties if needed:
+#quarkus.oidc.tls.trust-store-alias=certAlias
+----
+
[[integration-testing]]
=== Testing
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java
index 5797251f2f195..c8512c4ae35b6 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java
@@ -426,6 +426,39 @@ public enum Verification {
@ConfigItem
public Optional verification = Optional.empty();
+ /**
+ * An optional key store which holds the certificate information instead of specifying separate files.
+ */
+ @ConfigItem
+ public Optional keyStoreFile = Optional.empty();
+
+ /**
+ * An optional parameter to specify type of the key store file. If not given, the type is automatically detected
+ * based on the file name.
+ */
+ @ConfigItem
+ public Optional keyStoreFileType = Optional.empty();
+
+ /**
+ * A parameter to specify the password of the key store file. If not given, the default ("password") is used.
+ */
+ @ConfigItem(defaultValue = "password")
+ public String keyStorePassword;
+
+ /**
+ * An optional parameter to select a specific key in the key store. When SNI is disabled, if the key store contains
+ * multiple
+ * keys and no alias is specified, the behavior is undefined.
+ */
+ @ConfigItem
+ public Optional keyStoreKeyAlias = Optional.empty();
+
+ /**
+ * An optional parameter to define the password for the key, in case it's different from {@link #keyStorePassword}.
+ */
+ @ConfigItem
+ public Optional keyStoreKeyPassword = Optional.empty();
+
/**
* An optional trust store which holds the certificate information of the certificates to trust
*/
@@ -444,6 +477,13 @@ public enum Verification {
@ConfigItem
public Optional trustStoreCertAlias = Optional.empty();
+ /**
+ * An optional parameter to specify type of the trust store file. If not given, the type is automatically detected
+ * based on the file name.
+ */
+ @ConfigItem
+ public Optional trustStoreFileType = Optional.empty();
+
public Optional getVerification() {
return verification;
}
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
index faeedb3440126..e92b7881ffb38 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
@@ -130,7 +130,7 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t
.setPassword(oidcConfig.tls.getTrustStorePassword().orElse("password"))
.setAlias(oidcConfig.tls.getTrustStoreCertAlias().orElse(null))
.setValue(io.vertx.core.buffer.Buffer.buffer(trustStoreData))
- .setType("JKS");
+ .setType(getStoreType(oidcConfig.tls.trustStoreFileType, oidcConfig.tls.trustStoreFile.get()));
options.setTrustOptions(trustStoreOptions);
if (Verification.CERTIFICATE_VALIDATION == oidcConfig.tls.verification.orElse(Verification.REQUIRED)) {
options.setVerifyHost(false);
@@ -141,6 +141,23 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t
oidcConfig.tls.trustStoreFile.get().toString()), ex);
}
}
+ if (oidcConfig.tls.keyStoreFile.isPresent()) {
+ try {
+ byte[] keyStoreData = getFileContent(oidcConfig.tls.keyStoreFile.get());
+ io.vertx.core.net.KeyStoreOptions keyStoreOptions = new KeyStoreOptions()
+ .setPassword(oidcConfig.tls.keyStorePassword)
+ .setAlias(oidcConfig.tls.keyStoreKeyAlias.orElse(null))
+ .setAliasPassword(oidcConfig.tls.keyStoreKeyPassword.orElse(null))
+ .setValue(io.vertx.core.buffer.Buffer.buffer(keyStoreData))
+ .setType(getStoreType(oidcConfig.tls.keyStoreFileType, oidcConfig.tls.keyStoreFile.get()));
+ options.setKeyCertOptions(keyStoreOptions);
+
+ } catch (IOException ex) {
+ throw new ConfigurationException(String.format(
+ "OIDC keystore file does not exist or can not be read",
+ oidcConfig.tls.keyStoreFile.get().toString()), ex);
+ }
+ }
Optional proxyOpt = toProxyOptions(oidcConfig.getProxy());
if (proxyOpt.isPresent()) {
options.setProxyOptions(proxyOpt.get());
@@ -154,6 +171,19 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t
options.setConnectTimeout((int) oidcConfig.getConnectionTimeout().toMillis());
}
+ private static String getStoreType(Optional fileType, Path storePath) {
+ if (fileType.isPresent()) {
+ return fileType.get().toUpperCase();
+ }
+ final String pathName = storePath.toString();
+ if (pathName.endsWith(".p12") || pathName.endsWith(".pkcs12") || pathName.endsWith(".pfx")) {
+ return "PKCS12";
+ } else {
+ // assume jks
+ return "JKS";
+ }
+ }
+
public static String getAuthServerUrl(OidcCommonConfig oidcConfig) {
return removeLastPathSeparator(oidcConfig.getAuthServerUrl().get());
}
diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml
index e4259d8420468..cfcee425cb1de 100644
--- a/integration-tests/oidc/pom.xml
+++ b/integration-tests/oidc/pom.xml
@@ -28,15 +28,23 @@
- io.quarkus
- quarkus-test-keycloak-server
+ org.keycloak
+ keycloak-adapter-core
test
+
+ org.keycloak
+ keycloak-core
+
io.quarkus
quarkus-test-security-oidc
test
+
+ org.testcontainers
+ testcontainers
+
io.quarkus
quarkus-junit5
@@ -146,7 +154,7 @@
false
- ${keycloak.docker.image}
+ ${keycloak.version}
@@ -155,7 +163,7 @@
false
- ${keycloak.docker.image}
+ ${keycloak.version}
diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties
index 147ded03cb1c2..2dc3b881d4408 100644
--- a/integration-tests/oidc/src/main/resources/application.properties
+++ b/integration-tests/oidc/src/main/resources/application.properties
@@ -1,12 +1,16 @@
# Configuration file
-quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
+quarkus.oidc.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.token.principal-claim=email
-quarkus.oidc.tls.verification=none
-#quarkus.oidc.tls.verification=required
-#quarkus.oidc.tls.trust-store-file=keycloak.jks
-#quarkus.oidc.tls.trust-store-password=secret
+
+quarkus.oidc.tls.verification=certificate-validation
+quarkus.oidc.tls.trust-store-file=client-truststore.jks
+quarkus.oidc.tls.trust-store-password=password
+quarkus.oidc.tls.key-store-file=client-keystore.jks
+quarkus.oidc.tls.key-store-password=password
+quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks
+
quarkus.http.cors=true
quarkus.http.auth.basic=true
diff --git a/integration-tests/oidc/src/main/resources/client-keystore.jks b/integration-tests/oidc/src/main/resources/client-keystore.jks
new file mode 100644
index 0000000000000..cf6d6ba454864
Binary files /dev/null and b/integration-tests/oidc/src/main/resources/client-keystore.jks differ
diff --git a/integration-tests/oidc/src/main/resources/client-truststore.jks b/integration-tests/oidc/src/main/resources/client-truststore.jks
new file mode 100644
index 0000000000000..112fb9857fbd7
Binary files /dev/null and b/integration-tests/oidc/src/main/resources/client-truststore.jks differ
diff --git a/integration-tests/oidc/src/main/resources/keycloak.jks b/integration-tests/oidc/src/main/resources/keycloak.jks
deleted file mode 100644
index f3035286e1f32..0000000000000
Binary files a/integration-tests/oidc/src/main/resources/keycloak.jks and /dev/null differ
diff --git a/integration-tests/oidc/src/main/resources/server-keystore.jks b/integration-tests/oidc/src/main/resources/server-keystore.jks
new file mode 100644
index 0000000000000..d8991ddbd627d
Binary files /dev/null and b/integration-tests/oidc/src/main/resources/server-keystore.jks differ
diff --git a/integration-tests/oidc/src/main/resources/server-truststore.jks b/integration-tests/oidc/src/main/resources/server-truststore.jks
new file mode 100644
index 0000000000000..8ec8e126507b6
Binary files /dev/null and b/integration-tests/oidc/src/main/resources/server-truststore.jks differ
diff --git a/integration-tests/oidc/src/main/resources/tls.crt b/integration-tests/oidc/src/main/resources/tls.crt
deleted file mode 100644
index 659ff364761e1..0000000000000
Binary files a/integration-tests/oidc/src/main/resources/tls.crt and /dev/null differ
diff --git a/integration-tests/oidc/src/main/resources/tls.key b/integration-tests/oidc/src/main/resources/tls.key
deleted file mode 100644
index 0cd503ce2aa4d..0000000000000
--- a/integration-tests/oidc/src/main/resources/tls.key
+++ /dev/null
@@ -1,32 +0,0 @@
-Bag Attributes
- friendlyName: localhost
- localKeyID: 54 69 6D 65 20 31 36 32 34 30 33 32 35 33 30 39 31 33
-Key Attributes:
------BEGIN PRIVATE KEY-----
-MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCREYdYCTRamvG1
-/vFhAiknv8R95WV3GTMKIrJjHjYDTY8fx7kjX5JIy6y51emSBVW/jouhVCrfa12j
-t+V9y66tkbEM8kQlhIldhuf3c4QTgEQHNd/4OGSGFmAYpvx+EfHILTD+5mArbhMA
-FB3Cn5GfW8/4AdKotWsFEorZL2eqcbs+7qjO26CF0uV8Zk4F7vwhaenVKiWyUbaQ
-8TtRPOXmtmUDaJvcBOTSJdmFxHYUsJOzojmDAMzJqu0M6sEOfoOzx8UGhYYPWLaz
-kfpDOJ4Rua71usLjQGEOoeUD6xf6rpB4eKdlLPNPXlTa7JzwvBtPM6E/XXY3ZuCa
-Mrx/PbA1AgMBAAECggEBAIOso2rXP/wVs9v8AmCJM43u1I1pkMWfy+IhSEYLf/9T
-gNvZz0Q6VW9Z3/f2IEH4MbLj0f2nhhqxO5eFLfsWzACjw076/7wGJyELeLX01idV
-P2pEDn0hwqyq1qLJv1k3NHz7+AMGXLhO+1QQ7kpfyDAbiBOWo/2aXf+Gqx0jmDbu
-Ed+vRNmNpod5hOVHUjo2W500aFCcmtt2vMym713pVXfqNP6bQPAkO8VFJ7vdD63F
-OIx85wcyTlTrCc0bitHaQouG3B56+T4Eg6OoVjMpFrjO4GCcqZAZyiN5QcwMEpZt
-VkRCKGJfnIlNeES20I6/qURmhfkptHdJNRaD/v39cKkCgYEAzkDJ1BY+1EgyMHY0
-mRM6CiGzAbVOlW32cACYM3m6qYbM14iw2Gw2pbEmfTeuhUayRQgjOIyWEX7HzAiu
-6OzI8lEXoow0ewW4E6duqHtqJy3rl2ZqYLUuVfPlhx+NcEH6cxYfknHGMCX4wUyU
-pIf8Yf5qJ9zb38tqLE0bVDOt/18CgYEAtA7cy8z5g0YWmupgDKXB0n2D6XRDYl+z
-8/+3PCJ9kJtNbwqREWkn7IvVQlMTCsKPME4wcvreoLedScMDIfLQNH+7F87Q6TC6
-/kOt8gvW/pkSVRXYujFK1O9KORUmuN1YHGD+rdX5T5ufA2DVWkX+Hc+5lXrHjlgY
-/Eq2EnuWPOsCgYEAsgH0rxjr7ObKekzqpFqVsvzWs9i5E/qtwIii03pyAbIXxMVy
-a7cpiuNTpqqR8vDLFw0o6LtdIYhcA9pSqzEBVTFrxpxfBvYuorfUp5CsU1gshqSb
-lw+ICCLRrEctGP+4me80HH4ZYKDFCn9/omjDCAg9sl3JXmL/JXD+7zMTLt0CgYBD
-KpQklgaxeHCwQyOnNCH0IgwWBt+oD6kyKL6yeO88BSLCfD+XLhHNhG/9+L1Oszr0
-uwYJrhlj/Hp47Hz7qfcOzmL9Q5Hcmuf2N0ro0o/Vk0YqZSbedcrDWavnVUOHjFH0
-7B20vO/uSU/s069iqF9dwYIqB43vRF+1pSz8AgwOFwKBgFGw8EkhByIzXpNX8Z9s
-5nhC32vt2DgttcaSNCo0jqBUns3YgkKd1gLDppk66ZSU8xLP+TP7ge8DPpBEGERd
-A/vrq2U515eqiOxu0RHOKp4cn57i+6lLpAqFz8hxkRBAPpeVNL4Yn6BHF/ouvPIW
-yN6B3X4uVS/RCx4It50S8jlu
------END PRIVATE KEY-----
diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
index d97629c30a20c..993d3b9ac2bd5 100644
--- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
+++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
@@ -1,7 +1,7 @@
package io.quarkus.it.keycloak;
-import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken;
-import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getRefreshToken;
+import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken;
+import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getRefreshToken;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.equalTo;
@@ -14,14 +14,13 @@
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
-import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
import io.restassured.RestAssured;
/**
* @author Pedro Igor
*/
@QuarkusTest
-@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
+@QuarkusTestResource(KeycloakXTestResourceLifecycleManager.class)
public class BearerTokenAuthorizationTest {
@Test
diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java
new file mode 100644
index 0000000000000..38e5901371f0d
--- /dev/null
+++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java
@@ -0,0 +1,191 @@
+package io.quarkus.it.keycloak;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.RolesRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.util.JsonSerialization;
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import io.restassured.RestAssured;
+import io.restassured.specification.RequestSpecification;
+
+public class KeycloakXTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager {
+ private static final Logger LOGGER = Logger.getLogger(KeycloakXTestResourceLifecycleManager.class);
+ private GenericContainer> keycloak;
+
+ private static String KEYCLOAK_SERVER_URL;
+ private static final String KEYCLOAK_REALM = "quarkus";
+ private static final String KEYCLOAK_SERVICE_CLIENT = "quarkus-service-app";
+ private static final String KEYCLOAK_VERSION = System.getProperty("keycloak.version");
+
+ private static String CLIENT_KEYSTORE = "client-keystore.jks";
+ private static String CLIENT_TRUSTSTORE = "client-truststore.jks";
+
+ private static String SERVER_KEYSTORE = "server-keystore.jks";
+ private static String SERVER_KEYSTORE_MOUNTED_PATH = "/etc/server-keystore.jks";
+ private static String SERVER_TRUSTSTORE = "server-truststore.jks";
+ private static String SERVER_TRUSTSTORE_MOUNTED_PATH = "/etc/server-truststore.jks";
+
+ @SuppressWarnings("resource")
+ @Override
+ public Map start() {
+ keycloak = new GenericContainer<>("quay.io/keycloak/keycloak-x:" + KEYCLOAK_VERSION)
+ .withExposedPorts(8080, 8443)
+ .withEnv("KEYCLOAK_ADMIN", "admin")
+ .withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin")
+ .waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1));
+
+ keycloak = keycloak
+ .withClasspathResourceMapping(SERVER_KEYSTORE, SERVER_KEYSTORE_MOUNTED_PATH, BindMode.READ_ONLY)
+ .withClasspathResourceMapping(SERVER_TRUSTSTORE, SERVER_TRUSTSTORE_MOUNTED_PATH, BindMode.READ_ONLY)
+ .withCommand("build --https-client-auth=required")
+ .withCommand(String.format(
+ "start --https-client-auth=required --hostname-strict=false --hostname-strict-https=false"
+ + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password",
+ SERVER_KEYSTORE_MOUNTED_PATH, SERVER_TRUSTSTORE_MOUNTED_PATH));
+ keycloak.start();
+ LOGGER.info(keycloak.getLogs());
+
+ KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443);
+
+ RealmRepresentation realm = createRealm(KEYCLOAK_REALM);
+ postRealm(realm);
+
+ return Map.of("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM);
+ }
+
+ private static void postRealm(RealmRepresentation realm) {
+ try {
+ createRequestSpec().auth().oauth2(getAdminAccessToken())
+ .contentType("application/json")
+ .body(JsonSerialization.writeValueAsBytes(realm))
+ .when()
+ .post(KEYCLOAK_SERVER_URL + "/admin/realms").then()
+ .statusCode(201);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static RealmRepresentation createRealm(String name) {
+ RealmRepresentation realm = new RealmRepresentation();
+
+ realm.setRealm(name);
+ realm.setEnabled(true);
+ realm.setUsers(new ArrayList<>());
+ realm.setClients(new ArrayList<>());
+ realm.setAccessTokenLifespan(3);
+ realm.setSsoSessionMaxLifespan(3);
+
+ RolesRepresentation roles = new RolesRepresentation();
+ List realmRoles = new ArrayList<>();
+
+ roles.setRealm(realmRoles);
+ realm.setRoles(roles);
+
+ realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false));
+ realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false));
+ realm.getRoles().getRealm().add(new RoleRepresentation("confidential", null, false));
+
+ realm.getClients().add(createServiceClient(KEYCLOAK_SERVICE_CLIENT));
+
+ realm.getUsers().add(createUser("alice", List.of("user")));
+ realm.getUsers().add(createUser("admin", List.of("user", "admin")));
+ realm.getUsers().add(createUser("jdoe", List.of("user", "confidential")));
+
+ return realm;
+ }
+
+ private static String getAdminAccessToken() {
+ return createRequestSpec()
+ .param("grant_type", "password")
+ .param("username", "admin")
+ .param("password", "admin")
+ .param("client_id", "admin-cli")
+ .when()
+ .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token")
+ .as(AccessTokenResponse.class).getToken();
+ }
+
+ private static ClientRepresentation createServiceClient(String clientId) {
+ ClientRepresentation client = new ClientRepresentation();
+
+ client.setClientId(clientId);
+ client.setPublicClient(false);
+ client.setSecret("secret");
+ client.setDirectAccessGrantsEnabled(true);
+ client.setServiceAccountsEnabled(true);
+ client.setEnabled(true);
+
+ return client;
+ }
+
+ private static UserRepresentation createUser(String username, List realmRoles) {
+ UserRepresentation user = new UserRepresentation();
+
+ user.setUsername(username);
+ user.setEnabled(true);
+ user.setCredentials(new ArrayList<>());
+ user.setRealmRoles(realmRoles);
+ user.setEmail(username + "@gmail.com");
+
+ CredentialRepresentation credential = new CredentialRepresentation();
+
+ credential.setType(CredentialRepresentation.PASSWORD);
+ credential.setValue(username);
+ credential.setTemporary(false);
+
+ user.getCredentials().add(credential);
+
+ return user;
+ }
+
+ public static String getAccessToken(String userName) {
+ return createRequestSpec().param("grant_type", "password")
+ .param("username", userName)
+ .param("password", userName)
+ .param("client_id", KEYCLOAK_SERVICE_CLIENT)
+ .param("client_secret", "secret")
+ .when()
+ .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token")
+ .as(AccessTokenResponse.class).getToken();
+ }
+
+ public static String getRefreshToken(String userName) {
+ return createRequestSpec().param("grant_type", "password")
+ .param("username", userName)
+ .param("password", userName)
+ .param("client_id", KEYCLOAK_SERVICE_CLIENT)
+ .param("client_secret", "secret")
+ .when()
+ .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token")
+ .as(AccessTokenResponse.class).getRefreshToken();
+ }
+
+ @Override
+ public void stop() {
+ createRequestSpec().auth().oauth2(getAdminAccessToken())
+ .when()
+ .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204);
+
+ keycloak.stop();
+ }
+
+ private static RequestSpecification createRequestSpec() {
+ return RestAssured.given().trustStore(CLIENT_TRUSTSTORE, "password")
+ .keyStore(CLIENT_KEYSTORE, "password");
+ }
+}
diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java
index a8397e06db357..50a993da34439 100644
--- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java
+++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java
@@ -1,6 +1,6 @@
package io.quarkus.it.keycloak;
-import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken;
+import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken;
import java.net.URI;
import java.util.concurrent.LinkedBlockingDeque;
@@ -18,11 +18,10 @@
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;
-import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
import io.quarkus.websockets.BearerTokenClientEndpointConfigurator;
@QuarkusTest
-@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
+@QuarkusTestResource(KeycloakXTestResourceLifecycleManager.class)
public class WebsocketOidcTestCase {
@TestHTTPResource("secured-hello")
diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java
index dd8c8eb963c43..4b1d8b4676950 100644
--- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java
+++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java
@@ -15,7 +15,6 @@
import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.util.JsonSerialization;
-import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -28,7 +27,6 @@ public class KeycloakTestResourceLifecycleManager implements QuarkusTestResource
private GenericContainer> keycloak;
private static String KEYCLOAK_SERVER_URL;
- private static final Boolean KEYCLOAK_TRUSTSTORE_REQUIRED = false;
private static final String KEYCLOAK_REALM = System.getProperty("keycloak.realm", "quarkus");
private static final String KEYCLOAK_SERVICE_CLIENT = System.getProperty("keycloak.service.client", "quarkus-service-app");
private static final String KEYCLOAK_WEB_APP_CLIENT = System.getProperty("keycloak.web-app.client", "quarkus-web-app");
@@ -39,16 +37,8 @@ public class KeycloakTestResourceLifecycleManager implements QuarkusTestResource
private static final String TOKEN_USER_ROLES = System.getProperty("keycloak.token.user-roles", "user");
private static final String TOKEN_ADMIN_ROLES = System.getProperty("keycloak.token.admin-roles", "user,admin");
- private static String KEYCLOAK_TRUSTSTORE_PATH = "keycloak.jks";
- private static String KEYCLOAK_TRUSTSTORE_SECRET = "secret";
- private static String KEYCLOAK_TLS_KEY = "tls.key";
- private static String KEYCLOAK_TLS_KEY_MOUNTED_PATH = "/etc/x509/https";
- private static String KEYCLOAK_TLS_CRT = "tls.crt";
- private static String KEYCLOAK_TLS_CRT_MOUNTED_PATH = "/etc/x509/https";
-
static {
- //KEYCLOAK_TRUSTSTORE_REQUIRED = Thread.currentThread().getContextClassLoader().getResource(KEYCLOAK_TLS_KEY) != null;
- if (KEYCLOAK_USE_HTTPS && !KEYCLOAK_TRUSTSTORE_REQUIRED) {
+ if (KEYCLOAK_USE_HTTPS) {
RestAssured.useRelaxedHTTPSValidation();
}
}
@@ -72,16 +62,6 @@ public Map start() {
.withEnv("KEYCLOAK_PASSWORD", "admin")
.waitingFor(Wait.forHttp("/auth").forPort(8080));
- if (KEYCLOAK_USE_HTTPS && KEYCLOAK_TRUSTSTORE_REQUIRED) {
- keycloak = keycloak
- .withClasspathResourceMapping(KEYCLOAK_TLS_KEY, KEYCLOAK_TLS_KEY_MOUNTED_PATH, BindMode.READ_ONLY)
- .withClasspathResourceMapping(KEYCLOAK_TLS_CRT, KEYCLOAK_TLS_CRT_MOUNTED_PATH, BindMode.READ_ONLY);
- //.withCopyFileToContainer(MountableFile.forClasspathResource(KEYCLOAK_TLS_KEY),
- // KEYCLOAK_TLS_KEY_MOUNTED_PATH)
- //.withCopyFileToContainer(MountableFile.forClasspathResource(KEYCLOAK_TLS_CRT),
- // KEYCLOAK_TLS_CRT_MOUNTED_PATH);
- }
-
keycloak.start();
if (KEYCLOAK_USE_HTTPS) {
@@ -239,10 +219,6 @@ private static List getUserRoles() {
}
private static RequestSpecification createRequestSpec() {
- RequestSpecification spec = RestAssured.given();
- if (KEYCLOAK_TRUSTSTORE_REQUIRED) {
- spec = spec.trustStore(KEYCLOAK_TRUSTSTORE_PATH, KEYCLOAK_TRUSTSTORE_SECRET);
- }
- return spec;
+ return RestAssured.given();
}
}