Repository: vert-x3/vertx-web
Issue: AuthorizationHandlerImpl: hanging requests - ctx.fail() is never called at denied request with all authorizations already cached in session

### Version

4.1.1

### Context

When `AuthorizationHandlerImpl` handles a denied request for a user with an active session where all authorizations are already fetched from all providers (at a previous request of the same user session) then `ctx.fail(...)` is never called, which causes the request to hang.

### Steps to reproduce

_With sessions/session-cookies enabled:_

1. Try to access an forbidden ressource -> forbidden is returned
2. Try to access an forbidden ressource again -> the request hangs



Changed files:
vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthorizationHandlerImpl.java

diff --git a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthorizationHandlerImpl.java b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthorizationHandlerImpl.java
index 10d4a1b04..e794cea21 100644
--- a/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthorizationHandlerImpl.java
+++ b/vertx-web/src/main/java/io/vertx/ext/web/handler/impl/AuthorizationHandlerImpl.java
@@ -44,11 +44,13 @@ public class AuthorizationHandlerImpl implements AuthorizationHandler {
 
   private final Authorization authorization;
   private final Collection<AuthorizationProvider> authorizationProviders;
+  private final Collection<String> authorizationProviderIds;
   private BiConsumer<RoutingContext, AuthorizationContext> variableHandler;
 
   public AuthorizationHandlerImpl(Authorization authorization) {
     this.authorization = Objects.requireNonNull(authorization);
     this.authorizationProviders = new ArrayList<>();
+    this.authorizationProviderIds = new ArrayList<>();
   }
 
   @Override
@@ -105,7 +107,7 @@ public class AuthorizationHandlerImpl implements AuthorizationHandler {
 
     final User user = ctx.user();
 
-    if (user == null || !providers.hasNext()) {
+    if (user == null || !providers.hasNext() || allAuthorizationsFetched(user)) {
       // resume as the error handler may allow this request to become valid again
       resume(ctx.request(), parseEnded);
       ctx.fail(FORBIDDEN_CODE, FORBIDDEN_EXCEPTION);
@@ -135,8 +137,8 @@ public class AuthorizationHandlerImpl implements AuthorizationHandler {
   @Override
   public AuthorizationHandler addAuthorizationProvider(AuthorizationProvider authorizationProvider) {
     Objects.requireNonNull(authorizationProvider);
-
     this.authorizationProviders.add(authorizationProvider);
+    this.authorizationProviderIds.add(authorizationProvider.getId());
     return this;
   }
 
@@ -145,4 +147,17 @@ public class AuthorizationHandlerImpl implements AuthorizationHandler {
       request.resume();
     }
   }
+
+  /**
+   * Returns <code>true</code> when the authorizations of all available
+   * providers have been loaded for the given user which may have happened in a
+   * previous request of the current user session.
+   *
+   * @param user
+   * @return <code>true</code> when the authorizations of all available
+   *         providers have been loaded for the user
+   */
+  private boolean allAuthorizationsFetched(final User user) {
+      return user.authorizations().getProviderIds().containsAll(authorizationProviderIds);
+  }
 }