How to add header to SOAP request?
Asked Answered
C

4

22

I try to invoke HTTPS SOAP web service through java code:

    URL url = new URL("https://somehost:8181/services/"SomeService?wsdl");
    QName qname = new QName("http://services.somehost.com/", "SomeService");
    Service service = Service.create(url, qname);
    SomeService port = service.getPort(SomeService .class);
    port.doSomething();

But get exception:

threw an unexpected exception: javax.xml.ws.soap.SOAPFaultException: Security Requirements not met - No Security header in message

When I analized correct request sample I determined it have to contain header:

 <S:Header>
  <To xmlns="http://www.w3.org/2005/08/addressing">http://somehost:8181/services/SomeService</To>
  <Action xmlns="http://www.w3.org/2005/08/addressing">https://somehost:8181/services/"SomeService/doSomethingRequest</Action>
  <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
     <Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
  </ReplyTo>
  <MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:3428539e-d645-72ae-adc0-5423c1e68942</MessageID>
  <wsse:Security S:mustUnderstand="true">
     <wsu:Timestamp wsu:Id="_1" xmlns:ns14="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:ns13="http://schemas.xmlsoap.org/soap/envelope/">
        <wsu:Created>2013-01-15T16:36:30Z</wsu:Created>
        <wsu:Expires>2014-01-15T14:06:30Z</wsu:Expires>
     </wsu:Timestamp>
  </wsse:Security>

So how to add this header to my SOAP request?

Crossjack answered 25/1, 2013 at 13:40 Comment(0)
F
51

I personally add two classes: HeaderHandler and HeaderHandlerResolver:

import java.util.HashSet;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;


public class HeaderHandler implements SOAPHandler<SOAPMessageContext> {

public boolean handleMessage(SOAPMessageContext smc) {

    Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

    if (outboundProperty.booleanValue()) {

        SOAPMessage message = smc.getMessage();

        try {

            SOAPEnvelope envelope = smc.getMessage().getSOAPPart().getEnvelope();
            SOAPHeader header = envelope.addHeader();

            SOAPElement security =
                    header.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");



            SOAPElement usernameToken =
                    security.addChildElement("UsernameToken", "wsse");
            usernameToken.addAttribute(new QName("xmlns:wsu"), "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

            SOAPElement username =
                    usernameToken.addChildElement("Username", "wsse");
            username.addTextNode("test");

            SOAPElement password =
                    usernameToken.addChildElement("Password", "wsse");
            password.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
           password.addTextNode("test321");

            //Print out the outbound SOAP message to System.out
            message.writeTo(System.out);
            System.out.println("");

        } catch (Exception e) {
            e.printStackTrace();
        }

    } else {
        try {

            //This handler does nothing with the response from the Web Service so
            //we just print out the SOAP message.
            SOAPMessage message = smc.getMessage();
            message.writeTo(System.out);
            System.out.println("");

        } catch (Exception ex) {
            ex.printStackTrace();
        } 
    }


    return outboundProperty;

}

public Set getHeaders() {
    // The code below is added on order to invoke Spring secured WS.
    // Otherwise,
    // http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
    // won't be recognised 
    final QName securityHeader = new QName(
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
            "Security", "wsse");

    final HashSet headers = new HashSet();
    headers.add(securityHeader);

    return headers;
}

public boolean handleFault(SOAPMessageContext context) {
    //throw new UnsupportedOperationException("Not supported yet.");
    return true;
}

public void close(MessageContext context) {
//throw new UnsupportedOperationException("Not supported yet.");
}
}

And

import java.util.ArrayList;
import java.util.List;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;


public class HeaderHandlerResolver implements HandlerResolver {

public List<Handler> getHandlerChain(PortInfo portInfo) {
  List<Handler> handlerChain = new ArrayList<Handler>();

  HeaderHandler hh = new HeaderHandler();

  handlerChain.add(hh);

  return handlerChain;
   }
}

In the HeaderHandler class, you can add needed credentials. To use them finally:

HeaderHandlerResolver handlerResolver = new HeaderHandlerResolver();
service.setHandlerResolver(handlerResolver);
Feuchtwanger answered 25/1, 2013 at 14:34 Comment(5)
This was exactly what I was looking for. Thank you for your thorough example.Elexa
I'm so happy my answer could be of some helpFeuchtwanger
I wonder why things are so complex with SOAP! Thanks a lot. You saved my day :)Murchison
@LaabidiRaissi : I have followed the steps which you have mentioned. The code works fine but it never appends the security element under the header. I have confirmed it by printing out the outbound SOAP message to System.out. Can you suggest me what I am missing? Does I need to set the message again in the envelope ? In between i have tried that one as well, but no luck.Misdirect
I have found that the SOAPMessage needs to be saved explicitly. After adding message.save(), it worked.Misdirect
M
9

I have followed the steps mentioned by @LaabidiRaissi. The code works fine but it never appends the security element under the header. I have confirmed it by printing out the outbound SOAP message to System.out. After a deep research, I have found that the SOAPMessage needs to be explicitly saved for reflecting the updated message header.

soapMessage.saveChanges();

For more reference - Check this link

Misdirect answered 18/6, 2015 at 7:11 Comment(1)
The code from @Laabidi's answer used to work for me with Java 7 and tomcat 7. After upgrading to Java 8 and tomcat 8 soapMessage.saveChanges() is needed.Myrtamyrtaceous
P
5

Sample main Class:

package test;

import java.util.ArrayList;
import java.util.List;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;

// next headers is generated from "NetBeans New Webservice Client"
import sk.firma.wstest.definitions.*;
import sk.firma.wstest.schemas.*;

/**
 *
 * @author Jan
 */
public class TestWSService {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        try {

            WsService service = new WsService();
            Ws port = service.getWsKsSoap11();

            // This is the block that apply the Ws Security to the request
            BindingProvider bindingProvider = (BindingProvider) port;
            @SuppressWarnings("rawtypes")
            List<Handler> handlerChain = new ArrayList<Handler>();
            handlerChain.add(new WSSecurityHeaderSOAPHandler("username", "password"));
            bindingProvider.getBinding().setHandlerChain(handlerChain);

            // Initialize and Run Service
            InVal inVal = new InVal();
            ReturnValue retVal = port.test(inVal);

        } catch (Exception e) {
            e.printStackTrace();
        }       
    }
}

And now WS-Security Header SOAP Handler:

package test;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class WSSecurityHeaderSOAPHandler implements SOAPHandler<SOAPMessageContext> {

    private static final String URL_WSSE_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    private static final String URL_WSU_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

    private final String usernameText;
    private final String passwordText;

    public WSSecurityHeaderSOAPHandler(String usernameText, String passwordText) {
        this.usernameText = usernameText;
        this.passwordText = passwordText;

    }

    public String getCurrentDateTime() {
        /* e.g. 2001-10-13T09:00:00Z */
        final SimpleDateFormat FORMATTER_DATETIME_NO_MS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");

        DateFormat dfETC = FORMATTER_DATETIME_NO_MS;
        dfETC.setTimeZone(TimeZone.getTimeZone("CET"));

        StringBuffer dateETC = new StringBuffer(dfETC.format(new Date()));
        dateETC.append('Z');
        return dateETC.toString();
    }

    public String getCurrentDateTimePlusDelay(long delayInSeconds) {
        /* e.g. 2001-10-13T09:00:00Z */
        final SimpleDateFormat FORMATTER_DATETIME_NO_MS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");

        DateFormat dfETC = FORMATTER_DATETIME_NO_MS;
        dfETC.setTimeZone(TimeZone.getTimeZone("CET"));
        Date date = new Date();

        long timeInMsecs = date.getTime();
        date.setTime(timeInMsecs + delayInSeconds*1000L);
        StringBuffer dateETC = new StringBuffer(dfETC.format(date));
        dateETC.append('Z');
        return dateETC.toString();
    }

    @Override
    public boolean handleMessage(SOAPMessageContext soapMessageContext) {

        Boolean outboundProperty = (Boolean) soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (outboundProperty) {

            try {
                SOAPEnvelope soapEnvelope = soapMessageContext.getMessage().getSOAPPart().getEnvelope();

                SOAPHeader header = soapEnvelope.getHeader();
                if (header == null) {
                    header = soapEnvelope.addHeader();
                }

                SOAPElement securityHeaderElement = header.addChildElement("Security", "wsse", URL_WSSE_NAMESPACE);
                securityHeaderElement.addAttribute(soapEnvelope.createName("S:mustUnderstand"), "1");

                // Add Timestamp element to "Security" soapHeaderElement
                // Sample:  <u:Timestamp>
                //              <u:Created>2011-10-13T08:20:01.183Z</u:Created>
                //              <u:Expires>2011-10-13T17:25:01.183Z</u:Expires>
                //          </u:Timestamp>
                javax.xml.soap.Name timestampElementName = soapEnvelope.createName("Timestamp", "wsu", URL_WSU_NAMESPACE);
                SOAPElement timestampSOAPElement = securityHeaderElement.addChildElement(timestampElementName);
                String created = getCurrentDateTime(); 
                String expires = getCurrentDateTimePlusDelay(60L*60L); /* 60 minutes delay */

                // Add Created to Timestamp
                SOAPElement createdSOAPElement = timestampSOAPElement
                        .addChildElement("Created"/* local name */, "wsu" /* prefix */, URL_WSU_NAMESPACE);
                createdSOAPElement.addTextNode(created);

                // Add Expires to Timestamp
                SOAPElement expiresSOAPElement = timestampSOAPElement
                        .addChildElement("Expires"/* local name */, "wsu" /* prefix */,URL_WSU_NAMESPACE);
                expiresSOAPElement.addTextNode(expires);

                // Add usernameToken to "Security" soapHeaderElement
                javax.xml.soap.Name usernameTokenElementName = soapEnvelope.createName("UsernameToken", "wsse", 
                                            URL_WSSE_NAMESPACE);
                SOAPElement usernameTokenSOAPElement = securityHeaderElement.addChildElement(usernameTokenElementName);

                // Add Username to usernameToken
                SOAPElement userNameSOAPElement = usernameTokenSOAPElement
                        .addChildElement("Username"/* local name */, "wsse" /* prefix */, URL_WSSE_NAMESPACE);
                userNameSOAPElement.addTextNode(this.usernameText);

                // Add password to UsernameToken
                javax.xml.soap.Name passwordElementName = soapEnvelope.createName("Password", "wsse", URL_WSSE_NAMESPACE);
                SOAPElement passwordSOAPElement = usernameTokenSOAPElement.addChildElement(passwordElementName);

                /* Add "Type" attribute to <Password> header element */
                //passwordSOAPElement.addAttribute(soapEnvelope.createName("Type", "", ""), 
                //      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
                passwordSOAPElement.addTextNode(this.passwordText);

            } catch (Exception e) {
                throw new RuntimeException("Error on wsSecurityHandler: " + e.getMessage());
            }

        }

        return true;
    }

    @Override
    public void close(MessageContext context) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public Set<QName> getHeaders() {
        // throw new UnsupportedOperationException("Not supported yet.");
        final QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", 
                                                    "Security", "wsse");
        final HashSet headers = new HashSet();
        headers.add(securityHeader);

        return headers;
    }

}
Piton answered 17/6, 2018 at 10:48 Comment(1)
@Laabidi Raissi solution didn't work for me. But this one worked for!Bonzer
T
2

You can also use Apache wss4j to easily add the header and also encrypt you password.

import org.apache.ws.security.WSConstants;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSecUsernameToken;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.util.Set;

public class WSSecurityHeaderSOAPHandler implements SOAPHandler<SOAPMessageContext> {


    private final String usernameText;
    private final String passwordText;

    public WSSecurityHeaderSOAPHandler(String usernameText, String passwordText) {
        this.usernameText = usernameText;
        this.passwordText = passwordText;
    }

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (outboundProperty.booleanValue()) {

            try {
                SOAPMessage soapMessage = context.getMessage();
                soapMessage.removeAllAttachments();

                SOAPPart soappart = soapMessage.getSOAPPart();
                WSSecHeader wsSecHeader = new WSSecHeader();
                wsSecHeader.insertSecurityHeader(soappart);
                WSSecUsernameToken token = new WSSecUsernameToken();
                token.setPasswordType(WSConstants.PASSWORD_DIGEST);
                token.setUserInfo(usernameText, passwordText);
                token.build(soappart, wsSecHeader);

                soapMessage.saveChanges();
            } catch (Exception e) {
                throw new RuntimeException("Error on wsSecurityHandler: " + e.getMessage());
            }

        }

        return true;
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        return false;
    }

    @Override
    public void close(MessageContext context) {

    }

    @Override
    public Set<QName> getHeaders() {
        return null;
    }
}

And you need to update your request like this:

// This is the block that apply the Ws Security to the request
BindingProvider bindingProvider = (BindingProvider) portType;
List<Handler> handlerChain = new ArrayList<>();
handlerChain.add(new WSSecurityHeaderSOAPHandler("username", "password"));
bindingProvider.getBinding().setHandlerChain(handlerChain);

Maven Dependency:

        <dependency>
            <groupId>org.apache.ws.security</groupId>
            <artifactId>wss4j</artifactId>
            <version>1.6.19</version>
        </dependency>
Trussing answered 25/9, 2017 at 10:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.