Tag Archives: spring-ws

Spring WS Security on both client and server

Recently, I have been playing with Spring WS with WS-Security. I just want to write down how it works. Do not except anything special, just simple example of basic security operations.

The example

We want to implement both client and server side. The client will sign the message, encrypt some part of it and add a timestamp. To make it more complex and real-life like we will sign the message using private key with alias “client” and encrypt the message using public key called “server”. Server will validate that the request is valid and will just sign the response using his key called “server”. Please note that I have picked Wss4j implementation because the configuration seemed to be easier than Xws.

Client

It’s easy to do configure client interceptor like this.

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
	<property name="interceptors">
		<list>
			<ref local="wsClientSecurityInterceptor"/>
		</list>
	</property>
	...
</bean>

<bean id="wsClientSecurityInterceptor"
	class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
	<property name="securementActions" value="Timestamp Signature Encrypt" />
	<!-- Key alias for signature -->
	<property name="securementUsername" value="client" />
	<property name="securementPassword" value="" />
	<property name="securementSignatureCrypto" ref="clientCrypto"/>
	<property name="securementEncryptionCrypto" ref="clientCrypto"/>
	<property name="securementEncryptionParts" value="{Content}{http://javacrumbs.net/calc}a"/>
	<!-- Key alias for encryption -->
	<property name="securementEncryptionUser" value="server"/>
	
	<!-- Validation config -->
	<property name="validationActions" value="Signature" />
	<property name="validationSignatureCrypto" ref="clientCrypto"/>
</bean>

<bean id="clientCrypto" class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
    <property name="keyStorePassword" value="mypasswd"/>
    <property name="keyStoreLocation" value="classpath:security/client-keystore.jks"/>
</bean>

As you can see, there is nothing special. We just define which actions to take and properties. The only confusing part is, that key alias is defined as “securementUsername”.

Whit this configuration we will get following SOAP message.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
	<SOAP-ENV:Header>
		<wsse:Security
			xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
			SOAP-ENV:mustUnderstand="1">
			<xenc:EncryptedKey Id="EncKeyId-F5114C147B958E706212759086159355"
				xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
				<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
				<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
					<wsse:SecurityTokenReference
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
						<ds:X509Data>
							<ds:X509IssuerSerial>
								<ds:X509IssuerName>CN=Test Server,OU=Test</ds:X509IssuerName>
								<ds:X509SerialNumber>1275904530</ds:X509SerialNumber>
							</ds:X509IssuerSerial>
						</ds:X509Data>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
				<xenc:CipherData>
					<xenc:CipherValue>fwFM7ShJ1xd7dTGrkh0410sTmW92OPB1q1fpzB21XFIe36siDDJWGgbw5B94yjmGK2YaPOWLb7cpVTYPzc9VUDs7Jc42CtrhT2H6eZ7CDiA60Ugz+qi2UyyfMDK6Vrdj9J68rij5P12AiBeTnd2wlhI29+71XbUpD5weHDHjMtQ=
					</xenc:CipherValue>
				</xenc:CipherData>
				<xenc:ReferenceList>
					<xenc:DataReference URI="#EncDataId-4" />
				</xenc:ReferenceList>
			</xenc:EncryptedKey>
			<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
				Id="Signature-2">
				<ds:SignedInfo>
					<ds:CanonicalizationMethod
						Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
					<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
					<ds:Reference URI="#id-3">
						<ds:Transforms>
							<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
						</ds:Transforms>
						<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
						<ds:DigestValue>AU9utUgz5RylYCRDUAO0JWM48kM=</ds:DigestValue>
					</ds:Reference>
				</ds:SignedInfo>
				<ds:SignatureValue>
					NHjjgpb9/alUOq50CqPKLcdYrp7edYdKJDNvIhh+2OAhYdDvZmD1qGsVKd1H9oKPF17uaF2Sv3aY
					0le6BrvzVx3n2+nYYlHwAWlzBk7wsBt4vLll6q6juLCP+siupTIb1PeZDf3WrAbHUQh5oqjD6cZB
					Sc89pDspWRABQ8wPxYE=
</ds:SignatureValue>
				<ds:KeyInfo Id="KeyId-F5114C147B958E706212759086157652">
					<wsse:SecurityTokenReference
						xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
						wsu:Id="STRId-F5114C147B958E706212759086157673"
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
						<ds:X509Data>
							<ds:X509IssuerSerial>
								<ds:X509IssuerName>CN=Lukas Krecan,OU=Test</ds:X509IssuerName>
								<ds:X509SerialNumber>1275900789</ds:X509SerialNumber>
							</ds:X509IssuerSerial>
						</ds:X509Data>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
			</ds:Signature>
			<wsu:Timestamp
				xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
				wsu:Id="Timestamp-1">
				<wsu:Created>2010-06-07T11:03:35.749Z</wsu:Created>
				<wsu:Expires>2010-06-07T11:08:35.749Z</wsu:Expires>
			</wsu:Timestamp>
		</wsse:Security>
	</SOAP-ENV:Header>
	<SOAP-ENV:Body
		xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
		wsu:Id="id-3">
		<ns2:plusRequest xmlns:ns2="http://javacrumbs.net/calc">
			<ns2:a>
				<xenc:EncryptedData Id="EncDataId-4"
					Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
					<xenc:EncryptionMethod
						Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
					<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
						<wsse:SecurityTokenReference
							xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
							<wsse:Reference URI="#EncKeyId-F5114C147B958E706212759086159355"
								xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" />
						</wsse:SecurityTokenReference>
					</ds:KeyInfo>
					<xenc:CipherData>
						<xenc:CipherValue>81TEtUhHXo6iZeAmYrtYlm2ObAqOBpjfzf2VOVUg4Hs=
						</xenc:CipherValue>
					</xenc:CipherData>
				</xenc:EncryptedData>
			</ns2:a>
			<ns2:b>2</ns2:b>
		</ns2:plusRequest>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Server config

To configure server, you have to define Spring WS server interceptor like this (full example).

<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
	<property name="interceptors">
		<list>
			<ref local="wsServerSecurityInterceptor" />
		</list>
	</property>
</bean>

<bean id="wsServerSecurityInterceptor"	class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
	<!-- Validation part -->
	<property name="validationActions" value="Timestamp Signature Encrypt"/>
	<property name="validationSignatureCrypto" ref="serverCrypto"/>
	<property name="validationDecryptionCrypto" ref="serverCrypto"/>
	<property name="validationCallbackHandler">
		<bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
			<property name="keyStore">
				<bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
				    <property name="password" value="mypasswd"/>
				</bean>
			</property>
			<property name="privateKeyPassword" value=""/>
		</bean> 
	</property>
	<!-- Sign the response -->
	<property name="securementActions" value="Signature" />
	<property name="securementUsername" value="server" />
	<property name="securementPassword" value="" />
	<property name="securementSignatureCrypto" ref="serverCrypto"/>
</bean>

<bean id="serverCrypto" class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
    <property name="keyStorePassword" value="mypasswd"/>
    <property name="keyStoreLocation" value="classpath:security/server-keystore.jks"/>
</bean>

No surprise here neither. The response will look like this.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
	<SOAP-ENV:Header>
		<wsse:Security
			xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
			SOAP-ENV:mustUnderstand="1">
			<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
				Id="Signature-6">
				<ds:SignedInfo>
					<ds:CanonicalizationMethod
						Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
					<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
					<ds:Reference URI="#id-7">
						<ds:Transforms>
							<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
						</ds:Transforms>
						<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
						<ds:DigestValue>hEdDfxM6Nfs62Jxe8EOsELCDtUk=</ds:DigestValue>
					</ds:Reference>
					<ds:Reference URI="#SigConf-5">
						<ds:Transforms>
							<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
						</ds:Transforms>
						<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
						<ds:DigestValue>TTSRri5KJqXeMJfjzXyVmUewPxc=</ds:DigestValue>
					</ds:Reference>
				</ds:SignedInfo>
				<ds:SignatureValue>
					V5by3bOoGQNajfs7i9xQ+cbAqIkI0NS9N9FQlLb/dAuQfguE7jKRP9iypOeRLHCPr7g3BNg+NCrX
					6YcgDQ0TfXNhdL00AmoEfDmWSNvIVNE49kZEn3Ji/RW4VtdEiV79VD7Vuay0YAYGo9DSQvzq3FP6
					YEhfzfMqvfbWMdEKcO8=
</ds:SignatureValue>
				<ds:KeyInfo Id="KeyId-F5114C147B958E706212759086160837">
					<wsse:SecurityTokenReference
						xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
						wsu:Id="STRId-F5114C147B958E706212759086160838"
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
						<ds:X509Data>
							<ds:X509IssuerSerial>
								<ds:X509IssuerName>CN=Test Server,OU=Test</ds:X509IssuerName>
								<ds:X509SerialNumber>1275904530</ds:X509SerialNumber>
							</ds:X509IssuerSerial>
						</ds:X509Data>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
			</ds:Signature>
			<wsse11:SignatureConfirmation
				xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
				xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
				Value="NHjjgpb9/alUOq50CqPKLcdYrp7edYdKJDNvIhh+2OAhYdDvZmD1qGsVKd1H9oKPF17uaF2Sv3aY0le6BrvzVx3n2+nYYlHwAWlzBk7wsBt4vLll6q6juLCP+siupTIb1PeZDf3WrAbHUQh5oqjD6cZBSc89pDspWRABQ8wPxYE="
				wsu:Id="SigConf-5" />
		</wsse:Security>
	</SOAP-ENV:Header>
	<SOAP-ENV:Body
		xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
		wsu:Id="id-7">
		<ns2:plusResponse xmlns:ns2="http://javacrumbs.net/calc">
			<ns2:result>3</ns2:result>
		</ns2:plusResponse>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

As we have seen it’s possible to configure WS-Security without much hassle. To learn more, visit the official Spring WS reference. You can download full example here.

Spring WS Test

Last few weeks I have been working on one of my pet projects. Its name is Spring WS Test. As the name implies, its main purpose is to simplify Spring WS tests.

Again, I am scratching my own itch. I am quite test infected and I have needed something that allows me to write functional tests of my application without having to depend on an external server. Until now, you basically had two options. This first one is to test WS client application using plain old JUnit together with a library like EasyMock. But usually this test are quite ugly and hard to read. Moreover this type of tests does not test your configuration. The second option is to create a functional test that calls an external mock service. But this solution requires you to have two JVM, its configuration is complicated and error prone.

Classical WS test

I have been looking for something in between, for something that would allow me to write functional tests using JUnit and would be able to run in the same JVM as the test. Unfortunately I have not been able to find anything similar.

Spring WS Test test

That’s the reason why I have created Spring WS Test project. It’s quite simple and easy even though I had to spent lot of my evenings getting it into a publishable state.

Basic configuration looks like this

<beans ...>
  <!-- Creates mock message sender -->
  <bean id="messageSender" class="net.javacrumbs.springws.test.MockWebServiceMessageSender"/>
  
  <!-- Injects mock message sender into WebServiceTemplate -->
  <bean class="net.javacrumbs.springws.test.util.MockMessageSenderInjector"/>
        
  <!-- Looks for responses on the disc based on the provided XPath -->
  <bean class="net.javacrumbs.springws.test.generator.DefaultResponseGeneratorFactoryBean">
     <property name="namespaceMap">
         <map>
            <entry key="soapenv" 
                value="http://schemas.xmlsoap.org/soap/envelope/"/>
            <entry key="ns" 
                value="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"/>
         </map>
     </property>
     <property name="XPathExpressions">
         <list>
             <value>
                 concat(local-name(//soapenv:Body/*[1]),'/default-response.xml')
             </value>
         </list>
     </property>             
 </bean>
</beans>   

Here we have MockWebServiceMessageSender that replaces standard Spring WebServiceMessageSender. The replacement is done by MockMessageSenderInjector. The only other thing you have to do is to define ResponseGenerator. It’s main purpose is to look for files in you test classpath and return them as mock responses.

Of course it has to decide, which file to use. By default a XPath expression is used to determine the resource name. In our example it is concat(local-name(//soapenv:Body/*[1]),'/default-response.xml'). It takes name of the payload (first soap:Body child) and uses it as a directory name. File “default-response.xml” from this directory is used as the mock response. Simple isn’t it?

Of course you can define more complicated XPaths, you can use XSLT templates to generate your responses, you can validate your requests etc. More details can be found in the documentation.

Now I am looking for some end-user feedback. So please, if you are using Spring WS on the client side do not hesitate and test it. It should be stable enough to be used although there might be a bug here and there.

Spring WS fault detail

Spring WS project provides nice and versatile exception handling tools. But in some scenarios predefined Exception Resolvers are not sufficient. For example if you want to provide additional error info in the soap:fault detail like in this example:


   
   
      
         SOAP-ENV:Client
         Something wrong happened
         
            Error
            BigTrouble
         
      
   

Fortunately it is quite easy to add similar behavior using Spring WS. You can easily extend existing SoapFaultMappingExceptionResolver and customize the fault (please note that EndpointExeption is project specific exception that provides necessary data):

 public class EndpointExceptionResolver extends SoapFaultMappingExceptionResolver {
	private static final QName CODE = new QName("code");
	private static final QName SUB_CODE = new QName("sub-code");

	@Override
	protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
		logger.warn("Exception processed ",ex);
		if (ex instanceof EndpointException) {
			EndpointException ee = (EndpointException) ex;
			SoapFaultDetail detail = fault.addFaultDetail();
			detail.addFaultDetailElement(CODE).addText(ee.getCode());
			detail.addFaultDetailElement(SUB_CODE).addText(ee.getSubCode());
		}
	}
}