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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new ac50a34b96c5 CAMEL-26831: Preserve request bodies during JWT-SVID 
validation (#29638)
ac50a34b96c5 is described below

commit ac50a34b96c50bf8b0aa1752b387f6d7d978a913
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Nov 12 21:45:39 2026 +0100

    CAMEL-26831: Preserve request bodies during JWT-SVID validation (#29638)
    
    Add opt-in body-preserving JWT-SVID validation and a reusable 
SpiffeJwtSecurityPolicy. The policy exposes the immutable verified identity as 
an exchange property; producer identity headers keep their existing contract.
    
    The policy inherits the SPIFFE component configuration, with explicitly 
configured policy client/socket settings taking precedence. Failures clear 
stale identity, use sanitized authorization exceptions and log failure class 
names without SDK messages or credentials. Shared audience parsing keeps the 
configured-audience and Bearer-before-body security decisions.
    
    Includes Java/XML/YAML documentation, generated metadata and DSL, and real 
SPIRE policy tests covering request-body preservation, wrong-audience denial 
and route restart.
    
    Codex and Claude Code on behalf of oscerd.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Co-authored-by: Codex <[email protected]>
---
 .../org/apache/camel/catalog/beans.properties      |   1 +
 .../catalog/beans/SpiffeJwtSecurityPolicy.json     |  16 ++
 .../apache/camel/catalog/components/spiffe.json    |  24 +-
 .../camel/catalog/docs/spiffe-component.adoc       | 135 +++++++++
 .../spiffe/SpiffeComponentConfigurer.java          |   6 +
 .../component/spiffe/SpiffeEndpointConfigurer.java |   6 +
 .../component/spiffe/SpiffeEndpointUriFactory.java |   3 +-
 .../spiffe/SpiffeJwtSecurityPolicyConfigurer.java  |  73 +++++
 .../org/apache/camel/component/spiffe/spiffe.json  |  24 +-
 .../services/org/apache/camel/bean.properties      |   2 +-
 .../apache/camel/bean/SpiffeJwtSecurityPolicy.json |  16 ++
 ....camel.component.spiffe.SpiffeJwtSecurityPolicy |   2 +
 .../src/main/docs/spiffe-component.adoc            | 135 +++++++++
 .../component/spiffe/SpiffeConfiguration.java      |  16 ++
 .../camel/component/spiffe/SpiffeConstants.java    |   8 +-
 .../camel/component/spiffe/SpiffeJwtIdentity.java  |  90 ++++++
 .../component/spiffe/SpiffeJwtSecurityPolicy.java  | 144 ++++++++++
 .../spiffe/SpiffeJwtSecurityProcessor.java         |  90 ++++++
 .../camel/component/spiffe/SpiffeJwtValidator.java | 129 +++++++++
 .../camel/component/spiffe/SpiffeProducer.java     |  94 +-----
 .../component/spiffe/SpiffeJwtValidationTest.java  | 316 +++++++++++++++++++++
 .../spiffe/integration/SpiffeWorkloadApiIT.java    |  84 ++++++
 .../java/org/apache/camel/util/ArtifactUtils.java  |   2 +
 .../dsl/SpiffeComponentBuilderFactory.java         |  22 ++
 .../endpoint/dsl/SpiffeEndpointBuilderFactory.java |  43 ++-
 25 files changed, 1368 insertions(+), 113 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
index 10f0d5a67519..42d0d6092bca 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
@@ -56,6 +56,7 @@ RedisAggregationRepository
 RedisKeyValueRepository
 ShiroSecurityPolicy
 SimpleScheduledRoutePolicy
+SpiffeJwtSecurityPolicy
 SpiffeSSLContextParameters
 SpiffeSecurityPolicy
 SpringCacheIdempotentRepository
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeJwtSecurityPolicy.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeJwtSecurityPolicy.json
new file mode 100644
index 000000000000..aa7360e6dcc6
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/SpiffeJwtSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "SpiffeJwtSecurityPolicy",
+    "javaType": "org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Spiffe Jwt Security Policy",
+    "description": "Validates a JWT-SVID and authorizes its SPIFFE identity 
before a route segment, preserving the application body. A missing, rejected or 
unauthorized token fails closed.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-spiffe",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "audience": { "index": 0, "kind": "property", 
"displayName": "Audience", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Comma-separated audiences to validate against. A token is 
accepted when it validates for any configured audience. Message headers cannot 
override this check." }, "acceptedSpiffeIds": { "index": 1, "kind": "property", 
"displayName": "Accepted Spiffe Ids", "r [...]
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spiffe.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spiffe.json
index 9e4d228ec454..69434d105cbc 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spiffe.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spiffe.json
@@ -28,18 +28,19 @@
     "configuration": { "index": 1, "kind": "property", "displayName": 
"Configuration", "group": "producer", "label": "", "required": false, "type": 
"object", "javaType": "org.apache.camel.component.spiffe.SpiffeConfiguration", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
component configuration." },
     "lazyStartProducer": { "index": 2, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
     "operation": { "index": 3, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "", "required": false, "type": "enum", 
"javaType": "org.apache.camel.component.spiffe.SpiffeOperation", "enum": [ 
"fetchX509Svid", "fetchJwtSvid", "validateJwtSvid" ], "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "fetchX509Svid", 
"configurationClass": "org.apache.camel.component.spiffe.SpiffeConfiguration", 
"configurationField": "configuration", "descr [...]
-    "autowiredEnabled": { "index": 4, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
-    "workloadApiClient": { "index": 5, "kind": "property", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does not [...]
-    "allowOperationHeader": { "index": 6, "kind": "property", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header may 
[...]
-    "spiffeSocketPath": { "index": 7, "kind": "property", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tcp [...]
-    "x509Response": { "index": 8, "kind": "property", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": " [...]
+    "preserveBody": { "index": 4, "kind": "property", "displayName": "Preserve 
Body", "group": "producer", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether validateJwtSvid leaves the application 
body untouched and publishes the verified identit [...]
+    "autowiredEnabled": { "index": 5, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
+    "workloadApiClient": { "index": 6, "kind": "property", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does not [...]
+    "allowOperationHeader": { "index": 7, "kind": "property", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header may 
[...]
+    "spiffeSocketPath": { "index": 8, "kind": "property", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tcp [...]
+    "x509Response": { "index": 9, "kind": "property", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": " [...]
   },
   "headers": {
     "CamelSpiffeOperation": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"org.apache.camel.component.spiffe.SpiffeOperation or String", "deprecated": 
false, "autowired": false, "secret": false, "description": "Overrides the 
operation to be used by the producer. Ignored unless the endpoint sets 
allowOperationHeader=true, because the operation decides whether the endpoint 
validates a token or mints one.", "const [...]
     "CamelSpiffeAudience": { "index": 1, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The comma-separated audience(s) for the fetchJwtSvid operation. 
Ignored by validateJwtSvid, which always validates against the configured 
audience: there the audience is the check that binds the token to this 
workload, not a parameter.", "constantName": "or [...]
     "CamelSpiffeToken": { "index": 2, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The JWT-SVID token to validate, for the validateJwtSvid 
operation.", "constantName": 
"org.apache.camel.component.spiffe.SpiffeConstants#TOKEN" },
     "CamelSpiffeSpiffeId": { "index": 3, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The SPIFFE ID of the returned SVID.", "constantName": 
"org.apache.camel.component.spiffe.SpiffeConstants#SPIFFE_ID" },
-    "CamelSpiffeExpiry": { "index": 4, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"java.util.Date", "deprecated": false, "autowired": false, "secret": false, 
"description": "The expiry of the returned SVID: the token expiry for 
fetchJwtSvid, or the leaf certificate's notAfter for fetchX509Svid.", 
"constantName": "org.apache.camel.component.spiffe.SpiffeConstants#EXPIRY" }
+    "CamelSpiffeExpiry": { "index": 4, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"java.util.Date", "deprecated": false, "autowired": false, "secret": false, 
"description": "The expiry of the returned SVID: the token expiry for 
fetchJwtSvid or validateJwtSvid, or the leaf certificate's notAfter for 
fetchX509Svid.", "constantName": 
"org.apache.camel.component.spiffe.SpiffeConstants#EXPIRY" }
   },
   "bodyTypes": {
     "producerIn": { "carriers": [ "any" ], "description": "Only 
validateJwtSvid reads the body: the token as text, when neither the 
CamelSpiffeToken header nor an Authorization: Bearer header carries it. The 
fetch operations ignore the body" },
@@ -49,10 +50,11 @@
     "label": { "index": 0, "kind": "path", "displayName": "Label", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Logical name of the endpoint" },
     "audience": { "index": 1, "kind": "parameter", "displayName": "Audience", 
"group": "producer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The comma-separated audience(s) to request for 
a JWT-SVID (fetchJwtSvid) or to validate against (validateJwtSvid). fetc [...]
     "operation": { "index": 2, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": "org.apache.camel.component.spiffe.SpiffeOperation", 
"enum": [ "fetchX509Svid", "fetchJwtSvid", "validateJwtSvid" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "fetchX509Svid", 
"configurationClass": "org.apache.camel.component.spiffe.SpiffeConfiguration", 
"configurationField": "configuration", "desc [...]
-    "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
-    "workloadApiClient": { "index": 4, "kind": "parameter", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does no [...]
-    "allowOperationHeader": { "index": 5, "kind": "parameter", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header ma 
[...]
-    "spiffeSocketPath": { "index": 6, "kind": "parameter", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tc [...]
-    "x509Response": { "index": 7, "kind": "parameter", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description":  [...]
+    "preserveBody": { "index": 3, "kind": "parameter", "displayName": 
"Preserve Body", "group": "producer", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether validateJwtSvid leaves the application 
body untouched and publishes the verified identi [...]
+    "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "workloadApiClient": { "index": 5, "kind": "parameter", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does no [...]
+    "allowOperationHeader": { "index": 6, "kind": "parameter", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header ma 
[...]
+    "spiffeSocketPath": { "index": 7, "kind": "parameter", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tc [...]
+    "x509Response": { "index": 8, "kind": "parameter", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description":  [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spiffe-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spiffe-component.adoc
index a7ad55e2bb67..c5e571cc5a82 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spiffe-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spiffe-component.adoc
@@ -119,6 +119,141 @@ A missing token fails with `IllegalArgumentException`; a 
rejected one (invalid,
 with `io.spiffe.exception.JwtSvidException`. Catch both — 
`onException(io.spiffe.exception.JwtSvidException.class,
 IllegalArgumentException.class)` — to answer `401`.
 
+== Preserve an application body during JWT validation
+
+Set `preserveBody=true` on `validateJwtSvid` to validate a request without 
replacing or reading its body. In this mode
+supply the token through `CamelSpiffeToken` or `Authorization: Bearer`; there 
is no body-token fallback, including
+when the header is missing. The default remains `preserveBody=false`, which 
returns the SDK `JwtSvid` in the body.
+
+After validation the `CamelSpiffeJwtIdentity` exchange property contains a 
`SpiffeJwtIdentity` with getters for
+`spiffeId`, `expiry` (`Instant`), `audience` and `claims`. It contains no 
bearer token. Claims are copied into
+unmodifiable maps/lists and omitted from `toString`, but can still contain 
sensitive application data: select only
+the fields a downstream authorization policy needs. The identity property and 
identity/expiry output headers are
+cleared before each validation, including one that fails.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("platform-http:/orders")
+    
.to("spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/orders&preserveBody=true")
+    .removeHeaders("Authorization")
+    .setProperty("subject", 
simple("${exchangeProperty.CamelSpiffeJwtIdentity.spiffeId}"))
+    .to("direct:handleOrder");
+----
+XML::
++
+[source,xml]
+----
+<route>
+  <from uri="platform-http:/orders"/>
+  <to 
uri="spiffe:auth?operation=validateJwtSvid&amp;audience=spiffe://example.org/orders&amp;preserveBody=true"/>
+  <removeHeaders pattern="Authorization"/>
+  <setProperty 
name="subject"><simple>${exchangeProperty.CamelSpiffeJwtIdentity.spiffeId}</simple></setProperty>
+  <to uri="direct:handleOrder"/>
+</route>
+----
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: platform-http:/orders
+      steps:
+        - to:
+            uri: 
"spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/orders&preserveBody=true"
+        - removeHeaders:
+            pattern: Authorization
+        - setProperty:
+            name: subject
+            simple: "${exchangeProperty.CamelSpiffeJwtIdentity.spiffeId}"
+        - to:
+            uri: direct:handleOrder
+----
+====
+
+== Protect a route segment with a JWT-SVID policy
+
+`SpiffeJwtSecurityPolicy` combines audience validation with an explicit 
identity rule, preserves the application body,
+and throws `CamelAuthorizationException` before the guarded processor runs for 
missing, rejected or unauthorized
+tokens. Set `audience` and exactly one of `acceptedSpiffeIds` or 
`acceptAnySpiffeId=true`. Audience and identity rules
+come from policy configuration; message headers cannot override them. The 
policy reads tokens from the same two
+headers as body-preserving producer validation and never from the body.
+
+Each guarded processor owns an endpoint-managed Workload API client, released 
when its route stops and recreated
+on restart. The policy starts from the configuration of the `spiffe` 
component, so the `camel.component.spiffe.*`
+settings and a client autowired on the component apply to it; its own 
`workloadApiClient` and `spiffeSocketPath`
+options take precedence, and `SPIFFE_ENDPOINT_SOCKET` is used when neither 
names a client or a socket. An injected
+`workloadApiClient` remains owned by its supplier and is never closed by the 
policy.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+SpiffeJwtSecurityPolicy jwtPolicy = new SpiffeJwtSecurityPolicy();
+jwtPolicy.setAudience("spiffe://example.org/orders");
+jwtPolicy.setAcceptedSpiffeIds("spiffe://example.org/frontend");
+getContext().getRegistry().bind("jwtPolicy", jwtPolicy);
+
+from("platform-http:/orders")
+    .policy(jwtPolicy)
+    .removeHeaders("Authorization")
+    .to("direct:handleOrder");
+----
+XML::
++
+[source,xml]
+----
+<bean id="jwtPolicy" 
class="org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy">
+  <property name="audience" value="spiffe://example.org/orders"/>
+  <property name="acceptedSpiffeIds" value="spiffe://example.org/frontend"/>
+</bean>
+<route>
+  <from uri="platform-http:/orders"/>
+  <policy ref="jwtPolicy">
+    <removeHeaders pattern="Authorization"/>
+    <to uri="direct:handleOrder"/>
+  </policy>
+</route>
+----
+YAML::
++
+[source,yaml]
+----
+- beans:
+    - name: jwtPolicy
+      type: org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy
+      properties:
+        audience: spiffe://example.org/orders
+        acceptedSpiffeIds: spiffe://example.org/frontend
+- route:
+    from:
+      uri: platform-http:/orders
+      steps:
+        - policy:
+            ref: jwtPolicy
+            steps:
+              - removeHeaders:
+                  pattern: Authorization
+              - to:
+                  uri: direct:handleOrder
+----
+====
+
+The successful policy publishes the same token-free `CamelSpiffeJwtIdentity` 
property, and nothing else: unlike the
+producer, it sets no `CamelSpiffeSpiffeId` or `CamelSpiffeExpiry` header, so 
the identity is a value the route
+established, which a sender cannot put on the message, and it does not travel 
to
+outbound producers. Check the property, not a header, further down the route. 
Extract verified fields into
+exchange properties to compose with OPA or OpenFGA; for example, copy its 
`spiffeId` into `subject`, use
+`includeProperties=subject` with OPA, or a configured OpenFGA `user` 
expression based on that property. Choosing
+`acceptAnySpiffeId` asserts that audience validation is sufficient at this 
step and leaves finer authorization to
+the downstream policy. Always remove credential headers before forwarding 
requests to an unrelated destination.
+
 == Mutual TLS with SPIFFE (SSLContextParameters)
 
 For X.509-based zero-trust mTLS, the component provides
diff --git 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeComponentConfigurer.java
 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeComponentConfigurer.java
index 6f9c99a2e708..13d66f8a2f44 100644
--- 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeComponentConfigurer.java
+++ 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeComponentConfigurer.java
@@ -40,6 +40,8 @@ public class SpiffeComponentConfigurer extends 
PropertyConfigurerSupport impleme
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
         case "operation": 
getOrCreateConfiguration(target).setOperation(property(camelContext, 
org.apache.camel.component.spiffe.SpiffeOperation.class, value)); return true;
+        case "preservebody":
+        case "preserveBody": 
getOrCreateConfiguration(target).setPreserveBody(property(camelContext, 
boolean.class, value)); return true;
         case "spiffesocketpath":
         case "spiffeSocketPath": 
getOrCreateConfiguration(target).setSpiffeSocketPath(property(camelContext, 
java.lang.String.class, value)); return true;
         case "workloadapiclient":
@@ -67,6 +69,8 @@ public class SpiffeComponentConfigurer extends 
PropertyConfigurerSupport impleme
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
         case "operation": return 
org.apache.camel.component.spiffe.SpiffeOperation.class;
+        case "preservebody":
+        case "preserveBody": return boolean.class;
         case "spiffesocketpath":
         case "spiffeSocketPath": return java.lang.String.class;
         case "workloadapiclient":
@@ -90,6 +94,8 @@ public class SpiffeComponentConfigurer extends 
PropertyConfigurerSupport impleme
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
         case "operation": return 
getOrCreateConfiguration(target).getOperation();
+        case "preservebody":
+        case "preserveBody": return 
getOrCreateConfiguration(target).isPreserveBody();
         case "spiffesocketpath":
         case "spiffeSocketPath": return 
getOrCreateConfiguration(target).getSpiffeSocketPath();
         case "workloadapiclient":
diff --git 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointConfigurer.java
 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointConfigurer.java
index 5e2c3527b378..69273dc83252 100644
--- 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointConfigurer.java
+++ 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointConfigurer.java
@@ -30,6 +30,8 @@ public class SpiffeEndpointConfigurer extends 
PropertyConfigurerSupport implemen
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
         case "operation": 
target.getConfiguration().setOperation(property(camelContext, 
org.apache.camel.component.spiffe.SpiffeOperation.class, value)); return true;
+        case "preservebody":
+        case "preserveBody": 
target.getConfiguration().setPreserveBody(property(camelContext, boolean.class, 
value)); return true;
         case "spiffesocketpath":
         case "spiffeSocketPath": 
target.getConfiguration().setSpiffeSocketPath(property(camelContext, 
java.lang.String.class, value)); return true;
         case "workloadapiclient":
@@ -54,6 +56,8 @@ public class SpiffeEndpointConfigurer extends 
PropertyConfigurerSupport implemen
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
         case "operation": return 
org.apache.camel.component.spiffe.SpiffeOperation.class;
+        case "preservebody":
+        case "preserveBody": return boolean.class;
         case "spiffesocketpath":
         case "spiffeSocketPath": return java.lang.String.class;
         case "workloadapiclient":
@@ -74,6 +78,8 @@ public class SpiffeEndpointConfigurer extends 
PropertyConfigurerSupport implemen
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
         case "operation": return target.getConfiguration().getOperation();
+        case "preservebody":
+        case "preserveBody": return target.getConfiguration().isPreserveBody();
         case "spiffesocketpath":
         case "spiffeSocketPath": return 
target.getConfiguration().getSpiffeSocketPath();
         case "workloadapiclient":
diff --git 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointUriFactory.java
 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointUriFactory.java
index 5af759af544b..86d4b10cd3aa 100644
--- 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointUriFactory.java
+++ 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeEndpointUriFactory.java
@@ -24,12 +24,13 @@ public class SpiffeEndpointUriFactory extends 
org.apache.camel.support.component
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(8);
+        Set<String> props = new HashSet<>(9);
         props.add("allowOperationHeader");
         props.add("audience");
         props.add("label");
         props.add("lazyStartProducer");
         props.add("operation");
+        props.add("preserveBody");
         props.add("spiffeSocketPath");
         props.add("workloadApiClient");
         props.add("x509Response");
diff --git 
a/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityPolicyConfigurer.java
 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityPolicyConfigurer.java
new file mode 100644
index 000000000000..0a3cc8d6f592
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityPolicyConfigurer.java
@@ -0,0 +1,73 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.spiffe;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class SpiffeJwtSecurityPolicyConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy target = 
(org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": 
target.setAcceptAnySpiffeId(property(camelContext, boolean.class, value)); 
return true;
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": 
target.setAcceptedSpiffeIds(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "audience": target.setAudience(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "spiffesocketpath":
+        case "spiffeSocketPath": 
target.setSpiffeSocketPath(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "workloadapiclient":
+        case "workloadApiClient": 
target.setWorkloadApiClient(property(camelContext, 
io.spiffe.workloadapi.WorkloadApiClient.class, value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": return boolean.class;
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": return java.lang.String.class;
+        case "audience": return java.lang.String.class;
+        case "spiffesocketpath":
+        case "spiffeSocketPath": return java.lang.String.class;
+        case "workloadapiclient":
+        case "workloadApiClient": return 
io.spiffe.workloadapi.WorkloadApiClient.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy target = 
(org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "acceptanyspiffeid":
+        case "acceptAnySpiffeId": return target.isAcceptAnySpiffeId();
+        case "acceptedspiffeids":
+        case "acceptedSpiffeIds": return target.getAcceptedSpiffeIds();
+        case "audience": return target.getAudience();
+        case "spiffesocketpath":
+        case "spiffeSocketPath": return target.getSpiffeSocketPath();
+        case "workloadapiclient":
+        case "workloadApiClient": return target.getWorkloadApiClient();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/org/apache/camel/component/spiffe/spiffe.json
 
b/components/camel-spiffe/src/generated/resources/META-INF/org/apache/camel/component/spiffe/spiffe.json
index 9e4d228ec454..69434d105cbc 100644
--- 
a/components/camel-spiffe/src/generated/resources/META-INF/org/apache/camel/component/spiffe/spiffe.json
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/org/apache/camel/component/spiffe/spiffe.json
@@ -28,18 +28,19 @@
     "configuration": { "index": 1, "kind": "property", "displayName": 
"Configuration", "group": "producer", "label": "", "required": false, "type": 
"object", "javaType": "org.apache.camel.component.spiffe.SpiffeConfiguration", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
component configuration." },
     "lazyStartProducer": { "index": 2, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
     "operation": { "index": 3, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "", "required": false, "type": "enum", 
"javaType": "org.apache.camel.component.spiffe.SpiffeOperation", "enum": [ 
"fetchX509Svid", "fetchJwtSvid", "validateJwtSvid" ], "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "fetchX509Svid", 
"configurationClass": "org.apache.camel.component.spiffe.SpiffeConfiguration", 
"configurationField": "configuration", "descr [...]
-    "autowiredEnabled": { "index": 4, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
-    "workloadApiClient": { "index": 5, "kind": "property", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does not [...]
-    "allowOperationHeader": { "index": 6, "kind": "property", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header may 
[...]
-    "spiffeSocketPath": { "index": 7, "kind": "property", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tcp [...]
-    "x509Response": { "index": 8, "kind": "property", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": " [...]
+    "preserveBody": { "index": 4, "kind": "property", "displayName": "Preserve 
Body", "group": "producer", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether validateJwtSvid leaves the application 
body untouched and publishes the verified identit [...]
+    "autowiredEnabled": { "index": 5, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
+    "workloadApiClient": { "index": 6, "kind": "property", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does not [...]
+    "allowOperationHeader": { "index": 7, "kind": "property", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header may 
[...]
+    "spiffeSocketPath": { "index": 8, "kind": "property", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tcp [...]
+    "x509Response": { "index": 9, "kind": "property", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": " [...]
   },
   "headers": {
     "CamelSpiffeOperation": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"org.apache.camel.component.spiffe.SpiffeOperation or String", "deprecated": 
false, "autowired": false, "secret": false, "description": "Overrides the 
operation to be used by the producer. Ignored unless the endpoint sets 
allowOperationHeader=true, because the operation decides whether the endpoint 
validates a token or mints one.", "const [...]
     "CamelSpiffeAudience": { "index": 1, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The comma-separated audience(s) for the fetchJwtSvid operation. 
Ignored by validateJwtSvid, which always validates against the configured 
audience: there the audience is the check that binds the token to this 
workload, not a parameter.", "constantName": "or [...]
     "CamelSpiffeToken": { "index": 2, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The JWT-SVID token to validate, for the validateJwtSvid 
operation.", "constantName": 
"org.apache.camel.component.spiffe.SpiffeConstants#TOKEN" },
     "CamelSpiffeSpiffeId": { "index": 3, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The SPIFFE ID of the returned SVID.", "constantName": 
"org.apache.camel.component.spiffe.SpiffeConstants#SPIFFE_ID" },
-    "CamelSpiffeExpiry": { "index": 4, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"java.util.Date", "deprecated": false, "autowired": false, "secret": false, 
"description": "The expiry of the returned SVID: the token expiry for 
fetchJwtSvid, or the leaf certificate's notAfter for fetchX509Svid.", 
"constantName": "org.apache.camel.component.spiffe.SpiffeConstants#EXPIRY" }
+    "CamelSpiffeExpiry": { "index": 4, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"java.util.Date", "deprecated": false, "autowired": false, "secret": false, 
"description": "The expiry of the returned SVID: the token expiry for 
fetchJwtSvid or validateJwtSvid, or the leaf certificate's notAfter for 
fetchX509Svid.", "constantName": 
"org.apache.camel.component.spiffe.SpiffeConstants#EXPIRY" }
   },
   "bodyTypes": {
     "producerIn": { "carriers": [ "any" ], "description": "Only 
validateJwtSvid reads the body: the token as text, when neither the 
CamelSpiffeToken header nor an Authorization: Bearer header carries it. The 
fetch operations ignore the body" },
@@ -49,10 +50,11 @@
     "label": { "index": 0, "kind": "path", "displayName": "Label", "group": 
"producer", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Logical name of the endpoint" },
     "audience": { "index": 1, "kind": "parameter", "displayName": "Audience", 
"group": "producer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The comma-separated audience(s) to request for 
a JWT-SVID (fetchJwtSvid) or to validate against (validateJwtSvid). fetc [...]
     "operation": { "index": 2, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": "org.apache.camel.component.spiffe.SpiffeOperation", 
"enum": [ "fetchX509Svid", "fetchJwtSvid", "validateJwtSvid" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "fetchX509Svid", 
"configurationClass": "org.apache.camel.component.spiffe.SpiffeConfiguration", 
"configurationField": "configuration", "desc [...]
-    "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
-    "workloadApiClient": { "index": 4, "kind": "parameter", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does no [...]
-    "allowOperationHeader": { "index": 5, "kind": "parameter", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header ma 
[...]
-    "spiffeSocketPath": { "index": 6, "kind": "parameter", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tc [...]
-    "x509Response": { "index": 7, "kind": "parameter", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description":  [...]
+    "preserveBody": { "index": 3, "kind": "parameter", "displayName": 
"Preserve Body", "group": "producer", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether validateJwtSvid leaves the application 
body untouched and publishes the verified identi [...]
+    "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "workloadApiClient": { "index": 5, "kind": "parameter", "displayName": 
"Workload Api Client", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": "io.spiffe.workloadapi.WorkloadApiClient", 
"deprecated": false, "autowired": true, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "An existing WorkloadApiClient to use. When 
set, the component does no [...]
+    "allowOperationHeader": { "index": 6, "kind": "parameter", "displayName": 
"Allow Operation Header", "group": "security", "label": "security", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "security": "insecure:dev", 
"defaultValue": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "Whether the CamelSpiffeOperation header ma 
[...]
+    "spiffeSocketPath": { "index": 7, "kind": "parameter", "displayName": 
"Spiffe Socket Path", "group": "security", "label": "security", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description": "The address of the SPIFFE Workload API 
endpoint (for example unix:\/\/\/tmp\/agent.sock or tc [...]
+    "x509Response": { "index": 8, "kind": "parameter", "displayName": "X509 
Response", "group": "security", "label": "producer,security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.spiffe.SpiffeX509Response", "enum": [ "svid", 
"chain", "id" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "chain", "configurationClass": 
"org.apache.camel.component.spiffe.SpiffeConfiguration", "configurationField": 
"configuration", "description":  [...]
   }
 }
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
index 094c2262df2c..9366d02e694c 100644
--- 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-bean=SpiffeSSLContextParameters SpiffeSecurityPolicy
+bean=SpiffeJwtSecurityPolicy SpiffeSSLContextParameters SpiffeSecurityPolicy
 groupId=org.apache.camel
 artifactId=camel-spiffe
 version=4.24.0-SNAPSHOT
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeJwtSecurityPolicy.json
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeJwtSecurityPolicy.json
new file mode 100644
index 000000000000..aa7360e6dcc6
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/bean/SpiffeJwtSecurityPolicy.json
@@ -0,0 +1,16 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "SpiffeJwtSecurityPolicy",
+    "javaType": "org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy",
+    "interfaceType": "org.apache.camel.spi.AuthorizationPolicy",
+    "title": "Spiffe Jwt Security Policy",
+    "description": "Validates a JWT-SVID and authorizes its SPIFFE identity 
before a route segment, preserving the application body. A missing, rejected or 
unauthorized token fails closed.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-spiffe",
+    "version": "4.24.0-SNAPSHOT",
+    "properties": { "audience": { "index": 0, "kind": "property", 
"displayName": "Audience", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Comma-separated audiences to validate against. A token is 
accepted when it validates for any configured audience. Message headers cannot 
override this check." }, "acceptedSpiffeIds": { "index": 1, "kind": "property", 
"displayName": "Accepted Spiffe Ids", "r [...]
+  }
+}
+
diff --git 
a/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy
 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy
new file mode 100644
index 000000000000..f664a48e71a8
--- /dev/null
+++ 
b/components/camel-spiffe/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicyConfigurer
diff --git a/components/camel-spiffe/src/main/docs/spiffe-component.adoc 
b/components/camel-spiffe/src/main/docs/spiffe-component.adoc
index a7ad55e2bb67..c5e571cc5a82 100644
--- a/components/camel-spiffe/src/main/docs/spiffe-component.adoc
+++ b/components/camel-spiffe/src/main/docs/spiffe-component.adoc
@@ -119,6 +119,141 @@ A missing token fails with `IllegalArgumentException`; a 
rejected one (invalid,
 with `io.spiffe.exception.JwtSvidException`. Catch both — 
`onException(io.spiffe.exception.JwtSvidException.class,
 IllegalArgumentException.class)` — to answer `401`.
 
+== Preserve an application body during JWT validation
+
+Set `preserveBody=true` on `validateJwtSvid` to validate a request without 
replacing or reading its body. In this mode
+supply the token through `CamelSpiffeToken` or `Authorization: Bearer`; there 
is no body-token fallback, including
+when the header is missing. The default remains `preserveBody=false`, which 
returns the SDK `JwtSvid` in the body.
+
+After validation the `CamelSpiffeJwtIdentity` exchange property contains a 
`SpiffeJwtIdentity` with getters for
+`spiffeId`, `expiry` (`Instant`), `audience` and `claims`. It contains no 
bearer token. Claims are copied into
+unmodifiable maps/lists and omitted from `toString`, but can still contain 
sensitive application data: select only
+the fields a downstream authorization policy needs. The identity property and 
identity/expiry output headers are
+cleared before each validation, including one that fails.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("platform-http:/orders")
+    
.to("spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/orders&preserveBody=true")
+    .removeHeaders("Authorization")
+    .setProperty("subject", 
simple("${exchangeProperty.CamelSpiffeJwtIdentity.spiffeId}"))
+    .to("direct:handleOrder");
+----
+XML::
++
+[source,xml]
+----
+<route>
+  <from uri="platform-http:/orders"/>
+  <to 
uri="spiffe:auth?operation=validateJwtSvid&amp;audience=spiffe://example.org/orders&amp;preserveBody=true"/>
+  <removeHeaders pattern="Authorization"/>
+  <setProperty 
name="subject"><simple>${exchangeProperty.CamelSpiffeJwtIdentity.spiffeId}</simple></setProperty>
+  <to uri="direct:handleOrder"/>
+</route>
+----
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: platform-http:/orders
+      steps:
+        - to:
+            uri: 
"spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/orders&preserveBody=true"
+        - removeHeaders:
+            pattern: Authorization
+        - setProperty:
+            name: subject
+            simple: "${exchangeProperty.CamelSpiffeJwtIdentity.spiffeId}"
+        - to:
+            uri: direct:handleOrder
+----
+====
+
+== Protect a route segment with a JWT-SVID policy
+
+`SpiffeJwtSecurityPolicy` combines audience validation with an explicit 
identity rule, preserves the application body,
+and throws `CamelAuthorizationException` before the guarded processor runs for 
missing, rejected or unauthorized
+tokens. Set `audience` and exactly one of `acceptedSpiffeIds` or 
`acceptAnySpiffeId=true`. Audience and identity rules
+come from policy configuration; message headers cannot override them. The 
policy reads tokens from the same two
+headers as body-preserving producer validation and never from the body.
+
+Each guarded processor owns an endpoint-managed Workload API client, released 
when its route stops and recreated
+on restart. The policy starts from the configuration of the `spiffe` 
component, so the `camel.component.spiffe.*`
+settings and a client autowired on the component apply to it; its own 
`workloadApiClient` and `spiffeSocketPath`
+options take precedence, and `SPIFFE_ENDPOINT_SOCKET` is used when neither 
names a client or a socket. An injected
+`workloadApiClient` remains owned by its supplier and is never closed by the 
policy.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+SpiffeJwtSecurityPolicy jwtPolicy = new SpiffeJwtSecurityPolicy();
+jwtPolicy.setAudience("spiffe://example.org/orders");
+jwtPolicy.setAcceptedSpiffeIds("spiffe://example.org/frontend");
+getContext().getRegistry().bind("jwtPolicy", jwtPolicy);
+
+from("platform-http:/orders")
+    .policy(jwtPolicy)
+    .removeHeaders("Authorization")
+    .to("direct:handleOrder");
+----
+XML::
++
+[source,xml]
+----
+<bean id="jwtPolicy" 
class="org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy">
+  <property name="audience" value="spiffe://example.org/orders"/>
+  <property name="acceptedSpiffeIds" value="spiffe://example.org/frontend"/>
+</bean>
+<route>
+  <from uri="platform-http:/orders"/>
+  <policy ref="jwtPolicy">
+    <removeHeaders pattern="Authorization"/>
+    <to uri="direct:handleOrder"/>
+  </policy>
+</route>
+----
+YAML::
++
+[source,yaml]
+----
+- beans:
+    - name: jwtPolicy
+      type: org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy
+      properties:
+        audience: spiffe://example.org/orders
+        acceptedSpiffeIds: spiffe://example.org/frontend
+- route:
+    from:
+      uri: platform-http:/orders
+      steps:
+        - policy:
+            ref: jwtPolicy
+            steps:
+              - removeHeaders:
+                  pattern: Authorization
+              - to:
+                  uri: direct:handleOrder
+----
+====
+
+The successful policy publishes the same token-free `CamelSpiffeJwtIdentity` 
property, and nothing else: unlike the
+producer, it sets no `CamelSpiffeSpiffeId` or `CamelSpiffeExpiry` header, so 
the identity is a value the route
+established, which a sender cannot put on the message, and it does not travel 
to
+outbound producers. Check the property, not a header, further down the route. 
Extract verified fields into
+exchange properties to compose with OPA or OpenFGA; for example, copy its 
`spiffeId` into `subject`, use
+`includeProperties=subject` with OPA, or a configured OpenFGA `user` 
expression based on that property. Choosing
+`acceptAnySpiffeId` asserts that audience validation is sufficient at this 
step and leaves finer authorization to
+the downstream policy. Always remove credential headers before forwarding 
requests to an unrelated destination.
+
 == Mutual TLS with SPIFFE (SSLContextParameters)
 
 For X.509-based zero-trust mTLS, the component provides
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConfiguration.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConfiguration.java
index 98fc0e053286..57e4bc99fea5 100644
--- 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConfiguration.java
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConfiguration.java
@@ -37,6 +37,9 @@ public class SpiffeConfiguration implements Cloneable {
     @UriParam
     private String audience;
 
+    @UriParam(defaultValue = "false")
+    private boolean preserveBody;
+
     @UriParam(label = "security", defaultValue = "false", security = 
"insecure:dev")
     private boolean allowOperationHeader;
 
@@ -99,6 +102,19 @@ public class SpiffeConfiguration implements Cloneable {
         this.audience = audience;
     }
 
+    public boolean isPreserveBody() {
+        return preserveBody;
+    }
+
+    /**
+     * Whether validateJwtSvid leaves the application body untouched and 
publishes the verified identity through the
+     * CamelSpiffeJwtIdentity exchange property. In this mode the token must 
be in CamelSpiffeToken or an Authorization
+     * Bearer header: the body is never read as a token. The default returns 
the JwtSvid in the body as before.
+     */
+    public void setPreserveBody(boolean preserveBody) {
+        this.preserveBody = preserveBody;
+    }
+
     public boolean isAllowOperationHeader() {
         return allowOperationHeader;
     }
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConstants.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConstants.java
index 48bc80e91412..6a3fb689b841 100644
--- 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConstants.java
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeConstants.java
@@ -43,7 +43,7 @@ public final class SpiffeConstants {
     public static final String SPIFFE_ID = HEADER_PREFIX + "SpiffeId";
 
     @Metadata(label = "producer",
-              description = "The expiry of the returned SVID: the token expiry 
for fetchJwtSvid, or the leaf"
+              description = "The expiry of the returned SVID: the token expiry 
for fetchJwtSvid or validateJwtSvid, or the leaf"
                             + " certificate's notAfter for fetchX509Svid.",
               javaType = "java.util.Date")
     public static final String EXPIRY = HEADER_PREFIX + "Expiry";
@@ -55,6 +55,12 @@ public final class SpiffeConstants {
      */
     public static final String PEER_SPIFFE_ID = HEADER_PREFIX + "PeerId";
 
+    /**
+     * Exchange property carrying the token-free verified JWT identity and 
claims as a {@link SpiffeJwtIdentity}.
+     * Cleared before each validation and populated only after successful 
validation and, for a policy, authorization.
+     */
+    public static final String JWT_IDENTITY = HEADER_PREFIX + "JwtIdentity";
+
     private SpiffeConstants() {
     }
 }
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtIdentity.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtIdentity.java
new file mode 100644
index 000000000000..6d59639e3a4a
--- /dev/null
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtIdentity.java
@@ -0,0 +1,90 @@
+/*
+ * 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.spiffe;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import io.spiffe.svid.jwtsvid.JwtSvid;
+
+/**
+ * A snapshot of a validated JWT-SVID's identity and claims, without the 
bearer token. Claims may still contain
+ * application-sensitive information; they are deliberately omitted from 
{@link #toString()}.
+ */
+public final class SpiffeJwtIdentity {
+    private final String spiffeId;
+    private final Instant expiry;
+    private final Set<String> audience;
+    private final Map<String, Object> claims;
+
+    SpiffeJwtIdentity(JwtSvid svid) {
+        spiffeId = svid.getSpiffeId().toString();
+        expiry = svid.getExpiry() != null ? svid.getExpiry().toInstant() : 
null;
+        audience = svid.getAudience() != null ? Set.copyOf(svid.getAudience()) 
: Set.of();
+        claims = svid.getClaims() != null ? copyClaims(svid.getClaims()) : 
Map.of();
+    }
+
+    public String getSpiffeId() {
+        return spiffeId;
+    }
+
+    public Instant getExpiry() {
+        return expiry;
+    }
+
+    public Set<String> getAudience() {
+        return audience;
+    }
+
+    public Map<String, Object> getClaims() {
+        return claims;
+    }
+
+    private static Map<String, Object> copyClaims(Map<String, Object> values) {
+        Map<String, Object> copy = new LinkedHashMap<>();
+        values.forEach((key, value) -> copy.put(key, snapshot(value)));
+        return Collections.unmodifiableMap(copy);
+    }
+
+    private static Object snapshot(Object value) {
+        if (value instanceof Map<?, ?> values) {
+            Map<Object, Object> copy = new LinkedHashMap<>();
+            values.forEach((key, item) -> copy.put(key, snapshot(item)));
+            return Collections.unmodifiableMap(copy);
+        }
+        if (value instanceof List<?> values) {
+            List<Object> copy = new ArrayList<>(values.size());
+            values.forEach(item -> copy.add(snapshot(item)));
+            return Collections.unmodifiableList(copy);
+        }
+        if (value instanceof Date date) {
+            return date.toInstant();
+        }
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return "SpiffeJwtIdentity[spiffeId=" + spiffeId + ", expiry=" + expiry 
+ "]";
+    }
+}
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityPolicy.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityPolicy.java
new file mode 100644
index 000000000000..52e77aa53711
--- /dev/null
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityPolicy.java
@@ -0,0 +1,144 @@
+/*
+ * 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.spiffe;
+
+import java.util.Set;
+
+import io.spiffe.spiffeid.SpiffeId;
+import io.spiffe.workloadapi.WorkloadApiClient;
+import org.apache.camel.NamedNode;
+import org.apache.camel.Processor;
+import org.apache.camel.Route;
+import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * Validates an incoming JWT-SVID for configured audiences and authorizes its 
SPIFFE identity before a route segment.
+ * The application body is never read as a token or replaced. The verified, 
token-free identity and claims are exposed
+ * as the {@link SpiffeConstants#JWT_IDENTITY} exchange property.
+ */
+@Metadata(label = "bean",
+          description = "Validates a JWT-SVID and authorizes its SPIFFE 
identity before a route segment, preserving the"
+                        + " application body. A missing, rejected or 
unauthorized token fails closed.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.AuthorizationPolicy" })
+@Configurer(metadataOnly = true)
+public class SpiffeJwtSecurityPolicy implements AuthorizationPolicy {
+    @Metadata(required = true,
+              description = "Comma-separated audiences to validate against. A 
token is accepted when it validates for"
+                            + " any configured audience. Message headers 
cannot override this check.")
+    private String audience;
+    @Metadata(description = "Comma-separated allow-list of validated SPIFFE 
identities to authorize. Set exactly one"
+                            + " of acceptedSpiffeIds and acceptAnySpiffeId.")
+    private String acceptedSpiffeIds;
+    @Metadata(defaultValue = "false",
+              description = "Authorize any SPIFFE identity whose JWT-SVID 
validates for a configured audience, leaving"
+                            + " further authorization to a downstream policy. 
Mutually exclusive with acceptedSpiffeIds.")
+    private boolean acceptAnySpiffeId;
+    @Metadata(label = "security",
+              description = "Path to the Workload API endpoint. Defaults to 
the spiffeSocketPath of the spiffe component,"
+                            + " then to the SPIFFE_ENDPOINT_SOCKET environment 
variable. Ignored when a"
+                            + " workloadApiClient is used.")
+    private String spiffeSocketPath;
+    @Metadata(label = "advanced",
+              description = "An existing WorkloadApiClient to use. Defaults to 
the client of the spiffe component, when"
+                            + " it has one. The client remains owned by its 
supplier and is not closed when the guarded"
+                            + " route stops.")
+    private WorkloadApiClient workloadApiClient;
+
+    private volatile Set<SpiffeId> acceptedSet = Set.of();
+
+    @Override
+    public void beforeWrap(Route route, NamedNode definition) {
+        SpiffeJwtValidator.audiences(audience, "set the audience option");
+        boolean haveAllowList = ObjectHelper.isNotEmpty(acceptedSpiffeIds);
+        if (acceptAnySpiffeId == haveAllowList) {
+            throw new IllegalStateException(
+                    "SpiffeJwtSecurityPolicy requires exactly one of 
acceptedSpiffeIds and acceptAnySpiffeId");
+        }
+        Set<SpiffeId> parsed = haveAllowList ? 
SpiffeSecurityPolicy.parseSpiffeIds(acceptedSpiffeIds) : Set.of();
+        if (!acceptAnySpiffeId && parsed.isEmpty()) {
+            throw new IllegalStateException("acceptedSpiffeIds did not contain 
any SPIFFE ID after trimming");
+        }
+        acceptedSet = parsed;
+    }
+
+    @Override
+    public Processor wrap(Route route, Processor processor) {
+        // Start from the configuration of the spiffe component, so that its 
camel.component.spiffe.* settings and a
+        // client autowired on it apply to the policy as well; what is set on 
the policy itself takes precedence
+        SpiffeComponent component = 
route.getCamelContext().getComponent("spiffe", SpiffeComponent.class);
+        SpiffeConfiguration configuration = component.getConfiguration() != 
null
+                ? component.getConfiguration().copy() : new 
SpiffeConfiguration();
+        configuration.setOperation(SpiffeOperation.validateJwtSvid);
+        configuration.setAudience(audience);
+        configuration.setPreserveBody(true);
+        if (spiffeSocketPath != null) {
+            configuration.setSpiffeSocketPath(spiffeSocketPath);
+        }
+        if (workloadApiClient != null) {
+            configuration.setWorkloadApiClient(workloadApiClient);
+        }
+        SpiffeEndpoint endpoint = new SpiffeEndpoint("spiffe:jwt-policy", 
component, configuration);
+        return new SpiffeJwtSecurityProcessor(processor, this, endpoint);
+    }
+
+    boolean isAuthorized(SpiffeId id) {
+        return acceptAnySpiffeId || acceptedSet.contains(id);
+    }
+
+    public String getAudience() {
+        return audience;
+    }
+
+    public void setAudience(String audience) {
+        this.audience = audience;
+    }
+
+    public String getAcceptedSpiffeIds() {
+        return acceptedSpiffeIds;
+    }
+
+    public void setAcceptedSpiffeIds(String acceptedSpiffeIds) {
+        this.acceptedSpiffeIds = acceptedSpiffeIds;
+    }
+
+    public boolean isAcceptAnySpiffeId() {
+        return acceptAnySpiffeId;
+    }
+
+    public void setAcceptAnySpiffeId(boolean acceptAnySpiffeId) {
+        this.acceptAnySpiffeId = acceptAnySpiffeId;
+    }
+
+    public String getSpiffeSocketPath() {
+        return spiffeSocketPath;
+    }
+
+    public void setSpiffeSocketPath(String spiffeSocketPath) {
+        this.spiffeSocketPath = spiffeSocketPath;
+    }
+
+    public WorkloadApiClient getWorkloadApiClient() {
+        return workloadApiClient;
+    }
+
+    public void setWorkloadApiClient(WorkloadApiClient workloadApiClient) {
+        this.workloadApiClient = workloadApiClient;
+    }
+}
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityProcessor.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityProcessor.java
new file mode 100644
index 000000000000..f38dad28c227
--- /dev/null
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtSecurityProcessor.java
@@ -0,0 +1,90 @@
+/*
+ * 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.spiffe;
+
+import io.spiffe.exception.JwtSvidException;
+import io.spiffe.svid.jwtsvid.JwtSvid;
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.CamelAuthorizationException;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.support.processor.DelegateAsyncProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Keeps JWT validation and its Workload API client within the guarded 
route's lifecycle. */
+final class SpiffeJwtSecurityProcessor extends DelegateAsyncProcessor {
+    private static final Logger LOG = 
LoggerFactory.getLogger(SpiffeJwtSecurityProcessor.class);
+
+    private final SpiffeJwtSecurityPolicy policy;
+    private final SpiffeEndpoint endpoint;
+
+    SpiffeJwtSecurityProcessor(Processor processor, SpiffeJwtSecurityPolicy 
policy, SpiffeEndpoint endpoint) {
+        super(processor);
+        this.policy = policy;
+        this.endpoint = endpoint;
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        endpoint.start();
+        try {
+            super.doStart();
+        } catch (Exception e) {
+            endpoint.stop();
+            throw e;
+        }
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        try {
+            super.doStop();
+        } finally {
+            endpoint.stop();
+        }
+    }
+
+    @Override
+    public boolean process(Exchange exchange, AsyncCallback callback) {
+        try {
+            JwtSvid svid = SpiffeJwtValidator.validate(
+                    endpoint.getWorkloadApiClient(), exchange, 
endpoint.getConfiguration().getAudience(), false);
+            if (!policy.isAuthorized(svid.getSpiffeId())) {
+                throw new IllegalArgumentException("Validated SPIFFE identity 
is not authorized by this policy");
+            }
+            SpiffeJwtValidator.recordIdentity(exchange, svid, false);
+        } catch (Exception e) {
+            // Only the class of the failure is logged: the message of the SDK 
exception may contain the credential.
+            // A failure that is not a verdict on the token, such as the 
Workload API being unreachable, is worth a
+            // warning, since to the caller every failure is the same 401 and 
an outage would look like a 401 storm.
+            if (e instanceof JwtSvidException || e instanceof 
IllegalArgumentException) {
+                LOG.debug("JWT-SVID authentication or authorization failed: 
{}", e.getClass().getName());
+            } else {
+                LOG.warn("JWT-SVID validation could not be performed ({}): is 
the Workload API reachable?",
+                        e.getClass().getName());
+            }
+            SpiffeJwtValidator.clearIdentity(exchange);
+            
exchange.getMessage().setHeader(Exchange.AUTHENTICATION_FAILURE_POLICY_ID, 
policy.getClass().getSimpleName());
+            // Do not include the SDK exception message: it may contain the 
presented credential.
+            exchange.setException(new CamelAuthorizationException("JWT-SVID 
authentication or authorization failed", exchange));
+            callback.done(true);
+            return true;
+        }
+        return super.process(exchange, callback);
+    }
+}
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtValidator.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtValidator.java
new file mode 100644
index 000000000000..efeb28ba5d1b
--- /dev/null
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeJwtValidator.java
@@ -0,0 +1,129 @@
+/*
+ * 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.spiffe;
+
+import java.util.Arrays;
+
+import io.spiffe.exception.JwtSvidException;
+import io.spiffe.svid.jwtsvid.JwtSvid;
+import io.spiffe.workloadapi.WorkloadApiClient;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.util.ObjectHelper;
+
+/** Shared JWT validation for the producer and the route security policy. */
+final class SpiffeJwtValidator {
+    private SpiffeJwtValidator() {
+    }
+
+    static JwtSvid validate(WorkloadApiClient client, Exchange exchange, 
String configuredAudience, boolean allowBodyToken)
+            throws JwtSvidException {
+        clearIdentity(exchange);
+        Message message = exchange.getMessage();
+        String token = message.getHeader(SpiffeConstants.TOKEN, String.class);
+        if (ObjectHelper.isEmpty(token)) {
+            // a JWT-SVID is presented over HTTP as "Authorization: Bearer 
<token>", so an HTTP route can validate it
+            // without a bean to strip the scheme; it is checked before the 
body so that a request payload on a POST
+            // or PUT is not mistaken for the token
+            token = bearerToken(message.getHeader("Authorization", 
String.class));
+        }
+        if (ObjectHelper.isEmpty(token) && allowBodyToken) {
+            token = message.getBody(String.class);
+        }
+        if (ObjectHelper.isEmpty(token)) {
+            throw new IllegalArgumentException(
+                    "A JWT-SVID token is required (set CamelSpiffeToken or an 
Authorization: Bearer header"
+                                               + (allowBodyToken ? ", or the 
message body)" : ")"));
+        }
+        // The audience is the check here, not a parameter: it is what binds 
the token to THIS workload, so it comes
+        // from the configuration only. Honouring a CamelSpiffeAudience header 
would let a caller validate a token
+        // minted for someone else against an audience of their choosing.
+        //
+        // The Workload API validates against one audience at a time, so a 
configured list is tried in turn and the
+        // token is accepted when any audience matches. Taking only the first 
would silently enforce a narrower rule
+        // than the configuration asks for, which is the wrong failure mode 
for a check that decides whether a caller
+        // is authenticated.
+        JwtSvidException failure = null;
+        for (String audience : audiences(configuredAudience, "set the audience 
option")) {
+            try {
+                return client.validateJwtSvid(token, audience);
+            } catch (JwtSvidException e) {
+                // could be this audience, or the token itself; only once 
every audience has failed do we know
+                failure = e;
+            }
+        }
+        // audiences never returns an empty array, so the loop ran and failure 
is set; be explicit rather than leaving
+        // a reader (or a static analyser) to prove it
+        if (failure == null) {
+            throw new IllegalStateException("No configured audience was 
evaluated");
+        }
+        throw failure;
+    }
+
+    /**
+     * Splits a comma-separated audience list, trimming each entry and 
dropping blank ones.
+     *
+     * @param  configured the configured list
+     * @param  how        how to configure it, for the error when it is empty
+     * @return            the audiences, never empty
+     */
+    static String[] audiences(String configured, String how) {
+        if (ObjectHelper.isEmpty(configured)) {
+            throw new IllegalArgumentException("At least one audience is 
required (" + how + ")");
+        }
+        String[] audiences = 
Arrays.stream(configured.split(",")).map(String::trim).filter(s -> !s.isEmpty())
+                .toArray(String[]::new);
+        if (audiences.length == 0) {
+            throw new IllegalArgumentException("At least one non-blank 
audience is required (" + how + ")");
+        }
+        return audiences;
+    }
+
+    static void clearIdentity(Exchange exchange) {
+        exchange.removeProperty(SpiffeConstants.JWT_IDENTITY);
+        exchange.getMessage().removeHeader(SpiffeConstants.SPIFFE_ID);
+        exchange.getMessage().removeHeader(SpiffeConstants.EXPIRY);
+    }
+
+    /**
+     * Publishes the verified identity. The exchange property is the value the 
route established, which a sender cannot
+     * put on the message, and the one to check further down the route. The 
producer also sets the identity headers, its
+     * documented output; the policy does not, so that the identity does not 
travel in two channels, nor to outbound
+     * producers.
+     */
+    static void recordIdentity(Exchange exchange, JwtSvid svid, boolean 
headers) {
+        SpiffeJwtIdentity identity = new SpiffeJwtIdentity(svid);
+        exchange.setProperty(SpiffeConstants.JWT_IDENTITY, identity);
+        if (headers) {
+            exchange.getMessage().setHeader(SpiffeConstants.SPIFFE_ID, 
identity.getSpiffeId());
+            exchange.getMessage().setHeader(SpiffeConstants.EXPIRY, 
svid.getExpiry());
+        }
+    }
+
+    private static String bearerToken(String authorization) {
+        if (ObjectHelper.isEmpty(authorization)) {
+            return null;
+        }
+        String value = authorization.trim();
+        String scheme = "Bearer ";
+        if (value.length() > scheme.length() && value.regionMatches(true, 0, 
scheme, 0, scheme.length())) {
+            String token = value.substring(scheme.length()).trim();
+            return token.isEmpty() ? null : token;
+        }
+        return null;
+    }
+}
diff --git 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeProducer.java
 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeProducer.java
index 25ecd8632109..5018738e2502 100644
--- 
a/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeProducer.java
+++ 
b/components/camel-spiffe/src/main/java/org/apache/camel/component/spiffe/SpiffeProducer.java
@@ -18,7 +18,6 @@ package org.apache.camel.component.spiffe;
 
 import java.util.Arrays;
 
-import io.spiffe.exception.JwtSvidException;
 import io.spiffe.svid.jwtsvid.JwtSvid;
 import io.spiffe.svid.x509svid.X509Svid;
 import io.spiffe.workloadapi.WorkloadApiClient;
@@ -79,72 +78,13 @@ public class SpiffeProducer extends DefaultProducer {
     }
 
     private void validateJwtSvid(WorkloadApiClient client, Exchange exchange) 
throws Exception {
-        String token = exchange.getIn().getHeader(SpiffeConstants.TOKEN, 
String.class);
-        if (ObjectHelper.isEmpty(token)) {
-            // a JWT-SVID is presented over HTTP as "Authorization: Bearer 
<token>", so an HTTP route can validate it
-            // without a bean to strip the scheme; check it before the body so 
a request payload on a POST/PUT is not
-            // mistaken for the token
-            token = bearerToken(exchange.getIn().getHeader("Authorization", 
String.class));
+        boolean preserveBody = 
getEndpoint().getConfiguration().isPreserveBody();
+        JwtSvid svid = SpiffeJwtValidator.validate(
+                client, exchange, 
getEndpoint().getConfiguration().getAudience(), !preserveBody);
+        SpiffeJwtValidator.recordIdentity(exchange, svid, true);
+        if (!preserveBody) {
+            getMessageForResponse(exchange).setBody(svid);
         }
-        if (ObjectHelper.isEmpty(token)) {
-            token = exchange.getIn().getBody(String.class);
-        }
-        if (ObjectHelper.isEmpty(token)) {
-            throw new IllegalArgumentException(
-                    "A JWT-SVID token is required for validateJwtSvid (set the 
CamelSpiffeToken header, an"
-                                               + " Authorization: Bearer 
header, or the message body)");
-        }
-        // the audience is the check here, not a parameter: it is what binds 
the token to THIS workload, so it
-        // comes from the configuration only. Honouring CamelSpiffeAudience 
would let a caller validate a token
-        // minted for someone else against an audience of their choosing.
-        JwtSvid svid = validateAgainstAnyAudience(client, token, 
resolveConfiguredAudiences());
-        Message message = getMessageForResponse(exchange);
-        message.setBody(svid);
-        message.setHeader(SpiffeConstants.SPIFFE_ID, 
svid.getSpiffeId().toString());
-    }
-
-    /**
-     * Validates the token against the configured audiences, accepting it if 
<em>any</em> of them matches.
-     * <p/>
-     * The Workload API validates against one audience at a time, so a 
configured list has to be tried in turn. Taking
-     * only the first would silently enforce a narrower rule than the 
configuration asks for, which is the wrong failure
-     * mode for a check that decides whether a caller is authenticated.
-     */
-    private JwtSvid validateAgainstAnyAudience(WorkloadApiClient client, 
String token, String[] audiences)
-            throws JwtSvidException {
-        JwtSvidException failure = null;
-        for (String audience : audiences) {
-            try {
-                return client.validateJwtSvid(token, audience);
-            } catch (JwtSvidException e) {
-                // could be this audience, or the token itself; only once 
every audience has failed do we know
-                failure = e;
-            }
-        }
-        // resolveConfiguredAudiences never returns an empty array, so the 
loop ran and failure is set; be explicit
-        // rather than leaving a reader (or a static analyser) to prove it
-        if (failure == null) {
-            throw new IllegalStateException("No audience was configured to 
validate against");
-        }
-        throw failure;
-    }
-
-    /**
-     * Extracts the token from an {@code Authorization: Bearer <token>} value, 
matching the scheme case-insensitively
-     * and trimming the token. Returns {@code null} for a missing, empty or 
non-bearer value, so such a request falls
-     * through to the single "token required" error rather than a 
scheme-specific one.
-     */
-    private static String bearerToken(String authorization) {
-        if (ObjectHelper.isEmpty(authorization)) {
-            return null;
-        }
-        String value = authorization.trim();
-        String scheme = "Bearer ";
-        if (value.length() > scheme.length() && value.regionMatches(true, 0, 
scheme, 0, scheme.length())) {
-            String token = value.substring(scheme.length()).trim();
-            return token.isEmpty() ? null : token;
-        }
-        return null;
     }
 
     private SpiffeOperation determineOperation(Exchange exchange) {
@@ -168,26 +108,8 @@ public class SpiffeProducer extends DefaultProducer {
         return splitAudiences(audience, "set the audience option or the 
CamelSpiffeAudience header");
     }
 
-    /**
-     * Audience for a validation: taken from the configuration only, never 
from the message.
-     */
-    private String[] resolveConfiguredAudiences() {
-        // deliberately does not mention the header: it is ignored for 
validation
-        return splitAudiences(getEndpoint().getConfiguration().getAudience(), 
"set the audience option");
-    }
-
-    private String[] splitAudiences(String audience, String how) {
-        if (ObjectHelper.isEmpty(audience)) {
-            throw new IllegalArgumentException("At least one audience is 
required (" + how + ")");
-        }
-        String[] parts = Arrays.stream(audience.split(","))
-                .map(String::trim)
-                .filter(s -> !s.isEmpty())
-                .toArray(String[]::new);
-        if (parts.length == 0) {
-            throw new IllegalArgumentException("At least one non-blank 
audience is required (" + how + ")");
-        }
-        return parts;
+    private static String[] splitAudiences(String audience, String how) {
+        return SpiffeJwtValidator.audiences(audience, how);
     }
 
     private static Message getMessageForResponse(Exchange exchange) {
diff --git 
a/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/SpiffeJwtValidationTest.java
 
b/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/SpiffeJwtValidationTest.java
new file mode 100644
index 000000000000..d628dba5d572
--- /dev/null
+++ 
b/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/SpiffeJwtValidationTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.spiffe;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.spiffe.exception.JwtSvidException;
+import io.spiffe.spiffeid.SpiffeId;
+import io.spiffe.svid.jwtsvid.JwtSvid;
+import io.spiffe.workloadapi.DefaultWorkloadApiClient;
+import io.spiffe.workloadapi.WorkloadApiClient;
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelAuthorizationException;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.Route;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.support.service.ServiceHelper;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class SpiffeJwtValidationTest {
+    private static final String CALLER = "spiffe://example.org/caller";
+    private static final String AUDIENCE = "spiffe://example.org/api";
+    private static final String TOKEN = "dummy-token";
+
+    @Test
+    void preservesAPojoAndPublishesATokenFreeIdentitySnapshot() throws 
Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            JwtSvid svid = svid(CALLER);
+            List<String> roles = new ArrayList<>(List.of("reader"));
+            Map<String, Object> claims = new LinkedHashMap<>();
+            claims.put("roles", roles);
+            when(svid.getClaims()).thenReturn(claims);
+            when(svid.getToken()).thenReturn("private-token-value");
+            when(client.validateJwtSvid(TOKEN, AUDIENCE)).thenReturn(svid);
+            Object body = new Object();
+            Exchange exchange = exchange(context, body, true);
+            producer(context, client, true, AUDIENCE).process(exchange);
+
+            SpiffeJwtIdentity identity = 
exchange.getProperty(SpiffeConstants.JWT_IDENTITY, SpiffeJwtIdentity.class);
+            assertThat(exchange.getMessage().getBody()).isSameAs(body);
+            assertThat(identity.getSpiffeId()).isEqualTo(CALLER);
+            assertThat(identity.getAudience()).containsExactly(AUDIENCE);
+            
assertThat(identity.getExpiry()).isEqualTo(svid.getExpiry().toInstant());
+            roles.add("writer");
+            claims.put("extra", "secret");
+            assertThat(identity.getClaims()).containsOnlyKeys("roles");
+            
assertThat(identity.getClaims().get("roles")).isEqualTo(List.of("reader"));
+            assertThat(identity.toString()).doesNotContain("reader", "roles", 
"private-token-value");
+            assertThatThrownBy(() -> identity.getClaims().put("extra", 
"value"))
+                    .isInstanceOf(UnsupportedOperationException.class);
+        }
+    }
+
+    @Test
+    void preservesAStreamWithAndWithoutAnAuthorizationHeader() throws 
Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            doReturn(svid(CALLER)).when(client).validateJwtSvid(TOKEN, 
AUDIENCE);
+            byte[] payload = "application 
payload".getBytes(StandardCharsets.UTF_8);
+            ByteArrayInputStream body = new ByteArrayInputStream(payload);
+            Exchange exchange = exchange(context, body, true);
+            SpiffeProducer producer = producer(context, client, true, 
AUDIENCE);
+            producer.process(exchange);
+            assertThat(exchange.getMessage().getBody()).isSameAs(body);
+            assertThat(body.available()).isEqualTo(payload.length);
+
+            exchange.getMessage().removeHeader("Authorization");
+            assertThatThrownBy(() -> 
producer.process(exchange)).isInstanceOf(IllegalArgumentException.class);
+            assertThat(exchange.getMessage().getBody()).isSameAs(body);
+            assertThat(body.available()).isEqualTo(payload.length);
+            
assertThat(exchange.getProperty(SpiffeConstants.JWT_IDENTITY)).isNull();
+            
assertThat(exchange.getMessage().getHeader(SpiffeConstants.SPIFFE_ID)).isNull();
+        }
+    }
+
+    @Test
+    void retainsTheExistingDefaultBodyResponseAndTokenBodyFallback() throws 
Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            JwtSvid svid = svid(CALLER);
+            when(client.validateJwtSvid(TOKEN, AUDIENCE)).thenReturn(svid);
+            Exchange exchange = exchange(context, TOKEN, false);
+            producer(context, client, false, AUDIENCE).process(exchange);
+            assertThat(exchange.getMessage().getBody()).isSameAs(svid);
+            assertThat(exchange.getProperty(SpiffeConstants.JWT_IDENTITY, 
SpiffeJwtIdentity.class).getSpiffeId())
+                    .isEqualTo(CALLER);
+        }
+    }
+
+    @Test
+    void rejectedTokensClearEarlierVerifiedOutputsWithoutChangingTheBody() 
throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            SpiffeProducer producer = producer(context, client, true, 
AUDIENCE);
+            for (String reason : List.of("invalid signature", "expired token", 
"wrong audience")) {
+                doThrow(new 
JwtSvidException(reason)).when(client).validateJwtSvid(TOKEN, AUDIENCE);
+                Object body = new Object();
+                Exchange exchange = exchange(context, body, true);
+                exchange.setProperty(SpiffeConstants.JWT_IDENTITY, "stale");
+                exchange.getMessage().setHeader(SpiffeConstants.SPIFFE_ID, 
CALLER);
+                exchange.getMessage().setHeader(SpiffeConstants.EXPIRY, new 
Date());
+                assertThatThrownBy(() -> 
producer.process(exchange)).isInstanceOf(JwtSvidException.class);
+                assertThat(exchange.getMessage().getBody()).isSameAs(body);
+                
assertThat(exchange.getProperty(SpiffeConstants.JWT_IDENTITY)).isNull();
+                
assertThat(exchange.getMessage().getHeader(SpiffeConstants.SPIFFE_ID)).isNull();
+                
assertThat(exchange.getMessage().getHeader(SpiffeConstants.EXPIRY)).isNull();
+            }
+        }
+    }
+
+    @Test
+    void triesConfiguredAudiencesAndIgnoresTheMessageAudience() throws 
Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            when(client.validateJwtSvid(TOKEN, "first")).thenThrow(new 
JwtSvidException("wrong audience"));
+            doReturn(svid(CALLER)).when(client).validateJwtSvid(TOKEN, 
AUDIENCE);
+            Exchange exchange = exchange(context, "payload", true);
+            exchange.getMessage().setHeader(SpiffeConstants.AUDIENCE, 
"message-selected-audience");
+            producer(context, client, true, "first, " + 
AUDIENCE).process(exchange);
+            verify(client).validateJwtSvid(TOKEN, "first");
+            verify(client).validateJwtSvid(TOKEN, AUDIENCE);
+            verify(client, never()).validateJwtSvid(TOKEN, 
"message-selected-audience");
+            assertThat(exchange.getMessage().getBody()).isEqualTo("payload");
+        }
+    }
+
+    @Test
+    void policyPreservesBodyAndStopsTheGuardedProcessorOnEveryDenial() throws 
Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            SpiffeJwtSecurityPolicy policy = new SpiffeJwtSecurityPolicy();
+            policy.setAudience(AUDIENCE);
+            policy.setAcceptedSpiffeIds(CALLER);
+            policy.setWorkloadApiClient(client);
+            Route route = mock(Route.class);
+            when(route.getCamelContext()).thenReturn(context);
+            policy.beforeWrap(route, null);
+            AtomicInteger received = new AtomicInteger();
+            Processor wrapped = policy.wrap(route, e -> 
received.incrementAndGet());
+            ServiceHelper.startService(wrapped);
+            try {
+                doReturn(svid(CALLER)).when(client).validateJwtSvid(TOKEN, 
AUDIENCE);
+                Exchange allowed = exchange(context, "payload", true);
+                ((AsyncProcessor) wrapped).process(allowed, done -> {
+                });
+                assertThat(received).hasValue(1);
+                
assertThat(allowed.getMessage().getBody()).isEqualTo("payload");
+                
assertThat(allowed.getProperty(SpiffeConstants.JWT_IDENTITY)).isInstanceOf(SpiffeJwtIdentity.class);
+                // the policy publishes the identity as a property only, 
unlike the producer
+                
assertThat(allowed.getMessage().getHeader(SpiffeConstants.SPIFFE_ID)).isNull();
+                
assertThat(allowed.getMessage().getHeader(SpiffeConstants.EXPIRY)).isNull();
+
+                for (String reason : List.of("missing", "unlisted", "invalid", 
"expired", "wrong-audience")) {
+                    Exchange denied = exchange(context, "payload", 
!reason.equals("missing"));
+                    denied.setProperty(SpiffeConstants.JWT_IDENTITY, "stale");
+                    if (reason.equals("unlisted")) {
+                        
doReturn(svid("spiffe://example.org/other")).when(client).validateJwtSvid(TOKEN,
 AUDIENCE);
+                    } else if (!reason.equals("missing")) {
+                        doThrow(new 
JwtSvidException(reason)).when(client).validateJwtSvid(TOKEN, AUDIENCE);
+                    }
+                    ((AsyncProcessor) wrapped).process(denied, done -> {
+                    });
+                    
assertThat(denied.getException()).isInstanceOf(CamelAuthorizationException.class);
+                    
assertThat(denied.getProperty(SpiffeConstants.JWT_IDENTITY)).isNull();
+                    
assertThat(denied.getMessage().getBody()).isEqualTo("payload");
+                    assertThat(received).hasValue(1);
+                }
+            } finally {
+                ServiceHelper.stopService(wrapped);
+            }
+            verify(client, never()).close();
+        }
+    }
+
+    @Test
+    void policyUsesTheClientOfTheComponentWhenItSetsNone() throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            WorkloadApiClient client = mock(WorkloadApiClient.class);
+            doReturn(svid(CALLER)).when(client).validateJwtSvid(TOKEN, 
AUDIENCE);
+            // the client is configured on the component, as 
camel.component.spiffe.workload-api-client or autowiring
+            // would do, and the policy names none
+            SpiffeConfiguration componentConfiguration = new 
SpiffeConfiguration();
+            componentConfiguration.setWorkloadApiClient(client);
+            context.getComponent("spiffe", 
SpiffeComponent.class).setConfiguration(componentConfiguration);
+
+            SpiffeJwtSecurityPolicy policy = new SpiffeJwtSecurityPolicy();
+            policy.setAudience(AUDIENCE);
+            policy.setAcceptedSpiffeIds(CALLER);
+            Route route = mock(Route.class);
+            when(route.getCamelContext()).thenReturn(context);
+            policy.beforeWrap(route, null);
+            AtomicInteger received = new AtomicInteger();
+            Processor wrapped = policy.wrap(route, e -> 
received.incrementAndGet());
+            ServiceHelper.startService(wrapped);
+            try {
+                Exchange allowed = exchange(context, "payload", true);
+                ((AsyncProcessor) wrapped).process(allowed, done -> {
+                });
+                assertThat(allowed.getException()).isNull();
+                assertThat(received).hasValue(1);
+                
assertThat(allowed.getProperty(SpiffeConstants.JWT_IDENTITY)).isInstanceOf(SpiffeJwtIdentity.class);
+                verify(client).validateJwtSvid(TOKEN, AUDIENCE);
+            } finally {
+                ServiceHelper.stopService(wrapped);
+            }
+            // the component's client is not the policy's to close
+            verify(client, never()).close();
+        }
+    }
+
+    @Test
+    void policyRequiresAudienceAndAnExplicitIdentityRule() {
+        SpiffeJwtSecurityPolicy policy = new SpiffeJwtSecurityPolicy();
+        assertThatThrownBy(() -> policy.beforeWrap(null, 
null)).isInstanceOf(IllegalArgumentException.class);
+        policy.setAudience(AUDIENCE);
+        assertThatThrownBy(() -> policy.beforeWrap(null, 
null)).isInstanceOf(IllegalStateException.class);
+        policy.setAcceptAnySpiffeId(true);
+        policy.beforeWrap(null, null);
+        policy.setAcceptedSpiffeIds(CALLER);
+        assertThatThrownBy(() -> policy.beforeWrap(null, 
null)).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void policyClosesItsOwnedClientAndRecreatesItOnRestart() throws Exception {
+        try (CamelContext context = new DefaultCamelContext();
+             MockedStatic<DefaultWorkloadApiClient> factory = 
mockStatic(DefaultWorkloadApiClient.class)) {
+            WorkloadApiClient first = mock(WorkloadApiClient.class);
+            WorkloadApiClient second = mock(WorkloadApiClient.class);
+            
factory.when(DefaultWorkloadApiClient::newClient).thenReturn(first, second);
+            SpiffeJwtSecurityPolicy policy = new SpiffeJwtSecurityPolicy();
+            policy.setAudience(AUDIENCE);
+            policy.setAcceptAnySpiffeId(true);
+            Route route = mock(Route.class);
+            when(route.getCamelContext()).thenReturn(context);
+            policy.beforeWrap(route, null);
+            Processor wrapped = policy.wrap(route, e -> {
+            });
+
+            ServiceHelper.startService(wrapped);
+            ServiceHelper.stopService(wrapped);
+            verify(first).close();
+            ServiceHelper.startService(wrapped);
+            ServiceHelper.stopService(wrapped);
+            verify(first).close();
+            verify(second).close();
+        }
+    }
+
+    private static JwtSvid svid(String caller) {
+        JwtSvid svid = mock(JwtSvid.class);
+        when(svid.getSpiffeId()).thenReturn(SpiffeId.parse(caller));
+        when(svid.getExpiry()).thenReturn(new Date(2000000000000L));
+        when(svid.getAudience()).thenReturn(Set.of(AUDIENCE));
+        return svid;
+    }
+
+    private static Exchange exchange(CamelContext context, Object body, 
boolean authorization) {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody(body);
+        if (authorization) {
+            exchange.getMessage().setHeader("Authorization", "Bearer " + 
TOKEN);
+        }
+        return exchange;
+    }
+
+    private static SpiffeProducer producer(CamelContext context, 
WorkloadApiClient client, boolean preserve, String audience) {
+        SpiffeConfiguration configuration = new SpiffeConfiguration();
+        configuration.setOperation(SpiffeOperation.validateJwtSvid);
+        configuration.setAudience(audience);
+        configuration.setPreserveBody(preserve);
+        SpiffeEndpoint endpoint = new SpiffeEndpoint("spiffe:validation", new 
SpiffeComponent(context), configuration) {
+            @Override
+            public WorkloadApiClient getWorkloadApiClient() {
+                return client;
+            }
+        };
+        return new SpiffeProducer(endpoint);
+    }
+}
diff --git 
a/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/integration/SpiffeWorkloadApiIT.java
 
b/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/integration/SpiffeWorkloadApiIT.java
index c8e0eb6e61bd..c58c8960287a 100644
--- 
a/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/integration/SpiffeWorkloadApiIT.java
+++ 
b/components/camel-spiffe/src/test/java/org/apache/camel/component/spiffe/integration/SpiffeWorkloadApiIT.java
@@ -16,12 +16,20 @@
  */
 package org.apache.camel.component.spiffe.integration;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 import io.spiffe.exception.JwtSvidException;
+import org.apache.camel.CamelAuthorizationException;
 import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.spiffe.SpiffeComponent;
 import org.apache.camel.component.spiffe.SpiffeConstants;
+import org.apache.camel.component.spiffe.SpiffeJwtIdentity;
+import org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy;
 import org.apache.camel.test.infra.spiffe.services.SpiffeService;
 import org.apache.camel.test.infra.spiffe.services.SpiffeServiceFactory;
 import org.apache.camel.test.junit6.CamelTestSupport;
@@ -55,6 +63,9 @@ class SpiffeWorkloadApiIT extends CamelTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() {
+                // The JWT policy inherits the component's Workload API 
socket; its audience remains policy-owned.
+                getContext().getComponent("spiffe", 
SpiffeComponent.class).getConfiguration()
+                        
.setSpiffeSocketPath(service.getWorkloadApiSocketPath());
                 from("direct:x509")
                         
.toF("spiffe:x509?operation=fetchX509Svid&spiffeSocketPath=%s", socket());
                 from("direct:jwt")
@@ -64,10 +75,25 @@ class SpiffeWorkloadApiIT extends CamelTestSupport {
                 from("direct:validateWrongAudience")
                         
.toF("spiffe:valWrong?operation=validateJwtSvid&audience=other-audience&spiffeSocketPath=%s",
                                 socket());
+                from("direct:jwtPolicy").routeId("jwtPolicy")
+                        .process(SpiffeWorkloadApiIT::capturePolicyBody)
+                        .policy(jwtPolicy("my-audience"))
+                        .setProperty("guardedProcessorRan", constant(true));
+                from("direct:wrongAudiencePolicy")
+                        .process(SpiffeWorkloadApiIT::capturePolicyBody)
+                        .policy(jwtPolicy("other-audience"))
+                        .setProperty("guardedProcessorRan", constant(true));
             }
         };
     }
 
+    private static SpiffeJwtSecurityPolicy jwtPolicy(String audience) {
+        SpiffeJwtSecurityPolicy policy = new SpiffeJwtSecurityPolicy();
+        policy.setAudience(audience);
+        policy.setAcceptedSpiffeIds(service.getWorkloadSpiffeId());
+        return policy;
+    }
+
     @Test
     void fetchesAnX509SvidForThisWorkload() {
         Exchange out = template.request("direct:x509", e -> {
@@ -114,4 +140,62 @@ class SpiffeWorkloadApiIT extends CamelTestSupport {
         // the token is valid but minted for my-audience, so validation 
against other-audience must fail
         assertThat(out.getException()).isInstanceOf(JwtSvidException.class);
     }
+
+    @Test
+    void 
jwtPolicyPreservesThePostBodyRejectsTheWrongAudienceAndValidatesAgainAfterRouteRestart()
 throws Exception {
+        String token = template.requestBody("direct:jwt", null, String.class);
+        byte[] payload = "{\"order\":123}".getBytes(StandardCharsets.UTF_8);
+
+        ByteArrayInputStream body = new ByteArrayInputStream(payload);
+        Exchange allowed = policyRequest("direct:jwtPolicy", token, body);
+        assertThat(allowed.getException()).isNull();
+        assertPolicyBodyPreserved(allowed, payload);
+        assertThat(allowed.getProperty("guardedProcessorRan")).isEqualTo(true);
+        assertThat(allowed.getProperty(SpiffeConstants.JWT_IDENTITY, 
SpiffeJwtIdentity.class).getSpiffeId())
+                .isEqualTo(service.getWorkloadSpiffeId());
+        
assertThat(allowed.getMessage().getHeader(SpiffeConstants.SPIFFE_ID)).isNull();
+        
assertThat(allowed.getMessage().getHeader(SpiffeConstants.EXPIRY)).isNull();
+
+        ByteArrayInputStream rejectedBody = new ByteArrayInputStream(payload);
+        Exchange rejected = policyRequest("direct:wrongAudiencePolicy", token, 
rejectedBody);
+        
assertThat(rejected.getException()).isInstanceOf(CamelAuthorizationException.class);
+        assertThat(rejected.getProperty("guardedProcessorRan")).isNull();
+        
assertThat(rejected.getProperty(SpiffeConstants.JWT_IDENTITY)).isNull();
+        assertPolicyBodyPreserved(rejected, payload);
+
+        context.getRouteController().stopRoute("jwtPolicy");
+        context.getRouteController().startRoute("jwtPolicy");
+        ByteArrayInputStream restartedBody = new ByteArrayInputStream(payload);
+        Exchange restarted = policyRequest("direct:jwtPolicy", token, 
restartedBody);
+        assertThat(restarted.getException()).isNull();
+        
assertThat(restarted.getProperty("guardedProcessorRan")).isEqualTo(true);
+        assertPolicyBodyPreserved(restarted, payload);
+    }
+
+    private static void capturePolicyBody(Exchange exchange) throws 
IOException {
+        // Camel may cache the caller's stream before entering this route. 
Compare at the policy boundary so the
+        // assertion detects policy replacement or consumption, rather than 
the framework's stream-cache wrapper.
+        Object body = exchange.getMessage().getBody();
+        exchange.setProperty("bodyBeforeJwtPolicy", body);
+        exchange.setProperty("availableBeforeJwtPolicy", ((InputStream) 
body).available());
+    }
+
+    private static void assertPolicyBodyPreserved(Exchange exchange, byte[] 
payload) throws IOException {
+        Object actual = exchange.getMessage().getBody();
+        
assertThat(actual).isSameAs(exchange.getProperty("bodyBeforeJwtPolicy")).isInstanceOf(InputStream.class);
+        InputStream body = (InputStream) actual;
+        
assertThat(body.available()).isEqualTo(exchange.getProperty("availableBeforeJwtPolicy",
 Integer.class))
+                .isEqualTo(payload.length);
+        assertThat(body.readAllBytes()).containsExactly(payload);
+    }
+
+    private Exchange policyRequest(String route, String token, 
ByteArrayInputStream body) {
+        return template.request(route, exchange -> {
+            exchange.getMessage().setHeader(Exchange.HTTP_METHOD, "POST");
+            exchange.getMessage().setHeader("Authorization", "Bearer " + 
token);
+            exchange.getMessage().setHeader(SpiffeConstants.SPIFFE_ID, 
"unverified-sender-identity");
+            exchange.setProperty(SpiffeConstants.JWT_IDENTITY, 
"stale-identity");
+            exchange.getMessage().setBody(body);
+        });
+    }
 }
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
index fd413175aaf4..265860dbaefc 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
@@ -676,6 +676,8 @@ public final class ArtifactUtils {
                     
"org.apache.camel.component.shiro.security.ShiroSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-shiro"),
             Map.<String, String> entry("SimpleScheduledRoutePolicy",
                     
"org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-quartz"),
+            Map.<String, String> entry("SpiffeJwtSecurityPolicy",
+                    
"org.apache.camel.component.spiffe.SpiffeJwtSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-spiffe"),
             Map.<String, String> entry("SpiffeSSLContextParameters",
                     
"org.apache.camel.component.spiffe.SpiffeSSLContextParameters|org.apache.camel.support.jsse.SSLContextParameters|camel-spiffe"),
             Map.<String, String> entry("SpiffeSecurityPolicy",
diff --git 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SpiffeComponentBuilderFactory.java
 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SpiffeComponentBuilderFactory.java
index 1ecc13436cde..0b5c2ea51b8e 100644
--- 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SpiffeComponentBuilderFactory.java
+++ 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SpiffeComponentBuilderFactory.java
@@ -132,6 +132,27 @@ public interface SpiffeComponentBuilderFactory {
         }
     
         
+        /**
+         * Whether validateJwtSvid leaves the application body untouched and
+         * publishes the verified identity through the CamelSpiffeJwtIdentity
+         * exchange property. In this mode the token must be in 
CamelSpiffeToken
+         * or an Authorization Bearer header: the body is never read as a 
token.
+         * The default returns the JwtSvid in the body as before.
+         * 
+         * The option is a: &lt;code&gt;boolean&lt;/code&gt; type.
+         * 
+         * Default: false
+         * Group: producer
+         * 
+         * @param preserveBody the value to set
+         * @return the dsl builder
+         */
+        default SpiffeComponentBuilder preserveBody(boolean preserveBody) {
+            doSetProperty("preserveBody", preserveBody);
+            return this;
+        }
+    
+        
         /**
          * Whether autowiring is enabled. This is used for automatic autowiring
          * options (the option must be marked as autowired) by looking up in 
the
@@ -257,6 +278,7 @@ public interface SpiffeComponentBuilderFactory {
             case "configuration": ((SpiffeComponent) 
component).setConfiguration((org.apache.camel.component.spiffe.SpiffeConfiguration)
 value); return true;
             case "lazyStartProducer": ((SpiffeComponent) 
component).setLazyStartProducer((boolean) value); return true;
             case "operation": getOrCreateConfiguration((SpiffeComponent) 
component).setOperation((org.apache.camel.component.spiffe.SpiffeOperation) 
value); return true;
+            case "preserveBody": getOrCreateConfiguration((SpiffeComponent) 
component).setPreserveBody((boolean) value); return true;
             case "autowiredEnabled": ((SpiffeComponent) 
component).setAutowiredEnabled((boolean) value); return true;
             case "workloadApiClient": 
getOrCreateConfiguration((SpiffeComponent) 
component).setWorkloadApiClient((io.spiffe.workloadapi.WorkloadApiClient) 
value); return true;
             case "allowOperationHeader": 
getOrCreateConfiguration((SpiffeComponent) 
component).setAllowOperationHeader((boolean) value); return true;
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpiffeEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpiffeEndpointBuilderFactory.java
index 3d9bcef65c5b..a2bdcbc73f93 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpiffeEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpiffeEndpointBuilderFactory.java
@@ -96,6 +96,44 @@ public interface SpiffeEndpointBuilderFactory {
             doSetProperty("operation", operation);
             return this;
         }
+        /**
+         * Whether validateJwtSvid leaves the application body untouched and
+         * publishes the verified identity through the CamelSpiffeJwtIdentity
+         * exchange property. In this mode the token must be in 
CamelSpiffeToken
+         * or an Authorization Bearer header: the body is never read as a 
token.
+         * The default returns the JwtSvid in the body as before.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: producer
+         * 
+         * @param preserveBody the value to set
+         * @return the dsl builder
+         */
+        default SpiffeEndpointBuilder preserveBody(boolean preserveBody) {
+            doSetProperty("preserveBody", preserveBody);
+            return this;
+        }
+        /**
+         * Whether validateJwtSvid leaves the application body untouched and
+         * publishes the verified identity through the CamelSpiffeJwtIdentity
+         * exchange property. In this mode the token must be in 
CamelSpiffeToken
+         * or an Authorization Bearer header: the body is never read as a 
token.
+         * The default returns the JwtSvid in the body as before.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: producer
+         * 
+         * @param preserveBody the value to set
+         * @return the dsl builder
+         */
+        default SpiffeEndpointBuilder preserveBody(String preserveBody) {
+            doSetProperty("preserveBody", preserveBody);
+            return this;
+        }
         /**
          * Whether the CamelSpiffeOperation header may override the configured
          * operation. Disabled by default: the operation decides whether this
@@ -412,8 +450,9 @@ public interface SpiffeEndpointBuilderFactory {
             return "CamelSpiffeSpiffeId";
         }
         /**
-         * The expiry of the returned SVID: the token expiry for fetchJwtSvid,
-         * or the leaf certificate's notAfter for fetchX509Svid.
+         * The expiry of the returned SVID: the token expiry for fetchJwtSvid 
or
+         * validateJwtSvid, or the leaf certificate's notAfter for
+         * fetchX509Svid.
          * 
          * The option is a: {@code java.util.Date} type.
          * 

Reply via email to