Repository: quarkusio/quarkus
Issue: OIDC will request UserInfo and TokenIntrospection even if they are cached

### Describe the bug

Users have discovered that even if OIDC caches UserInfo it will still request UserInfo remotely, thus making cache ineffective as far as the performance savings are concerned. Same applies to caching TokenIntrospection

### Expected behavior

If UserInfo is required and the cache is enabled then repeatedly calling the Quarkus endpoint with the same token should result in a single remote UserInfo call only until the cache entry has expired or the cache limit has been reached.

### Actual behavior

_No response_

### How to Reproduce?

_No response_

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

_No response_

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

_No response_

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

_No response_

### Additional information

_No response_

Changed files:
integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java

diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
index 2202a7cc74a..5d61792865d 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
@@ -447,7 +447,7 @@ private Uni<TokenVerificationResult> refreshJwksAndVerifyTokenUni(TenantConfigCo
                 .recoverWithUni(f -> introspectTokenUni(resolvedContext, token));
     }
 
-    private Uni<TokenVerificationResult> introspectTokenUni(TenantConfigContext resolvedContext, String token) {
+    private Uni<TokenVerificationResult> introspectTokenUni(TenantConfigContext resolvedContext, final String token) {
         TokenIntrospectionCache tokenIntrospectionCache = tenantResolver.getTokenIntrospectionCache();
         Uni<TokenIntrospection> tokenIntrospectionUni = tokenIntrospectionCache == null ? null
                 : tokenIntrospectionCache
@@ -456,7 +456,12 @@ private Uni<TokenVerificationResult> introspectTokenUni(TenantConfigContext reso
             tokenIntrospectionUni = newTokenIntrospectionUni(resolvedContext, token);
         } else {
             tokenIntrospectionUni = tokenIntrospectionUni.onItem().ifNull()
-                    .switchTo(newTokenIntrospectionUni(resolvedContext, token));
+                    .switchTo(new Supplier<Uni<? extends TokenIntrospection>>() {
+                        @Override
+                        public Uni<TokenIntrospection> get() {
+                            return newTokenIntrospectionUni(resolvedContext, token);
+                        }
+                    });
         }
         return tokenIntrospectionUni.onItem().transform(t -> new TokenVerificationResult(null, t));
     }
@@ -501,10 +506,8 @@ private Uni<UserInfo> getUserInfoUni(RoutingContext vertxContext, TokenAuthentic
         }
 
         LOG.debug("Requesting UserInfo");
-        String accessToken = vertxContext.get(OidcConstants.ACCESS_TOKEN_VALUE);
-        if (accessToken == null) {
-            accessToken = request.getToken().getToken();
-        }
+        String contextAccessToken = vertxContext.get(OidcConstants.ACCESS_TOKEN_VALUE);
+        final String accessToken = contextAccessToken != null ? contextAccessToken : request.getToken().getToken();
 
         UserInfoCache userInfoCache = tenantResolver.getUserInfoCache();
         Uni<UserInfo> userInfoUni = userInfoCache == null ? null
@@ -513,7 +516,12 @@ private Uni<UserInfo> getUserInfoUni(RoutingContext vertxContext, TokenAuthentic
             userInfoUni = newUserInfoUni(resolvedContext, accessToken);
         } else {
             userInfoUni = userInfoUni.onItem().ifNull()
-                    .switchTo(newUserInfoUni(resolvedContext, accessToken));
+                    .switchTo(new Supplier<Uni<? extends UserInfo>>() {
+                        @Override
+                        public Uni<UserInfo> get() {
+                            return newUserInfoUni(resolvedContext, accessToken);
+                        }
+                    });
         }
         return userInfoUni;
     }
diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java
index a653e097e34..ddc772b6feb 100644
--- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java
+++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowUserInfoResource.java
@@ -31,11 +31,9 @@ public class CodeFlowUserInfoResource {
     @GET
     @Path("/code-flow-user-info-only")
     public String access() {
-        int cacheSize = tokenCache.getCacheSize();
-        tokenCache.clearCache();
         return identity.getPrincipal().getName() + ":" + userInfo.getPreferredUserName() + ":" + accessToken.getName()
                 + ", cache size: "
-                + cacheSize;
+                + tokenCache.getCacheSize();
     }
 
     @GET
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
index 0a3536eeb0d..2fd589694bc 100644
--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
@@ -3,8 +3,10 @@
 import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
 import static com.github.tomakehurst.wiremock.client.WireMock.containing;
 import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
 import static com.github.tomakehurst.wiremock.client.WireMock.matching;
 import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -214,9 +216,11 @@ public void testCodeFlowUserInfo() throws Exception {
         defineCodeFlowAuthorizationOauth2TokenStub();
 
         doTestCodeFlowUserInfo("code-flow-user-info-only", 300);
+        clearCache();
         doTestCodeFlowUserInfo("code-flow-user-info-github", 360);
+        clearCache();
         doTestCodeFlowUserInfo("code-flow-user-info-dynamic-github", 301);
-
+        clearCache();
         doTestCodeFlowUserInfoCashedInIdToken();
     }
 
@@ -256,6 +260,13 @@ private void doTestCodeFlowUserInfo(String tenantId, long internalIdTokenLifetim
             page = form.getInputByValue("login").click();
 
             assertEquals("alice:alice:alice, cache size: 1", page.getBody().asNormalizedText());
+            page = webClient.getPage("http://localhost:8081/" + tenantId);
+            assertEquals("alice:alice:alice, cache size: 1", page.getBody().asNormalizedText());
+            page = webClient.getPage("http://localhost:8081/" + tenantId);
+            assertEquals("alice:alice:alice, cache size: 1", page.getBody().asNormalizedText());
+
+            wireMockServer.verify(1, getRequestedFor(urlPathMatching("/auth/realms/quarkus/protocol/openid-connect/userinfo")));
+            wireMockServer.resetRequests();
 
             JsonObject idTokenClaims = decryptIdToken(webClient, tenantId);
             assertNull(idTokenClaims.getJsonObject(OidcUtils.USER_INFO_ATTRIBUTE));