This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch backport/27718-to-camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 58e0b90ebd80743d7cbdc77f308513225a311819
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Oct 13 11:37:38 2026 +0000

    [backport camel-4.22.x] CAMEL-25568: camel-oauth - make 
OAuthBearerTokenProcessor stateless and read credentials from HTTP request 
headers
---
 components/camel-oauth/pom.xml                     |  22 ++
 .../org/apache/camel/oauth/HttpRequestHeaders.java | 124 ++++++++++
 .../apache/camel/oauth/InMemorySessionStore.java   |  14 +-
 .../camel/oauth/NettyHttpRequestHeaders.java       |  38 ++++
 .../main/java/org/apache/camel/oauth/OAuth.java    |   7 +
 .../camel/oauth/OAuthBearerTokenProcessor.java     |  25 ++-
 .../camel/oauth/ServletHttpRequestHeaders.java     |  81 +++++++
 .../camel/oauth/VertxHttpRequestHeaders.java       |  38 ++++
 .../http/springboot/PlatformHttpMessage.java       |  43 ++++
 .../oauth/AbstractOAuthRequestHeadersTest.java     | 249 +++++++++++++++++++++
 .../apache/camel/oauth/HttpRequestHeadersTest.java | 168 ++++++++++++++
 .../camel/oauth/OAuthBearerTokenProcessorTest.java |  85 +++++++
 .../oauth/OAuthRequestHeadersNettyHttpTest.java    |  43 ++++
 .../oauth/OAuthRequestHeadersServletTest.java      |  66 ++++++
 .../oauth/OAuthRequestHeadersUndertowTest.java     |  59 +++++
 .../camel/oauth/OAuthRequestHeadersVertxTest.java  |  45 ++++
 .../java/org/apache/camel/oauth/StubOAuth.java     |  74 ++++++
 17 files changed, 1173 insertions(+), 8 deletions(-)

diff --git a/components/camel-oauth/pom.xml b/components/camel-oauth/pom.xml
index bd374c4192fa..1b0373dda78a 100644
--- a/components/camel-oauth/pom.xml
+++ b/components/camel-oauth/pom.xml
@@ -66,6 +66,23 @@
             <artifactId>camel-platform-http-main</artifactId>
             <scope>provided</scope>
         </dependency>
+        <!-- to read the request headers of the servlet based and the netty 
http consumers -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-http-common</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <version>${jakarta-servlet-api-version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-netty-http</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents.core5</groupId>
             <artifactId>httpcore5</artifactId>
@@ -89,6 +106,11 @@
             <artifactId>camel-servlet</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-undertow</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>io.undertow</groupId>
             <artifactId>undertow-servlet</artifactId>
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/HttpRequestHeaders.java
 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/HttpRequestHeaders.java
new file mode 100644
index 000000000000..3cf1fe56e80a
--- /dev/null
+++ 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/HttpRequestHeaders.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reads a header of the HTTP request a message was created from.
+ * <p>
+ * HTTP consumers also map query and form parameters into the message headers, 
so a message header is not necessarily
+ * the request header of that name. Credentials are therefore read from the 
HTTP request itself whenever the message
+ * carries it: platform-http on Vert.x and on Spring Boot, the servlet based 
consumers (camel-servlet, camel-jetty) and
+ * camel-netty-http. The message types are matched by name, and a transport 
specific class is only loaded for a message
+ * of its own type, so none of these transports has to be on the classpath.
+ * <p>
+ * Any other message falls back to the message headers, leaving out a value 
that also appears in the query string of the
+ * request: the consumer may have mapped it from a query parameter, and a 
credential does not belong in a url anyway
+ * (RFC 6750 section 5.3).
+ */
+final class HttpRequestHeaders {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(HttpRequestHeaders.class);
+
+    private static final String VERTX_HTTP_MESSAGE = 
"org.apache.camel.component.platform.http.vertx.HttpMessage";
+    private static final String SERVLET_HTTP_MESSAGE = 
"org.apache.camel.http.common.HttpMessage";
+    private static final String NETTY_HTTP_MESSAGE = 
"org.apache.camel.component.netty.http.NettyHttpMessage";
+    // from camel-platform-http-starter, which camel-oauth does not depend on; 
it gives the servlet request
+    private static final String SPRING_BOOT_PLATFORM_HTTP_MESSAGE
+            = 
"org.apache.camel.component.platform.http.springboot.PlatformHttpMessage";
+
+    private HttpRequestHeaders() {
+    }
+
+    /**
+     * The values of the named header of the HTTP request that the message 
comes from.
+     *
+     * @param  message the message
+     * @param  name    the header name, matched ignoring case
+     * @return         the header values, possibly none
+     */
+    static List<String> values(Message message, String name) {
+        return requestHeaderValues(message, name).orElseGet(() -> 
messageHeaderValues(message, name));
+    }
+
+    private static Optional<List<String>> requestHeaderValues(Message message, 
String name) {
+        for (Class<?> type = message.getClass(); type != null; type = 
type.getSuperclass()) {
+            if (VERTX_HTTP_MESSAGE.equals(type.getName())) {
+                return VertxHttpRequestHeaders.values(message, name);
+            }
+            if (SERVLET_HTTP_MESSAGE.equals(type.getName())) {
+                return ServletHttpRequestHeaders.values(message, name);
+            }
+            if (NETTY_HTTP_MESSAGE.equals(type.getName())) {
+                return NettyHttpRequestHeaders.values(message, name);
+            }
+            if (SPRING_BOOT_PLATFORM_HTTP_MESSAGE.equals(type.getName())) {
+                return 
ServletHttpRequestHeaders.valuesOfSpringBootMessage(message, name);
+            }
+        }
+        return Optional.empty();
+    }
+
+    private static List<String> messageHeaderValues(Message message, String 
name) {
+        List<String> values = new ArrayList<>();
+        Object header = message.getHeader(name);
+        if (header instanceof Iterable<?> iterable) {
+            iterable.forEach(value -> {
+                if (value != null) {
+                    values.add(value.toString());
+                }
+            });
+        } else if (header != null) {
+            values.add(header.toString());
+        }
+
+        String query = message.getHeader(Exchange.HTTP_RAW_QUERY, 
String.class);
+        if (query == null) {
+            query = message.getHeader(Exchange.HTTP_QUERY, String.class);
+        }
+        if (query != null && !query.isEmpty() && !values.isEmpty()) {
+            String rawQuery = query;
+            String decodedQuery = decode(rawQuery);
+            if (decodedQuery == null) {
+                // a query string that cannot be decoded cannot be checked 
either, so none of the values is used
+                LOG.debug("The {} message header is not used: the query string 
of the request cannot be decoded", name);
+                values.clear();
+            } else if (values.removeIf(value -> rawQuery.contains(value) || 
decodedQuery.contains(value))) {
+                LOG.debug("A {} message header value is not used: the query 
string of the request holds it too", name);
+            }
+        }
+        return values;
+    }
+
+    private static String decode(String query) {
+        try {
+            return URLDecoder.decode(query, StandardCharsets.UTF_8);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+}
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java
 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java
index 22244eae6c90..70c10047455c 100644
--- 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java
+++ 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java
@@ -80,13 +80,13 @@ public class InMemorySessionStore implements 
OAuthSessionStore {
     private Map<String, String> getCookies(Exchange exchange) {
 
         var msg = exchange.getMessage();
-        var maybeCookie = Optional.ofNullable(msg.getHeader("Cookie"));
+        var maybeCookie = getCookieHeader(msg);
         if (maybeCookie.isEmpty()) {
             log.warn("No Cookie in HTTP request");
             return Map.of();
         }
 
-        var value = maybeCookie.get().toString();
+        var value = maybeCookie.get();
         var cookieMap = Arrays.stream(value.split(";"))
                 .map(String::trim)
                 .map(s -> s.split("=", 2))
@@ -97,6 +97,16 @@ public class InMemorySessionStore implements 
OAuthSessionStore {
         return cookieMap;
     }
 
+    /**
+     * The Cookie header of the request, read with {@link HttpRequestHeaders} 
because the message headers also hold the
+     * query and form parameters of the request.
+     */
+    private static Optional<String> getCookieHeader(Message msg) {
+        var values = HttpRequestHeaders.values(msg, "Cookie");
+        // HTTP/2 may send the cookies in several Cookie headers (RFC 9113 
section 8.2.3)
+        return values.isEmpty() ? Optional.empty() : 
Optional.of(String.join("; ", values));
+    }
+
     private void setSessionCookie(Message msg, OAuthSession session) {
         var sessionId = session.getSessionId();
         var cookieId = "%s=%s".formatted(CAMEL_OAUTH_COOKIE, sessionId);
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/NettyHttpRequestHeaders.java
 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/NettyHttpRequestHeaders.java
new file mode 100644
index 000000000000..914e3e6f9420
--- /dev/null
+++ 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/NettyHttpRequestHeaders.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.camel.Message;
+import org.apache.camel.component.netty.http.NettyHttpMessage;
+
+/**
+ * Reads request headers from a camel-netty-http message. Only loaded for a 
message of that type, see
+ * {@link HttpRequestHeaders}.
+ */
+final class NettyHttpRequestHeaders {
+
+    private NettyHttpRequestHeaders() {
+    }
+
+    static Optional<List<String>> values(Message message, String name) {
+        var request = ((NettyHttpMessage) message).getHttpRequest();
+        return request != null ? Optional.of(request.headers().getAll(name)) : 
Optional.empty();
+    }
+}
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java
index a88f472c6b54..3d2ce2583b98 100644
--- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java
+++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuth.java
@@ -43,6 +43,13 @@ public abstract class OAuth {
     //
     public static final String CAMEL_OAUTH_COOKIE = "camel.oauth.session";
 
+    // Camel OAuth Exchange Properties
+    //
+    /**
+     * The {@link UserProfile} that {@link OAuthBearerTokenProcessor} 
authenticated for the current request.
+     */
+    public static final String CAMEL_OAUTH_USER_PROFILE = 
"CamelOAuthUserProfile";
+
     protected OAuthConfig config;
     protected OAuthSessionStore sessionStore;
 
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
index 337a29968946..cf9ec132a470 100644
--- 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
+++ 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthBearerTokenProcessor.java
@@ -20,6 +20,14 @@ import org.apache.camel.Exchange;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_USER_PROFILE;
+
+/**
+ * Authenticates the bearer token of the request, as sent in its {@code 
Authorization} header (RFC 6750 section 2.1).
+ * <p>
+ * Bearer authentication is per request: the authenticated {@link UserProfile} 
is set as the
+ * {@link OAuth#CAMEL_OAUTH_USER_PROFILE} exchange property, and no {@link 
OAuthSession} is created or updated.
+ */
 public class OAuthBearerTokenProcessor extends AbstractOAuthProcessor {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -33,14 +41,20 @@ public class OAuthBearerTokenProcessor extends 
AbstractOAuthProcessor {
 
         // Validate Authorization header
         //
-        var authHeader = msg.getHeader("Authorization", String.class);
-        if (authHeader == null) {
+        var authHeaders = HttpRequestHeaders.values(msg, "Authorization");
+        if (authHeaders.isEmpty()) {
             log.error("No Authorization header in request");
             rejectUnauthorized(exchange, "Authorization header");
             return;
         }
 
-        var toks = authHeader.split(" ");
+        if (authHeaders.size() > 1) {
+            log.error("Invalid Authorization header: expected exactly one, got 
{}", authHeaders.size());
+            rejectUnauthorized(exchange, "Invalid Authorization header");
+            return;
+        }
+
+        var toks = authHeaders.get(0).split(" ");
         if (toks.length != 2 || !"Bearer".equals(toks[0])) {
             log.error("Invalid Authorization header");
             rejectUnauthorized(exchange, "Invalid Authorization header");
@@ -59,10 +73,9 @@ public class OAuthBearerTokenProcessor extends 
AbstractOAuthProcessor {
         var access_token = toks[1];
         var userProfile = oauth.authenticate(new 
TokenCredentials(access_token));
 
-        // Get or create the OAuthSession
+        // The token authenticates this request only, so the profile goes on 
the exchange and not into an OAuthSession
         //
-        var session = oauth.getOrCreateSession(exchange);
-        session.putUserProfile(userProfile);
+        exchange.setProperty(CAMEL_OAUTH_USER_PROFILE, userProfile);
 
         log.info("Authenticated {}", userProfile.subject());
         userProfile.logDetails();
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/ServletHttpRequestHeaders.java
 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/ServletHttpRequestHeaders.java
new file mode 100644
index 000000000000..d9c0947413ca
--- /dev/null
+++ 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/ServletHttpRequestHeaders.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.http.common.HttpMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reads request headers from a message of the servlet based HTTP consumers. 
Only loaded for a message of such a type,
+ * see {@link HttpRequestHeaders}.
+ */
+final class ServletHttpRequestHeaders {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ServletHttpRequestHeaders.class);
+
+    private ServletHttpRequestHeaders() {
+    }
+
+    /**
+     * Reads the headers of the request of a camel-http-common {@link 
HttpMessage} (camel-servlet, camel-jetty).
+     */
+    static Optional<List<String>> values(Message message, String name) {
+        return values(((HttpMessage) message).getRequest(), name);
+    }
+
+    /**
+     * Reads the headers of the request of a platform-http message on Spring 
Boot. The request comes from the
+     * {@code getRequest()} method of the message, which camel-oauth does not 
depend on, or else from the
+     * {@link Exchange#HTTP_SERVLET_REQUEST} header that the message also 
sets, but that a route may remove. Without a
+     * request no value is read at all, rather than the message headers, which 
also hold the form parameters.
+     */
+    static Optional<List<String>> valuesOfSpringBootMessage(Message message, 
String name) {
+        var request = springBootRequest(message);
+        if (request == null) {
+            LOG.debug("No HTTP request on the platform-http message, so no {} 
header is read", name);
+            return Optional.of(List.of());
+        }
+        return Optional.of(values(request, name).orElse(List.of()));
+    }
+
+    private static HttpServletRequest springBootRequest(Message message) {
+        try {
+            if (message.getClass().getMethod("getRequest").invoke(message) 
instanceof HttpServletRequest request) {
+                return request;
+            }
+        } catch (ReflectiveOperationException | SecurityException e) {
+            LOG.debug("Cannot call getRequest() on {}, reading the {} header 
instead", message.getClass().getName(),
+                    Exchange.HTTP_SERVLET_REQUEST, e);
+        }
+        return message.getHeader(Exchange.HTTP_SERVLET_REQUEST) instanceof 
HttpServletRequest request ? request : null;
+    }
+
+    private static Optional<List<String>> values(HttpServletRequest request, 
String name) {
+        // a container that does not allow access to the headers returns null
+        var values = request != null ? request.getHeaders(name) : null;
+        return values != null ? Optional.of(Collections.list(values)) : 
Optional.empty();
+    }
+}
diff --git 
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/VertxHttpRequestHeaders.java
 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/VertxHttpRequestHeaders.java
new file mode 100644
index 000000000000..48c23920bd43
--- /dev/null
+++ 
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/VertxHttpRequestHeaders.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.camel.Message;
+import org.apache.camel.component.platform.http.vertx.HttpMessage;
+
+/**
+ * Reads request headers from a platform-http message on Vert.x. Only loaded 
for a message of that type, see
+ * {@link HttpRequestHeaders}.
+ */
+final class VertxHttpRequestHeaders {
+
+    private VertxHttpRequestHeaders() {
+    }
+
+    static Optional<List<String>> values(Message message, String name) {
+        var request = ((HttpMessage) message).getRequest();
+        return request != null ? Optional.of(request.headers().getAll(name)) : 
Optional.empty();
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
new file mode 100644
index 000000000000..5f9aa36db903
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.platform.http.springboot;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.support.DefaultMessage;
+
+/**
+ * Test stand-in for the message of platform-http on Spring Boot 
(camel-platform-http-starter, in camel-spring-boot),
+ * which camel-oauth does not depend on. It has the same name and, like the 
real one, gives the servlet request from
+ * {@link #getRequest()} and holds it in the {@link 
Exchange#HTTP_SERVLET_REQUEST} header.
+ */
+public class PlatformHttpMessage extends DefaultMessage {
+
+    private final HttpServletRequest request;
+
+    public PlatformHttpMessage(CamelContext camelContext, HttpServletRequest 
request) {
+        super(camelContext);
+        this.request = request;
+        setHeader(Exchange.HTTP_SERVLET_REQUEST, request);
+    }
+
+    public HttpServletRequest getRequest() {
+        return request;
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/AbstractOAuthRequestHeadersTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/AbstractOAuthRequestHeadersTest.java
new file mode 100644
index 000000000000..2fa2eeb6643f
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/AbstractOAuthRequestHeadersTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * The OAuth processors behind a real HTTP consumer: the bearer token and the 
session cookie must come from the
+ * {@code Authorization} and {@code Cookie} request headers, although the 
consumer also maps the query and form
+ * parameters of the request into the message headers. The identity provider 
is {@link StubOAuth}.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+abstract class AbstractOAuthRequestHeadersTest {
+
+    private final HttpClient client = 
HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER).build();
+
+    private int port;
+    private CamelContext context;
+    private OAuth oauth;
+
+    /**
+     * Starts the HTTP server, before the routes are added to the context.
+     */
+    abstract void startServer(CamelContext context, int port) throws Exception;
+
+    abstract void stopServer() throws Exception;
+
+    /**
+     * The consumer endpoint uri for the given path.
+     */
+    abstract String endpointUri(String path);
+
+    /**
+     * Whether the processors can read the HTTP request of this consumer. When 
they cannot, they read the message
+     * headers, without the values that also appear in the query string.
+     */
+    boolean readsTheRequestHeaders() {
+        return true;
+    }
+
+    @BeforeAll
+    void start() throws Exception {
+        port = AvailablePortFinder.getNextAvailable();
+        context = new DefaultCamelContext();
+        
context.getPropertiesComponent().addInitialProperty(OAuth.CAMEL_OAUTH_REDIRECT_URI,
 "https://app.example/auth";);
+        oauth = new StubOAuth();
+        context.getRegistry().bind(OAuth.class.getName(), oauth);
+        startServer(context, port);
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from(endpointUri("/api"))
+                        .process(new OAuthBearerTokenProcessor())
+                        .process(e -> e.getMessage().setBody("api:" + 
bearerSubject(e)));
+                from(endpointUri("/auth"))
+                        .process(new OAuthCodeFlowCallback());
+                from(endpointUri("/app"))
+                        .process(new OAuthCodeFlowProcessor())
+                        .process(e -> e.getMessage().setBody("app:" + 
sessionSubject(e)));
+            }
+        });
+        context.start();
+    }
+
+    @AfterAll
+    void stop() throws Exception {
+        if (context != null) {
+            context.stop();
+        }
+        stopServer();
+    }
+
+    @Test
+    void bearerTokenIsReadFromTheAuthorizationHeader() throws Exception {
+        var response = get("/api", null, "Bearer " + StubOAuth.ALICE_TOKEN);
+
+        assertEquals(200, response.statusCode());
+        assertEquals("api:alice", response.body());
+        assertNull(setCookie(response), "a bearer request does not start a 
session");
+    }
+
+    @Test
+    void bearerTokenIsNotReadFromAQueryParameter() throws Exception {
+        var response = get("/api?Authorization=Bearer%20" + 
StubOAuth.ALICE_TOKEN, null, null);
+
+        assertEquals(401, response.statusCode());
+        assertNull(setCookie(response));
+    }
+
+    @Test
+    void bearerTokenIsNotReadFromAFormParameter() throws Exception {
+        assumeTrue(readsTheRequestHeaders(),
+                "the request of this consumer cannot be read from the route, 
so its form parameters are message headers");
+
+        var request = request("/api")
+                .header("Content-Type", "application/x-www-form-urlencoded")
+                
.POST(HttpRequest.BodyPublishers.ofString("Authorization=Bearer%20" + 
StubOAuth.ALICE_TOKEN))
+                .build();
+        var response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(401, response.statusCode());
+        assertNull(setCookie(response));
+    }
+
+    @Test
+    void authorizationHeaderIsReadWhateverTheQueryStringHolds() throws 
Exception {
+        assumeTrue(readsTheRequestHeaders(),
+                "the request of this consumer cannot be read from the route, 
so a value in the query string is left out");
+
+        var response = get("/api?note=Bearer%20" + StubOAuth.ALICE_TOKEN, 
null, "Bearer " + StubOAuth.ALICE_TOKEN);
+
+        assertEquals(200, response.statusCode());
+        assertEquals("api:alice", response.body());
+    }
+
+    @Test
+    void aQueryParameterDoesNotReplaceTheAuthorizationHeader() throws 
Exception {
+        var response = get("/api?Authorization=Bearer%20" + 
StubOAuth.BOB_TOKEN, null, "Bearer " + StubOAuth.ALICE_TOKEN);
+
+        assertEquals(200, response.statusCode());
+        assertEquals("api:alice", response.body());
+    }
+
+    @Test
+    void severalAuthorizationHeadersAreRejected() throws Exception {
+        var request = request("/api")
+                .header("Authorization", "Bearer " + StubOAuth.ALICE_TOKEN)
+                .header("Authorization", "Bearer " + StubOAuth.BOB_TOKEN)
+                .GET()
+                .build();
+        var response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(401, response.statusCode());
+    }
+
+    @Test
+    void sessionCookieIsReadFromTheCookieHeader() throws Exception {
+        var cookie = loginAsBob();
+
+        var response = get("/app", cookie, null);
+
+        assertEquals(200, response.statusCode());
+        assertEquals("app:bob", response.body());
+    }
+
+    @Test
+    void sessionCookieIsNotReadFromAQueryParameter() throws Exception {
+        var cookie = loginAsBob();
+
+        var response = get("/app?Cookie=" + URLEncoder.encode(cookie, 
StandardCharsets.UTF_8), null, null);
+
+        assertEquals(302, response.statusCode(), "a request without a session 
cookie is sent to the login");
+    }
+
+    @Test
+    void bearerRequestLeavesTheSessionUnchanged() throws Exception {
+        var cookie = loginAsBob();
+
+        var response = get("/api", cookie, "Bearer " + StubOAuth.ALICE_TOKEN);
+
+        assertEquals(200, response.statusCode());
+        assertEquals("api:alice", response.body());
+        assertNull(setCookie(response));
+        assertEquals("app:bob", get("/app", cookie, null).body());
+    }
+
+    /**
+     * Runs the authorization code flow and returns the session cookie the 
browser holds afterwards.
+     */
+    private String loginAsBob() throws Exception {
+        var start = get("/app", null, null);
+        assertEquals(302, start.statusCode());
+        var sessionCookie = setCookie(start);
+        assertNotNull(sessionCookie);
+        var cookie = sessionCookie.substring(0, sessionCookie.indexOf(';'));
+        var location = start.headers().firstValue("Location").orElseThrow();
+        var state = location.substring(location.indexOf("state=") + 
"state=".length());
+
+        var callback = get("/auth?code=" + StubOAuth.BOB_CODE + "&state=" + 
URLEncoder.encode(state, StandardCharsets.UTF_8),
+                cookie, null);
+        assertEquals(302, callback.statusCode());
+        return cookie;
+    }
+
+    HttpResponse<String> get(String pathAndQuery, String cookie, String 
authorization) throws Exception {
+        var builder = request(pathAndQuery).GET();
+        if (cookie != null) {
+            builder.header("Cookie", cookie);
+        }
+        if (authorization != null) {
+            builder.header("Authorization", authorization);
+        }
+        return client.send(builder.build(), 
HttpResponse.BodyHandlers.ofString());
+    }
+
+    private HttpRequest.Builder request(String pathAndQuery) {
+        return HttpRequest.newBuilder(URI.create("http://127.0.0.1:"; + port + 
pathAndQuery));
+    }
+
+    private static String setCookie(HttpResponse<?> response) {
+        return response.headers().firstValue("Set-Cookie").orElse(null);
+    }
+
+    private static String bearerSubject(Exchange exchange) {
+        var profile = exchange.getProperty(OAuth.CAMEL_OAUTH_USER_PROFILE, 
UserProfile.class);
+        return profile != null ? profile.subject() : "none";
+    }
+
+    private String sessionSubject(Exchange exchange) {
+        return oauth.getSession(exchange)
+                .flatMap(OAuthSession::getUserProfile)
+                .map(UserProfile::subject)
+                .orElse("none");
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/HttpRequestHeadersTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/HttpRequestHeadersTest.java
new file mode 100644
index 000000000000..07fb18aa50b6
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/HttpRequestHeadersTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.component.platform.http.springboot.PlatformHttpMessage;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A message that gives no access to the HTTP request: the header values are 
read from the message, without the values
+ * that also appear in the query string of the request. And the message of 
platform-http on Spring Boot, which holds the
+ * servlet request in a header.
+ */
+class HttpRequestHeadersTest {
+
+    @Test
+    void messageHeaderIsReadWhenThereIsNoQueryString() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new DefaultExchange(context).getMessage();
+            message.setHeader("Authorization", "Bearer token");
+
+            assertEquals(List.of("Bearer token"), 
HttpRequestHeaders.values(message, "Authorization"));
+        }
+    }
+
+    @Test
+    void messageHeaderIsReadWhenTheQueryStringDoesNotHoldIt() throws Exception 
{
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new DefaultExchange(context).getMessage();
+            message.setHeader("Authorization", "Bearer token");
+            message.setHeader(Exchange.HTTP_RAW_QUERY, "page=2&sort=name");
+
+            assertEquals(List.of("Bearer token"), 
HttpRequestHeaders.values(message, "Authorization"));
+        }
+    }
+
+    @Test
+    void valueThatTheQueryStringHoldsIsLeftOut() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new DefaultExchange(context).getMessage();
+            message.setHeader("Authorization", "Bearer token");
+            message.setHeader(Exchange.HTTP_RAW_QUERY, 
"Authorization=Bearer%20token");
+
+            assertEquals(List.of(), HttpRequestHeaders.values(message, 
"Authorization"));
+        }
+    }
+
+    @Test
+    void valueThatTheUndecodedQueryStringHoldsIsLeftOut() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new DefaultExchange(context).getMessage();
+            message.setHeader("Cookie", "camel.oauth.session=abc");
+            message.setHeader(Exchange.HTTP_QUERY, 
"Cookie=camel.oauth.session=abc");
+
+            assertEquals(List.of(), HttpRequestHeaders.values(message, 
"Cookie"));
+        }
+    }
+
+    @Test
+    void onlyTheValueThatTheQueryStringHoldsIsLeftOut() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new DefaultExchange(context).getMessage();
+            message.setHeader("Authorization", List.of("Bearer header-token", 
"Bearer query-token"));
+            message.setHeader(Exchange.HTTP_RAW_QUERY, 
"Authorization=Bearer%20query-token");
+
+            assertEquals(List.of("Bearer header-token"), 
HttpRequestHeaders.values(message, "Authorization"));
+        }
+    }
+
+    @Test
+    void noValueIsReadWhenTheQueryStringCannotBeDecoded() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new DefaultExchange(context).getMessage();
+            message.setHeader("Authorization", "Bearer token");
+            message.setHeader(Exchange.HTTP_RAW_QUERY, "discount=100%");
+
+            assertEquals(List.of(), HttpRequestHeaders.values(message, 
"Authorization"));
+        }
+    }
+
+    @Test
+    void springBootMessageIsReadFromTheServletRequest() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new PlatformHttpMessage(
+                    context, servletRequest(Map.of("authorization", 
List.of("Bearer request-token"))));
+            // what the binding maps from a form field of the same name
+            message.setHeader("Authorization", "Bearer form-token");
+
+            assertEquals(List.of("Bearer request-token"), 
HttpRequestHeaders.values(message, "Authorization"));
+        }
+    }
+
+    @Test
+    void springBootMessageHeaderIsNotReadWhenTheRequestHasNone() throws 
Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new PlatformHttpMessage(context, 
servletRequest(Map.of()));
+            message.setHeader("Authorization", "Bearer form-token");
+
+            assertEquals(List.of(), HttpRequestHeaders.values(message, 
"Authorization"));
+        }
+    }
+
+    @Test
+    void springBootMessageIsReadWhenTheRouteRemovedTheServletRequestHeader() 
throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new PlatformHttpMessage(
+                    context, servletRequest(Map.of("authorization", 
List.of("Bearer request-token"))));
+            message.setHeader("Authorization", "Bearer form-token");
+            // as the hardening checklist of the security model advises for 
untrusted callers
+            message.removeHeaders("Camel*");
+
+            assertEquals(List.of("Bearer request-token"), 
HttpRequestHeaders.values(message, "Authorization"));
+        }
+    }
+
+    @Test
+    void springBootMessageWithoutARequestReadsNothing() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            Message message = new PlatformHttpMessage(context, null);
+            message.setHeader("Authorization", "Bearer form-token");
+
+            assertEquals(List.of(), HttpRequestHeaders.values(message, 
"Authorization"));
+        }
+    }
+
+    /**
+     * A servlet request with the given headers, keyed by lower case name, 
that only answers {@code getHeaders}.
+     */
+    private static HttpServletRequest servletRequest(Map<String, List<String>> 
headers) {
+        return (HttpServletRequest) 
Proxy.newProxyInstance(HttpRequestHeadersTest.class.getClassLoader(),
+                new Class<?>[] { HttpServletRequest.class },
+                (proxy, method, args) -> switch (method.getName()) {
+                    case "getHeaders" -> Collections.enumeration(
+                            headers.getOrDefault(((String) 
args[0]).toLowerCase(Locale.ROOT), List.of()));
+                    case "toString" -> "HttpServletRequest" + headers;
+                    case "hashCode" -> System.identityHashCode(proxy);
+                    case "equals" -> proxy == args[0];
+                    default -> throw new 
UnsupportedOperationException(method.getName());
+                });
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthBearerTokenProcessorTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthBearerTokenProcessorTest.java
new file mode 100644
index 000000000000..3ab41a6a3a83
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthBearerTokenProcessorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Bearer authentication on a message that carries no HTTP request, so the 
processor reads the message headers.
+ */
+class OAuthBearerTokenProcessorTest {
+
+    @Test
+    void authenticatedProfileIsSetOnTheExchange() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.getRegistry().bind(OAuth.class.getName(), new StubOAuth());
+            Exchange exchange = new DefaultExchange(context);
+            exchange.getMessage().setHeader("Authorization", "Bearer " + 
StubOAuth.ALICE_TOKEN);
+
+            new OAuthBearerTokenProcessor().process(exchange);
+
+            UserProfile profile = 
exchange.getProperty(OAuth.CAMEL_OAUTH_USER_PROFILE, UserProfile.class);
+            assertNotNull(profile);
+            assertEquals("alice", profile.subject());
+            assertFalse(exchange.isRouteStop());
+        }
+    }
+
+    @Test
+    void noSessionIsCreated() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            OAuth oauth = new StubOAuth();
+            context.getRegistry().bind(OAuth.class.getName(), oauth);
+            Exchange exchange = new DefaultExchange(context);
+            exchange.getMessage().setHeader("Authorization", "Bearer " + 
StubOAuth.ALICE_TOKEN);
+
+            new OAuthBearerTokenProcessor().process(exchange);
+
+            assertTrue(oauth.getSession(exchange).isEmpty());
+            assertNull(exchange.getMessage().getHeader("Set-Cookie"));
+            
assertNull(exchange.getMessage().getHeader(OAuth.CAMEL_OAUTH_SESSION_ID));
+        }
+    }
+
+    @Test
+    void existingSessionIsLeftUnchanged() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            OAuth oauth = new StubOAuth();
+            context.getRegistry().bind(OAuth.class.getName(), oauth);
+            OAuthSession session = oauth.createSession(new 
DefaultExchange(context));
+            session.putUserProfile(oauth.authenticate(new 
TokenCredentials(StubOAuth.BOB_TOKEN)));
+
+            Exchange exchange = new DefaultExchange(context);
+            exchange.getMessage().setHeader("Cookie", OAuth.CAMEL_OAUTH_COOKIE 
+ "=" + session.getSessionId());
+            exchange.getMessage().setHeader("Authorization", "Bearer " + 
StubOAuth.ALICE_TOKEN);
+
+            new OAuthBearerTokenProcessor().process(exchange);
+
+            assertEquals("alice", 
exchange.getProperty(OAuth.CAMEL_OAUTH_USER_PROFILE, 
UserProfile.class).subject());
+            assertEquals("bob", 
session.getUserProfile().orElseThrow().subject());
+        }
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersNettyHttpTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersNettyHttpTest.java
new file mode 100644
index 000000000000..fc85816ee6b2
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersNettyHttpTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * The OAuth processors behind camel-netty-http.
+ */
+class OAuthRequestHeadersNettyHttpTest extends AbstractOAuthRequestHeadersTest 
{
+
+    private int port;
+
+    @Override
+    void startServer(CamelContext context, int port) {
+        // the consumers start the server
+        this.port = port;
+    }
+
+    @Override
+    void stopServer() {
+        // stopped with the context
+    }
+
+    @Override
+    String endpointUri(String path) {
+        return "netty-http:http://127.0.0.1:"; + port + path;
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersServletTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersServletTest.java
new file mode 100644
index 000000000000..aa801d2c57a3
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersServletTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import io.undertow.Handlers;
+import io.undertow.Undertow;
+import io.undertow.servlet.Servlets;
+import io.undertow.servlet.api.DeploymentManager;
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.servlet.CamelHttpTransportServlet;
+
+/**
+ * The OAuth processors behind camel-servlet.
+ */
+class OAuthRequestHeadersServletTest extends AbstractOAuthRequestHeadersTest {
+
+    // a servlet name of its own, so that other servlet tests of the module do 
not share the servlet registry entry
+    private static final String SERVLET_NAME = "OAuthRequestHeadersServlet";
+
+    private DeploymentManager manager;
+    private Undertow server;
+
+    @Override
+    void startServer(CamelContext context, int port) throws Exception {
+        var deploymentInfo = Servlets.deployment()
+                .setContextPath("/")
+                .setDeploymentName(SERVLET_NAME)
+                
.setClassLoader(OAuthRequestHeadersServletTest.class.getClassLoader())
+                .addServlet(Servlets.servlet(SERVLET_NAME, 
CamelHttpTransportServlet.class).addMapping("/*"));
+        manager = Servlets.newContainer().addDeployment(deploymentInfo);
+        manager.deploy();
+        var path = Handlers.path(Handlers.redirect("/")).addPrefixPath("/", 
manager.start());
+        server = Undertow.builder().addHttpListener(port, 
"127.0.0.1").setHandler(path).build();
+        server.start();
+    }
+
+    @Override
+    void stopServer() throws Exception {
+        if (manager != null) {
+            manager.stop();
+            manager.undeploy();
+        }
+        if (server != null) {
+            server.stop();
+        }
+    }
+
+    @Override
+    String endpointUri(String path) {
+        return "servlet:" + path + "?servletName=" + SERVLET_NAME;
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersUndertowTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersUndertowTest.java
new file mode 100644
index 000000000000..043bf2da45f2
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersUndertowTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.CamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * The OAuth processors behind camel-undertow, whose messages do not give 
access to the HTTP request: the processors
+ * read the message headers, without the values that also appear in the query 
string.
+ */
+class OAuthRequestHeadersUndertowTest extends AbstractOAuthRequestHeadersTest {
+
+    private int port;
+
+    @Override
+    void startServer(CamelContext context, int port) {
+        // the consumers start the server
+        this.port = port;
+    }
+
+    @Override
+    void stopServer() {
+        // stopped with the context
+    }
+
+    @Override
+    String endpointUri(String path) {
+        return "undertow:http://127.0.0.1:"; + port + path;
+    }
+
+    @Override
+    boolean readsTheRequestHeaders() {
+        return false;
+    }
+
+    @Test
+    void authorizationHeaderThatTheQueryStringAlsoHoldsIsNotRead() throws 
Exception {
+        var response = get("/api?note=Bearer%20" + StubOAuth.ALICE_TOKEN, 
null, "Bearer " + StubOAuth.ALICE_TOKEN);
+
+        assertEquals(401, response.statusCode());
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersVertxTest.java
 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersVertxTest.java
new file mode 100644
index 000000000000..993c1ca04d24
--- /dev/null
+++ 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/OAuthRequestHeadersVertxTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpServer;
+import 
org.apache.camel.component.platform.http.vertx.VertxPlatformHttpServerConfiguration;
+
+/**
+ * The OAuth processors behind platform-http on Vert.x.
+ */
+class OAuthRequestHeadersVertxTest extends AbstractOAuthRequestHeadersTest {
+
+    @Override
+    void startServer(CamelContext context, int port) throws Exception {
+        var conf = new VertxPlatformHttpServerConfiguration();
+        conf.setBindHost("127.0.0.1");
+        conf.setBindPort(port);
+        context.addService(new VertxPlatformHttpServer(conf));
+    }
+
+    @Override
+    void stopServer() {
+        // stopped with the context
+    }
+
+    @Override
+    String endpointUri(String path) {
+        return "platform-http:" + path;
+    }
+}
diff --git 
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/StubOAuth.java 
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/StubOAuth.java
new file mode 100644
index 000000000000..e078d22777b6
--- /dev/null
+++ b/components/camel-oauth/src/test/java/org/apache/camel/oauth/StubOAuth.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import com.google.gson.JsonObject;
+import org.apache.camel.CamelContext;
+
+/**
+ * An identity provider stand-in: it knows the access tokens of alice and bob, 
and one authorization code that logs bob
+ * in. Everything else is rejected.
+ */
+class StubOAuth extends OAuth {
+
+    static final String ALICE_TOKEN = "alice-token";
+    static final String BOB_TOKEN = "bob-token";
+    static final String BOB_CODE = "bob-code";
+
+    @Override
+    public void discoverOAuthConfig(CamelContext ctx) {
+        // nothing to discover
+    }
+
+    @Override
+    public UserProfile authenticate(Credentials creds) {
+        String token;
+        if (creds instanceof TokenCredentials tokenCreds) {
+            token = tokenCreds.getToken();
+        } else if (creds instanceof UserCredentials userCreds) {
+            token = userCreds.getUserProfile().accessToken().orElseThrow();
+        } else if (creds instanceof AuthCodeCredentials codeCreds && 
BOB_CODE.equals(codeCreds.getCode())) {
+            token = BOB_TOKEN;
+        } else {
+            throw new OAuthException("Unsupported credentials");
+        }
+        String user;
+        if (ALICE_TOKEN.equals(token)) {
+            user = "alice";
+        } else if (BOB_TOKEN.equals(token)) {
+            user = "bob";
+        } else {
+            throw new OAuthException("Unknown token");
+        }
+        var principal = new JsonObject();
+        principal.addProperty("access_token", token);
+        principal.addProperty("username", user);
+        var attributes = new JsonObject();
+        attributes.addProperty("exp", System.currentTimeMillis() / 1000L + 
3600);
+        return new UserProfile(principal, attributes);
+    }
+
+    @Override
+    public String buildLogoutRequestUrl(OAuthLogoutParams params) {
+        return "https://idp.example/logout";;
+    }
+
+    @Override
+    public String buildCodeFlowAuthRequestUrl(OAuthCodeFlowParams params) {
+        return "https://idp.example/auth?state="; + params.getState();
+    }
+}

Reply via email to