Saturday, July 7. 2018
WS-AT interoperability between wildfly XTS and .NET (wildfly side)
During the last year I have been involved in a very strange problem using Web Service Atomic Transaction (WS-AT) between Microsoft and Wildlfy. The WS-AT is an old OASIS standard to provide atomic transactions in the web between different services. The issue happened when the windows client started the transaction and a wildfly endpoint was called, in that moment the endpoint should register in the transaction and that operation failed. There was a lot of investigation and the final analysis was that the call was executed synchronously by wildfly but the Microsoft endpoint wanted it asynchronously. The standard is not very specific in this point and windows asynchronous requirement is, at least, controversial, an enhancement for wildfly was filed anyway.
In the last days the needed modifications have been included in the not yet released wildfly 14 and I decided to test it with a nightly snapshot. This entry is the wildfly setup and a continuation post will detail the windows side (much more complicated in my humble opinion). The steps to configure current snapshot are the following.
Download wildfly 14 (remember that now you can just download a nightly build). In my case I used the snapshot from Jun 30th 2018.
Install the bundle; copy the standalone-xts.xml configuration from the examples to the configuration folder; add an administrator user and, finally, start it using the xts configuration.
unzip wildfly-14.0.0.Beta1-SNAPSHOT.zip cd wildfly-14.0.0.Beta1-SNAPSHOT cp docs/examples/configs/standalone-xts.xml standalone/configuration/ cd bin ./add-user.sh -u admin -p XXXXX ./standalone.sh -c standalone-xts.xml
Configure the asynchronous registration for XTS. There is a new attribute in the XTS subsystem async-registration that should be changed to true (there is also going to be a system property to do the same in the future, mainly thinking for older versions). At the same time the server is configured to listen in all the interfaces of my laptop (the windows virtual machine should contact the server using the internal network).
./jboss-cli.sh --connect /interface=public:write-attribute(name=inet-address, value=0.0.0.0) /subsystem=xts:write-attribute(name=async-registration, value=true) reload
This property enables two new endpoints in the XTS subsystem to receive the Microsoft responses for the registration call. Those endpoints are registered at the following locations:
- http://localhost:8080/ws-c11/RegistrationResponseService?wsdl
- http://localhost:8080/ws-c11/CoordinationFaultService?wsdl
The windows implementation requires mutual TLS authentication for the web services so it is important to create two certificates for each partner (windows and wildfly). In my environment the wildfly machine is my laptop (daredevil.sample.com/192.168.100.1) and the windows host is a 2016 essentials VM (remoteserver2.sample2.com/192.168.100.14). A CA is used to sign the two certificates for each server.
Generate CA cert and export it to a file.
keytool -genkeypair -keystore ca.jks -storepass changeit -dname "CN=CA,O=sample,C=com" -alias ca -keyalg rsa -keysize 2048 -validity 1000 -ext BasicConstraints:critical=ca:true keytool -exportcert -keystore ca.jks -alias ca -file ca.crt
Generate and sign the certificate for windows. The key is generated, then a request, the request is signed with the CA and finally the CA and the signed certificate are imported into the store:
keytool -genkey -keyalg RSA -alias win2016 -keystore win2016.p12 -storetype pkcs12 -storepass changeit -dname "CN=remoteserver2.sample2.com,O=sample,C=com" -validity 1000 -keysize 2048 -ext KU=digitalSignature,keyEncipherment,dataEncipherment -ext san=dns:remoteserver2.sample2.com,ip:192.168.100.14 keytool -certreq -keystore win2016.p12 -storetype pkcs12 -alias win2016 -keyalg rsa -file win2016.csr keytool -gencert -infile win2016.csr -outfile win2016.crt -keystore ca.jks -alias ca -validity 1000 -keysize 2048 -ext KU=digitalSignature,keyEncipherment,dataEncipherment -ext san=dns:remoteserver2.sample2.com,ip:192.168.100.14 keytool -import -keystore win2016.p12 -storetype pkcs12 -file ca.crt -alias ca -trustcacerts keytool -import -keystore win2016.p12 -storetype pkcs12 -file win2016.crt -alias win2016
Same steps for the wildfly machine.
keytool -genkey -keyalg RSA -alias wildfly -keystore wildfly.jks -storepass changeit -dname "CN=daredevil.sample.com,O=sample,C=com" -validity 1000 -keysize 2048 -ext KU=digitalSignature,keyEncipherment,dataEncipherment -ext san=dns:daredevil.sample.com,ip:192.168.100.1 keytool -certreq -keystore wildfly.jks -alias wildfly -keyalg rsa -file wildfly.csr keytool -gencert -infile wildfly.csr -outfile wildfly.crt -keystore ca.jks -alias ca -validity 1000 -keysize 2048 -ext KU=digitalSignature,keyEncipherment,dataEncipherment -ext san=dns:daredevil.sample.com,ip:192.168.100.1 keytool -import -keystore wildfly.jks -file ca.crt -alias ca -trustcacerts keytool -import -keystore wildfly.jks -file wildfly.crt -alias wildfly
Finally copy the wildfly stores to the configuration directory:
cp ca.jks wildfly.jks ${JBOSS_HOME}/standalone/configuration/
Now the certificates for wildfly are going to be installed. For that elytron is going to be configured to use HTTPS with the previously created certificates (the procedure is identical to the one followed in this previous entry).
./jboss-cli.sh --connect /subsystem=elytron/key-store=wildfly:add(type=jks, relative-to=jboss.server.config.dir, path=wildfly.jks, credential-reference={clear-text=changeit}) /subsystem=elytron/key-store=ca:add(type=jks, relative-to=jboss.server.config.dir, path=ca.jks, credential-reference={clear-text=changeit}) /subsystem=elytron/key-manager=wildfly-manager:add(key-store=wildfly, alias-filter=wildfly, credential-reference={clear-text=changeit}) /subsystem=elytron/trust-manager=ca-manager:add(key-store=ca) /subsystem=elytron/server-ssl-context=wildfly-context:add(key-manager=wildfly-manager, protocols=["TLSv1", "TLSv1.1", "TLSv1.2"], trust-manager=ca-manager, want-client-auth=true) batch /subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm) /subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context, value=wildfly-context) run-batch
The default ssl options are also configured to use those stores when using a common WS client:
/system-property=javax.net.ssl.keyStore:add(value="${jboss.server.config.dir}/wildfly.jks") /system-property=javax.net.ssl.keyStorePassword:add(value=changeit) /system-property=javax.net.ssl.trustStore:add(value="${jboss.server.config.dir}/ca.jks")
Although the asynchronous endpoints are available and running after adding the XTS property, they are not used by default. Think that two parts are needed, one is having the endpoints deployed and the other is modifying the register call to send those endpoints, transforming the call from synchronous to asynchronous. This last part is managed using the xts-properties.xml configuration file for XTS.
Extract the default configuration xts-properties.xml from the library (jbossxts-X.X.X.jar, inside the modules folder of the wildfly installation), it's at first level inside the jar. Modify the file adding the property org.jboss.jbossts.xts.useAsynchronousRequest to SECURE (there is an example of the property but commented inside the file).
<entry key="org.jboss.jbossts.xts.useAsynchronousRequest">SECURE</entry>
This way the XTS implementation will use the secure endpoints (https) in the registration call and the windows counterpart will call them with the response or the fault. Move the xts-properties.xml to the configuration folder (${JBOSS_HOME}/standalone/configuration/). The following CLI commands configure it to be used (there is a java option for that) besides some logging is also added to debug the calls during the process and web services are configured to use https scheme and the correct server name by default.
/system-property=org.jboss.jbossts.xts.propertiesFile:add(value="${jboss.server.config.dir}/xts-properties.xml") /subsystem=logging/logger=com.arjuna.wsc:add(category=com.arjuna.wsc, level=ALL) /system-property=org.apache.cxf.logging.enabled:add(value=true) /subsystem=webservices:write-attribute(name=wsdl-uri-scheme, value=https) /subsystem=webservices:write-attribute(name=wsdl-host, value="${jboss.bind.address:daredevil.sample.com}")
The sample WS-AT application that is integrated in the wildfly quickstarts is going to be used for testing. Some minor changes are needed to accommodate the code to windows. First clone the quickstart repository.
git clone https://github.com/wildfly/quickstart cd quickstart/wsat-simple
Then three modifications are done: windows prefers DOCUMENT style instead of RPC; by default Microsoft uses SOAP12, so the endpoint is tagged to be SOAP12HTTP_BINDING; finally the test client is configured to use the https endpoint in my wildfly machine (forcing https communication). Here it is my patch for the application.
diff --git a/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/RestaurantServiceATImpl.java b/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/RestaurantServiceATImpl.java index 7f5f2f80..62afa1ab 100644 --- a/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/RestaurantServiceATImpl.java +++ b/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/RestaurantServiceATImpl.java @@ -28,6 +28,8 @@ import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.servlet.annotation.WebServlet; import java.util.UUID; +import javax.xml.ws.BindingType; /** * An adapter class that exposes the RestaurantManager business API as a transactional Web Service. @@ -38,7 +40,8 @@ import java.util.UUID; @WebService(serviceName = "RestaurantServiceATService", portName = "RestaurantServiceAT", name = "RestaurantServiceAT", targetNamespace = "http://www.jboss.org/jboss-jdf/jboss-as-quickstart/wsat/simple/Restaurant") @HandlerChain(file = "/context-handlers.xml", name = "Context Handlers") -@SOAPBinding(style = SOAPBinding.Style.RPC) +@SOAPBinding(style = SOAPBinding.Style.DOCUMENT) +@BindingType(value = javax.xml.ws.soap.SOAPBinding.SOAP12HTTP_BINDING) @WebServlet("/RestaurantServiceAT") public class RestaurantServiceATImpl implements RestaurantServiceAT { diff --git a/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/jaxws/RestaurantServiceAT.java b/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/jaxws/RestaurantServiceAT.java index c2bc4bb2..39d3bda2 100644 --- a/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/jaxws/RestaurantServiceAT.java +++ b/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/jaxws/RestaurantServiceAT.java @@ -20,6 +20,7 @@ import org.jboss.as.quickstarts.wsat.simple.RestaurantException; import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; +import javax.xml.ws.BindingType; /** * Interface to a simple Restaurant. Provides simple methods to manipulate bookings. @@ -27,7 +28,8 @@ import javax.jws.soap.SOAPBinding; * @author paul.robinson@redhat.com, 2012-01-04 */ @WebService(name = "RestaurantServiceAT", targetNamespace = "http://www.jboss.org/jboss-jdf/jboss-as-quickstart/wsat/simple/Restaurant") -@SOAPBinding(style = SOAPBinding.Style.RPC) +@SOAPBinding(style = SOAPBinding.Style.DOCUMENT) +@BindingType(value = javax.xml.ws.soap.SOAPBinding.SOAP12HTTP_BINDING) public interface RestaurantServiceAT { /** diff --git a/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/servlet/WSATSimpleServletClient.java b/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/servlet/WSATSimpleServletClient.java index 015885ce..58ff26d7 100644 --- a/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/servlet/WSATSimpleServletClient.java +++ b/wsat-simple/src/main/java/org/jboss/as/quickstarts/wsat/simple/servlet/WSATSimpleServletClient.java @@ -55,7 +55,7 @@ public class WSATSimpleServletClient extends HttpServlet { private static final long serialVersionUID = -8314035702649252239L; - @WebServiceRef(value = RestaurantServiceATService.class) + @WebServiceRef(value = RestaurantServiceATService.class, wsdlLocation = "https://daredevil.sample.com:8443/RestaurantServiceAT?wsdl") private RestaurantServiceAT client; @Override
Finally just package the application and deploy the resulting wsat-simple.war file in the wildfly server.
mvn clean package
And that is all. The wildfly application should work locally in the server. The video shows how you can go to the https port (application is deployed in / by default) and the local client is used to start a WS-AT call to the same server (both client and server are inside the wildfly server). The client starts the transaction and the ws endpoint registers asynchronously against the coordination endpoints inside the same server. The successful page is shown saying the transaction was completed with no issue. You can check the logs to see that the registration call was made contacting to the new asynchronous endpoints (the ReplyTo is filled with the new async URL and the response is received to that endpoint).
The most interesting part is left for the next entry. A .NET application will be integrated to call the WS-AT endpoint in the wsat-simple application. In turn the wildfly web service will call the windows coordination endpoint (which require asynchronous calling) to be registered as a partner in the transaction. The configuration of both sides is not trivial so I prefer to divide the entry in two parts. I have the demo working but I need to collect some screenshots for the windows configuration (I am sorry in advance about the Microsoft part, please consider I never use windows and every time I have to work with it the results are a big mess).
Asynchronous regards!
Comments