3 Ways to Add Custom Header in Spring SOAP Request

author-image  By Dhiraj, 09 March, 2019   1K

In this article, we will check out 3 different ways to add a custom header in Spring SOAP(Simple Object Access Protocol) request. In my last article, we created a Spring Boot SOAP client and then discussed about handling exceptions in it. Now, let us add a custom header in the request. This is mostly needed to add authentication related details in the header while invoking SOAP web services.

In most cases, SOAP headers are not specified in the WSDL document and hence we need to manually add those headers in the request. While using WebServiceTemplate, Spring provides numerous ways to intercept the request and modify the request and response. Hence, the interceptor can be a one way to add a header in the request. Similarly, we can implement WebServiceMessageCallback and override doWithMessage() method to add custom header. Also, we can use JAXB Marshaller to add headers.

Below is the sample XML header that we will be adding in the header of SOAP request. The header element is a complex type.

<soapenv:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsse:Username>abc</wsse:Username>
            <wsse:Password>abc</wsse:Password>
        </wsse:UsernameToken>
    </wsse:Security>
</soapenv:Header>

SOAP Web Service Adapter

We had a defined a very simple adapter class implementation in our last example. We will be using the same here. Below is the implementation of it.

BlzServiceAdapter.java
public class BlzServiceAdapter extends WebServiceGatewaySupport {

	private static final Logger logger = LoggerFactory.getLogger(BlzServiceAdapter.class);

	public GetBankResponseType getBank(String url, Object requestPayload){
		WebServiceTemplate webServiceTemplate = getWebServiceTemplate();
		JAXBElement res = null;
		try {
			res = (JAXBElement) webServiceTemplate.marshalSendAndReceive(url, requestPayload);
		}catch(SoapFaultClientException ex){
			logger.error(ex.getMessage());
		}
		return res.getValue();
	}
}

Using TransformerFactory to Transform XML to Header

An instance of this abstract class can transform a source tree into a result tree. To use this, first we will have the XML structure predefined in our workspace. We will be using the XML defined above. We have below entry in application.properties.

soap.auth.header=<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><wsse:Username>%(loginuser)</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">%(loginpass)</wsse:Password></wsse:UsernameToken></wsse:Security>
soap.auth.username=testuser
soap.auth.password=testpass

Below is the new implementation of getBank(). The XML defined in properties file will be transformed in the SOAP header.

private static final Logger logger = LoggerFactory.getLogger(BlzServiceAdapter.class);

@Autowired
private Environment environment;

public GetBankResponseType getBank(String url, Object requestPayload){
	WebServiceTemplate webServiceTemplate = getWebServiceTemplate();
	JAXBElement res = null;
	try {
		res = (JAXBElement) webServiceTemplate.marshalSendAndReceive(url, requestPayload, new WebServiceMessageCallback() {

			@Override
			public void doWithMessage(WebServiceMessage message) {
				try {
					SoapHeader soapHeader = ((SoapMessage) message).getSoapHeader();
					Map mapRequest = new HashMap();

					mapRequest.put("loginuser", environment.getProperty("soap.auth.username"));
					mapRequest.put("loginpass", environment.getProperty("soap.auth.password"));
					StringSubstitutor substitutor = new StringSubstitutor(mapRequest, "%(", ")");
					String finalXMLRequest = substitutor.replace(environment.getProperty("soap.auth.header"));
					StringSource headerSource = new StringSource(finalXMLRequest);
					Transformer transformer = TransformerFactory.newInstance().newTransformer();
					transformer.transform(headerSource, soapHeader.getResult());
					logger.info("Marshalling of SOAP header success.");
				} catch (Exception e) {
					logger.error("error during marshalling of the SOAP headers", e);
				}
			}
		});
	}catch (SoapFaultClientException e){
		logger.error("Error while invoking session service : " + e.getMessage());
		return null;
	}
	return res.getValue();
}

The problem with this method is thaat the XML should be valid. So, if you have a new node after the node security in the header, then you will get an exception as The markup in the document following the root element must be well-formed. To avoid this, we can manually add SOAPHeaderElement in the header.

Using SOAPElement to Header Manually

With this method, doWithMessage() implementation will change. Below, we are manually creating SOAPHeaderElement and SOAPElement provided by javax.xml.soap and adding these nodes to an existing SOAP header.

TokenHeaderRequestCallback.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.soap.saaj.SaajSoapMessage;

import javax.xml.soap.*;

public class TokenHeaderRequestCallback implements WebServiceMessageCallback {

    private static final Logger logger = LoggerFactory.getLogger(SessionHeaderRequestCallback.class);

    private String username;
    private String password;

    public TokenHeaderRequestCallback(String username, String password){
        this.username = username;
        this.password = password;
    }

    public void doWithMessage(WebServiceMessage message) {

        try {

            SaajSoapMessage saajSoapMessage = (SaajSoapMessage)message;

            SOAPMessage soapMessage = saajSoapMessage.getSaajMessage();

            SOAPPart soapPart = soapMessage.getSOAPPart();

            SOAPEnvelope soapEnvelope = soapPart.getEnvelope();

            SOAPHeader soapHeader = soapEnvelope.getHeader();

            Name headerElementName = soapEnvelope.createName(
                    "Security",
                    "wsse",
                    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
            );
            SOAPHeaderElement soapHeaderElement = soapHeader.addHeaderElement(headerElementName);

            SOAPElement usernameTokenSOAPElement = soapHeaderElement.addChildElement("UsernameToken", "wsse");

            SOAPElement userNameSOAPElement = usernameTokenSOAPElement.addChildElement("Username", "wsse");
            logger.info(this.username);
            userNameSOAPElement.addTextNode(this.username);

            SOAPElement passwordSOAPElement = usernameTokenSOAPElement.addChildElement("Password", "wsse");

            passwordSOAPElement.addTextNode(this.password);

            soapMessage.saveChanges();
        } catch (SOAPException soapException) {
            throw new RuntimeException("TokenHeaderRequestCallback", soapException);
        }
    }
}

Using JAXB Marshaller to Add Header

To use JAXB marshaller, we need to create the SOAP header using the corresponding JAXB object and marshal it into the SOAPHeader. But, in most of the cases we don't find corresponding JAXB object as SOAP headers are not specified in the WSDL. We can use below implementation for the same.

@Override
public void doWithMessage(WebServiceMessage message) {
	try {
		SoapHeader soapHeader = ((SoapMessage) message).getSoapHeader();
		ObjectFactory factory = new ObjectFactory();
		AuthSoapHeaders authSoapHeaders =
				factory.createAuthSoapHeaders();
		authSoapHeaders.setUsername("testuser");
		authSoapHeaders.setPassword("testpass");
		JAXBElement headers =
				factory.createAuthSoapHeaders(AuthSoapHeaders);
		JAXBContext context = JAXBContext.newInstance(AuthSoapHeaders.class);
		Marshaller marshaller = context.createMarshaller();
		marshaller.marshal(authSoapHeaders, soapHeader.getResult());
	} catch (Exception e) {
		logger.error("error during marshalling of the SOAP headers", e);
	}
}
});

Conclusion

In this example, we learned about different ways of adding custom headers to Spring SOAP request header.

About The Author

author-image

Further Reading on Spring MVC

1. Spring Boot Soap Client

2. Exception Handling Spring Soap Client