Repository: quarkusio/quarkus
Issue: @RolesAllowed causes CompletionStage to be serialized as itself

### Describe the bug

Consider the following method:

    @GET
    @Path("protected")
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("protected-role")
    public CompletionStage<String> getProtected() {
        return CompletableFuture.completedFuture("{}");
    }

The CompletionStage is actually serialized instead of its result.

### Expected behavior

The result should be serialized, that is the string `{}` should be returned by the endpoint:
```
HTTP/1.1 200 OK
Content-Type: application/json
content-length: 2

{}
```

### Actual behavior

The serialized JSON is that of the CompletionStage:
```
HTTP/1.1 200 OK
Content-Type: application/json
content-length: 85

{
  "cancelled": false,
  "done": true,
  "completedExceptionally": false,
  "numberOfDependents": 0
}
```

In a QuarkusTest I actually get a different error than while running the application:

```
2021-10-25 13:33:02,686 ERROR [org.jbo.res.res.i18n] (executor-thread-0) RESTEASY002020: Unhandled asynchronous exception, sending back 500: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `java.util.concurrent.CompletableFuture$MinimalStage`: Failed to construct BeanSerializer for [simple type, class java.util.concurrent.CompletableFuture$MinimalStage]: (java.lang.reflect.InaccessibleObjectException) Unable to make public boolean java.util.concurrent.CompletableFuture$MinimalStage.isCancelled() accessible: module java.base does not "opens java.util.concurrent" to unnamed module @328e4ec2
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:72)
...
```

### How to Reproduce?

https://github.com/antoniomacri/quarkus-completionstage-rolesallowed

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

Java 11

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.4.0.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Maven 3.8.1

### Additional information

_No response_

Changed files:
extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/app/BeanWithSecuredMethods.java
extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java
extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/CDIAccessDefaultTest.java

diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/CDIAccessDefaultTest.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/CDIAccessDefaultTest.java
index 60e9a2eb516..757223d2b5b 100644
--- a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/CDIAccessDefaultTest.java
+++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/CDIAccessDefaultTest.java
@@ -6,6 +6,8 @@
 import static io.quarkus.security.test.utils.SecurityTestUtils.assertFailureFor;
 import static io.quarkus.security.test.utils.SecurityTestUtils.assertSuccess;
 
+import java.util.concurrent.ExecutionException;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -13,6 +15,7 @@
 import org.jboss.shrinkwrap.api.spec.JavaArchive;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.function.Executable;
 
 import io.quarkus.security.ForbiddenException;
 import io.quarkus.security.UnauthorizedException;
@@ -73,6 +76,33 @@ public void shouldRestrictAccessToSpecificRole() {
         assertSuccess(() -> bean.securedMethod(), "accessibleForAdminOnly", ADMIN);
     }
 
+    @Test
+    public void shouldRestrictAccessToSpecificRoleUni() {
+        assertFailureFor(() -> bean.securedMethodUni().await().indefinitely(), UnauthorizedException.class, ANONYMOUS);
+        assertFailureFor(() -> bean.securedMethodUni().await().indefinitely(), ForbiddenException.class, USER);
+        assertSuccess(() -> bean.securedMethodUni().await().indefinitely(), "accessibleForAdminOnly", ADMIN);
+    }
+
+    @Test
+    public void shouldRestrictAccessToSpecificRoleCompletionState() {
+        Executable executable = () -> {
+            try {
+                bean.securedMethodCompletionStage().toCompletableFuture().get();
+            } catch (ExecutionException e) {
+                throw e.getCause();
+            }
+        };
+        assertFailureFor(executable, UnauthorizedException.class, ANONYMOUS);
+        assertFailureFor(executable, ForbiddenException.class, USER);
+        assertSuccess(() -> {
+            try {
+                return bean.securedMethodCompletionStage().toCompletableFuture().get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }, "accessibleForAdminOnly", ADMIN);
+    }
+
     @Test
     public void shouldFailToAccessForbiddenOnClass() {
         assertFailureFor(() -> denyAllBean.noAdditionalConstraints(), UnauthorizedException.class, ANONYMOUS);
diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/app/BeanWithSecuredMethods.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/app/BeanWithSecuredMethods.java
index 8dca89dfe04..987620fa5e9 100644
--- a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/app/BeanWithSecuredMethods.java
+++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/app/BeanWithSecuredMethods.java
@@ -1,10 +1,14 @@
 package io.quarkus.security.test.cdi.app;
 
+import java.util.concurrent.CompletionStage;
+
 import javax.annotation.security.DenyAll;
 import javax.annotation.security.RolesAllowed;
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Named;
 
+import io.smallrye.mutiny.Uni;
+
 /**
  * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com
  */
@@ -23,6 +27,16 @@ public String securedMethod() {
         return "accessibleForAdminOnly";
     }
 
+    @RolesAllowed("admin")
+    public Uni<String> securedMethodUni() {
+        return Uni.createFrom().item("accessibleForAdminOnly");
+    }
+
+    @RolesAllowed("admin")
+    public CompletionStage<String> securedMethodCompletionStage() {
+        return Uni.createFrom().item("accessibleForAdminOnly").subscribeAsCompletionStage();
+    }
+
     public String unsecuredMethod() {
         return "accessibleForAll";
     }
diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java
index 0a58e6fed97..209d37d1ab4 100644
--- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java
+++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java
@@ -1,6 +1,5 @@
 package io.quarkus.security.runtime.interceptor;
 
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.function.Function;
 
@@ -33,8 +32,13 @@ public Object handle(InvocationContext ic) throws Exception {
                     .onItem().transformToUni(new UniContinuation(ic));
         } else if (CompletionStage.class.isAssignableFrom(returnType)) {
             return constrainer.nonBlockingCheck(ic.getMethod(), ic.getParameters())
-                    .subscribeAsCompletionStage()
-                    .thenApply(new CompletionStageContinuation(ic));
+                    .onItem().transformToUni((s) -> {
+                        try {
+                            return Uni.createFrom().completionStage((CompletionStage<?>) ic.proceed());
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    }).subscribeAsCompletionStage();
         } else if (Multi.class.isAssignableFrom(returnType)) {
             return constrainer.nonBlockingCheck(ic.getMethod(), ic.getParameters())
                     .onItem().transformToMulti(new MultiContinuation(ic));
@@ -65,23 +69,6 @@ public Uni<?> apply(Object o) {
         }
     }
 
-    private static class CompletionStageContinuation implements Function<Object, CompletionStage<?>> {
-        private final InvocationContext ic;
-
-        CompletionStageContinuation(InvocationContext invocationContext) {
-            ic = invocationContext;
-        }
-
-        @Override
-        public CompletionStage<?> apply(Object o) {
-            try {
-                return (CompletionStage<?>) ic.proceed();
-            } catch (Exception e) {
-                return CompletableFuture.failedFuture(e);
-            }
-        }
-    }
-
     private static class MultiContinuation implements Function<Object, Multi<?>> {
         private final InvocationContext ic;