diff --git a/http/servlet-undertow/pom.xml b/http/servlet-undertow/pom.xml
index bdf9f165c..49398c31c 100644
--- a/http/servlet-undertow/pom.xml
+++ b/http/servlet-undertow/pom.xml
@@ -19,5 +19,9 @@
io.quarkus
quarkus-micrometer
+
+ io.quarkus
+ quarkus-security
+
diff --git a/http/servlet-undertow/src/main/java/io/quarkus/ts/http/undertow/security/ServletBasicAuthIdentityProvider.java b/http/servlet-undertow/src/main/java/io/quarkus/ts/http/undertow/security/ServletBasicAuthIdentityProvider.java
new file mode 100644
index 000000000..9ab2fa2d2
--- /dev/null
+++ b/http/servlet-undertow/src/main/java/io/quarkus/ts/http/undertow/security/ServletBasicAuthIdentityProvider.java
@@ -0,0 +1,59 @@
+package io.quarkus.ts.http.undertow.security;
+
+import java.util.Set;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.control.ActivateRequestContext;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.security.identity.AuthenticationRequestContext;
+import io.quarkus.security.identity.IdentityProvider;
+import io.quarkus.security.identity.SecurityIdentity;
+import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
+import io.quarkus.security.runtime.QuarkusPrincipal;
+import io.quarkus.security.runtime.QuarkusSecurityIdentity;
+import io.smallrye.mutiny.Uni;
+
+@ApplicationScoped
+public class ServletBasicAuthIdentityProvider implements IdentityProvider {
+ @Override
+ public Class getRequestType() {
+ return UsernamePasswordAuthenticationRequest.class;
+ }
+
+ @Override
+ public Uni authenticate(UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationRequest,
+ AuthenticationRequestContext authenticationRequestContext) {
+ return authenticationRequestContext.runBlocking(() -> withIdentity(usernamePasswordAuthenticationRequest));
+ }
+
+ @ActivateRequestContext
+ SecurityIdentity withIdentity(UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationRequest) {
+ final SecurityIdentity identity;
+ var username = usernamePasswordAuthenticationRequest.getUsername();
+ var isPablo = "Pablo".equals(username);
+ if (isPablo || "Rocky".equals(username)) {
+
+ if (!Arc.container().requestContext().isActive()) {
+ throw new IllegalStateException("The request scope should be active");
+ }
+
+ final Set roles;
+ if (isPablo) {
+ roles = Set.of("granados");
+ } else {
+ // unauthorized
+ roles = Set.of();
+ }
+ identity = QuarkusSecurityIdentity
+ .builder()
+ .setPrincipal(new QuarkusPrincipal(username))
+ .addRoles(roles)
+ .build();
+ } else {
+ // unauthenticated
+ identity = null;
+ }
+ return identity;
+ }
+}
diff --git a/http/servlet-undertow/src/main/java/io/quarkus/ts/http/undertow/servlets/SecuredWorld.java b/http/servlet-undertow/src/main/java/io/quarkus/ts/http/undertow/servlets/SecuredWorld.java
new file mode 100644
index 000000000..b881e2ee9
--- /dev/null
+++ b/http/servlet-undertow/src/main/java/io/quarkus/ts/http/undertow/servlets/SecuredWorld.java
@@ -0,0 +1,40 @@
+package io.quarkus.ts.http.undertow.servlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.HttpConstraint;
+import jakarta.servlet.annotation.ServletSecurity;
+import jakarta.servlet.annotation.WebInitParam;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.jboss.logging.Logger;
+
+import io.vertx.ext.web.RoutingContext;
+
+@ServletSecurity(@HttpConstraint(rolesAllowed = { "pablo", "gonzalez", "granados" }))
+@WebServlet(name = "SecuredWorldServlet", urlPatterns = "/secured", initParams = {
+ @WebInitParam(name = "message", value = "A secured message") })
+@ApplicationScoped
+public class SecuredWorld extends HttpServlet {
+
+ private static final Logger LOG = Logger.getLogger(SecuredWorld.class);
+
+ @Inject
+ RoutingContext routingContext;
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+ LOG.info(req.getSession().getId());
+ PrintWriter writer = resp.getWriter();
+ writer.write(routingContext.queryParam("secured-servlet-key").get(0));
+ writer.close();
+ }
+
+}
diff --git a/http/servlet-undertow/src/main/resources/application.properties b/http/servlet-undertow/src/main/resources/application.properties
index a46cb34e6..af20738f6 100644
--- a/http/servlet-undertow/src/main/resources/application.properties
+++ b/http/servlet-undertow/src/main/resources/application.properties
@@ -1,4 +1,6 @@
quarkus.http.root-path=/app
quarkus.servlet.context-path=/servlet
-quarkus.micrometer.export.json.enabled=true
\ No newline at end of file
+quarkus.micrometer.export.json.enabled=true
+
+quarkus.http.auth.basic=true
diff --git a/http/servlet-undertow/src/test/java/io/quarkus/ts/http/undertow/HttpServletWithSessionListenerIT.java b/http/servlet-undertow/src/test/java/io/quarkus/ts/http/undertow/HttpServletWithSessionListenerIT.java
index fc5a2fb95..e36219ff4 100644
--- a/http/servlet-undertow/src/test/java/io/quarkus/ts/http/undertow/HttpServletWithSessionListenerIT.java
+++ b/http/servlet-undertow/src/test/java/io/quarkus/ts/http/undertow/HttpServletWithSessionListenerIT.java
@@ -9,17 +9,25 @@
import org.apache.http.HttpStatus;
import org.apache.http.util.Asserts;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
import io.quarkus.test.scenarios.QuarkusScenario;
import io.restassured.RestAssured;
+import io.restassured.response.ValidatableResponse;
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@QuarkusScenario
public class HttpServletWithSessionListenerIT {
static final Duration ACTIVE_SESSION_TIMEOUT = Duration.ofMinutes(2);
static final Duration REST_ASSURANCE_POLL_INTERVAL = Duration.ofSeconds(1);
+ @Order(1)
@Test
public void sessionEviction() {
int activeSessions = 20;
@@ -28,11 +36,39 @@ public void sessionEviction() {
thenWaitToEvictSessionsAndCheckActiveSessionsEqualTo(0);
}
+ @Tag("QUARKUS-2819")
+ @Order(2)
+ @Test
+ public void sessionSecured() {
+ // main objection is to test that CDI request scope can be activated during auth without
+ // having issue to use request scope later during processing
+ // session secured portion of this test is in order to stick to a test class theme
+ thenMakeSecuredWorldQuery("Rambo", 401);
+ thenCheckActiveSessionsEqualTo(0);
+ thenMakeSecuredWorldQuery("Rocky", 403);
+ thenCheckActiveSessionsEqualTo(0);
+ thenMakeSecuredWorldQuery("Pablo", 200).body(Matchers.is("secured-servlet-value"));
+ thenCheckActiveSessionsEqualTo(1);
+ }
+
private double getActiveSessions() {
- return (Double) RestAssured.given().when()
+ var activeSessions = (Double) RestAssured.given().when()
.get("/app/q/metrics")
.then()
.statusCode(HttpStatus.SC_OK).extract().as(Map.class).get(GAUGE_ACTIVE_SESSION);
+ if (activeSessions == null) {
+ return 0;
+ }
+ return activeSessions;
+ }
+
+ private ValidatableResponse thenMakeSecuredWorldQuery(String user, int httpStatus) {
+ return RestAssured.given().when()
+ .auth().basic(user, user)
+ .queryParam("secured-servlet-key", "secured-servlet-value")
+ .get("/app/servlet/secured")
+ .then()
+ .statusCode(httpStatus);
}
private void thenMakeHelloWorldQuery(int requestAmount) {