Skip to content

Commit 0c751fe

Browse files
authored
Merge pull request #34963 from michalvavrik/feature/rr-smallrye-jwt-fix-ex-mappers-for-http2
Run authentication request blocking tasks on Vert.x duplicated context
2 parents e54d3b1 + ef034bc commit 0c751fe

File tree

6 files changed

+186
-27
lines changed

6 files changed

+186
-27
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.quarkus.security.spi.runtime;
2+
3+
import java.util.concurrent.Executor;
4+
import java.util.function.Consumer;
5+
import java.util.function.Supplier;
6+
7+
import io.quarkus.security.identity.AuthenticationRequestContext;
8+
import io.smallrye.mutiny.Uni;
9+
import io.smallrye.mutiny.subscription.UniEmitter;
10+
11+
/**
12+
* Blocking executor used for security purposes such {@link AuthenticationRequestContext#runBlocking(Supplier)}.
13+
* Extensions may provide their own implementation if they need a single thread pool.
14+
*/
15+
public interface BlockingSecurityExecutor {
16+
17+
<T> Uni<T> executeBlocking(Supplier<? extends T> supplier);
18+
19+
static BlockingSecurityExecutor createBlockingExecutor(Supplier<Executor> executorSupplier) {
20+
return new BlockingSecurityExecutor() {
21+
@Override
22+
public <T> Uni<T> executeBlocking(Supplier<? extends T> function) {
23+
return Uni.createFrom().emitter(new Consumer<UniEmitter<? super T>>() {
24+
@Override
25+
public void accept(UniEmitter<? super T> uniEmitter) {
26+
executorSupplier.get().execute(new Runnable() {
27+
@Override
28+
public void run() {
29+
try {
30+
uniEmitter.complete(function.get());
31+
} catch (Throwable t) {
32+
uniEmitter.fail(t);
33+
}
34+
}
35+
});
36+
}
37+
});
38+
}
39+
};
40+
}
41+
42+
}

extensions/security/runtime/src/main/java/io/quarkus/security/runtime/IdentityProviderManagerCreator.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package io.quarkus.security.runtime;
22

33
import java.util.concurrent.Executor;
4+
import java.util.function.Supplier;
45

56
import jakarta.enterprise.context.ApplicationScoped;
67
import jakarta.enterprise.inject.Instance;
78
import jakarta.enterprise.inject.Produces;
89
import jakarta.inject.Inject;
910

11+
import io.quarkus.arc.DefaultBean;
1012
import io.quarkus.runtime.ExecutorRecorder;
1113
import io.quarkus.security.identity.IdentityProvider;
1214
import io.quarkus.security.identity.IdentityProviderManager;
1315
import io.quarkus.security.identity.SecurityIdentityAugmentor;
1416
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest;
17+
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
1518

1619
/**
1720
* CDI bean than manages the lifecycle of the {@link io.quarkus.security.identity.IdentityProviderManager}
@@ -25,6 +28,21 @@ public class IdentityProviderManagerCreator {
2528
@Inject
2629
Instance<SecurityIdentityAugmentor> augmentors;
2730

31+
@Inject
32+
BlockingSecurityExecutor blockingExecutor;
33+
34+
@ApplicationScoped
35+
@DefaultBean
36+
@Produces
37+
BlockingSecurityExecutor defaultBlockingExecutor() {
38+
return BlockingSecurityExecutor.createBlockingExecutor(new Supplier<Executor>() {
39+
@Override
40+
public Executor get() {
41+
return ExecutorRecorder.getCurrent();
42+
}
43+
});
44+
}
45+
2846
@Produces
2947
@ApplicationScoped
3048
public IdentityProviderManager ipm() {
@@ -42,13 +60,7 @@ public IdentityProviderManager ipm() {
4260
for (SecurityIdentityAugmentor i : augmentors) {
4361
builder.addSecurityIdentityAugmentor(i);
4462
}
45-
builder.setBlockingExecutor(new Executor() {
46-
@Override
47-
public void execute(Runnable command) {
48-
//TODO: should we be using vert.x blocking tasks here? We really should only have a single thread pool
49-
ExecutorRecorder.getCurrent().execute(command);
50-
}
51-
});
63+
builder.setBlockingExecutor(blockingExecutor);
5264
return builder.build();
5365
}
5466

extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusIdentityProviderManagerImpl.java

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package io.quarkus.security.runtime;
22

3+
import static io.quarkus.security.spi.runtime.BlockingSecurityExecutor.createBlockingExecutor;
4+
35
import java.util.ArrayList;
46
import java.util.Comparator;
57
import java.util.HashMap;
68
import java.util.List;
79
import java.util.Map;
810
import java.util.concurrent.Executor;
9-
import java.util.function.Consumer;
1011
import java.util.function.Function;
1112
import java.util.function.Supplier;
1213

@@ -21,8 +22,8 @@
2122
import io.quarkus.security.identity.SecurityIdentityAugmentor;
2223
import io.quarkus.security.identity.request.AnonymousAuthenticationRequest;
2324
import io.quarkus.security.identity.request.AuthenticationRequest;
25+
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
2426
import io.smallrye.mutiny.Uni;
25-
import io.smallrye.mutiny.subscription.UniEmitter;
2627

2728
/**
2829
* A manager that can be used to get a specific type of identity provider.
@@ -32,7 +33,7 @@ public class QuarkusIdentityProviderManagerImpl implements IdentityProviderManag
3233

3334
private final Map<Class<? extends AuthenticationRequest>, List<IdentityProvider>> providers;
3435
private final List<SecurityIdentityAugmentor> augmenters;
35-
private final Executor blockingExecutor;
36+
private final BlockingSecurityExecutor blockingExecutor;
3637

3738
private final AuthenticationRequestContext blockingRequestContext = new AuthenticationRequestContext() {
3839
@Override
@@ -48,21 +49,7 @@ public Uni<SecurityIdentity> get() {
4849
return Uni.createFrom().failure(t);
4950
}
5051
} else {
51-
return Uni.createFrom().emitter(new Consumer<UniEmitter<? super SecurityIdentity>>() {
52-
@Override
53-
public void accept(UniEmitter<? super SecurityIdentity> uniEmitter) {
54-
blockingExecutor.execute(new Runnable() {
55-
@Override
56-
public void run() {
57-
try {
58-
uniEmitter.complete(function.get());
59-
} catch (Throwable t) {
60-
uniEmitter.fail(t);
61-
}
62-
}
63-
});
64-
}
65-
});
52+
return blockingExecutor.executeBlocking(function);
6653
}
6754
}
6855
});
@@ -199,7 +186,7 @@ public static class Builder {
199186

200187
private final Map<Class<? extends AuthenticationRequest>, List<IdentityProvider>> providers = new HashMap<>();
201188
private final List<SecurityIdentityAugmentor> augmentors = new ArrayList<>();
202-
private Executor blockingExecutor;
189+
private BlockingSecurityExecutor blockingExecutor;
203190
private boolean built = false;
204191

205192
/**
@@ -231,11 +218,20 @@ public Builder addSecurityIdentityAugmentor(SecurityIdentityAugmentor augmentor)
231218
* @param blockingExecutor The executor to use for blocking tasks
232219
* @return this builder
233220
*/
234-
public Builder setBlockingExecutor(Executor blockingExecutor) {
221+
public Builder setBlockingExecutor(BlockingSecurityExecutor blockingExecutor) {
235222
this.blockingExecutor = blockingExecutor;
236223
return this;
237224
}
238225

226+
/**
227+
* @param blockingExecutor The executor to use for blocking tasks
228+
* @return this builder
229+
*/
230+
public Builder setBlockingExecutor(Executor blockingExecutor) {
231+
this.blockingExecutor = createBlockingExecutor(() -> blockingExecutor);
232+
return this;
233+
}
234+
239235
/**
240236
* @return a new {@link QuarkusIdentityProviderManagerImpl}
241237
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.quarkus.jwt.test;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.io.IOException;
6+
import java.net.URISyntaxException;
7+
import java.net.URL;
8+
import java.net.http.HttpClient;
9+
import java.net.http.HttpRequest;
10+
import java.net.http.HttpResponse;
11+
12+
import jakarta.ws.rs.core.Response;
13+
14+
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
15+
import org.jboss.shrinkwrap.api.asset.StringAsset;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.extension.RegisterExtension;
18+
19+
import io.quarkus.security.AuthenticationFailedException;
20+
import io.quarkus.test.QuarkusUnitTest;
21+
import io.quarkus.test.common.http.TestHTTPResource;
22+
23+
public class EnabledProactiveAuthFailedExceptionMapperHttp2Test {
24+
25+
private static final String CUSTOMIZED_RESPONSE = "AuthenticationFailedException";
26+
protected static final Class<?>[] classes = { JsonValuejectionEndpoint.class, TokenUtils.class,
27+
AuthFailedExceptionMapper.class };
28+
29+
@RegisterExtension
30+
static final QuarkusUnitTest config = new QuarkusUnitTest()
31+
.withApplicationRoot((jar) -> jar
32+
.addClasses(classes)
33+
.addAsResource(new StringAsset("quarkus.http.auth.proactive=true\n" +
34+
"quarkus.smallrye-jwt.blocking-authentication=true\n"), "application.properties"));
35+
36+
@TestHTTPResource
37+
URL url;
38+
39+
@Test
40+
public void testExMapperCustomizedResponse() throws IOException, InterruptedException, URISyntaxException {
41+
var client = HttpClient.newBuilder()
42+
.version(HttpClient.Version.HTTP_2)
43+
.build();
44+
45+
var response = client.send(
46+
HttpRequest.newBuilder()
47+
.GET()
48+
.header("Authorization", "Bearer 12345")
49+
.uri(url.toURI())
50+
.build(),
51+
HttpResponse.BodyHandlers.ofString());
52+
53+
assertEquals(401, response.statusCode());
54+
}
55+
56+
public static class AuthFailedExceptionMapper {
57+
58+
@ServerExceptionMapper(value = AuthenticationFailedException.class)
59+
public Response unauthorized() {
60+
return Response
61+
.status(401)
62+
.entity(CUSTOMIZED_RESPONSE).build();
63+
}
64+
65+
}
66+
}

extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.vertx.http.deployment;
22

3+
import static io.quarkus.arc.processor.DotNames.APPLICATION_SCOPED;
34
import static org.jboss.jandex.AnnotationTarget.Kind.CLASS;
45

56
import java.security.Permission;
@@ -54,6 +55,7 @@
5455
import io.quarkus.vertx.http.runtime.security.PermitSecurityPolicy;
5556
import io.quarkus.vertx.http.runtime.security.RolesAllowedHttpSecurityPolicy;
5657
import io.quarkus.vertx.http.runtime.security.SupplierImpl;
58+
import io.quarkus.vertx.http.runtime.security.VertxBlockingSecurityExecutor;
5759
import io.vertx.core.http.ClientAuth;
5860
import io.vertx.ext.web.RoutingContext;
5961

@@ -261,6 +263,9 @@ void setupAuthenticationMechanisms(
261263
}
262264

263265
if (capabilities.isPresent(Capability.SECURITY)) {
266+
beanProducer
267+
.produce(AdditionalBeanBuildItem.builder().setUnremovable()
268+
.addBeanClass(VertxBlockingSecurityExecutor.class).setDefaultScope(APPLICATION_SCOPED).build());
264269
beanProducer
265270
.produce(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClass(HttpAuthenticator.class)
266271
.addBeanClass(HttpAuthorizer.class).build());
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.quarkus.vertx.http.runtime.security;
2+
3+
import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe;
4+
import static io.smallrye.common.vertx.VertxContext.getOrCreateDuplicatedContext;
5+
6+
import java.util.function.Supplier;
7+
8+
import jakarta.inject.Inject;
9+
10+
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor;
11+
import io.smallrye.mutiny.Uni;
12+
import io.vertx.core.Context;
13+
import io.vertx.core.Handler;
14+
import io.vertx.core.Promise;
15+
import io.vertx.core.Vertx;
16+
17+
public class VertxBlockingSecurityExecutor implements BlockingSecurityExecutor {
18+
19+
@Inject
20+
Vertx vertx;
21+
22+
@Override
23+
public <T> Uni<T> executeBlocking(Supplier<? extends T> supplier) {
24+
Context local = getOrCreateDuplicatedContext(vertx);
25+
setContextSafe(local, true);
26+
return Uni
27+
.createFrom()
28+
.completionStage(
29+
local
30+
.executeBlocking(new Handler<Promise<T>>() {
31+
@Override
32+
public void handle(Promise<T> promise) {
33+
promise.complete(supplier.get());
34+
}
35+
})
36+
.toCompletionStage());
37+
}
38+
}

0 commit comments

Comments
 (0)