Sometimes when accessing SOAP APIs, our SOAP client needs to sign the request. How this can be achieved using Spring Boot’s WebserviceTemplate within a few steps is the scope of this short article.

This snippet only deals with the client side not with the security configuration on the server side.

Also it assumes, that you have already set up your keystore/truststore and that you’re loading these with your Spring Boot application’s startup without errors.

Using Maven, we need the following two dependencies in our pom.xml:

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws-security</artifactId>
</dependency>

The following configuration sets up our CryptoFactoryBean, the SecurityInterceptor for our SOAP requests and finally the WebserviceTemplate:

WssConfiguration.java
package com.hascode.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor;
import org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean;

import java.io.IOException;

@Configuration
public class WssConfiguration {
    private static final Logger log = LoggerFactory.getLogger(WssConfiguration.class);

    private final Resource keystore;
    private final String keystorePassword;
    private final String signatureUser = "foo";

    public WssConfiguration(@Value("${server.ssl.key-store}") Resource keystore,
                           @Value("${server.ssl.key-store-password}") String keystorePassword) {
        this.keystore = keystore;
        this.keystorePassword = keystorePassword;
    }


    @Bean
    public CryptoFactoryBean getCryptoFactoryBean() throws IOException {
        CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
        cryptoFactoryBean.setKeyStorePassword(keystorePassword);
        cryptoFactoryBean.setKeyStoreLocation(keystore);
        return cryptoFactoryBean;
    }

    @Bean
    public Wss4jSecurityInterceptor serverSecurityInterceptor() {
        var securityInterceptor = new Wss4jSecurityInterceptor();
        try {
            securityInterceptor
                .setValidationSignatureCrypto(getCryptoFactoryBean().getObject());
        } catch (Exception e) {
            log.error("error accessing the truststore", e);
        }
        securityInterceptor.setSecurementActions("Signature"); // Signing only, no Timestamp
        securityInterceptor.setSecurementUsername(signatureUser);
        securityInterceptor.setSecurementPassword(keystorePassword);
        try {
            securityInterceptor
                .setSecurementSignatureCrypto(getCryptoFactoryBean().getObject());
        } catch (Exception e) {
            log.error("error accessing the keystore", e);
        }
        return securityInterceptor;
    }

    @Bean
    public WebServiceTemplate getWebServiceTemplate(Jaxb2Marshaller marshaller) {
        var webServiceTemplate = new WebServiceTemplateBuilder().build();
        ClientInterceptor[] interceptors = new ClientInterceptor[] {serverSecurityInterceptor()};
        webServiceTemplate.setInterceptors(interceptors);
        webServiceTemplate.setMarshaller(marshaller);
        webServiceTemplate.setUnmarshaller(marshaller);
        return webServiceTemplate;
    }
}
application.properties
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.ws.client.MessageTracing.sent=DEBUG
logging.level.org.springframework.ws.server.MessageTracing.sent=DEBUG
logging.level.org.springframework.ws.client.MessageTracing.received=TRACE
logging.level.org.springframework.ws.server.MessageTracing.received=TRACE

Sending a SOAP request with the WebserviceTemplate now should produce a WSS compliant SOAP request with a signed envelope.

Resources: