3
  1. I am using mule without anypoint studio.
  2. Exposed webservice in mule. Client sends a request, I transform the message (f.e. fill in some fields) and forward it to the external webservice (not managed by me).
  3. Java classes for the external webservice were created using wsimport based on their wsdl
  4. They require wss xml signature based on the keystore they gave me
  5. Their service (and wsdl) is accessible via https
  6. My goal: I want to add ws-security (just Signature action for now) in order to be able to make requests to their WS.
  7. In conclusion: Client sends a request to WS on the mule I own (http), I transform it, add ws-security and send it to the external webservice (https).

This is my WS:

@WebService
public interface LFlow {

@WebMethod
String exec(@WebParam(name = "pname") String pname,
           @WebParam(name = "mname") String mname,
           @WebParam(name = "parameters") String parameters);
}

And the request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:abc="http://a.b.c/">
   <soapenv:Header/>
   <soapenv:Body>
      <abc:exec>
         <pname>B</pname>
         <mname>getC</mname>
         <parameters>smth</parameters>
      </abc:exec>
   </soapenv:Body>
</soapenv:Envelope>

This is mule config I have for that:

<?xml version="1.0" encoding="UTF-8"?>
<mule <!--(...)--> >

    <context:property-placeholder location="classpath:l.properties, classpath:wss.properties"
                                  ignore-resource-not-found="true"/>

(...)

    <flow name="lFlow">
        <!-- Defined in other place: <http:connector name="domain-http-connector" /> -->
        <http:inbound-endpoint connector-ref="domain-http-connector" address="${l.bind.address}" exchange-pattern="request-response"
                               doc:name="HTTP">
            <cxf:jaxws-service serviceClass="a.b.c.LFlow" mtomEnabled="true">
                <cxf:outFaultInterceptors>
                    <spring:bean class="a.b.c.interceptors.FaultSoapInterceptor"/>
                </cxf:outFaultInterceptors>
            </cxf:jaxws-service>
        </http:inbound-endpoint>
        <choice doc:name="Forward to proper process">
            <!--(...)-->
            <when expression="#[payload[0] == 'B']">
                <flow-ref name="b" doc:name="b"/>
            </when>
        </choice>
    </flow>

    <!--(...)-->

    <sub-flow name="b">
        <choice doc:name="forward to proper method">
            <!--(...)-->
            <when expression="#[payload[1] == 'getC']">
                <invoke object-ref="aHandler" method="getC" doc:name="Get C element"
                        methodArgumentTypes="java.lang.String" methodArguments="#[payload[2]]"/>
            </when>
        </choice>
    </sub-flow>

</mule>

the wss.properties file is in the src/main/resources directory and looks like this:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin 
> org.apache.ws.security.crypto.merlin.keystore.type=jks 
> org.apache.ws.security.crypto.merlin.keystore.password=pass 
> org.apache.ws.security.crypto.merlin.keystore.alias=alias org.apache.ws.security.crypto.merlin.file=path-to-keystore

handler on my side that does some transformations (not in this exmaple for simplicity):

@Component
public class AHandler {

    private final AService aService;

    @Autowired
    public AHandler(final AService aService) {
        this.aService = aService;
    }

    public GetCResponse getC(final String jsonPayload) {
        return aService.getC(mapToGetCRequest(jsonPayload));
    }
}

then it is routed to the class that is supposed to send it to the external web-service:

@Service
public class AServiceImpl implements AService {

    // (...)

    @Override
    public GetCResponse getC(final GetCRequest request) {
        return getInstance().getC(request);
    }

    private BInterface getInstance() {
        /**
         I have generated *.jar for the external B service using wsimport from their wsdl-url
        */

        final B service = new B();
        final BInterface port = service.getBSOAP();
        final BindingProvider bindingProvider = (BindingProvider) port;

        final Map<String, Object> context = bindingProvider.getRequestContext();

        context.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, /*wsdl-url*/);
        context.put("thread.local.request.context", "true");

        return port;
    }

}

and this is part of the wsdl:

(...) <?xml name=B>
  </wsdl:types>
  <wsdl:message name="GetCRequest">
    <wsdl:part element="b:GetCRequest" name="payload">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="GetCResponse">
    <wsdl:part element="b:GetCResponse" name="payload">
    </wsdl:part>
  
  <wsdl:portType name="BInterface">
    
    <wsdl:operation name="getC">
      <wsdl:input message="b:GetCRequest">
    </wsdl:input>
      <wsdl:output message="b:GetCResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="BSOAPBinding" type="b:BInterface">
    <soap:binding style="b" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getC">
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="B">
    <wsdl:port binding="b:BSOAPBinding" name="BSOAP">
      <soap:address location="https://b.local/cxf/ba/b"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Communication works in soapui. I added outgoing ws-security configuration, added "signature" entry, chosen previously added keystore, with alias and password, and there it works. But I can't make it work in mule, hence the question.


As far as I know, I should either:

  • create WSS4JOutInterceptor with configured: action, signatureUser, signaturePropFile, passwordCallbackRef and somehow connect it with mule outgoing messages or

  • add <jaxws-client> or <proxy-client> with wss config embedded:

         <cxf:ws-security>
             <cxf:ws-config>
                 <cxf:property key="action" value="Signature"/>
                 <cxf:property key="signaturePropFile" value="wss.properties"/>
                 <cxf:property key="passwordCallbackClass" value="com.mulesoft.mule.example.security.PasswordCallback"/>
             </cxf:ws-config>
         </cxf:ws-security>
    

But I just can't make it work. I would really appreciate any help


Edited (24.08.2021T12:03:00Z):

  1. Mule sdk 3.8.0
  2. To answer what happens when I apply my solutions is complicated. I am not sure where exactly I should put the wss config. Should I use <jaxws-client> or <proxy-client> ? should it be in the <subflow> or <flow> ? what "attributes" to these elements I should pass (bare minimum, required to test if it even works) ? or maybe I should use <cxf:inInterceptors>/<cxf:outInterceptors> ?

I tried different configurations but I am not sure if I did it the right way, so the errors I was getting were probably results of my improper use. So I didn't put them here, cause they might make it harder to read my question.


Edited (24.08.2021T12:54:00Z):

But according to the doc:

proxy-client provides raw SOAP and WS-* processing for outgoing XML messages, allowing you to send outgoing messages in raw XML form and apply things like WS-Security to them.

I should use <proxy-client> and I believe it should be in the "subflow":

<sub-flow name="b">
    <cxf:proxy-client>
       <cxf:ws-security>
           <cxf:ws-config>
               <cxf:property key="action" value="Signature"/>
               <cxf:property key="signatureUser" value="keystore-alias"/>
               <cxf:property key="signaturePropFile" value="wss.properties"/>
               <cxf:property key="passwordCallbackClass" value="com.mulesoft.mule.example.security.PasswordCallback"/>
           </cxf:ws-config>
       </cxf:ws-security>
    </cxf:proxy-client>
    <choice doc:name="forward to proper method">
        <!--(...)-->
        <when expression="#[payload[1] == 'getC']">
            <invoke object-ref="aHandler" method="getC" doc:name="Get C element"
                    methodArgumentTypes="java.lang.String" methodArguments="#[payload[2]]"/>
        </when>
    </choice>
</sub-flow>

Is that correct ? what attributes should I define for <cxf:proxy-client> ? or maybe instead of defining <cxf:ws-security> inside, I should define interceptors like this:

<bean id="clientWss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
    <constructor-arg>
        <map>
            <entry key="action" value="Signature"/>
            <entry key="signatureUser" value=""/>
            <entry key="signaturePropFile" value=""/>
            <entry key="passwordCallbackRef" value-ref=""/>
        </map>
    </constructor-arg>
</bean>
(...)
<sub-flow name="b">
<cxf:proxy-client doc:name="Proxy client">
    <cxf:outInterceptors>
        <spring:bean id="clientWss4jOutInterceptor">
        </spring:bean>
    </cxf:outInterceptors>
</cxf:proxy-client>
(...)

Edited (25.08.2021T11:28:00Z):

When try to use consumer like this (mule-config.xml):

<?xml version="1.0" encoding="UTF-8"?>
<mule 
  xmlns:context="http://www.springframework.org/schema/context" 
  xmlns:tls="http://www.mulesoft.org/schema/mule/tls"          
  version="CE-3.8.0"
       
  xmlns:ws="http://www.mulesoft.org/schema/mule/ws"
          
  (...)
  <tls:context name="tlsContext">
    <tls:key-store
                    
      path="C:\\Users\\john\\Documents\\integrationB.keystore"
                    
      keyPassword="privatekey-password"
                    
      password="keystore-password"
                    
      alias="alias" />
  </tls:context>
  
  <!--service, port, wsdl-url, address - all taken from wsdl-->
  <ws:consumer-config 
    name="WebServiceConsumer"
    
    serviceAddress="https://b.local/cxf/ba/b"
     
    wsdlLocation="https://b.local/cxf/ba/b?wsdl"
    
    service="B"
    port="BSOAP">
    <ws:security>
      <ws:wss-sign tlsContext-ref="tlsContext" />
    </ws:security>
  </ws:consumer-config>
  <flow name="lFlow">
    <http:inbound-endpoint 
      connector-ref="domain-http-connector" 
      address="${l.bind.address}" 
      exchange-pattern="request-response"
           
      doc:name="HTTP">
      <cxf:jaxws-service serviceClass="a.b.c.LFlow" mtomEnabled="true">
          <cxf:outFaultInterceptors>
              <spring:bean class="a.b.c.interceptors.FaultSoapInterceptor"/>
          </cxf:outFaultInterceptors>
      </cxf:jaxws-service>
    </http:inbound-endpoint>
    <http:listener config-ref="HTTP_Listener_Configuration" path="*" doc:name="HTTP">
      <http:response-builder statusCode="200"/>
    </http:listener>
    <ws:consumer config-ref="WebServiceConsumer" operation="getC" doc:name="Get C element"/>
    <choice doc:name="Forward to proper process">
        <!--(...)-->
        <when expression="#[payload[0] == 'B']">
            <flow-ref name="b" doc:name="b"/>
        </when>
    </choice>
  </flow>
  <!--(...)-->

    <sub-flow name="b">
        <choice doc:name="forward to proper method">
            <!--(...)-->
            <when expression="#[payload[1] == 'getC']">
                <invoke object-ref="aHandler" method="getC" doc:name="Get C element"
                        methodArgumentTypes="java.lang.String" methodArguments="#[payload[2]]"/>
            </when>
        </choice>
    </sub-flow>
</mule>

I get during app-start:

Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 46 in XML document from URL [file:/(...)/mule-config.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 46; columnNumber: 61; cvc-complex-type.2.4.a: Invalid content was found starting with element 'ws:consumer-config'. One of (...) is expected.

46:61 points to the last charatcer of: port="BSOAP">.

Filip Kowalski
  • 144
  • 1
  • 3
  • 15
  • What is the exact Mule version? What are the results of implementing each solution you mentioned? – aled Aug 24 '21 at 11:57
  • Edited my question. But for clarity. Mule sdk 3.8.0. My improper use of the solutions could wrong potential helping-person so I skipped them. I will add them, when someone guide me which way I should go. – Filip Kowalski Aug 24 '21 at 12:31

1 Answers1

1

If possible I would avoid using CXF and try the Web Service Consumer that it is much easier to use:

<tls:context name="tlsContext">
   <tls:key-store path="path" keyPassword="pass" password="pass" alias="keyalias" />
</tls:context>

<ws:consumer-config name="Web_Service_Consumerweather" serviceAddress="http://localhost/test" wsdlLocation="Test.wsdl"
                    service="TestService" port="TestPort">
        <ws:security>
            <ws:wss-sign tlsContext-ref="tlsContext" />
        </ws:security>
</ws:consumer-config>

<flow name="listInventory" doc:name="listInventory">
        <http:listener config-ref="HTTP_Listener_Configuration" path="inventory" doc:name="HTTP">
            <http:response-builder statusCode="200"/>
        </http:listener>
        <ws:consumer config-ref="Web_Service_Consumer" operation="ListInventory" doc:name="List Inventory"/>
    </flow>

Also note that Mule 3.8 has been replaced by Mule 3.9. The latest release is Mule 4.3 which is not compatible with Mule 3.x and doesn't support CXF.

Documentation: https://docs.mulesoft.com/web-service-consumer-connector/0.3.9/

aled
  • 21,330
  • 3
  • 27
  • 34
  • I am keep getting and error for ``. Put that in the last "edit" section in my question. – Filip Kowalski Aug 25 '21 at 11:36
  • HTTP Listener is the newer HTTP connector source. The transport based HTTP connector (Inbound/outbound endpoints) was deprecated in Mule 3.6.0 (https://docs.mulesoft.com/release-notes/mule-runtime/mule-esb-3.6.0-release-notes#deprecated-in-this-release). You have to use either connector as the flow source (beginning of the flow) but not in the middle. Once that is resolved you may need to do some transformation. The Web Service Consumer probably is not expecting CXF objects as input but plain objects or XML. – aled Aug 25 '21 at 11:51
  • Keep getting "xml is invalid" for `consumer-config`. I have added: (1) `name` attribute of both `service` and `port` elements from `wsdl` to `consumer-config`'s 'service' and 'port'. Didn't work. Then tried: (2) `BService` class (generated from wsdl, annotated with "WebServiceClient") and `BServiceIntefrace` class (generated from wsdl, port class). None of the them work. I believe this is your exmaple. What did you exactly put in "service" and "port" here: https://github.com/mulesoft/docs-connectors/blob/latest/web-service-consumer/0.3.5/modules/ROOT/pages/web-service-consumer-example.adoc – Filip Kowalski Aug 26 '21 at 12:46