diff --git a/README.md b/README.md index 998b10406..53a6a6142 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ Restrictions are defined using common annotations (`@RolesAllowed` etc.). A simple Keycloak realm with 1 client (protected application), 2 users and 2 roles is provided in `test-realm.json`. -### `security/keycloak-authz` +### `security/keycloak-authz-classic` Verifies token-based authn and URL-based authz. Authentication is OIDC, and Keycloak is used for issuing and verifying tokens. @@ -324,6 +324,14 @@ Authorization is based on URL patterns, and Keycloak is used for defining and en A simple Keycloak realm with 1 client (protected application), 2 users, 2 roles and 2 protected resources is provided in `test-realm.json`. +### `security/keycloak-authz-reactive` +QUARKUS-1257 - Verifies authenticated endpoints with a generic body in parent class +Verifies token-based authn and URL-based authz. +Authentication is OIDC, and Keycloak is used for issuing and verifying tokens. +Authorization is based on URL patterns, and Keycloak is used for defining and enforcing restrictions. + +A simple Keycloak realm with 1 client (protected application), 2 users, 2 roles and 3 protected resources is provided in `test-realm.json`. + ### `security/keycloak-webapp` Verifies authorization code flow and role-based authentication to protect web applications. diff --git a/pom.xml b/pom.xml index 04687d7a7..6083d5997 100644 --- a/pom.xml +++ b/pom.xml @@ -397,7 +397,8 @@ security/https security/jwt security/keycloak - security/keycloak-authz + security/keycloak-authz-classic + security/keycloak-authz-reactive security/keycloak-jwt security/keycloak-webapp security/keycloak-oauth2 diff --git a/security/keycloak-authz/pom.xml b/security/keycloak-authz-classic/pom.xml similarity index 96% rename from security/keycloak-authz/pom.xml rename to security/keycloak-authz-classic/pom.xml index 44725ed0e..a057ce829 100644 --- a/security/keycloak-authz/pom.xml +++ b/security/keycloak-authz-classic/pom.xml @@ -9,7 +9,7 @@ security-keycloak-authz jar - Quarkus QE TS: Security: Keycloak + Authorization + Quarkus QE TS: Security: Keycloak + Authorization + Classic io.quarkus diff --git a/security/keycloak-authz/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java b/security/keycloak-authz-classic/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java similarity index 100% rename from security/keycloak-authz/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java rename to security/keycloak-authz-classic/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java diff --git a/security/keycloak-authz/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java b/security/keycloak-authz-classic/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java similarity index 100% rename from security/keycloak-authz/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java rename to security/keycloak-authz-classic/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java diff --git a/security/keycloak-authz/src/main/resources/application.properties b/security/keycloak-authz-classic/src/main/resources/application.properties similarity index 100% rename from security/keycloak-authz/src/main/resources/application.properties rename to security/keycloak-authz-classic/src/main/resources/application.properties diff --git a/security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java b/security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java similarity index 100% rename from security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java rename to security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java diff --git a/security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java b/security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java similarity index 100% rename from security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java rename to security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java diff --git a/security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java b/security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java similarity index 100% rename from security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java rename to security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java diff --git a/security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java b/security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java similarity index 100% rename from security/keycloak-authz/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java rename to security/keycloak-authz-classic/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java diff --git a/security/keycloak-authz/src/test/resources/keycloak-realm.json b/security/keycloak-authz-classic/src/test/resources/keycloak-realm.json similarity index 100% rename from security/keycloak-authz/src/test/resources/keycloak-realm.json rename to security/keycloak-authz-classic/src/test/resources/keycloak-realm.json diff --git a/security/keycloak-authz/src/test/resources/test.properties b/security/keycloak-authz-classic/src/test/resources/test.properties similarity index 100% rename from security/keycloak-authz/src/test/resources/test.properties rename to security/keycloak-authz-classic/src/test/resources/test.properties diff --git a/security/keycloak-authz-reactive/pom.xml b/security/keycloak-authz-reactive/pom.xml new file mode 100644 index 000000000..4c2254083 --- /dev/null +++ b/security/keycloak-authz-reactive/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + io.quarkus.ts.qe + parent + 1.0.0-SNAPSHOT + ../.. + + security-keycloak-authz-reactive + jar + Quarkus QE TS: Security: Keycloak + Authorization + Reactive + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-keycloak-authorization + + + org.apache.httpcomponents + httpclient + test + + + io.quarkus.qe + quarkus-test-service-keycloak + test + + + + + + skip-tests-on-windows + + + windows + + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + + + + diff --git a/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java new file mode 100644 index 000000000..01e99f8e8 --- /dev/null +++ b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/AdminResource.java @@ -0,0 +1,34 @@ +package io.quarkus.ts.security.keycloak.authz; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.security.identity.SecurityIdentity; +import io.smallrye.mutiny.Uni; + +@Path("/admin") +public class AdminResource { + @Inject + SecurityIdentity identity; + + @Inject + JsonWebToken jwt; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Uni get() { + return Uni.createFrom().item("Hello, admin " + identity.getPrincipal().getName()); + } + + @GET + @Path("/issuer") + @Produces(MediaType.TEXT_PLAIN) + public Uni issuer() { + return Uni.createFrom().item("admin token issued by " + jwt.getIssuer()); + } +} diff --git a/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserAdvancedResource.java b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserAdvancedResource.java new file mode 100644 index 000000000..8a11bea57 --- /dev/null +++ b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserAdvancedResource.java @@ -0,0 +1,16 @@ +package io.quarkus.ts.security.keycloak.authz; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import io.quarkus.security.Authenticated; +import io.smallrye.mutiny.Uni; + +@Path("/user-details") +@Authenticated +public class UserAdvancedResource extends UserDetailsResource { + @GET + public Uni info() { + return Uni.createFrom().item(identity.getPrincipal().getName()); + } +} diff --git a/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserDetailsResource.java b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserDetailsResource.java new file mode 100644 index 000000000..106ead273 --- /dev/null +++ b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserDetailsResource.java @@ -0,0 +1,33 @@ +package io.quarkus.ts.security.keycloak.authz; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.quarkus.security.identity.SecurityIdentity; +import io.smallrye.mutiny.Uni; + +public class UserDetailsResource { + + @Inject + SecurityIdentity identity; + + @Path("/advanced") + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN) + public Uni getUserName(T body) { + return Uni.createFrom().item(identity.getPrincipal().getName()); + } + + @Path("/advanced-specific") + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN) + public Uni WebAuthnSpecific(String body) { + return Uni.createFrom().item(identity.getPrincipal().getName()); + } +} diff --git a/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java new file mode 100644 index 000000000..4270e15c3 --- /dev/null +++ b/security/keycloak-authz-reactive/src/main/java/io/quarkus/ts/security/keycloak/authz/UserResource.java @@ -0,0 +1,34 @@ +package io.quarkus.ts.security.keycloak.authz; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.security.identity.SecurityIdentity; +import io.smallrye.mutiny.Uni; + +@Path("/user") +public class UserResource { + @Inject + SecurityIdentity identity; + + @Inject + JsonWebToken jwt; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Uni get() { + return Uni.createFrom().item("Hello, user " + identity.getPrincipal().getName()); + } + + @GET + @Path("/issuer") + @Produces(MediaType.TEXT_PLAIN) + public Uni issuer() { + return Uni.createFrom().item("user token issued by " + jwt.getIssuer()); + } +} diff --git a/security/keycloak-authz-reactive/src/main/resources/application.properties b/security/keycloak-authz-reactive/src/main/resources/application.properties new file mode 100644 index 000000000..dcad830d4 --- /dev/null +++ b/security/keycloak-authz-reactive/src/main/resources/application.properties @@ -0,0 +1,8 @@ +quarkus.oidc.auth-server-url=${KEYCLOAK_HTTP_URL:http://localhost:8180}/auth/realms/test-realm +quarkus.oidc.client-id=test-application-client +quarkus.oidc.credentials.secret=test-application-client-secret +# tolerate 1 minute of clock skew between the Keycloak server and the application +quarkus.oidc.token.lifespan-grace=60 +quarkus.keycloak.policy-enforcer.enable=true +quarkus.keycloak.policy-enforcer.paths.health.path=/q/* +quarkus.keycloak.policy-enforcer.paths.health.enforcement-mode=DISABLED diff --git a/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java new file mode 100644 index 000000000..9f8f9756f --- /dev/null +++ b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/BaseAuthzSecurityIT.java @@ -0,0 +1,148 @@ +package io.quarkus.ts.security.keycloak.authz; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; +import org.keycloak.authorization.client.AuthzClient; + +import io.quarkus.test.bootstrap.KeycloakService; +import io.quarkus.test.bootstrap.RestService; +import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; +import io.vertx.core.http.HttpMethod; +import io.vertx.mutiny.core.buffer.Buffer; +import io.vertx.mutiny.ext.web.client.HttpRequest; +import io.vertx.mutiny.ext.web.client.HttpResponse; + +public abstract class BaseAuthzSecurityIT { + + static final String NORMAL_USER = "test-normal-user"; + static final String ADMIN_USER = "test-admin-user"; + static final String REALM_DEFAULT = "test-realm"; + static final String CLIENT_ID_DEFAULT = "test-application-client"; + static final String CLIENT_SECRET_DEFAULT = "test-application-client-secret"; + + private AuthzClient authzClient; + private UniAssertSubscriber> response; + + @BeforeEach + public void setup() { + authzClient = getKeycloak().createAuthzClient(CLIENT_ID_DEFAULT, CLIENT_SECRET_DEFAULT); + } + + @Tag("QUARKUS-1257") + @Test + public void genericAndExtendedSecuredEndpointShouldResponseOk() { + String bearerToken = getToken(NORMAL_USER, NORMAL_USER); + + whenMakeRequestTo(HttpMethod.GET, "/user-details", bearerToken); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyIs(NORMAL_USER); + + whenMakeRequestTo(HttpMethod.POST, "/user-details/advanced-specific", bearerToken); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyIs(NORMAL_USER); + + whenMakeRequestTo(HttpMethod.POST, "/user-details/advanced", bearerToken); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyIs(NORMAL_USER); + } + + @Test + public void normalUserUserResource() { + whenMakeRequestTo(HttpMethod.GET, "/user", getToken(NORMAL_USER, NORMAL_USER)); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyIs("Hello, user " + NORMAL_USER); + } + + @Test + public void normalUserUserResourceIssuer() { + whenMakeRequestTo(HttpMethod.GET, "/user/issuer", getToken(NORMAL_USER, NORMAL_USER)); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyStartWith("user token issued by " + getKeycloak().getHost()); + } + + @Test + public void normalUserAdminResource() { + whenMakeRequestTo(HttpMethod.GET, "/admin", getToken(NORMAL_USER, NORMAL_USER)); + thenStatusCodeIs(HttpStatus.SC_FORBIDDEN); + } + + @Test + public void adminUserUserResource() { + whenMakeRequestTo(HttpMethod.GET, "/user", getToken(ADMIN_USER, ADMIN_USER)); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyIs("Hello, user " + ADMIN_USER); + } + + @Test + public void adminUserAdminResource() { + whenMakeRequestTo(HttpMethod.GET, "/admin", getToken(ADMIN_USER, ADMIN_USER)); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyIs("Hello, admin " + ADMIN_USER); + } + + @Test + public void adminUserAdminResourceIssuer() { + whenMakeRequestTo(HttpMethod.GET, "/admin/issuer", getToken(ADMIN_USER, ADMIN_USER)); + thenStatusCodeIs(HttpStatus.SC_OK); + thenBodyStartWith("admin token issued by " + getKeycloak().getHost()); + } + + @Test + public void noUserUserResource() { + whenMakeRequestTo(HttpMethod.GET, "/user", ""); + thenStatusCodeIs(HttpStatus.SC_UNAUTHORIZED); + } + + @Test + public void noUserAdminResource() { + whenMakeRequestTo(HttpMethod.GET, "/admin", ""); + thenStatusCodeIs(HttpStatus.SC_UNAUTHORIZED); + } + + private void thenBodyIs(String expectedBody) { + String bodyAsString = response.await().assertCompleted().getItem().bodyAsString(); + assertThat(expectedBody, equalTo(bodyAsString)); + } + + private void thenBodyStartWith(String expectedBody) { + String bodyAsString = response.await().assertCompleted().getItem().bodyAsString(); + assertThat(bodyAsString, startsWith(expectedBody)); + } + + private void thenStatusCodeIs(int expectedCode) { + int statusCode = response.await().assertCompleted().getItem().statusCode(); + assertThat(expectedCode, equalTo(statusCode)); + } + + private void whenMakeRequestTo(HttpMethod method, String path, String bearerToken) { + HttpRequest req = createHttpRequest(method, path); + if (StringUtils.isNotBlank(bearerToken)) { + addHttpRequestBearerToken(req, bearerToken); + } + + response = req.send().subscribe().withSubscriber(UniAssertSubscriber.create()); + } + + private HttpRequest createHttpRequest(HttpMethod method, String path) { + return getApp().mutiny().request(method, path); + } + + private HttpRequest addHttpRequestBearerToken(HttpRequest req, String token) { + return req.bearerTokenAuthentication(token); + } + + protected abstract KeycloakService getKeycloak(); + + protected abstract RestService getApp(); + + private String getToken(String userName, String password) { + return authzClient.obtainAccessToken(userName, password).getToken(); + } +} diff --git a/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java new file mode 100644 index 000000000..75647b2b6 --- /dev/null +++ b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/KeycloakAuthzSecurityIT.java @@ -0,0 +1,32 @@ +package io.quarkus.ts.security.keycloak.authz; + +import io.quarkus.test.bootstrap.KeycloakService; +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.services.Container; +import io.quarkus.test.services.QuarkusApplication; + +@QuarkusScenario +public class KeycloakAuthzSecurityIT extends BaseAuthzSecurityIT { + + static final int KEYCLOAK_PORT = 8080; + + @Container(image = "${keycloak.image}", expectedLog = "Http management interface listening", port = KEYCLOAK_PORT) + static KeycloakService keycloak = new KeycloakService("/keycloak-realm.json", REALM_DEFAULT); + + @QuarkusApplication + static RestService app = new RestService() + .withProperty("quarkus.oidc.auth-server-url", () -> keycloak.getRealmUrl()) + .withProperty("quarkus.oidc.client-id", CLIENT_ID_DEFAULT) + .withProperty("quarkus.oidc.credentials.secret", CLIENT_SECRET_DEFAULT); + + @Override + protected KeycloakService getKeycloak() { + return keycloak; + } + + @Override + protected RestService getApp() { + return app; + } +} diff --git a/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java new file mode 100644 index 000000000..5f930a954 --- /dev/null +++ b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso73AuthzSecurityIT.java @@ -0,0 +1,33 @@ +package io.quarkus.ts.security.keycloak.authz; + +import io.quarkus.test.bootstrap.KeycloakService; +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.OpenShiftScenario; +import io.quarkus.test.services.Container; +import io.quarkus.test.services.QuarkusApplication; + +@OpenShiftScenario +public class OpenShiftRhSso73AuthzSecurityIT extends BaseAuthzSecurityIT { + + static final int KEYCLOAK_PORT = 8080; + + @Container(image = "${rhsso.73.image}", expectedLog = "Http management interface listening", port = KEYCLOAK_PORT) + static KeycloakService keycloak = new KeycloakService(REALM_DEFAULT) + .withProperty("SSO_IMPORT_FILE", "resource::/keycloak-realm.json"); + + @QuarkusApplication + static RestService app = new RestService() + .withProperty("quarkus.oidc.auth-server-url", () -> keycloak.getRealmUrl()) + .withProperty("quarkus.oidc.client-id", CLIENT_ID_DEFAULT) + .withProperty("quarkus.oidc.credentials.secret", CLIENT_SECRET_DEFAULT); + + @Override + protected KeycloakService getKeycloak() { + return keycloak; + } + + @Override + protected RestService getApp() { + return app; + } +} diff --git a/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java new file mode 100644 index 000000000..1851436b7 --- /dev/null +++ b/security/keycloak-authz-reactive/src/test/java/io/quarkus/ts/security/keycloak/authz/OpenShiftRhSso74AuthzSecurityIT.java @@ -0,0 +1,36 @@ +package io.quarkus.ts.security.keycloak.authz; + +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + +import io.quarkus.test.bootstrap.KeycloakService; +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.OpenShiftScenario; +import io.quarkus.test.services.Container; +import io.quarkus.test.services.QuarkusApplication; + +@OpenShiftScenario +@EnabledIfSystemProperty(named = "ts.redhat.registry.enabled", matches = "true") +public class OpenShiftRhSso74AuthzSecurityIT extends BaseAuthzSecurityIT { + + static final int KEYCLOAK_PORT = 8080; + + @Container(image = "${rhsso.74.image}", expectedLog = "Http management interface listening", port = KEYCLOAK_PORT) + static KeycloakService keycloak = new KeycloakService(REALM_DEFAULT) + .withProperty("SSO_IMPORT_FILE", "resource::/keycloak-realm.json"); + + @QuarkusApplication + static RestService app = new RestService() + .withProperty("quarkus.oidc.auth-server-url", () -> keycloak.getRealmUrl()) + .withProperty("quarkus.oidc.client-id", CLIENT_ID_DEFAULT) + .withProperty("quarkus.oidc.credentials.secret", CLIENT_SECRET_DEFAULT); + + @Override + protected KeycloakService getKeycloak() { + return keycloak; + } + + @Override + protected RestService getApp() { + return app; + } +} diff --git a/security/keycloak-authz-reactive/src/test/resources/keycloak-realm.json b/security/keycloak-authz-reactive/src/test/resources/keycloak-realm.json new file mode 100644 index 000000000..b37fd1f0f --- /dev/null +++ b/security/keycloak-authz-reactive/src/test/resources/keycloak-realm.json @@ -0,0 +1,105 @@ +{ + "realm": "test-realm", + "enabled": true, + "sslRequired": "none", + "roles": { + "realm": [ + { + "name": "test-user-role" + }, + { + "name": "test-admin-role" + } + ] + }, + "users": [ + { + "username": "test-normal-user", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "test-normal-user" + } + ], + "realmRoles": [ + "test-user-role" + ] + }, + { + "username": "test-admin-user", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "test-admin-user" + } + ], + "realmRoles": [ + "test-admin-role", + "test-user-role" + ] + } + ], + "clients": [ + { + "clientId": "test-application-client", + "enabled": true, + "protocol": "openid-connect", + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "test-application-client-secret", + "authorizationSettings": { + "resources": [ + { + "name": "test-user-resource", + "uris": [ + "/user/*", + "/user-details/*" + ] + }, + { + "name": "test-admin-resource", + "uris": [ + "/admin/*" + ] + } + ], + "policies": [ + { + "name": "test-user-policy", + "type": "role", + "config": { + "roles": "[{\"id\":\"test-user-role\",\"required\":true}]" + } + }, + { + "name": "test-admin-policy", + "type": "role", + "config": { + "roles": "[{\"id\":\"test-admin-role\",\"required\":true}]" + } + }, + { + "name": "test-user-permission", + "type": "resource", + "config": { + "resources": "[\"test-user-resource\"]", + "applyPolicies": "[\"test-user-policy\"]" + } + }, + { + "name": "test-admin-permission", + "type": "resource", + "config": { + "resources": "[\"test-admin-resource\"]", + "applyPolicies": "[\"test-admin-policy\"]" + } + } + ] + } + } + ] +} diff --git a/security/keycloak-authz-reactive/src/test/resources/test.properties b/security/keycloak-authz-reactive/src/test/resources/test.properties new file mode 100644 index 000000000..66b0dc508 --- /dev/null +++ b/security/keycloak-authz-reactive/src/test/resources/test.properties @@ -0,0 +1,2 @@ +ts.keycloak.log.enable=true +ts.app.log.enable=true