June, 2008

  • A Tryst With SOA

    Modifying the security header generated by WSE runtime

    • 2 Comments

    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>http://edb.att.com/BOTContractData/setBOTContractData</wsa:Action>

    <wsa:MessageID>uuid:15db114e-fa3c-4754-be71-4f2e84339e09</wsa:MessageID>

    <wsa:ReplyTo>

    <wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>

    </wsa:ReplyTo>

    <wsa:To>http://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="http://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","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";

                                                 //creating the <wsse:usernameToken> element

                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");

     

                //creating the <wsse:Username> element

                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 = "testuser";

     

                //creating the <wsse:password> element

                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 = "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="http://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="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

    <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">

                                                    <wsse:Username>testuser</wsse:Username>

    <wsse:Password Type="http://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.

Page 1 of 1 (1 items)