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 0a6f47b01151c7..ec0c6ea9578f65 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -582,7 +582,7 @@ It applies to ID tokens but also to access tokens in a JWT format if the `web-ap Please see link:security-openid-connect-client#token-propagation[Token Propagation] section about the Authorization Code Flow access token propagation to the downstream services. [[oidc-provider-client-authentication]] -=== Oidc Provider Client Authentication +== Oidc Provider Client Authentication `quarkus.oidc.runtime.OidcProviderClient` is used when a remote request to an OpenId Connect Provider has to be done. It has to authenticate to the OpenId Connect Provider when the authorization code has to be exchanged for the ID, access and refresh tokens, when the ID and access tokens have to be refreshed or introspected. @@ -784,10 +784,46 @@ Additionally, `OidcWiremockTestResource` set token issuer and audience to `https `OidcWiremockTestResource` can be used to emulate all OpenId Connect providers. +[[integration-testing-keycloak-devservices]] +=== Dev Services for Keycloak + +Using link:security-openid-connect-dev-services[Dev Services for Keycloak] is recommended for the integration testing against Keycloak. +`Dev Services for Keycloak` will launch and initialize a test container: it will create a `quarkus` realm, a `quarkus-app` client (`secret` secret) and add `alice` (`admin` and `user` roles) and `bob` (`user` role) users, where all of these properties can be customized. + +First prepare `application.properties`. You can start with a completely empty `application.properties` as `Dev Services for Keycloak` will register `quarkus.oidc.auth-server-url` pointing to the running test container as well as `quarkus.oidc.client-id=quarkus-app` and `quarkus.oidc.credentials.secret=secret`. + +But if you already have all the required `quarkus-oidc` properties configured then you only need to associate `quarkus.oidc.auth-server-url` with the `prod` profile for `Dev Services for Keycloak`to start a container, for example: + +[source,properties] +---- +%prod.quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus +---- + +If a custom realm file has to be imported into Keycloak before running the tests then you can configure `Dev Services for Keycloak` as follows: + +[source,properties] +---- +%prod.quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus +quarkus.keycloak.devservices.realm-path=quarkus-realm.json +---- + +Finally write a test code the same way as it is described in the <> section above. +The only difference is that `@QuarkusTestResource` is no longer needed: + +[source, java] +---- +@QuarkusTest +public class CodeFlowAuthorizationTest { +} +---- + [[integration-testing-keycloak]] -=== Keycloak +=== KeycloakTestResourceLifecycleManager -If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency: +If you need to do the integration testing against Keycloak then you are encouraged to do it with <>. +Use `KeycloakTestResourceLifecycleManager` for your tests only if there is a good reason not to use `Dev Services for Keycloak`. + +Start with adding the following dependency: [source,xml] ---- @@ -798,7 +834,9 @@ If you work with Keycloak then you can test against a live Keycloak instance by ---- -and configure `maven.surefire.plugin` as follows: +which provides `io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager` - an implementaion of `io.quarkus.test.common.QuarkusTestResourceLifecycleManager` which starts a Keycloak container. + +And configure `maven.surefire.plugin` as follows: [source,xml] ---- @@ -909,5 +947,5 @@ include::{generated-dir}/config/quarkus-oidc.adoc[opts=optional] * https://openid.net/connect/[OpenID Connect] * https://tools.ietf.org/html/rfc7519[JSON Web Token] * link:security-openid-connect-client[Quarkus - Using OpenID Connect and OAuth2 Client and Filters to manage access tokens] -* link:security-openid-connect-dev-services[Dev Services for OpenId Connect] +* link:security-openid-connect-dev-services[Dev Services for Keycloak] * link:security[Quarkus Security] diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 38fac3a4ef2b5b..f42ec7fe4048a0 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -508,7 +508,7 @@ Note it is also recommended to use `quarkus.oidc.token.audience` property to ver Please see link:security-openid-connect-client#token-propagation[Token Propagation] section about the Bearer access token propagation to the downstream services. [[oidc-provider-authentication]] -=== Oidc Provider Client Authentication +== Oidc Provider Client Authentication `quarkus.oidc.runtime.OidcProviderClient` is used when a remote request to an OpenId Connect Provider has to be done. If the bearer token has to be introspected then `OidcProviderClient` has to authenticate to the OpenId Connect Provider. Please see link:security-openid-connect-web-authentication#oidc-provider-client-authentication[OidcProviderClient Authentication] for more information about all the supported authentication options. @@ -602,10 +602,98 @@ public class BearerTokenAuthorizationTest { Testing your `quarkus-oidc` `service` application with `OidcWiremockTestResource` provides the best coverage as even the communication channel is tested against the Wiremock HTTP stubs. `OidcWiremockTestResource` will be enhanced going forward to support more complex Bearer token test scenarios. +[[integration-testing-keycloak-devservices]] +=== Dev Services for Keycloak + +Using link:security-openid-connect-dev-services[Dev Services for Keycloak] is recommended for the integration testing against Keycloak. +`Dev Services for Keycloak` will launch and initialize a test container: it will create a `quarkus` realm, a `quarkus-app` client (`secret` secret) and add `alice` (`admin` and `user` roles) and `bob` (`user` role) users, where all of these properties can be customized. + +First you need to add the following dependency: + +[source,xml] +---- + + io.quarkus + quarkus-test-keycloak-server + test + +---- + +which provides a utility class `io.quarkus.test.keycloak.client.KeycloakTestClient` you can use in tests for acquiring the access tokens. + +Next prepare `application.properties`. You can start with a completely empty `application.properties` as `Dev Services for Keycloak` will register `quarkus.oidc.auth-server-url` pointing to the running test container as well as `quarkus.oidc.client-id=quarkus-app` and `quarkus.oidc.credentials.secret=secret`. + +But if you already have all the required `quarkus-oidc` properties configured then you only need to associate `quarkus.oidc.auth-server-url` with the `prod` profile for `Dev Services for Keycloak`to start a container, for example: + +[source,properties] +---- +%prod.quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus +---- + +If a custom realm file has to be imported into Keycloak before running the tests then you can configure `Dev Services for Keycloak` as follows: + +[source,properties] +---- +%prod.quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus +quarkus.keycloak.devservices.realm-path=quarkus-realm.json +---- + +Finally write a test code which will work in the JVM mode: + +[source,java] +---- +package org.acme.security.openid.connect; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class BearerTokenAuthenticationTest { + + KeycloakTestClient keycloakClient = new KeycloakTestClient(); + + @Test + public void testAdminAccess() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/admin") + .then() + .statusCode(200); + RestAssured.given().auth().oauth2(getAccessToken("bob")) + .when().get("/api/admin") + .then() + .statusCode(403); + } + + protected String getAccessToken(String userName) { + return keycloakClient.getAccessToken(userName); + } +} +---- + +and the native mode: + +[source,java] +---- +package org.acme.security.openid.connect; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class NativeBearerTokenAuthenticationIT extends BearerTokenAuthenticationTest { +} +---- + +Please see link:security-openid-connect-dev-services[Dev Services for Keycloak] for more information about the way it is initialized and configured. + [[integration-testing-keycloak]] -=== Keycloak +=== KeycloakTestResourceLifecycleManager + +If you need to do the integration testing against Keycloak then you are encouraged to do it with <>. +Use `KeycloakTestResourceLifecycleManager` for your tests only if there is a good reason not to use `Dev Services for Keycloak`. -If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency: +Start with adding the following dependency: [source,xml] ---- @@ -616,7 +704,9 @@ If you work with Keycloak then you can test against a live Keycloak instance by ---- -and configure `maven.surefire.plugin` as follows: +which provides `io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager` - an implementaion of `io.quarkus.test.common.QuarkusTestResourceLifecycleManager` which starts a Keycloak container. + +And configure `maven.surefire.plugin` as follows: [source,xml] ---- @@ -917,5 +1007,5 @@ Note Quarkus `web-app` applications always require `quarkus.oidc.client-id` prop * https://openid.net/connect/[OpenID Connect] * https://tools.ietf.org/html/rfc7519[JSON Web Token] * link:security-openid-connect-client[Quarkus - Using OpenID Connect and OAuth2 Client and Filters to manage access tokens] -* link:security-openid-connect-dev-services[Dev Services for OpenId Connect] +* link:security-openid-connect-dev-services[Dev Services for Keycloak] * link:security[Quarkus Security] diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java index fe82852eafb169..5cb10d6bf605f9 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java @@ -40,15 +40,15 @@ public void testAccessAndRefreshTokenInjectionDevMode() throws IOException, Inte try { webClient.getPage("http://localhost:8080/protected"); - fail("Exception is expected because auth-server-url is not available and the authentication can not be completed"); + fail("Exception is expected because by default the bearer token is required"); } catch (FailingHttpStatusCodeException ex) { // Reported by Quarkus - assertEquals(500, ex.getStatusCode()); + assertEquals(401, ex.getStatusCode()); } - // Enable auth-server-url + // Enable 'web-app' application type test.modifyResourceFile("application.properties", - s -> s.replace("#quarkus.oidc.auth-server-url", "quarkus.oidc.auth-server-url")); + s -> s.replace("#quarkus.oidc.application-type=web-app", "quarkus.oidc.application-type=web-app")); HtmlPage page = webClient.getPage("http://localhost:8080/protected"); diff --git a/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties b/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties index 989e5ebc777c83..2853d435e676f6 100644 --- a/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties +++ b/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties @@ -1,9 +1,6 @@ -#quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus quarkus.oidc.client-id=quarkus-web-app quarkus.oidc.credentials.secret=secret -quarkus.oidc.application-type=web-app - -quarkus.keycloak.devservices.enabled=false +#quarkus.oidc.application-type=web-app quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java index 758e65299401cc..1f025d7793c838 100644 --- a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java +++ b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java @@ -7,7 +7,7 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.keycloak.server.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient; import io.restassured.RestAssured; @QuarkusTest diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestClient.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java similarity index 92% rename from test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestClient.java rename to test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java index f1120c07776b86..5c551dd34c4c05 100644 --- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestClient.java +++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java @@ -1,11 +1,10 @@ -package io.quarkus.test.keycloak.server; +package io.quarkus.test.keycloak.client; import org.eclipse.microprofile.config.ConfigProvider; import org.keycloak.representations.AccessTokenResponse; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.common.DevServicesContext; -import io.quarkus.test.junit.QuarkusIntegrationTest; import io.restassured.RestAssured; public class KeycloakTestClient implements DevServicesContext.ContextAware { @@ -24,10 +23,6 @@ public KeycloakTestClient() { } - public KeycloakTestClient(QuarkusIntegrationTest.Context testContext) { - this.testContext = testContext; - } - public String getAccessToken(String userName) { return getAccessToken(userName, getClientId()); } 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 f64f8cfac0c3b1..dd8c8eb963c436 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 @@ -95,6 +95,7 @@ public Map start() { Map conf = new HashMap<>(); conf.put("keycloak.url", KEYCLOAK_SERVER_URL); + conf.put("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM); return conf; }