Repository: quarkusio/quarkus
Issue: OIDC JWT introspection results are not registered as SecurityIdentity attributes

### Describe the bug

In most cases OIDC JWT tokens are verified locally, and sometimes (or always) they have to be introspected remotely. 
Opaque/binary tokens are always introspected remotely.

The problem is that a `TokenIntrospection` result is only registered as a `SecurityIdentity` attribute if it is an opaque token, while JWT tokens's content is used to build `SecurityIdentity` once the introspection has succeeded - in most cases it is fine - usually `TokenIntrospection` would only return a limited subset of what can be found in JWT - but `TokenIntrospection` might have more data as well and it is also impossible to write the code around `TokenIntrospection` only if both JWT and opaque tokens are processed

### Expected behavior

_No response_

### 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-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/TenantResource.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.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 59b9e61ba41..b34d04fbc62 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
@@ -161,7 +161,7 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult result, Throwable t)
                                 JsonObject rolesJson = getRolesJson(vertxContext, resolvedContext, tokenCred, tokenJson,
                                         userInfo);
                                 SecurityIdentity securityIdentity = validateAndCreateIdentity(vertxContext, tokenCred,
-                                        resolvedContext, tokenJson, rolesJson, userInfo);
+                                        resolvedContext, tokenJson, rolesJson, userInfo, result.introspectionResult);
                                 if (tokenAutoRefreshPrepared(tokenJson, vertxContext, resolvedContext.oidcConfig)) {
                                     return Uni.createFrom().failure(new TokenAutoRefreshException(securityIdentity));
                                 } else {
@@ -339,7 +339,7 @@ private static Uni<SecurityIdentity> validateTokenWithoutOidcServer(TokenAuthent
             TokenVerificationResult result = resolvedContext.provider.verifyJwtToken(request.getToken().getToken());
             return Uni.createFrom()
                     .item(validateAndCreateIdentity(null, request.getToken(), resolvedContext,
-                            result.localVerificationResult, result.localVerificationResult, null));
+                            result.localVerificationResult, result.localVerificationResult, null, null));
         } catch (Throwable t) {
             return Uni.createFrom().failure(new AuthenticationFailedException(t));
         }
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java
index aa1e1ac2b1d..c61086e0e1f 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java
@@ -143,7 +143,8 @@ private static List<String> convertJsonArrayToList(JsonArray claimValue) {
 
     static QuarkusSecurityIdentity validateAndCreateIdentity(
             RoutingContext vertxContext, TokenCredential credential,
-            TenantConfigContext resolvedContext, JsonObject tokenJson, JsonObject rolesJson, UserInfo userInfo) {
+            TenantConfigContext resolvedContext, JsonObject tokenJson, JsonObject rolesJson, UserInfo userInfo,
+            TokenIntrospection introspectionResult) {
 
         OidcTenantConfig config = resolvedContext.oidcConfig;
         QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
@@ -169,6 +170,7 @@ static QuarkusSecurityIdentity validateAndCreateIdentity(
         setRoutingContextAttribute(builder, vertxContext);
         setSecurityIdentityRoles(builder, config, rolesJson);
         setSecurityIdentityUserInfo(builder, userInfo);
+        setSecurityIdentityIntrospecton(builder, introspectionResult);
         setSecurityIdentityConfigMetadata(builder, resolvedContext);
         setBlockinApiAttribute(builder, vertxContext);
         setTenantIdAttribute(builder, config);
@@ -205,7 +207,9 @@ public static void setSecurityIdentityUserInfo(QuarkusSecurityIdentity.Builder b
     }
 
     public static void setSecurityIdentityIntrospecton(Builder builder, TokenIntrospection introspectionResult) {
-        builder.addAttribute(INTROSPECTION_ATTRIBUTE, introspectionResult);
+        if (introspectionResult != null) {
+            builder.addAttribute(INTROSPECTION_ATTRIBUTE, introspectionResult);
+        }
     }
 
     public static void setSecurityIdentityConfigMetadata(QuarkusSecurityIdentity.Builder builder,
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 758140bc4b2..7a3b0cf337f 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
@@ -13,6 +13,7 @@
 import io.quarkus.oidc.AccessTokenCredential;
 import io.quarkus.oidc.IdToken;
 import io.quarkus.oidc.OIDCException;
+import io.quarkus.oidc.TokenIntrospection;
 import io.quarkus.oidc.UserInfo;
 import io.quarkus.security.identity.SecurityIdentity;
 
@@ -54,7 +55,9 @@ public String userNameService(@PathParam("tenant") String tenant) {
 
         String response = tenant + ":" + name;
         if (tenant.startsWith("tenant-oidc-introspection-only")) {
-            response += (":" + tokenCache.getCacheSize());
+            TokenIntrospection introspection = securityIdentity.getAttribute("introspection");
+            response += (",active:" + introspection.getBoolean("active"));
+            response += (",cache-size:" + tokenCache.getCacheSize());
         }
         return response;
     }
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 8929327b868..7e7d6418e3d 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
@@ -360,7 +360,7 @@ public void testJwtTokenIntrospectionOnlyAndUserInfo() {
                     .when().get("/tenant/tenant-oidc-introspection-only/api/user")
                     .then()
                     .statusCode(200)
-                    .body(equalTo("tenant-oidc-introspection-only:alice:0"));
+                    .body(equalTo("tenant-oidc-introspection-only:alice,active:true,cache-size:0"));
         }
 
         RestAssured.when().get("/oidc/jwk-endpoint-call-count").then().body(equalTo("0"));
@@ -404,7 +404,7 @@ private void verifyTokenIntrospectionAndUserInfoAreCached(String token1, int exp
                     .when().get("/tenant/tenant-oidc-introspection-only-cache/api/user")
                     .then()
                     .statusCode(200)
-                    .body(equalTo("tenant-oidc-introspection-only-cache:alice:" + expectedCacheSize));
+                    .body(equalTo("tenant-oidc-introspection-only-cache:alice,active:true,cache-size:" + expectedCacheSize));
         }
         RestAssured.when().get("/oidc/introspection-endpoint-call-count").then().body(equalTo("1"));
         RestAssured.when().post("/oidc/introspection-endpoint-call-count").then().body(equalTo("0"));