ARTICLE

Custom WSE 3.0 Policy Assertion - Modifying the security header generated by WSE

Posted by Pradeep Chandraker Articles | Coding Best Practices May 04, 2010
Recently I came across with an issue in WSE security header. My ASP.Net application is consuming Java web service using WSE 3.0. Normally when you add UsernameToken in RequestSoapContext, the WSE runtime will automatically add timestamp in security header.
Reader Level:
Download Files:
 

Recently I came across with an issue in WSE security header. My ASP.Net application is consuming Java web service using WSE 3.0. Normally when you add UsernameToken in RequestSoapContext, the WSE runtime will automatically add timestamp in security header.

<soap:Header>
  <
wsse:Security soap:mustUnderstand="1">
    <wsu:Timestamp wsu:Id="Timestamp-61d96c21-b7da-4618-8efe-e298e1ef0382">
      <wsu:Created>2010-05-03T23:48:22Z</wsu:Created>
      <wsu:Expires>2010-05-03T23:53:22Z</wsu:Expires>
    </wsu:Timestamp>
    <wsse:UsernameToken wsu:Id="SecurityToken-e33aaf78-f40e-4cb9-8f54-5948321356af">
      <wsse:Username>myUser</wsse:Username>
      <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">myPassword</wsse:Password>
      <wsse:Nonce>Qo1qdJbPmenpiIClnBBwIQ==</wsse:Nonce>
      <wsu:Created>2010-04-29T18:48:22Z</wsu:Created>
    </wsse:UsernameToken>
  </
wsse:Security>
</soap:Header>

When I am requesting web service with timestamp in security header, I am getting "Security processing failed (actions mismatch)" fault exception from web service.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <
SOAP-ENV:Body>
    <
SOAP-ENV:Fault>
      <
faultcode>SOAP-ENV:Client</faultcode>
      <faultstring>Security processing failed (actions mismatch)</faultstring>
    </SOAP-ENV:Fault>
  </
SOAP-ENV:Body>
</SOAP-ENV:Envelope>

I copied SOAP request into soapUI (you can download this tool to check your web service response) and just removed timestamp from security header and ran the request. I got the expected response without any security exception.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <
mes:ResponseHeader xmlns:mes="http://xml.abc.com/services/messages">
      <typ:timestamp xsi:nil="true" xmlns:typ="http://xml.abc.com/services/types" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
    </mes:ResponseHeader>
  </
SOAP-ENV:Header>
  <
SOAP-ENV:Body>
    <
mes:Response xmlns:mes="http://xml.abc.com/services/messages">
      <!-- Response xml-->
    </
mes:Response>
  </
SOAP-ENV:Body>
</SOAP-ENV:Envelope>

At this point I came to know there is something wrong with the timestamp. It seems the Java web service was not accepting request with timestamp in security header. The next step was, how to fix this issue, first thing we can do is, we can ask web service team to fix this issue from there end, which is really going to take time and as this web service is used by so many other application they may not change. And second, remove timestamp from request header. We opted second way.

I did research and found that we can use custom policy assertion to modify the security header. Here are the steps to implement custom policy assertion.

  1. Implement PolicyAssertion and SOAPFilter

    To implement custom policy assertion, create a class which should be inherited from PolicyAssertion base class, override client and server input/output filter methods.

    PolicyAssertion base class, override client and server input/output filter methods.

    public
    class CustomHeadersAssertion : PolicyAssertion
        {
            public override SoapFilter CreateClientInputFilter(FilterCreationContext context)
            {
                return new ClientInputFilter();
            }

            public override SoapFilter CreateClientOutputFilter(FilterCreationContext context)
            {
                return new ClientOutputFilter();
            }

            public override SoapFilter CreateServiceInputFilter(FilterCreationContext context)

            {
                return new ServiceInputFilter();
            }
            public override SoapFilter CreateServiceOutputFilter(FilterCreationContext context)
            {
                return new ServiceOutputFilter();
            }
            public override System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Type>> GetExtensions()
            {
                return new KeyValuePair<string, Type>[] { new KeyValuePair<string, Type>("CustomHeadersAssertion", this.GetType()) };
            }
            public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions)
            {
                reader.ReadStartElement("CustomHeadersAssertion");
            }
        }

    To implement SOAPFilter create classes for client and server input/output filter which should be inherited from SoapFilter base class and override ProcessMessage method.

    public class ClientInputFilter : SoapFilter
        {
            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
            {
                return SoapFilterResult.Continue;
            }
        }
    public class ServiceInputFilter : SoapFilter
        {
            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
            {
                return SoapFilterResult.Continue;
            }
        }

    public class ServiceOutputFilter : SoapFilter
    {
            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
            {
                return SoapFilterResult.Continue;
            }
    }

    Same as above filter classes create ClientOutputFilter class and inherit it from SoapFilter and override ProcessMessage method which will have code for username Security Token. Here I am overriding the SOAP request security header. I am just adding username and password element in security token.

    public class ClientOutputFilter : SoapFilter
        {
            public ClientOutputFilter() : base()
            { }

            public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
            {
                XmlNode securityNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                XmlAttribute securityAttr = envelope.CreateAttribute("soap:mustUnderstand");

                securityAttr.Value = "1";

                XmlNode usernameTokenNode = envelope.CreateNode(XmlNodeType.Element, "wsse:UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                XmlElement userElement = usernameTokenNode as XmlElement;
                userElement.SetAttribute("xmlns:wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

                XmlNode userNameNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                userNameNode.InnerXml = "cdtuser";

                XmlNode pwdNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                XmlElement pwdElement = pwdNode as XmlElement;
                pwdElement.SetAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
                pwdNode.InnerXml = "huchEQe3";

                usernameTokenNode.AppendChild(userNameNode);
                usernameTokenNode.AppendChild(pwdNode);
                securityNode.AppendChild(usernameTokenNode);
                envelope.ImportNode(securityNode, true);               

                XmlNode node = envelope.Header;
                node.AppendChild(securityNode);

                return SoapFilterResult.Continue;
            }
        }

    You can customize both input and output envelop as per your requirement.


  2. Create Policy configuration

    Add wse3policyCache.config file in your web application project. Register the extension classes in this config file and create policy. Here I have created policy with the name "ClientPolicy".

    <policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
      <extensions>
        <
    extension name="RemoveAddressingHeadersAssertion" type="CustomAssertion.CustomHeaders.CustomHeadersAssertion, ClassLibrary1"/>
        <extension name="requireActionHeader" type="Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </extensions>
      <
    policy name="ClientPolicy">
        <requireActionHeader />
        <
    RemoveAddressingHeadersAssertion/>
      </
    policy>
    </policies>

    Add reference to policy configuration file in your application web.config file. Add below code inside configuration element.

    <microsoft.web.services3>
        <
    policy fileName="wse3policyCache.config"/>
    </microsoft.web.services3>
     

  3. Code to consume web service

    Once all the configurations are finished you can call web service method using the policy (ClientPolicy) you created above. You should set policy name using the SetPolicy method. Here the CPNIServiceWse is web service proxy and ProcessRequest is the web method.

    public static void GetAccountDetails(string actNumber)
    {
         try
         {
              CPNIServiceWse sw = new CPNIServiceWse();
              sw.Url = "your service URL";
              sw.SetPolicy("ClientPolicy");
              sw.Timeout = 60000;

              ProcessResponse response = sw.ProcessRequest(actNumber);
          }
          catch (Exception ex)
          {}
    }

Now you may run your application. You can put break point in above filter classes to see the actual request/response envelop generated by WSE runtime to verify the actual SOAP header passed to server.

You can download attached example to see complete code implementation. For the security reason I have removed web reference from the sample application, so you will not be able to run the application. But you can get the code for above implementation.

erver'>
Login to add your contents and source code to this article
post comment
     

In WSE3.0, I have done all I can see in the post and comments, except the call to SetPolicy. If SetPolicy is missing, change your web reference.cs to use : Microsoft.Web.Services3.WebServicesClientProtocol Also, the webservice I was calling required the security token first, not last. After making the change below, instead of using AppendChild, the client worked! XmlNode headerFirstChildNode = node.FirstChild; node.InsertBefore(securityNode, headerFirstChildNode); Thank You Pradeep! I could not have succeeded without you. Yeah!

Posted by Robert Nichter Dec 20, 2011

Thanks, Mi.

Posted by Pradeep Chandraker Aug 11, 2011

This comes at the right time for us & under very similar situation as Pradeep. Thanks. I did have to modify the code a little bit to fix an issue. Replaced "CustomHeadersAssertion" with "RemoveAddressingHeadersAssertion" in both the places.

Posted by mi ps Aug 03, 2011

Thanks, Mike.

Posted by Pradeep Chandraker May 21, 2011

Thank you ever so much. :o) I know it's WSE but if someone wants to detail how to do this very same scenario in WCF I would be all ears. For someone unfamiliar with soap it's worth pointing out a couple of things with this article: 1) Stick all your classes in one class file in your project (For those people not willing to create a class library) 2) This following line needs to tie up with your class file (The type attribute here is the full namespace of your CustomHeadersAssertion class ',' The dll in which it sits. I.E. Your bin folder.) <extension name="RemoveAddressingHeadersAssertion" type="CustomAssertion.CustomHeaders.CustomHeadersAssertion, ClassLibrary1"/> 3) The following methods in CustomHeadersAssertion class as per point 2 are incorrect. public override System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Type>> GetExtensions() { return new KeyValuePair<string, Type>[] { new KeyValuePair<string, Type>("CustomHeadersAssertion", this.GetType()) }; } public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions) { reader.ReadStartElement("CustomHeadersAssertion"); } This should be: public override System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Type>> GetExtensions() { return new KeyValuePair<string, Type>[] { new KeyValuePair<string, Type>("RemoveAddressingHeadersAssertion", this.GetType()) }; } public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions) { reader.ReadStartElement("RemoveAddressingHeadersAssertion"); } Thanks again and I hope my two pence helps some others.

Posted by Michael Tilbury Apr 25, 2011
COMMENT USING
PREMIUM SPONSORS
Over-C is a holistic consortium of communications and technology specialists. We build, deploy and market both business as well as consumer products and solutions.
Join a Chapter
SPONSORED BY
  • PDF reports have never been easier to create. With our included WYSIWYG Designer, you can layout your reports, set up your data source and let DynamicPDF ReportWriter do the rest.
Join a Chapter