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.javapublic class BlzServiceAdapter extends WebServiceGatewaySupport { private static final Logger logger = LoggerFactory.getLogger(BlzServiceAdapter.class); public GetBankResponseType getBank(String url, Object requestPayload){ WebServiceTemplate webServiceTemplate = getWebServiceTemplate(); JAXBElementres = 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(); JAXBElementres = 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.
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"); JAXBElementheaders = 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.