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.
madhan kumarPosted Jan 11, 2016, 6:08 AM
hi pradeep i have done all steps what you given in this article ..but i got the error "Referenced security token could not be retrieved" while i call the method any help ?
Sun ThomasPosted Jun 22, 2013, 1:11 AM
Hi pradeep, what if I wanted a binary security token in additon to the usernametoken. Adding Xmlnodes would be very tedious. can I do any kinda of policy assertions
Robert NichtereditedPosted Dec 20, 2011, 11:48 AMEdited Dec 20, 2011, 5:12 PM
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!
mi pseditedPosted Aug 3, 2011, 10:20 PMEdited Aug 4, 2011, 9:07 PM
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.
Michael TilburyeditedPosted Apr 25, 2011, 4:41 PMEdited Apr 25, 2011, 4:43 PM
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.