diff --git a/extensions/websockets-next/deployment/pom.xml b/extensions/websockets-next/deployment/pom.xml
index 9c33791094f427..78e90a6a619591 100644
--- a/extensions/websockets-next/deployment/pom.xml
+++ b/extensions/websockets-next/deployment/pom.xml
@@ -36,6 +36,16 @@
quarkus-test-vertx
test
+
+ io.quarkus
+ quarkus-security-deployment
+ test
+
+
+ io.quarkus
+ quarkus-security-test-utils
+ test
+
io.quarkus
quarkus-junit5-internal
diff --git a/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java b/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java
index 465873ae3cad06..c9c67b90298299 100644
--- a/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java
+++ b/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java
@@ -44,6 +44,8 @@
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.Types;
+import io.quarkus.deployment.Capabilities;
+import io.quarkus.deployment.Capability;
import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -65,6 +67,7 @@
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.runtime.HandlerType;
+import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.websockets.next.InboundProcessingMode;
import io.quarkus.websockets.next.WebSocketClientConnection;
import io.quarkus.websockets.next.WebSocketClientException;
@@ -79,6 +82,7 @@
import io.quarkus.websockets.next.runtime.ConnectionManager;
import io.quarkus.websockets.next.runtime.ContextSupport;
import io.quarkus.websockets.next.runtime.JsonTextMessageCodec;
+import io.quarkus.websockets.next.runtime.SecuritySupport;
import io.quarkus.websockets.next.runtime.WebSocketClientRecorder;
import io.quarkus.websockets.next.runtime.WebSocketClientRecorder.ClientEndpoint;
import io.quarkus.websockets.next.runtime.WebSocketConnectionBase;
@@ -400,12 +404,19 @@ public String apply(String name) {
@Record(RUNTIME_INIT)
@BuildStep
public void registerRoutes(WebSocketServerRecorder recorder, HttpRootPathBuildItem httpRootPath,
- List generatedEndpoints,
+ List generatedEndpoints, HttpBuildTimeConfig httpConfig, Capabilities capabilities,
BuildProducer routes) {
for (GeneratedEndpointBuildItem endpoint : generatedEndpoints.stream().filter(GeneratedEndpointBuildItem::isServer)
.toList()) {
- RouteBuildItem.Builder builder = RouteBuildItem.builder()
- .route(httpRootPath.relativePath(endpoint.path))
+ RouteBuildItem.Builder builder = RouteBuildItem.builder();
+ String relativePath = httpRootPath.relativePath(endpoint.path);
+ if (capabilities.isPresent(Capability.SECURITY) && !httpConfig.auth.proactive) {
+ // Add a special handler so that it's possible to capture the SecurityIdentity before the HTTP upgrade
+ builder.routeFunction(relativePath, recorder.initializeSecurityHandler());
+ } else {
+ builder.route(relativePath);
+ }
+ builder
.displayOnNotFoundPage("WebSocket Endpoint")
.handlerType(HandlerType.NORMAL)
.handler(recorder.createEndpointHandler(endpoint.generatedClassName, endpoint.endpointId));
@@ -546,8 +557,8 @@ private void validateOnClose(Callback callback) {
* }
*
* public Echo_WebSocketEndpoint(WebSocketConnection connection, Codecs codecs,
- * WebSocketRuntimeConfig config, ContextSupport contextSupport) {
- * super(connection, codecs, config, contextSupport);
+ * WebSocketRuntimeConfig config, ContextSupport contextSupport, SecuritySupport securitySupport) {
+ * super(connection, codecs, config, contextSupport, securitySupport);
* }
*
* public Uni doOnTextMessage(String message) {
@@ -617,12 +628,12 @@ static String generateEndpoint(WebSocketEndpointBuildItem endpoint,
.build();
MethodCreator constructor = endpointCreator.getConstructorCreator(WebSocketConnectionBase.class,
- Codecs.class, ContextSupport.class);
+ Codecs.class, ContextSupport.class, SecuritySupport.class);
constructor.invokeSpecialMethod(
MethodDescriptor.ofConstructor(WebSocketEndpointBase.class, WebSocketConnectionBase.class,
- Codecs.class, ContextSupport.class),
+ Codecs.class, ContextSupport.class, SecuritySupport.class),
constructor.getThis(), constructor.getMethodParam(0), constructor.getMethodParam(1),
- constructor.getMethodParam(2));
+ constructor.getMethodParam(2), constructor.getMethodParam(3));
constructor.returnNull();
MethodCreator inboundProcessingMode = endpointCreator.getMethodCreator("inboundProcessingMode",
@@ -1044,7 +1055,7 @@ private static ResultHandle encodeMessage(ResultHandle endpointThis, BytecodeCre
return uniOnFailureDoOnError(endpointThis, method, callback, uniChain, endpoint, globalErrorHandlers);
}
} else if (callback.isReturnTypeMulti()) {
- // return multiText(multi, broadcast, m -> {
+ // return multiText(multi, m -> {
// try {
// String text = encodeText(m);
// return sendText(buffer,broadcast);
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/RuntimeErrorTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/RuntimeErrorTest.java
index a519c95ea9be36..420f0ba1515ef0 100644
--- a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/RuntimeErrorTest.java
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/RuntimeErrorTest.java
@@ -80,8 +80,8 @@ String decodingError(BinaryDecodeException e) {
Uni runtimeProblem(RuntimeException e, WebSocketConnection connection) {
assertTrue(Context.isOnEventLoopThread());
assertEquals(connection.id(), this.connection.id());
- // The request context from @OnBinaryMessage is reused
- assertEquals("ok", requestBean.getState());
+ // A new request context is used
+ assertEquals("nok", requestBean.getState());
return connection.sendText(e.getMessage());
}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/UniFailureErrorTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/UniFailureErrorTest.java
index 933f681c26fcc2..17164eb98836cd 100644
--- a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/UniFailureErrorTest.java
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/errors/UniFailureErrorTest.java
@@ -80,8 +80,8 @@ String decodingError(BinaryDecodeException e) {
String runtimeProblem(RuntimeException e, WebSocketConnection connection) {
assertTrue(Context.isOnWorkerThread());
assertEquals(connection.id(), this.connection.id());
- // The request context from @OnBinaryMessage is reused
- assertEquals("ok", requestBean.getState());
+ // A new request context is used
+ assertEquals("nok", requestBean.getState());
return e.getMessage();
}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/AdminService.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/AdminService.java
new file mode 100644
index 00000000000000..38905495f4e661
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/AdminService.java
@@ -0,0 +1,14 @@
+package io.quarkus.websockets.next.test.security;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@RolesAllowed("admin")
+@ApplicationScoped
+public class AdminService {
+
+ public String ping() {
+ return "" + 24;
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/EagerSecurityTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/EagerSecurityTest.java
new file mode 100644
index 00000000000000..506c1a5a55cd2f
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/EagerSecurityTest.java
@@ -0,0 +1,59 @@
+package io.quarkus.websockets.next.test.security;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.security.Authenticated;
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.identity.CurrentIdentityAssociation;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.websockets.next.OnError;
+import io.quarkus.websockets.next.OnOpen;
+import io.quarkus.websockets.next.OnTextMessage;
+import io.quarkus.websockets.next.WebSocket;
+import io.quarkus.websockets.next.test.utils.WSClient;
+
+public class EagerSecurityTest extends SecurityTestBase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addAsResource(new StringAsset("quarkus.http.auth.proactive=true\n" +
+ "quarkus.http.auth.permission.secured.paths=/end\n" +
+ "quarkus.http.auth.permission.secured.policy=authenticated\n"), "application.properties")
+ .addClasses(Endpoint.class, WSClient.class, TestIdentityProvider.class, TestIdentityController.class));
+
+ @Authenticated
+ @WebSocket(path = "/end")
+ public static class Endpoint {
+
+ @Inject
+ CurrentIdentityAssociation currentIdentity;
+
+ @OnOpen
+ String open() {
+ return "ready";
+ }
+
+ @RolesAllowed("admin")
+ @OnTextMessage
+ String echo(String message) {
+ if (!currentIdentity.getIdentity().hasRole("admin")) {
+ throw new IllegalStateException();
+ }
+ return message;
+ }
+
+ @OnError
+ String error(ForbiddenException t) {
+ return "forbidden:" + currentIdentity.getIdentity().getPrincipal().getName();
+ }
+
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/EagerSecurityUniTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/EagerSecurityUniTest.java
new file mode 100644
index 00000000000000..809bacfdb0627d
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/EagerSecurityUniTest.java
@@ -0,0 +1,60 @@
+package io.quarkus.websockets.next.test.security;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.security.Authenticated;
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.identity.CurrentIdentityAssociation;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.websockets.next.OnError;
+import io.quarkus.websockets.next.OnOpen;
+import io.quarkus.websockets.next.OnTextMessage;
+import io.quarkus.websockets.next.WebSocket;
+import io.quarkus.websockets.next.test.utils.WSClient;
+import io.smallrye.mutiny.Uni;
+
+public class EagerSecurityUniTest extends SecurityTestBase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addAsResource(new StringAsset("quarkus.http.auth.proactive=true\n" +
+ "quarkus.http.auth.permission.secured.paths=/end\n" +
+ "quarkus.http.auth.permission.secured.policy=authenticated\n"), "application.properties")
+ .addClasses(Endpoint.class, WSClient.class, TestIdentityProvider.class, TestIdentityController.class));
+
+ @Authenticated
+ @WebSocket(path = "/end")
+ public static class Endpoint {
+
+ @Inject
+ CurrentIdentityAssociation currentIdentity;
+
+ @OnOpen
+ String open() {
+ return "ready";
+ }
+
+ @RolesAllowed("admin")
+ @OnTextMessage
+ Uni echo(String message) {
+ if (!currentIdentity.getIdentity().hasRole("admin")) {
+ throw new IllegalStateException();
+ }
+ return Uni.createFrom().item(message);
+ }
+
+ @OnError
+ String error(ForbiddenException t) {
+ return "forbidden:" + currentIdentity.getIdentity().getPrincipal().getName();
+ }
+
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/LazySecurityTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/LazySecurityTest.java
new file mode 100644
index 00000000000000..7d21f28dbc2c55
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/LazySecurityTest.java
@@ -0,0 +1,60 @@
+package io.quarkus.websockets.next.test.security;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.security.Authenticated;
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.identity.CurrentIdentityAssociation;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.websockets.next.OnError;
+import io.quarkus.websockets.next.OnOpen;
+import io.quarkus.websockets.next.OnTextMessage;
+import io.quarkus.websockets.next.WebSocket;
+import io.quarkus.websockets.next.test.security.EagerSecurityTest.Endpoint;
+import io.quarkus.websockets.next.test.utils.WSClient;
+
+public class LazySecurityTest extends SecurityTestBase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addAsResource(new StringAsset("quarkus.http.auth.proactive=false\n" +
+ "quarkus.http.auth.permission.secured.paths=/end\n" +
+ "quarkus.http.auth.permission.secured.policy=authenticated\n"), "application.properties")
+ .addClasses(Endpoint.class, WSClient.class, TestIdentityProvider.class, TestIdentityController.class));
+
+ @Authenticated
+ @WebSocket(path = "/end")
+ public static class Endpoint {
+
+ @Inject
+ CurrentIdentityAssociation currentIdentity;
+
+ @OnOpen
+ String open() {
+ return "ready";
+ }
+
+ @RolesAllowed("admin")
+ @OnTextMessage
+ String echo(String message) {
+ if (!currentIdentity.getIdentity().hasRole("admin")) {
+ throw new IllegalStateException();
+ }
+ return message;
+ }
+
+ @OnError
+ String error(ForbiddenException t) {
+ return "forbidden:" + currentIdentity.getIdentity().getPrincipal().getName();
+ }
+
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/LazySecurityUniTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/LazySecurityUniTest.java
new file mode 100644
index 00000000000000..cb968d397f890d
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/LazySecurityUniTest.java
@@ -0,0 +1,60 @@
+package io.quarkus.websockets.next.test.security;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.security.Authenticated;
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.identity.CurrentIdentityAssociation;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.websockets.next.OnError;
+import io.quarkus.websockets.next.OnOpen;
+import io.quarkus.websockets.next.OnTextMessage;
+import io.quarkus.websockets.next.WebSocket;
+import io.quarkus.websockets.next.test.utils.WSClient;
+import io.smallrye.mutiny.Uni;
+
+public class LazySecurityUniTest extends SecurityTestBase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addAsResource(new StringAsset("quarkus.http.auth.proactive=false\n" +
+ "quarkus.http.auth.permission.secured.paths=/end\n" +
+ "quarkus.http.auth.permission.secured.policy=authenticated\n"), "application.properties")
+ .addClasses(Endpoint.class, WSClient.class, TestIdentityProvider.class, TestIdentityController.class));
+
+ @Authenticated
+ @WebSocket(path = "/end")
+ public static class Endpoint {
+
+ @Inject
+ CurrentIdentityAssociation currentIdentity;
+
+ @OnOpen
+ String open() {
+ return "ready";
+ }
+
+ @RolesAllowed("admin")
+ @OnTextMessage
+ Uni echo(String message) {
+ if (!currentIdentity.getIdentity().hasRole("admin")) {
+ throw new IllegalStateException();
+ }
+ return Uni.createFrom().item(message);
+ }
+
+ @OnError
+ String error(ForbiddenException t) {
+ return "forbidden:" + currentIdentity.getIdentity().getPrincipal().getName();
+ }
+
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/RbacServiceSecurityTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/RbacServiceSecurityTest.java
new file mode 100644
index 00000000000000..0207d3f1b03fdf
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/RbacServiceSecurityTest.java
@@ -0,0 +1,84 @@
+package io.quarkus.websockets.next.test.security;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URI;
+import java.util.Set;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.security.test.utils.TestIdentityProvider;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.websockets.next.OnError;
+import io.quarkus.websockets.next.OnTextMessage;
+import io.quarkus.websockets.next.WebSocket;
+import io.quarkus.websockets.next.test.utils.WSClient;
+import io.vertx.core.Vertx;
+
+public class RbacServiceSecurityTest extends SecurityTestBase {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(root -> root.addClasses(Endpoint.class, AdminService.class, UserService.class,
+ TestIdentityProvider.class, TestIdentityController.class, WSClient.class));
+
+ @Inject
+ Vertx vertx;
+
+ @TestHTTPResource("end")
+ URI endUri;
+
+ @BeforeAll
+ public static void setupUsers() {
+ TestIdentityController.resetRoles()
+ .add("admin", "admin", "admin")
+ .add("user", "user", "user");
+ }
+
+ @Test
+ public void testEndpoint() {
+ try (WSClient client = new WSClient(vertx)) {
+ client.connect(basicAuth("admin", "admin"), endUri);
+ client.sendAndAwait("hello"); // admin service
+ client.sendAndAwait("hi"); // forbidden
+ client.waitForMessages(2);
+ assertEquals(Set.of("24", "forbidden"), Set.copyOf(client.getMessages().stream().map(Object::toString).toList()));
+ }
+ try (WSClient client = new WSClient(vertx)) {
+ client.connect(basicAuth("user", "user"), endUri);
+ client.sendAndAwait("hello"); // forbidden
+ client.sendAndAwait("hi"); // user service
+ client.waitForMessages(2);
+ assertEquals(Set.of("42", "forbidden"), Set.copyOf(client.getMessages().stream().map(Object::toString).toList()));
+ }
+ }
+
+ @WebSocket(path = "/end")
+ public static class Endpoint {
+
+ @Inject
+ UserService userService;
+
+ @Inject
+ AdminService adminService;
+
+ @OnTextMessage
+ String echo(String message) {
+ return message.equals("hello") ? adminService.ping() : userService.ping();
+ }
+
+ @OnError
+ String error(ForbiddenException t) {
+ return "forbidden";
+ }
+
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/SecurityTestBase.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/SecurityTestBase.java
new file mode 100644
index 00000000000000..a9c94143ae59bc
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/SecurityTestBase.java
@@ -0,0 +1,71 @@
+package io.quarkus.websockets.next.test.security;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.util.concurrent.CompletionException;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.runtime.util.ExceptionUtil;
+import io.quarkus.security.test.utils.TestIdentityController;
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.websockets.next.test.utils.WSClient;
+import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.UpgradeRejectedException;
+import io.vertx.core.http.WebSocketConnectOptions;
+import io.vertx.ext.auth.authentication.UsernamePasswordCredentials;
+
+public abstract class SecurityTestBase {
+
+ @Inject
+ Vertx vertx;
+
+ @TestHTTPResource("end")
+ URI endUri;
+
+ @BeforeAll
+ public static void setupUsers() {
+ TestIdentityController.resetRoles()
+ .add("admin", "admin", "admin")
+ .add("user", "user", "user");
+ }
+
+ @Test
+ public void testEndpoint() {
+ try (WSClient client = new WSClient(vertx)) {
+ CompletionException ce = assertThrows(CompletionException.class, () -> client.connect(endUri));
+ Throwable root = ExceptionUtil.getRootCause(ce);
+ assertTrue(root instanceof UpgradeRejectedException);
+ assertTrue(root.getMessage().contains("401"));
+ }
+ try (WSClient client = new WSClient(vertx)) {
+ client.connect(basicAuth("admin", "admin"), endUri);
+ client.waitForMessages(1);
+ assertEquals("ready", client.getMessages().get(0).toString());
+ client.sendAndAwait("hello");
+ client.waitForMessages(2);
+ assertEquals("hello", client.getMessages().get(1).toString());
+ }
+ try (WSClient client = new WSClient(vertx)) {
+ client.connect(basicAuth("user", "user"), endUri);
+ client.waitForMessages(1);
+ assertEquals("ready", client.getMessages().get(0).toString());
+ client.sendAndAwait("hello");
+ client.waitForMessages(2);
+ assertEquals("forbidden:user", client.getMessages().get(1).toString());
+ }
+ }
+
+ static WebSocketConnectOptions basicAuth(String username, String password) {
+ return new WebSocketConnectOptions().addHeader(HttpHeaders.AUTHORIZATION.toString(),
+ new UsernamePasswordCredentials(username, password).applyHttpChallenge(null).toHttpAuthorization());
+ }
+
+}
diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/UserService.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/UserService.java
new file mode 100644
index 00000000000000..b8e80453145117
--- /dev/null
+++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/security/UserService.java
@@ -0,0 +1,14 @@
+package io.quarkus.websockets.next.test.security;
+
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@RolesAllowed("user")
+@ApplicationScoped
+public class UserService {
+
+ public String ping() {
+ return "" + 42;
+ }
+
+}
diff --git a/extensions/websockets-next/runtime/pom.xml b/extensions/websockets-next/runtime/pom.xml
index 76f218d21b1254..d913689652388f 100644
--- a/extensions/websockets-next/runtime/pom.xml
+++ b/extensions/websockets-next/runtime/pom.xml
@@ -26,6 +26,11 @@
io.quarkus
quarkus-jackson
+
+
+ io.quarkus.security
+ quarkus-security
+
org.junit.jupiter
diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/ContextSupport.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/ContextSupport.java
index 0b018b6fe2eafd..b36d4dc834b3e0 100644
--- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/ContextSupport.java
+++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/ContextSupport.java
@@ -36,7 +36,6 @@ void start() {
void start(ContextState requestContextState) {
LOG.debugf("Start contexts: %s", connection);
startSession();
- // Activate a new request context
requestContext.activate(requestContextState);
}
diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java
index 85ab430d8dd525..e8ed61d23620ce 100644
--- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java
+++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/Endpoints.java
@@ -10,6 +10,9 @@
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InjectableContext;
+import io.quarkus.security.AuthenticationFailedException;
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.UnauthorizedException;
import io.quarkus.websockets.next.WebSocketException;
import io.quarkus.websockets.next.runtime.WebSocketSessionContext.SessionContextState;
import io.smallrye.mutiny.Multi;
@@ -25,7 +28,8 @@ class Endpoints {
private static final Logger LOG = Logger.getLogger(Endpoints.class);
static void initialize(Vertx vertx, ArcContainer container, Codecs codecs, WebSocketConnectionBase connection,
- WebSocketBase ws, String generatedEndpointClass, Optional autoPingInterval, Runnable onClose) {
+ WebSocketBase ws, String generatedEndpointClass, Optional autoPingInterval,
+ SecuritySupport securitySupport, Runnable onClose) {
Context context = vertx.getOrCreateContext();
@@ -38,7 +42,8 @@ static void initialize(Vertx vertx, ArcContainer container, Codecs codecs, WebSo
container.requestContext());
// Create an endpoint that delegates callbacks to the endpoint bean
- WebSocketEndpoint endpoint = createEndpoint(generatedEndpointClass, context, connection, codecs, contextSupport);
+ WebSocketEndpoint endpoint = createEndpoint(generatedEndpointClass, context, connection, codecs, contextSupport,
+ securitySupport);
// A broadcast processor is only needed if Multi is consumed by the callback
BroadcastProcessor