Friday, September 27, 2013

Soap WS-Security Header : One of "SOAP Header" elements required

I run a C# web service client to connect to a Java web service. Then, it throws a exception: [com.ibm.wsspi.wssecurity.SoapSecurityException:WSEC5048EL One of "SOAP Header" elements required]. As no header is defined in the wsdl, I have no idea about the exception and don't know what should be added to the header. Further, the generated web service client does not have a header property. That means, I cannot add the header to the client directly.

After searching, I found out that WS-Security is a standard. 


Then, I try to find out where I can insert the header to the requested XML. Below it's a one of the solutions that I found out on ASP.NET forum.

WSHeader .cs
 public class WSHeader : SoapExtension
    {
        public bool outgoing = true;
        public bool incoming = false;
        private Stream outputStream;
        public Stream oldStream;
        public Stream newStream;

        // The ChainStream, GetInitializers, Initialize, and ProcessMessage are required
        // for a Soap override.  ChainStream and ProcessMessage are what we need.
        /// <summary>
        /// Override.  Save old stream, create new one.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override Stream ChainStream(Stream stream)
        {
            // save a copy of the stream, create a new one for manipulating.
            this.outputStream = stream;
            oldStream = stream;
            newStream = new MemoryStream();
            return newStream;
        }
        #region "overrides of no interest"
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        public override object GetInitializer(Type serviceType)
        {
            return null;
        }
        public override void Initialize(object initializer)
        {
            return;
        }
        #endregion
        /// <summary>
        /// Return a string version of the XML
        /// </summary>
        /// <returns></returns>
        public string getXMLFromCache()
        {
            newStream.Position = 0; // start at the beginning!
            string strSOAPresponse = ExtractFromStream(newStream);
            return strSOAPresponse;
        }
        /// <summary>
        /// Transfer the text from the target stream from the 
        /// current position.
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        private String ExtractFromStream(Stream target)
        {
            if (target != null)
                return (new StreamReader(target)).ReadToEnd();
            return "";
        }
        /// <summary>
        /// Override.  Process .AfterSerialize and .BeforeDeserialize
        /// to insert a non-standard SOAP header with username and 
        /// password (currently hard-coded).
        /// </summary>
        /// <param name="message"></param>
        public override void ProcessMessage(SoapMessage message)
        {
            StreamReader readStr;
            StreamWriter writeStr;
            string soapMsg1;
            XmlDocument xDoc = new XmlDocument();
            // a SOAP message has 4 stages.  We're interested in .AfterSerialize
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;

                case SoapMessageStage.AfterSerialize:
                    {
                        // Get the SOAP body as a string, so we can manipulate...
                        String soapBodyString = getXMLFromCache();

                        // Strip off the old header stuff before the message body
                        // I'm not completely sure, but the soap:encodingStyle might be
                        // unique to the WebSphere environment.  Dunno.
                        String BodString = "<soap:Body>";
                        int pos1 = soapBodyString.IndexOf(BodString) + BodString.Length;
                        int pos2 = soapBodyString.Length - pos1;
                        soapBodyString = soapBodyString.Substring(pos1, pos2);
                        soapBodyString = "<soap:Body>" + soapBodyString;

                        // Create the SOAP Message 
                        // It's comprised of a <soap:Element> that's enclosed in <soap:Body>. 
                        // Pack the XML document inside the <soap:Body> element 
                        //String xmlVersionString = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
                        String soapEnvelopeBeginString = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:tns=\"http://tempuri.org/\" xmlns:types=\"http://tempuri.org/encodedTypes\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">";
                        String soapEnvHeaderString = "<soap:Header><Security><UsernameToken><Username>";
                        String soapEnvHeaderString2 = "</Username><Password>";
                        String soapEnvHeaderString3 = "</Password></UsernameToken></Security></soap:Header>";

                        Stream appOutputStream = new MemoryStream();
                        StreamWriter soapMessageWriter = new StreamWriter(appOutputStream);
                        //soapMessageWriter.Write(xmlVersionString);
                        soapMessageWriter.Write(soapEnvelopeBeginString);
                        //// The heavy-handed part - forcing the right headers AND the uname/pw :)
                        soapMessageWriter.Write(soapEnvHeaderString);
                        soapMessageWriter.Write(ConfigurationManager.AppSettings["WSUserName"]);
                        soapMessageWriter.Write(soapEnvHeaderString2);
                        soapMessageWriter.Write(ConfigurationManager.AppSettings["WSPassword"]);
                        soapMessageWriter.Write(soapEnvHeaderString3);
                        // End clubbing of baby seals
                        // Add the soapBodyString back in - it's got all the closing XML we need.
                        //soapBodyString = dammit;
                        soapMessageWriter.Write(soapBodyString);
                        // write it all out.
                        soapMessageWriter.Flush();
                        appOutputStream.Flush();
                        appOutputStream.Position = 0;
                        StreamReader reader = new StreamReader(appOutputStream);
                        StreamWriter writer = new StreamWriter(this.outputStream);
                        writer.Write(reader.ReadToEnd());
                        writer.Flush();
                        appOutputStream.Close();
                        this.outgoing = false;
                        this.incoming = true;
                        break;
                    }
                case SoapMessageStage.BeforeDeserialize:
                    {
                        //Make the output available for the client to parse...
                        readStr = new StreamReader(oldStream);
                        writeStr = new StreamWriter(newStream);
                        soapMsg1 = readStr.ReadToEnd();
                        xDoc.LoadXml(soapMsg1);
                        soapMsg1 = xDoc.InnerXml;
                        writeStr.Write(soapMsg1);
                        writeStr.Flush();
                        newStream.Position = 0;
                        break;
                    }
                case SoapMessageStage.AfterDeserialize:
                    break;
                default:
                    throw new Exception("invalid stage!");
            }
        }
    }

Add following to web.config 
<system.web>
    <webServices>
      <soapExtensionTypes>
        <add type="TDCWS.WSHeader,TDCWS" priority="1" group="Low"/>
      </soapExtensionTypes>
    </webServices>
  </system.web>


Hope that can help!

References: