Modifying the security header generated by WSE runtime
Often I have come across customers who complain about the security header generated by WSE runtime (WSE 2.0 / WSE 3.0). A little bit of probing and one can understand that it is an interop issue between a .NET client application implementing WSE and a non .NET web service. Either it is the overall layout of the header which is not acceptable to the service or the value of individual elements which do not meet the security requirements at the other end.
Let's take an example of a very simple web service which authenticates via user name / password. A corresponding .NET client needs to pass a username token to authenticate itself against the service. Following lines of code achieve that in WSE 2.0 :
UsernameToken usToken = new UsernameToken("test user", "test password");
<web service proxy object>.RequestSoapContext.Security.Tokens.Add(usToken);
The <soap:Header> in the generated SOAP request(you will find this in the outputTrace.webInfo) is as follows :
<soap:Header>
<wsa:Action>https://edb.att.com/BOTContractData/setBOTContractData</wsa:Action>
<wsa:MessageID>uuid:15db114e-fa3c-4754-be71-4f2e84339e09</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>https://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To>https://localhost/TestwebService/Service.asmx</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-a558ebfa-420f-4c5e-8af3-8fe849935bbe">
<wsu:Created>2008-06-16T22:12:56Z</wsu:Created>
<wsu:Expires>2008-06-16T22:17:56Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-90db33dc-ba79-48e9-b4a9-1fbe5e1f76c9">
<wsse:Username>test user</wsse:Username>
<wsse:Nonce>RFWac0T64oOOpb3tsH78Cw==</wsse:Nonce>
<wsu:Created>2008-06-16T22:12:56Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
The problem starts here. Either the receiving service does not like the addressing headers generated by WSE runtime or it complains about the values of certain elements like the "Nonce" and "Created". My take on this is to modify the SOAP Header according to the service requirements as WSE provides some really cool classes to plug in either a custom output filter (WSE 2.0) or a custom policy assertion (WSE 3.0). Lets first look into the implementation details required for a WSE 2.0 enabled client application.
WSE 2.0
1. Create a custom output filter to generate a custom SOAP Header.
namespace WSE2.Custom.OutputFilters
{
public class ModifyUsernameToken : SoapOutputFilter //inherit the custom class from SoapOutputFilter inside we want to modify a SOAP Request
{
public ModifyUsernameToken()
{ }
public override void ProcessMessage(SoapEnvelope envelope)
{
//creating the <wsse:Security> element in the outgoing message
XmlNode securityNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Security","https://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-secext-1.0.xsd");
XmlAttribute securityAttr = envelope.CreateAttribute("soap:mustunderstand");
securityAttr.Value = "1";
//creating the <wsse:usernameToken> element
XmlNode usernameTokenNode = envelope.CreateNode(XmlNodeType.Element, "wsse:UsernameToken", "https://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
XmlElement userElement = usernameTokenNode as XmlElement;
userElement.SetAttribute("xmlns:wsu", "https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
//creating the <wsse:Username> element
XmlNode userNameNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Username", "https://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
userNameNode.InnerXml = "testuser";
//creating the <wsse:password> element
XmlNode pwdNode = envelope.CreateNode(XmlNodeType.Element, "wsse:Password", "https://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-secext-1.0.xsd");
XmlElement pwdElement = pwdNode as XmlElement;
pwdElement.SetAttribute("Type", "https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
pwdNode.InnerXml = "test password";
usernameTokenNode.AppendChild(userNameNode);
usernameTokenNode.AppendChild(pwdNode);
securityNode.AppendChild(usernameTokenNode);
envelope.ImportNode(securityNode, true);
XmlNode node = envelope.Header;
node.AppendChild(securityNode);
//removing Addressing headers from the outgoing request
XmlNode actionNode = envelope.Header["wsa:Action"];
envelope.Header.RemoveChild(actionNode);
XmlNode messageNode = envelope.Header["wsa:MessageID"];
envelope.Header.RemoveChild(messageNode);
XmlNode replyToNode = envelope.Header["wsa:ReplyTo"];
envelope.Header.RemoveChild(replyToNode);
XmlNode toNode = envelope.Header["wsa:To"];
envelope.Header.RemoveChild(toNode);
}
}
}
2. Include a reference to this class library inside the client application. Remove the standard SecurityOutputFilter from the SOAP Pipeline. This is very important or else we will have two <wsse:Security> element in the final SOAP Request.
<web service proxy object>.Pipeline.OutputFilters.Remove(typeof(SecurityOutputFilter));
3. Include the custom output filter into the SOAP Pipeline.
WSE2.Custom.OutputFilters.ModifyUsernameToken token = new WSE2.Custom.OutputFilters.ModifyUsernameToken();
<web service proxy object>.Pipeline.OutputFilters.Add(token);
Thats it. The generated SOAP request will have the custom SOAP header. I have only provided a sample implementation inside the ProcessMessage() method above. One can include its own algorithm to evaluate values for <wsse:Password>, <wsse:Nonce> or <wsu:Created> elements.
WSE 3.0
With WSE 3.0, the concept of pipeline was replaced with "assertion". I won't go into explaning what an assertion is, but this makes the same implementation look very different.
1. Create a custom policy assertion by deriving from Microsoft.Web.Services3.Design.PolicyAssertion.
namespace WSE3.CustomAssertion.RemoveAddressingHeaders
{
public class RemoveAddressingHeadersAssertion : 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>("RemoveAddressingHeadersAssertion", this.GetType()) };
}
public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions)
{
reader.ReadStartElement("RemoveAddressingHeadersAssertion");
}
}
public class ClientInputFilter : SoapFilter
{
public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
{
return SoapFilterResult.Continue;
}
}
//provide implementation for only the ClientOutOutputFilter as we are trying to modify an outgoing soap request
public class ClientOutputFilter : SoapFilter
{
public ClientOutputFilter()
: base()
{}
public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
{
//include similar code inside this method as pasted earlier inside the WSE 2.0 output filter's ProcessMessage() method
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;
}
}
}
2. Include a reference to this class library inside the client application and modify the wse3policyCache.config file :
<policies xmlns="https://schemas.microsoft.com/wse/2005/06/policy">
<extensions>
<extension name="RemoveAddressingHeadersAssertion" type="WSE3.Addresssing.CustomAssertion.RemoveAddressingHeaders.RemoveAddressingHeadersAssertion,RemoveAddressingHeadersAssertion"/>
<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>
3. Add a reference to this policy file inside the configuration file of the application (app.config / web.config) and set the policy inside the code.
srvWSE.SetPolicy("ClientPolicy");
The final SOAP Header generated after passing through the custom output filter / policy assertion :
<soap:Header>
<wsse:Security xmlns:wsse="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken xmlns:wsu="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>testuser</wsse:Username>
<wsse:Password Type="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
Using this method, one can easily modify any SOAP Request generated by WSE runtime. This proves the flexibility which has been built into the runtime.
Comments
Anonymous
November 07, 2011
Could you pls post the complete XML config files for completenes; helps people new to web servicesAnonymous
January 18, 2012
This is extremely intriguing. I have never thought about there being problems with WSE accounts, but I'm glad that you have found the solution. I'm currently working in the field of <a href="www.bigpulse.com/elections">election services</a>, and you can imagine all the problems that we run into. I let the IT department handle most of those, but when I need a fix myself, it is sites like this that help the most. Thanks for the tips!