Custom WSE 3.0 Policy Assertion - Modifying the Security Header Generated by WSE

Recently I encountered an issue in the WSE security header. My ASP.Net application is consuming a Java web service using WSE 3.0. Normally when you add a UsernameToken to a RequestSoapContext, the WSE runtime will automatically add a timestamp in the 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 request a web service with a timestamp in the security header, I am getting a "Security processing failed (actions mismatch)" fault exception from the 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 the SOAP request into the soapUI (you can download this tool to check your web service response) and just removed the timestamp from the 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>

That enabled me to understand that there is something wrong with the timestamp. It seems the Java web service was not accepting the request with a timestamp in the security header. The issue then became how to fix this issue. The first thing we can do is, we can ask the web service team to fix this issue from their end, which is really going to take time and since this web service is used by so many other applications they may not change. And second, removal of the timestamp from the request header. We opted for the second way.

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

Implement PolicyAssertion and SOAPFilter

To implement custom policy assertion, create a class inherited from the PolicyAssertion base class then override the client and server input/output filter methods as in the following:

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 that 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;
    }
}

As in the filter classes above, create a ClientOutputFilter class and inherit it from SoapFilter and override the ProcessMessage method that will have code for the username Security Token. Here I am overriding the SOAP request security header. I am just adding a username and password element in the 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 envelopes as needed.

Create Policy configuration

Add a wse3policyCache.config file to your web application project. Register the extension classes in this config file and create a policy. Here I have created a 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 a reference to the policy configuration file in your application web.config file. Add the following code inside the configuration element.

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

Code to consume web service

Once all the configurations are finished you can call the web service method using the policy (ClientPolicy) you created above. You should set the 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 a breakpoint in the preceding filter classes to see the actual request/response envelope generated by the WSE runtime to verify that the actual SOAP header is being passed to server.

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


Similar Articles