- I am using mule without anypoint studio.
- 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).
- Java classes for the external webservice were created using wsimport based on their wsdl
- They require wss xml signature based on the keystore they gave me
- Their service (and wsdl) is accessible via https
- My goal: I want to add ws-security (just
Signature
action for now) in order to be able to make requests to their WS. - 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 oradd
<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):
- Mule sdk
3.8.0
- 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">
.