Spring Web Services (Spring-WS) are a neat way of declaratively creating SOAP web services using Spring with a minimum of boilerplate code usually associated with web services. I’d recommend it as the best way to create web services for a Spring application. When it comes to WS-Security (message encryption, authentication, signatures and so on) it is absolutely vital. It simplifies the very complicated business of securing messages to a few lines of declarative code.
I found the documentation provided by Spring on writing Spring-WS services and securing Spring-WS services very in depth and thorough but I’ve not yet found a good simple example app. This demo is about the simplest possible web service with the most standard WS-Security features enabled.
This demo is available to download. It is built using Maven 2.
Create the webapp
The Spring web application is fairly simple. The web.xml includes the Spring-WS MessageDispatcherServlet.
<servlet> <servlet-name>spring-ws</servlet-name> <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class> <init-param> <param-name>transformWsdlLocations</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
This servlet is where the magic happens. It reads its config from a file named after the servlet (in my case spring-ws-servlet.xml). This file contains configures exactly how my web service will behave.
The Endpoint
The endpoint is configured in spring-ws-servlet.xml as follows:
<bean id="endpoint"/> <bean> <property name="defaultEndpoint" ref="endpoint"/> <property name="interceptors"> <list> <ref bean="securityInterceptor"/> </list> </property> </bean>
The endpoint bean is the class that handles the SOAP message. Spring-WS provides a number of ways of handling your message, but the simplest is to subclass AbstractJDomPayloadEndpoint and implement the invokeInternal method. This lets you handle the request and response as JDOM Elements.
public class Endpoint extends AbstractJDomPayloadEndpoint { private static final Namespace NS = Namespace.getNamespace("ws", "http://dontpanic.org/ws/spannersws"); public Element invokeInternal(Element message) throws Exception { // Obtain the spanner id from the message XPath xpath = XPath.newInstance("//ws:id"); xpath.addNamespace(NS); String id = xpath.valueOf(message); // TODO Do some work. Request to retrieve spanner would go here. // Assemble response Element response = new Element("GetSpannerResponse", NS); response.addContent(new Element("id", NS).setText(id)); response.addContent(new Element("name", NS).setText("Spanner " + id)); response.addContent(new Element("size", NS).setText("42")); return response; } }
The other bean required here is the endpoint mapping. This simply forwards every request to the appropriate endpoint bean. I only have one endpoint bean and I want every request to go to it so I’ve not configured an endpointMap, I’ve just set the defaultEndpoint.
The interceptors property allows us to define any number of interceptors on the incoming and outgoing messages to the endpoint. This allows us to add features such as message logging, validation, error handling and (most importantly) security. We’ll look at the security interceptor later.
The WSDL
The WSDL defines our service contract. It defines (among other things) the format of the request and response messages. Some frameworks derive the WSDL from the Java classes that are used in the service classes. This can be useful but often causes all sorts of problems when the Java classes change and the WSDL has to be regenerated. Spring-WS is contract first meaning that we start with the WSDL and then create the service in line with the contract.
In fact, we don’t need to define the complete WSDL. We just need to define the XSD schema of our request and response messages. Our schema looks like this:
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://dontpanic.org/ws/spannersws" xmlns:sws="http://dontpanic.org/ws/spannersws"> <!-- GetSpanner request Message contains just the spanner id --> <xs:element name="GetSpannerRequest"> <xs:complexType> <xs:all> <xs:element name="id" type="xs:int"/> </xs:all> </xs:complexType> </xs:element> <!-- GetSpanner response Message contains spanner id, name and size --> <xs:element name="GetSpannerResponse"> <xs:complexType> <xs:all> <xs:element name="id" type="xs:int"/> <xs:element name="name" type="xs:string"/> <xs:element name="size" type="xs:int"/> </xs:all> </xs:complexType> </xs:element> </xs:schema>
The WSDL is configured in the spring-ws-servlet.xml from this schema:
<!-- WSDL definition --> <bean id="spannersws" p:schema-ref="schema" p:portTypeName="SpannersWS" p:locationUri="/spannersws" p:targetNamespace="http://dontpanic.org/ws/spannersws"/> <!-- WSDL schema --> <bean id="schema" p:xsd="/WEB-INF/spanners.xsd"/>
This will create a WSDL which will be served at http://localhost:8080/spannersws/spannersws.wsdl
The Security Configuration
The securityInterceptor injected into the endpoint mapping handles security considerations for the request and response messages.
<!-- WSS Security interceptor - encrypt entire body of SOAP response --> <bean id="securityInterceptor" p:validateRequest="true" p:validationActions="Signature Encrypt Timestamp" p:validationSignatureCrypto-ref="crypto" p:validationDecryptionCrypto-ref="crypto" p:validationCallbackHandler-ref="validationCallbackHandler" p:secureResponse="true" p:securementActions="Timestamp Signature Encrypt" p:securementSignatureKeyIdentifier="DirectReference" p:securementUsername="myAlias" p:securementPassword="myPass" p:securementSignatureCrypto-ref="crypto" p:securementEncryptionUser="myAlias" p:securementEncryptionKeyIdentifier="Thumbprint" p:securementEncryptionCrypto-ref="crypto" p:securementEncryptionSymAlgorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" p:securementEncryptionKeyTransportAlgorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/> <bean id="validationCallbackHandler" p:privateKeyPassword="myPass"/> <bean id="crypto" p:keyStorePassword="myPass" p:keyStoreLocation="/WEB-INF/crypto.keystore"/>
The validationActions define what actions will be performed on the incoming request. In this case, I want to check the signature, then decrypt, then check the timestamp (in that order). If the signature is incorrect, the message can’t be decrypted or the message is out of date, the security interceptor will reject it. This guarantees that the message was sent by an authorised party and that it was sent securely and recently. If any of these security considerations are not necessary, they can be removed from the validationActions. For example, we may not be concerned about replay attacks so we may choose to omit the Timestamp.
The securementActions define actions to be performed on the outgoing response. Again, we want the message timestamped, signed and encrypted (in that order).
For both validationActions and securementActions, we define a number of other properties and beans configuring our keystore (and its username and password) and algorithms for encryption.
The validateRequest and secureResponse properties are useful. Setting one or both of these to false switches off the validation / securement. This allows us to simply test the web service in plain text before we enable security.
Advantages of Spring-WS for Security
Security mechanisms are notoriously difficult to implement. By their nature they’re designed to prevent something from working unless it is used exactly correctly. Having Spring-WS do the heavy lifting makes our application far more likely to work and far more likely to be secure. Using Spring-WS means that we do not have to implement security in our own Java code nor do we have to manually craft or interpret SOAP headers. Which is just as well as the request and response messages look like this:
What could go wrong?
Sadly, although the code required to create web services is trivial, configuring and deploying the application can be tricky. Web service and XML based applications are notoriously prone to the Java equivalent of DLL hell with XML libraries deployed in the Java distribution, the application server and the application itself all conflicting with each other. I’ve noted a couple of these conflicts in a previous post regarding SAAJ in JBoss 5 but it’s likely that some serious Googling may be required for your particular setup. I’ve included the maven-jetty-pluginĀ (my new favourite Maven plugin!) in this example so that it can be run without having to configure a webserver. Just run
mvn jetty:run
to build the app and run it from Maven. It’s built for (Sun) Java 1.6 but also works fine for Java 1.5 so long as you include the SAAJ libraries in the war by marking them as compile scoped dependencies in the POM.
[…] my previous post regarding Spring-WS and Security I didn't mention anything about testing the resulting SOAP service. Particularly when it comes to […]
Can you please also place a text “XML/wsdl request” that I can use to test the example with in soapUI?
A sample request is included in the demo source. Just import spannersws/src/test/spannersws-soapui-project.xml into soapUI.
I import it in which creates this input:
1
After submitting input I get nothing back. The tomcat logs are empty, the console is empty.
When I send the input xml I get this error back:
SOAP-ENV:Client
No WS-Security header found
Looks like soapUI can’t find the required keystore/certificate file. It usually prompts you for this as it imports the project. If you’ve already imported the project, you can point it at the keystore file from the project view (right click on the project name in the navigator). It’s in WS-Security Configurations / Keystores.
Make sure that the provided crypto.keystore is in the list. You’ll find it in spannersws/src/main/webapp/WEB-INF/crypto.keystore.
soapUI sometimes has trouble with this as it uses absolute file references in its project file. That means it doesn’t always work if you copy the project file to another location.
We have to specify the Security Files (keystore & Certticate) while encrypting the message at client side…
Namely (.ksc, .csr)
Hi Stevie,
Could you please provide me a sample Java Spring-WS client or suggest the steps that can call this web service securely?
Anriban, the full source code is available for download at the top of this article. Good luck!
Hi Stevie,
Many thanks for your blog. Its really very very helpful.
I manage to create/build the project in eclipse and run it using jetty.
Having trouble to send request in soapUI and getting the same error “No WS-Security header found”. The Request xml is not similar to “http://www.disasterarea.co.uk/blog/wp-content/uploads/2011/02/SoapUI.png” and very small
1
Checked below things to correct
(1) I have corrected hardcoding keystore path “C:/Dev/projects/spannersws/src/main/webapp/WEB-INF/crypto.keystore” to my location in spannersws-soapui-project.xml
(2) I checked soapUI project -> WS-Security Configurations -> Keystores configuration which is also ok
Do you have any idea why i’m not able to see proper GetSpanner “Request 1” and run soapUI project ‘spannersws-soapui-project.xml’ ?
Your help would be much appreciated.
Many Thanks,
Pragun
(pragun.mehta@gb.unisys.com)
Manage to run it successfully with the help of your next blog
http://www.disasterarea.co.uk/blog/web-service-testing-with-soapui/
many thanks,
pragun.