Search code examples
soapapache-camelcxfws-securitycamel-cxf

Camel CXF SOAP API Consumer with https and Security Header


I'm currently facing the issue, that I can not consume a SOAP webservice via camel-cxf. The exception is the following:


org.apache.cxf.ws.policy.PolicyException: These policy alternatives can not be satisfied:
{http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportBinding: Received Timestamp does not match the requirements
{http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}IncludeTimestamp
        at org.apache.cxf.ws.policy.AssertionInfoMap.checkEffectivePolicy(AssertionInfoMap.java:179) ~[109:org.apache.cxf.cxf-rt-ws-policy:3.2.6]
        at org.apache.cxf.ws.policy.PolicyVerificationInInterceptor.handle(PolicyVerificationInInterceptor.java:102) ~[109:org.apache.cxf.cxf-rt-ws-policy:3.2.6]
        at org.apache.cxf.ws.policy.AbstractPolicyInterceptor.handleMessage(AbstractPolicyInterceptor.java:44) ~[109:org.apache.cxf.cxf-rt-ws-policy:3.2.6]
        at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) ~[78:org.apache.cxf.cxf-core:3.2.6]
        at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:813) [78:org.apache.cxf.cxf-core:3.2.6]
        at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1695) [102:org.apache.cxf.cxf-rt-transports-http:3.2.6]
        at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream$1.run(HTTPConduit.java:1194) [102:org.apache.cxf.cxf-rt-transports-http:3.2.6]
        at org.apache.cxf.workqueue.AutomaticWorkQueueImpl$3.run(AutomaticWorkQueueImpl.java:421) [78:org.apache.cxf.cxf-core:3.2.6]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:?]
        at org.apache.cxf.workqueue.AutomaticWorkQueueImpl$AWQThreadFactory$1.run(AutomaticWorkQueueImpl.java:346) [78:org.apache.cxf.cxf-core:3.2.6]
        at java.lang.Thread.run(Thread.java:748) [?:?]

and the SOAP answer the following:


<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</faultcode><faultstring xml:lang="de-DE">An error occurred when verifying security for the message.</faultstring></s:Fault></s:Body></s:Envelope>

I used the maven cxf-codegen-plugin to generate the Java-classes via the wsdl2java goal. The security part of the wsdl looks like this:


<wsp:Policy wsu:Id="BasicHttpBinding_IUserManagementService_policy">
    <wsp:ExactlyOne>
        <wsp:All>
            <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
                <wsp:Policy>
                    <sp:TransportToken>
                        <wsp:Policy>
                            <sp:HttpsToken RequireClientCertificate="false"/>
                        </wsp:Policy>
                    </sp:TransportToken>
                    <sp:AlgorithmSuite>
                        <wsp:Policy>
                            <sp:Basic256/>
                        </wsp:Policy>
                    </sp:AlgorithmSuite>
                    <sp:Layout>
                        <wsp:Policy>
                            <sp:Lax/>
                        </wsp:Policy>
                    </sp:Layout>
                    <sp:IncludeTimestamp/>
                </wsp:Policy>
            </sp:TransportBinding>
            <sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
                <wsp:Policy>
                    <sp:UsernameToken
                            sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">
                        <wsp:Policy>
                            <sp:WssUsernameToken10/>
                        </wsp:Policy>
                    </sp:UsernameToken>
                </wsp:Policy>
            </sp:SignedSupportingTokens>
            <sp:Wss10 xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
                <wsp:Policy/>
            </sp:Wss10>
        </wsp:All>
    </wsp:ExactlyOne>
</wsp:Policy>

and I want to use the UsernameToken authentication.

Maven dependencies:

camel version: 2.20.3


<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-blueprint</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-cxf</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-soap-starter</artifactId>
    <scope>provided</scope>
</dependency>


<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-ws-security</artifactId>
    <version>3.2.6</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-ws-policy</artifactId>
    <version>3.2.6</version>
    <scope>provided</scope>
</dependency>

I tried to connect to the api via SoapUI and everything worked well. Either with the authentication-part of SoapUI or with specifying the security part in the SoapHeader, both worked.

My camel route-builder looks like this:


SoapJaxbDataFormat soap = new SoapJaxbDataFormat("org.tempuri", new ServiceInterfaceStrategy(IUserManagementService.class, true));


from("direct:userdata.soap.requests")
// .marshal(soap) // not sure, if I need to marshal here
.to("cxf://{{SOAP_URL}}" +
        "?serviceClass=org.tempuri.IUserManagementService" +
        "&serviceName={http://tempuri.org/}UserManagementService" +
        "&endpointName={http://tempuri.org/}BasicHttpBinding_IUserManagementService" +
        "&wsdlURL={{WSDL_URL}}" +
        "&dataFormat=MESSAGE" +
        "&username={{SOAP_USERNAME}}" +
        "&password={{SOAP_PASSWORD}}" +
          "&allowStreaming=false");

and I'm sending to the queue like this:


@EndpointInject(uri = "direct:userdata.soap.requests")
Endpoint endpoint;

@Produce(uri = "direct:userdata.soap.requests")
ProducerTemplate channel;

....

private Object sendRequest(Object request, String operationName) throws Exception{
    Exchange inExchange = endpoint.createExchange(ExchangePattern.InOnly);
    inExchange.getIn().setHeader(CxfConstants.OPERATION_NAME, operationName);
    inExchange.getIn().setHeader(CxfConstants.OPERATION_NAMESPACE, "http://tempuri.org/");
    inExchange.getIn().setBody(request);

    Map<String, Object> context = new HashMap<>();
    context.put("ws-security.username", soapUsername);
    context.put("ws-security.password", soapPassword);
    inExchange.getIn().setHeader(Client.REQUEST_CONTEXT, context);

    Exchange outExchange = channel.send(inExchange);
    log.error(outExchange.getOut().getBody(String.class));
    Object result = outExchange.getIn().getBody(Object.class);
    if(result.getClass().equals(FaultException.class)){
        throw (FaultException) result;
    }
    return result;
}

where endpoint is type org.apache.camel.Endpoint and channel is type org.apache.camel.ProducerTemplate

The request-Object is of type from the autogenerated classes by the plugin.

I also tried, writing my own WSS4JOutInterceptor to handle the security part, but this also did not work.

Please let me know, if I need to provide more information.

Many thanks in advance


Solution

  • It turned out, there was an issue, interpreting the SOAP answer. So actually the route works like this. Just set the headers ws-security.username and ws-security.password and the cxf will take care of creating the correct header. I've also changed the dataformat to PAYLOAD and marshalling is not needed at this point.

    Thanks anyway for reading