This is an automated email from the ASF dual-hosted git repository. ffang pushed a commit to branch CAMEL-22977 in repository https://gitbox.apache.org/repos/asf/camel.git
commit f568fcd73131e6bb882b9f2a9c53c14faf9337bc Author: Freeman Fang <[email protected]> AuthorDate: Thu Feb 12 07:57:22 2026 -0500 [CAMEL-22977]DefaultCxfBinding: also populate credentials --- components/camel-cxf/camel-cxf-soap/pom.xml | 1 - .../component/cxf/jaxws/DefaultCxfBinding.java | 106 ++++++++++++++++++++- .../camel/AssertSubjectHasX509CertsProcessor.java | 51 ++++++++++ .../cxf/wssecurity/camel/camel-context.xml | 2 + 4 files changed, 158 insertions(+), 2 deletions(-) diff --git a/components/camel-cxf/camel-cxf-soap/pom.xml b/components/camel-cxf/camel-cxf-soap/pom.xml index 25beee21784d..ca101a6f9a3b 100644 --- a/components/camel-cxf/camel-cxf-soap/pom.xml +++ b/components/camel-cxf/camel-cxf-soap/pom.xml @@ -194,7 +194,6 @@ <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>${cxf-version}</version> - <scope>test</scope> </dependency> diff --git a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java index 326cd86d1e6c..2ba23b415b4d 100644 --- a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java +++ b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/DefaultCxfBinding.java @@ -23,6 +23,7 @@ import java.lang.reflect.Modifier; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -94,6 +95,9 @@ import org.apache.cxf.service.model.BindingOperationInfo; import org.apache.cxf.service.model.MessagePartInfo; import org.apache.cxf.service.model.OperationInfo; import org.apache.cxf.staxutils.StaxUtils; +import org.apache.wss4j.dom.engine.WSSecurityEngineResult; +import org.apache.wss4j.dom.handler.WSHandlerConstants; +import org.apache.wss4j.dom.handler.WSHandlerResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -368,18 +372,118 @@ public class DefaultCxfBinding implements CxfBinding, HeaderFilterStrategyAware SecurityContext securityContext = cxfMessage.get(SecurityContext.class); if (securityContext instanceof LoginSecurityContext && ((LoginSecurityContext) securityContext).getSubject() != null) { + Subject subject = ((LoginSecurityContext) securityContext).getSubject(); + // attach certs to the subject instance + addInboundX509CertificatesToSubject(cxfMessage, subject); camelExchange.getIn().getHeaders().put(CxfConstants.AUTHENTICATION, - ((LoginSecurityContext) securityContext).getSubject()); + subject); } else if (securityContext != null) { Principal user = securityContext.getUserPrincipal(); if (user != null) { Subject subject = new Subject(); subject.getPrincipals().add(user); + // attach certs to the subject instance + addInboundX509CertificatesToSubject(cxfMessage, subject); camelExchange.getIn().getHeaders().put(CxfConstants.AUTHENTICATION, subject); } } } + private static void addInboundX509CertificatesToSubject(Message cxfMessage, Subject subject) { + if (cxfMessage == null || subject == null) { + return; + } + // If it’s read-only, don’t break the route; just skip. + if (subject.isReadOnly()) { + return; + } + + final Object recv = cxfMessage.get(WSHandlerConstants.RECV_RESULTS); + if (recv == null) { + return; + } + + // We only need the cert objects. + Collection<X509Certificate> certs = null; + + if (recv instanceof Map) { + Object v = ((Map<?, ?>) recv).get(WSSecurityEngineResult.TAG_X509_CERTIFICATES); + if (v instanceof Collection) { + certs = new ArrayList<>(); + for (Object o : (Collection<?>) v) { + if (o instanceof X509Certificate) { + certs.add((X509Certificate) o); + } else if (o instanceof X509Certificate[]) { + for (X509Certificate c : (X509Certificate[]) o) { + if (c != null) { + certs.add(c); + } + } + } + } + } else if (v instanceof X509Certificate[]) { + certs = new ArrayList<>(); + for (X509Certificate c : (X509Certificate[]) v) { + if (c != null) { + certs.add(c); + } + } + } + } else if (recv instanceof List) { + // Typical CXF case: List<WSHandlerResult> + List<?> list = (List<?>) recv; + if (!list.isEmpty() && list.get(0) instanceof WSHandlerResult) { + certs = new ArrayList<>(); + for (Object hrObj : list) { + WSHandlerResult hr = (WSHandlerResult) hrObj; + // WSHandlerResult contains List<WSSecurityEngineResult> + for (WSSecurityEngineResult r : hr.getResults()) { + Object v = r.get(WSSecurityEngineResult.TAG_X509_CERTIFICATES); + if (v instanceof X509Certificate[]) { + for (X509Certificate c : (X509Certificate[]) v) { + if (c != null) { + certs.add(c); + } + } + } else if (v instanceof X509Certificate) { + certs.add((X509Certificate) v); + } else { + Object leaf = r.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE); + if (leaf instanceof X509Certificate) { + certs.add((X509Certificate) leaf); + } + } + } + } + if (certs.isEmpty()) { + certs = null; + } + } + } + if (certs == null || certs.isEmpty()) { + return; + } + + Set<Object> pub = (Set<Object>) subject.getPublicCredentials(); + for (Object o : certs) { + if (o instanceof X509Certificate) { + pub.add(o); + } else if (o instanceof X509Certificate[]) { + for (X509Certificate c : (X509Certificate[]) o) { + if (c != null) { + pub.add(c); + } + } + } else if (o instanceof Collection) { + for (Object nested : (Collection<?>) o) { + if (nested instanceof X509Certificate) { + pub.add(nested); + } + } + } + } + } + private static void setOperationNameViaMethod(Exchange camelExchange, Method method) { camelExchange.getIn().setHeader(CxfConstants.OPERATION_NAME, method.getName()); if (LOG.isTraceEnabled()) { diff --git a/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/wssecurity/camel/AssertSubjectHasX509CertsProcessor.java b/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/wssecurity/camel/AssertSubjectHasX509CertsProcessor.java new file mode 100644 index 000000000000..b7beb44af925 --- /dev/null +++ b/components/camel-cxf/camel-cxf-spring-soap/src/test/java/org/apache/camel/component/cxf/wssecurity/camel/AssertSubjectHasX509CertsProcessor.java @@ -0,0 +1,51 @@ +/* + * 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.cxf.wssecurity.camel; + +import java.security.cert.X509Certificate; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.component.cxf.common.message.CxfConstants; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AssertSubjectHasX509CertsProcessor implements Processor { + @Override + public void process(Exchange exchange) { + Object auth = exchange.getIn().getHeader(CxfConstants.AUTHENTICATION); + assertNotNull(auth, "Expected " + CxfConstants.AUTHENTICATION + " header"); + assertTrue(auth instanceof Subject, "Expected AUTHENTICATION to be a Subject but was: " + + auth.getClass()); + + Subject subject = (Subject) auth; + Set<X509Certificate> certs = subject.getPublicCredentials(X509Certificate.class); + + assertNotNull(certs, "Subject public credentials should not be null"); + assertFalse(certs.isEmpty(), "Expected at least one X509Certificate in Subject public credentials"); + + for (X509Certificate c : certs) { + assertNotNull(c); + assertNotNull(c.getSubjectX500Principal()); + } + } +} diff --git a/components/camel-cxf/camel-cxf-spring-soap/src/test/resources/org/apache/camel/component/cxf/wssecurity/camel/camel-context.xml b/components/camel-cxf/camel-cxf-spring-soap/src/test/resources/org/apache/camel/component/cxf/wssecurity/camel/camel-context.xml index 3e10b4fe291c..089a72d4410e 100644 --- a/components/camel-cxf/camel-cxf-spring-soap/src/test/resources/org/apache/camel/component/cxf/wssecurity/camel/camel-context.xml +++ b/components/camel-cxf/camel-cxf-spring-soap/src/test/resources/org/apache/camel/component/cxf/wssecurity/camel/camel-context.xml @@ -111,6 +111,7 @@ <camelContext xmlns="http://camel.apache.org/schema/spring" id="camel"> <route errorHandlerRef="noErrorHandler"> <from uri="cxf:bean:signatureRoute"/> + <process ref="assertSubjectHasX509Certs"/> <to uri="cxf:bean:signatureService"/> </route> <route errorHandlerRef="noErrorHandler"> @@ -128,6 +129,7 @@ </camelContext> <bean id="noErrorHandler" class="org.apache.camel.builder.NoErrorHandlerBuilder"/> + <bean id="assertSubjectHasX509Certs" class="org.apache.camel.component.cxf.wssecurity.camel.AssertSubjectHasX509CertsProcessor"/> <bean id="wss4jInInterceptor-signature" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> <constructor-arg>
