diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 5924bfab645b3..9dcd5aebf7359 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -495,6 +495,15 @@ If the access token needs to be refreshed but no refresh token is available then Please note that some OpenID Connect Providers will not return a refresh token in a `client_credentials` grant response. For example, starting from Keycloak 12 a refresh token will not be returned by default for `client_credentials`. The providers may also restrict a number of times a refresh token can be used. +[[revoke-access-tokens]] +=== Revoking Access Tokens + +If your OpenId Connect provider such as Keycloak supports a token revocation endpoint then `OidcClient#revokeAccessToken` can be used to revoke the current access token. The revocation endpoint URL will be discovered alongside the token request URI or can be configured with `quarkus.oidc-client.revoke-path`. + +You may want to have the access token revoked if using this token with a REST client fails with HTTP `401` or the access token has already been used for a long time and you'd like to refresh it. + +This can be achieved by requesting a token refresh using a refresh token. However, if the refresh token is not available then you can refresh it by revoking it first and then request a new access token. + [[oidc-client-authentication]] === OidcClient Authentication diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/OidcClient.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/OidcClient.java index 9b02282f6231c..fd2701cbbe7fb 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/OidcClient.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/OidcClient.java @@ -12,21 +12,35 @@ public interface OidcClient extends Closeable { /** - * Returns the grant tokens + * Get the grant access and refresh tokens. */ default Uni getTokens() { return getTokens(Collections.emptyMap()); } /** - * Returns the grant tokens + * Get the grant access and refresh tokens with additional grant parameters. * * @param additionalGrantParameters additional grant parameters + * @return Uni */ Uni getTokens(Map additionalGrantParameters); /** - * Refreshes the grant tokens + * Refresh and return a new pair of access and refresh tokens. + * Note a refresh token grant will typically return not only a new access token but also a new refresh token. + * + * @param refreshToken refresh token + * @return Uni */ Uni refreshTokens(String refreshToken); + + /** + * Revoke the access token. + * + * @param refreshToken access token which needs to be revoked + * @return Uni true if the token has been revoked or found already being invalidated, + * false if the token can not be currently revoked in which case a revocation request might be retried. + */ + Uni revokeAccessToken(String accessToken); } diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java index 38b7932631229..cbcadb897b430 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java @@ -20,6 +20,7 @@ import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.groups.UniOnItem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.DecodeException; import io.vertx.core.json.JsonObject; @@ -37,6 +38,7 @@ public class OidcClientImpl implements OidcClient { private final WebClient client; private final String tokenRequestUri; + private final String tokenRevokeUri; private final MultiMap tokenGrantParams; private final MultiMap commonRefreshGrantParams; private final String grantType; @@ -45,10 +47,11 @@ public class OidcClientImpl implements OidcClient { private final OidcClientConfig oidcConfig; private volatile boolean closed; - public OidcClientImpl(WebClient client, String tokenRequestUri, String grantType, + public OidcClientImpl(WebClient client, String tokenRequestUri, String tokenRevokeUri, String grantType, MultiMap tokenGrantParams, MultiMap commonRefreshGrantParams, OidcClientConfig oidcClientConfig) { this.client = client; this.tokenRequestUri = tokenRequestUri; + this.tokenRevokeUri = tokenRevokeUri; this.tokenGrantParams = tokenGrantParams; this.commonRefreshGrantParams = commonRefreshGrantParams; this.grantType = grantType; @@ -78,6 +81,32 @@ public Uni refreshTokens(String refreshToken) { return getJsonResponse(refreshGrantParams, Collections.emptyMap(), true); } + @Override + public Uni revokeAccessToken(String accessToken) { + checkClosed(); + if (accessToken == null) { + throw new OidcClientException("Access token is null"); + } + if (tokenRevokeUri != null) { + MultiMap tokenRevokeParams = new MultiMap(io.vertx.core.MultiMap.caseInsensitiveMultiMap()); + tokenRevokeParams.set(OidcConstants.REVOCATION_TOKEN, accessToken); + return postRequest(client.postAbs(tokenRevokeUri), tokenRevokeParams, Map.of(), false) + .transform(resp -> toRevokeResponse(resp)); + } else { + LOG.debugf("%s OidcClient can not revoke the access token because the revocation endpoint URL is not set"); + return Uni.createFrom().item(false); + } + + } + + private Boolean toRevokeResponse(HttpResponse resp) { + // Per RFC7009, 200 is returned if a token has been revoked successfully or if the client submitted an + // invalid token, https://datatracker.ietf.org/doc/html/rfc7009#section-2.2. + // 503 is at least theoretically possible if the OIDC server declines and suggests to Retry-After some period of time. + // However this period of time can be set to unpredictable value. + return resp.statusCode() == 503 ? false : true; + } + private Uni getJsonResponse(MultiMap formBody, Map additionalGrantParameters, boolean refresh) { //Uni needs to be lazy by default, we don't send the request unless //something has subscribed to it. This is important for the CAS state @@ -85,54 +114,64 @@ private Uni getJsonResponse(MultiMap formBody, Map addit return Uni.createFrom().deferred(new Supplier>() { @Override public Uni get() { - MultiMap body = formBody; - HttpRequest request = client.postAbs(tokenRequestUri); - request.putHeader(HttpHeaders.CONTENT_TYPE.toString(), - HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString()); - if (oidcConfig.headers != null) { - for (Map.Entry headerEntry : oidcConfig.headers.entrySet()) { - request.putHeader(headerEntry.getKey(), headerEntry.getValue()); - } - } - if (clientSecretBasicAuthScheme != null) { - request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme); - } else if (clientJwtKey != null) { - // if it is a refresh then a map has already been copied - body = !refresh ? copyMultiMap(body) : body; - String jwt = OidcCommonUtils.signJwtWithKey(oidcConfig, tokenRequestUri, clientJwtKey); - - if (OidcCommonUtils.isClientSecretPostJwtAuthRequired(oidcConfig.credentials)) { - body.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); - body.add(OidcConstants.CLIENT_SECRET, jwt); - } else { - body.add(OidcConstants.CLIENT_ASSERTION_TYPE, OidcConstants.JWT_BEARER_CLIENT_ASSERTION_TYPE); - body.add(OidcConstants.CLIENT_ASSERTION, jwt); - } - } else if (!OidcCommonUtils.isClientSecretPostAuthRequired(oidcConfig.credentials)) { - body = copyMultiMap(body).set(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); - } - if (!additionalGrantParameters.isEmpty()) { - body = copyMultiMap(body); - for (Map.Entry entry : additionalGrantParameters.entrySet()) { - body.add(entry.getKey(), entry.getValue()); - } - } - // Retry up to three times with a one-second delay between the retries if the connection is closed - Uni> response = request.sendBuffer(OidcCommonUtils.encodeForm(body)) - .onFailure(ConnectException.class) - .retry() - .atMost(oidcConfig.connectionRetryCount) - .onFailure().transform(t -> { - LOG.warn("OIDC Server is not available:", t.getCause() != null ? t.getCause() : t); - // don't wrap it to avoid information leak - return new OidcClientException("OIDC Server is not available"); - }); - return response.onItem() + return postRequest(client.postAbs(tokenRequestUri), formBody, additionalGrantParameters, refresh) .transform(resp -> emitGrantTokens(resp, refresh)); } }); } + private UniOnItem> postRequest(HttpRequest request, MultiMap formBody, + Map additionalGrantParameters, + boolean refresh) { + MultiMap body = formBody; + request.putHeader(HttpHeaders.CONTENT_TYPE.toString(), + HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString()); + if (oidcConfig.headers != null) { + for (Map.Entry headerEntry : oidcConfig.headers.entrySet()) { + request.putHeader(headerEntry.getKey(), headerEntry.getValue()); + } + } + if (clientSecretBasicAuthScheme != null) { + request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme); + } else if (clientJwtKey != null) { + // if it is a refresh then a map has already been copied + body = !refresh ? copyMultiMap(body) : body; + String jwt = OidcCommonUtils.signJwtWithKey(oidcConfig, tokenRequestUri, clientJwtKey); + + if (OidcCommonUtils.isClientSecretPostJwtAuthRequired(oidcConfig.credentials)) { + body.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + body.add(OidcConstants.CLIENT_SECRET, jwt); + } else { + body.add(OidcConstants.CLIENT_ASSERTION_TYPE, OidcConstants.JWT_BEARER_CLIENT_ASSERTION_TYPE); + body.add(OidcConstants.CLIENT_ASSERTION, jwt); + } + } else if (OidcCommonUtils.isClientSecretPostAuthRequired(oidcConfig.credentials)) { + body = !refresh ? copyMultiMap(body) : body; + body.set(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + body.set(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(oidcConfig.credentials)); + } else { + body = !refresh ? copyMultiMap(body) : body; + body = copyMultiMap(body).set(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); + } + if (!additionalGrantParameters.isEmpty()) { + body = copyMultiMap(body); + for (Map.Entry entry : additionalGrantParameters.entrySet()) { + body.add(entry.getKey(), entry.getValue()); + } + } + // Retry up to three times with a one-second delay between the retries if the connection is closed + Uni> response = request.sendBuffer(OidcCommonUtils.encodeForm(body)) + .onFailure(ConnectException.class) + .retry() + .atMost(oidcConfig.connectionRetryCount) + .onFailure().transform(t -> { + LOG.warn("OIDC Server is not available:", t.getCause() != null ? t.getCause() : t); + // don't wrap it to avoid information leak + return new OidcClientException("OIDC Server is not available"); + }); + return response.onItem(); + } + private Tokens emitGrantTokens(HttpResponse resp, boolean refresh) { if (resp.statusCode() == 200) { LOG.debugf("%s OidcClient has %s the tokens", oidcConfig.getId().get(), (refresh ? "refreshed" : "acquired")); diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java index 9f8e56561ee16..4d3015c436322 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java @@ -16,7 +16,6 @@ import io.quarkus.oidc.client.OidcClientException; import io.quarkus.oidc.client.OidcClients; import io.quarkus.oidc.client.Tokens; -import io.quarkus.oidc.common.runtime.OidcCommonConfig.Credentials; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.runtime.TlsConfig; @@ -121,28 +120,32 @@ protected static Uni createOidcClientUni(OidcClientConfig oidcConfig WebClient client = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx.get()), options); - Uni tokenRequestUriUni = null; + Uni tokenUrisUni = null; if (OidcCommonUtils.isAbsoluteUrl(oidcConfig.tokenPath)) { - tokenRequestUriUni = Uni.createFrom().item(oidcConfig.tokenPath.get()); + tokenUrisUni = Uni.createFrom().item( + new OidcConfigurationMetadata(oidcConfig.tokenPath.get(), + OidcCommonUtils.isAbsoluteUrl(oidcConfig.revokePath) ? oidcConfig.revokePath.get() : null)); } else { String authServerUriString = OidcCommonUtils.getAuthServerUrl(oidcConfig); if (!oidcConfig.discoveryEnabled.orElse(true)) { - tokenRequestUriUni = Uni.createFrom() - .item(OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath)); + tokenUrisUni = Uni.createFrom() + .item(new OidcConfigurationMetadata( + OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.tokenPath), + OidcCommonUtils.getOidcEndpointUrl(authServerUriString, oidcConfig.revokePath))); } else { - tokenRequestUriUni = discoverTokenRequestUri(client, authServerUriString.toString(), oidcConfig); + tokenUrisUni = discoverTokenUris(client, authServerUriString.toString(), oidcConfig); } } - return tokenRequestUriUni.onItemOrFailure() - .transform(new BiFunction() { + return tokenUrisUni.onItemOrFailure() + .transform(new BiFunction() { @Override - public OidcClient apply(String tokenRequestUri, Throwable t) { + public OidcClient apply(OidcConfigurationMetadata metadata, Throwable t) { if (t != null) { throw toOidcClientException(getEndpointUrl(oidcConfig), t); } - if (tokenRequestUri == null) { + if (metadata.tokenRequestUri == null) { throw new ConfigurationException( "OpenId Connect Provider token endpoint URL is not configured and can not be discovered"); } @@ -182,7 +185,8 @@ public OidcClient apply(String tokenRequestUri, Throwable t) { MultiMap commonRefreshGrantParams = new MultiMap(io.vertx.core.MultiMap.caseInsensitiveMultiMap()); setGrantClientParams(oidcConfig, commonRefreshGrantParams, OidcConstants.REFRESH_TOKEN_GRANT); - return new OidcClientImpl(client, tokenRequestUri, grantType, tokenGrantParams, + return new OidcClientImpl(client, metadata.tokenRequestUri, metadata.tokenRevokeUri, grantType, + tokenGrantParams, commonRefreshGrantParams, oidcConfig); } @@ -196,20 +200,17 @@ private static String getEndpointUrl(OidcClientConfig oidcConfig) { private static void setGrantClientParams(OidcClientConfig oidcConfig, MultiMap grantParams, String grantType) { grantParams.add(OidcConstants.GRANT_TYPE, grantType); - Credentials creds = oidcConfig.getCredentials(); - if (OidcCommonUtils.isClientSecretPostAuthRequired(creds)) { - grantParams.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get()); - grantParams.add(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(creds)); - } if (oidcConfig.getScopes().isPresent()) { grantParams.add(OidcConstants.TOKEN_SCOPE, oidcConfig.getScopes().get().stream().collect(Collectors.joining(" "))); } } - private static Uni discoverTokenRequestUri(WebClient client, String authServerUrl, OidcClientConfig oidcConfig) { + private static Uni discoverTokenUris(WebClient client, String authServerUrl, + OidcClientConfig oidcConfig) { final long connectionDelayInMillisecs = OidcCommonUtils.getConnectionDelayInMillis(oidcConfig); return OidcCommonUtils.discoverMetadata(client, authServerUrl, connectionDelayInMillisecs) - .onItem().transform(json -> json.getString("token_endpoint")); + .onItem().transform(json -> new OidcConfigurationMetadata(json.getString("token_endpoint"), + json.getString("revocation_endpoint"))); } protected static OidcClientException toOidcClientException(String authServerUrlString, Throwable cause) { @@ -233,8 +234,24 @@ public Uni refreshTokens(String refreshToken) { throw new DisabledOidcClientException(message); } + @Override + public Uni revokeAccessToken(String accessToken) { + throw new DisabledOidcClientException(message); + } + @Override public void close() throws IOException { } + + } + + private static class OidcConfigurationMetadata { + private final String tokenRequestUri; + private final String tokenRevokeUri; + + OidcConfigurationMetadata(String tokenRequestUri, String tokenRevokeUri) { + this.tokenRequestUri = tokenRequestUri; + this.tokenRevokeUri = tokenRevokeUri; + } } } 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 f06ff3754b26b..f3fcf81a68dd6 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 @@ -32,6 +32,12 @@ public class OidcCommonConfig { @ConfigItem public Optional tokenPath = Optional.empty(); + /** + * Relative path or absolute URL of the OIDC token revocation endpoint. + */ + @ConfigItem + public Optional revokePath = Optional.empty(); + /** * The client-id of the application. Each application has a client-id that is used to identify the application */ @@ -605,6 +611,14 @@ public void setTokenPath(String tokenPath) { this.tokenPath = Optional.of(tokenPath); } + public Optional getRevokePath() { + return revokePath; + } + + public void setRevokePath(String revokePath) { + this.revokePath = Optional.of(revokePath); + } + public Optional getClientId() { return clientId; } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java index 743d21274645c..1b14467645b86 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java @@ -27,6 +27,8 @@ public final class OidcConstants { public static final String INTROSPECTION_TOKEN_USERNAME = "username"; public static final String INTROSPECTION_TOKEN_SUB = "sub"; + public static final String REVOCATION_TOKEN = "token"; + public static final String PASSWORD_GRANT_USERNAME = "username"; public static final String PASSWORD_GRANT_PASSWORD = "password"; diff --git a/integration-tests/oidc-tenancy/pom.xml b/integration-tests/oidc-tenancy/pom.xml index dbf58957e44cc..9a098bfaab69c 100644 --- a/integration-tests/oidc-tenancy/pom.xml +++ b/integration-tests/oidc-tenancy/pom.xml @@ -22,6 +22,10 @@ io.quarkus quarkus-oidc + + io.quarkus + quarkus-oidc-client + io.quarkus quarkus-smallrye-jwt-build @@ -102,6 +106,19 @@ + + io.quarkus + quarkus-oidc-client-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-resteasy-jackson-deployment diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java index 36d3e48e65e57..6b7c4d98acd93 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/OidcResource.java @@ -36,6 +36,7 @@ public class OidcResource { private volatile boolean rotate; private volatile int jwkEndpointCallCount; private volatile int introspectionEndpointCallCount; + private volatile int revokeEndpointCallCount; private volatile int userInfoEndpointCallCount; private volatile boolean enableDiscovery = true; @@ -57,6 +58,7 @@ public String discovery() { " \"token_endpoint\":" + "\"" + baseUri + "/token\"," + " \"introspection_endpoint\":" + "\"" + baseUri + "/introspect\"," + " \"userinfo_endpoint\":" + "\"" + baseUri + "/userinfo\"," + + " \"revocation_endpoint\":" + "\"" + baseUri + "/revoke\"," + " \"jwks_uri\":" + "\"" + baseUri + "/jwks\"" + " }"; } else { @@ -143,6 +145,27 @@ public String introspect(@FormParam("client_id") String clientId, @FormParam("cl " }"; } + @GET + @Path("revoke-endpoint-call-count") + public int revokeEndpointCallCount() { + return revokeEndpointCallCount; + } + + @POST + @Path("revoke-endpoint-call-count") + public int resetRevokeEndpointCallCount() { + revokeEndpointCallCount = 0; + return revokeEndpointCallCount; + } + + @POST + @Path("revoke") + public void revoke(@FormParam("token") String token) throws Exception { + if (token != null) { + revokeEndpointCallCount++; + } + } + @GET @Path("userinfo-endpoint-call-count") public int userInfoEndpointCallCount() { diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/TenantResource.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/TenantResource.java index 1b9866f6aebca..becea4057a791 100644 --- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/TenantResource.java +++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/TenantResource.java @@ -17,6 +17,8 @@ import io.quarkus.oidc.OidcSession; import io.quarkus.oidc.TokenIntrospection; import io.quarkus.oidc.UserInfo; +import io.quarkus.oidc.client.OidcClientConfig; +import io.quarkus.oidc.client.OidcClients; import io.quarkus.security.identity.SecurityIdentity; import io.vertx.ext.web.RoutingContext; @@ -42,12 +44,15 @@ public class TenantResource { @Inject OidcSession oidcSession; + @Inject + OidcClients oidcClients; + @Inject RoutingContext routingContext; @GET @RolesAllowed("user") - public String userNameService(@PathParam("tenant") String tenant) { + public String userNameService(@PathParam("tenant") String tenant, @QueryParam("revoke") boolean revokeToken) { if (tenant.startsWith("tenant-web-app")) { throw new OIDCException("Wrong tenant"); } @@ -71,6 +76,19 @@ public String userNameService(@PathParam("tenant") String tenant) { response += (",active:" + introspection.getBoolean("active")); response += (",cache-size:" + tokenCache.getCacheSize()); } + + if (revokeToken) { + OidcClientConfig oidcClientConfig = new OidcClientConfig(); + oidcClientConfig.setClientId("client"); + oidcClientConfig.setId("clientId"); + oidcClientConfig.setTokenPath("http://localhost:8081/oidc/token"); + oidcClientConfig.setRevokePath("http://localhost:8081/oidc/revoke"); + + oidcClients.newClient(oidcClientConfig) + .chain(oidcClient -> oidcClient.revokeAccessToken(accessTokenCred.getToken())).await().indefinitely(); + + } + return response; } @@ -78,7 +96,7 @@ public String userNameService(@PathParam("tenant") String tenant) { @RolesAllowed("user") @Path("no-discovery") public String userNameServiceNoDiscovery(@PathParam("tenant") String tenant) { - return userNameService(tenant); + return userNameService(tenant, false); } @GET diff --git a/integration-tests/oidc-tenancy/src/main/resources/application.properties b/integration-tests/oidc-tenancy/src/main/resources/application.properties index b23930d586a2c..cc941e1a29ebb 100644 --- a/integration-tests/oidc-tenancy/src/main/resources/application.properties +++ b/integration-tests/oidc-tenancy/src/main/resources/application.properties @@ -8,6 +8,9 @@ quarkus.oidc.client-id=quarkus-app-a quarkus.oidc.credentials.secret=secret quarkus.oidc.application-type=service +# Oidc Client +quarkus.test.native-image-profile=test + # Tenant B quarkus.oidc.tenant-b.auth-server-url=${keycloak.url}/realms/quarkus-b quarkus.oidc.tenant-b.client-id=quarkus-app-b diff --git a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index 191ac584630fa..b480351c97ee9 100644 --- a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -298,6 +298,7 @@ public void testDefaultTenant() { public void testSimpleOidcJwtWithJwkRefresh() { RestAssured.when().post("/oidc/jwk-endpoint-call-count").then().body(equalTo("0")); RestAssured.when().post("/oidc/introspection-endpoint-call-count").then().body(equalTo("0")); + RestAssured.when().post("/oidc/revoke-endpoint-call-count").then().body(equalTo("0")); RestAssured.when().post("/oidc/disable-introspection").then().body(equalTo("false")); RestAssured.when().post("/oidc/disable-discovery").then().body(equalTo("false")); // Quarkus OIDC is initialized with JWK set with kid '1' as part of the discovery process @@ -322,7 +323,7 @@ public void testSimpleOidcJwtWithJwkRefresh() { RestAssured.when().post("/oidc/enable-introspection").then().body(equalTo("true")); // No timeout is required RestAssured.given().auth().oauth2(getAccessTokenFromSimpleOidc("3")) - .when().get("/tenant/tenant-oidc/api/user") + .when().get("/tenant/tenant-oidc/api/user?revoke=true") .then() .statusCode(200) .body(equalTo("tenant-oidc:alice")); @@ -340,6 +341,7 @@ public void testSimpleOidcJwtWithJwkRefresh() { RestAssured.when().get("/oidc/jwk-endpoint-call-count").then().body(equalTo("2")); // both requests with kid `3` and with the opaque token required the remote introspection RestAssured.when().get("/oidc/introspection-endpoint-call-count").then().body(equalTo("3")); + RestAssured.when().get("/oidc/revoke-endpoint-call-count").then().body(equalTo("1")); RestAssured.when().post("/oidc/disable-introspection").then().body(equalTo("false")); RestAssured.when().post("/oidc/enable-discovery").then().body(equalTo("true")); RestAssured.when().post("/oidc/disable-rotate").then().body(equalTo("false"));