A Practical Introduction to WS-Policy - Stuart Gunter
in

dotnet.org.za

South African .NET Developer Portal

This Blog

Syndication

News


Get Firefox!
<!-- Begin Nedstat Basic code --> <!-- Title: StuartGunter --> <!-- URL: http://dotnet.org.za/stuartg/ --> <!-- End Nedstat Basic code -->

Stuart Gunter

There's too much!

A Practical Introduction to WS-Policy

Introduction
Web services are still somewhat of a grey area for many developers. The pace at which this part of the industry has progressed has in some cases caused a hesitation in the adoption of web services technologies, and in other cases has led to very bad architectural decisions. Many developers are battling to keep up with the frequent changes in the WS-* specifications, which are addressing a number of issues of concern; including security & trust, reliability, addressing & routing, and policy (and numerous others). This article will be showing how to implement a basic policy in your web services in order to clearly define and enforce a full contract between the service and the client.

What is WS-Policy?
All web services have a minimum set of requirements that must be met in order to be consumed by a client. These are normally documented for the client developer, who takes it into consideration when building a consumer of the service. This documentation would typically include the following:

  • Must the message be digitally signed?
  • Must the message be encrypted?
  • What kind of security token must be used (i.e. Kerberos, x509, UsernameToken, etc.)?
  • What is the expiration of the message?

WS-Policy provides a means of documenting these minimum requirements, and having them automatically enforced and validated on your behalf, without having to affect too much code (if any). WS-Policy conforms to a set XML schema, and is very easy to implement. Once you have a few basic examples working, you’ll quickly see how easy it is to cater for policy assertions in your new and existing web services.

Making the Policy available
When web services originally came out, WSDL was the only standardised means of documenting service composition. Since then, the WS-* specifications have provided a number of extensions to provide additional functionality. Each of these specifications is completely separate and none are required for a web service to operate correctly. “Composability” is the new term describing the WS-*. It basically means that you can select whichever extensions you want, without having to implement them all.

I’m sure many of you that have created web services will know that there’s no place in WSDL that supports policy attachments as part of the standard. The recommended way of way of getting your policy recognised within WSDL is documented in the WS-PolicyAttachment specification here. I’m not going into this in detail, but I assume you can read up on it outside of this article. Unfortunately it complicates the WSDL file quite a bit, and the purpose of this article is more to demonstrate the power and simplicity of WS-Policy. For now, you’re best off publishing this to the same virtual directory as the web service itself; that’s assuming your hosting your web service in IIS. WSE 2.0 now supports hosts other than IIS for web services, but that’s not the topic of this article. If you’d like me to do an article on that, contact me and I’ll make it happen.

How does WS-Policy work?
Policies can be defined for three different message types: request, response, and fault. Quite often these have complementary features. For example, a policy may require that a request be digitally signed with an x509 certificate, which then allows the response to be sent back using the requester’s x509 certificate to encrypt the data.

The WSE does all the hard work of verifying compliance with the policies, and returns a SOAP fault to the caller if the message does not fully adhere to the requirements. This is all done at runtime and is completely transparent to the developer. No code needs to be written for this, as it is catered for by the WSE. The WSE filters operate on the message pipeline and do whatever work is necessary before any custom code is reached. If a request message is non-compliant, the operation (or web method) is not even called. A SOAP fault is immediately sent back to the client and no code within the web service is executed. The important factor to note here is that the SOAP fault can only be sent back to the client. It is very important that the service developer ensures compliance with the policy document, otherwise the client could receive a SOAP fault due to no fault of their own!

The Structure of a WS-Policy Assertion
The structure of a basic policy assertion is really very simple. These are the minimum required elements for a policy assertion file:

<policyDocument>
  <mappings
    <endpoint uri="[endpointURI]"> 
      <defaultOperation
        <request policy="[policyID]" /> 
        <response policy="[policyID]" /> 
        <fault policy="[policyID]" /> 
      </defaultOperation
    </endpoint
  </mappings
  <policies
    <policy /> 
  </policies>
</policyDocument>

A policy document is made up of two main sections, <mappings> and <policies>. The <mappings> section defines the endpoints to which the policy applies. This is where you would define the web service URI that the policy applies to, as well as the ID’s of the policies that must be applied for the request, response, and fault. The <policies> section contains any number of <policy> elements, each defining rules that must be complied with. This would typically contain rules that define the integrity, confidentiality, etc. of the message. The WSE 2.0 supports the following main elements in policy declarations:

  • Integrity (digital signatures)
  • Confidentiality (encryption)
  • MessagePredicate (specifies message parts that SOAP messages must contain)
  • MessageAge
  • SecurityToken

Of course, this is not the complete list (see the WSE 2.0 documentation for a full reference of the WS-Policy schema). You can also build your own extensions if required.

In the next few steps, we’ll be building a basic web service and client application. These will initially contain no security code whatsoever. Once we have these working, we’ll modify them to use WS-Policy to enforce some very basic security requirements. The purpose of the “coding lab” is to show how much WS-Policy reduces the coding required by the developer, as well as the flexibility it offers. This lab assumes you know how to create a web service and understand the concepts surrounding web services in their most basic form.

Example: Building a Simple Web Service
So as not to complicate the example, I’ll be using the standard web service created by Visual Studio for this example. Please follow these steps to configure your web service:

1.       Open Visual Studio .NET and create a new Web Service Project (the language is irrelevant, but my example is given in C#).

2.       Uncomment the sample “HelloWorld” web method. It should look like this:

          [WebMethod] 
          public string HelloWorld() 
         
                   return "Hello World"; 
          }

3.       Save the project.

That’s all you need for this example! I’ve deliberately kept the web method as simple as possible to demonstrate the power of WS-Policy. Please note that at this stage, there are no WSE 2.0 features in the web service you just created.

Example: Building a Simple Client
Building a client application is almost as simple.

1.       Add a new Windows Forms Project to the solution.

2.       Add a button to the form, then double-click to button to create a Click event handler.

3.       Add the following code as your event handler:

          try 
         
                   PolicyService.PolicyService serviceProxy = new TestClient.PolicyService.PolicyService(); 
                   MessageBox.Show(serviceProxy.HelloWorld()); 
         
          catch (Exception ex) 
         
                   StringBuilder sb = new StringBuilder(); 
                   if (ex is System.Web.Services.Protocols.SoapException) 
                  
                             System.Web.Services.Protocols.SoapException se = ex as System.Web.Services.Protocols.SoapException; 
                             sb.Append("SOAP-Fault code: " + se.Code.ToString()); 
                             sb.Append("\n"); 
                  
                   if (ex != null
                  
                             sb.Append(ex.ToString()); 
                  
                   MessageBox.Show(sb.ToString()); 
          }

4.       Make sure to replace the PolicyService class name with whatever you decided to name your proxy class.

At this stage, we have a fully functional web service and client application. This example doesn’t do anything useful, but once we’ve configured a policy you’ll understand the power we’re trying to demonstrate.

Example: Extending the Web Service with WS-Policy
Now we’re going to move on to the good stuff, and enable some new features that form part of the WSE 2.0!

1.       Right-click the Web Service Project and select the “WSE Settings 2.0…” option. This is a new option only available once you’ve installed WSE 2.0. You’ll need to add the following “using” directives to your ASMX code as follows:

using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;

2.       You’ll see two checkboxes on the first tab: “Enable this project for Web Services Enhancements” and “Enable Microsoft Web Service Enhancements Soap Extensions”. Select them both. These will automatically add a reference to the Microsoft.Web.Services2.dll assembly, which contains the full set of classes that enable WSE 2.0 functionality. They’ll also create some new entries in the web.config file.

3.       Right-click the project and select “Add New Item…” and choose XML File. Name the file “policyCache.config” and click Open.

4.       Type the following XML snippet into your policyCache file. You’ll probably notice that Microsoft has provided a “Policy” tab on the WSE Settings page. I’ve tried using this and found it quite difficult and inflexible. I could just be doing it wrong, so by all means give it a try. I’d very highly recommend typing it all manually until you’re comfortable with how it all hangs together.

<?xml version="1.0" encoding="utf-8"?>
<policyDocument xmlns="http://schemas.microsoft.com/wse/2003/06/Policy"> 
  <mappings
    <endpoint uri="http://localhost/policytestservice/policyservice.asmx"> 
      <defaultOperation
         <request policy="#SignedX509" /> 
        <response policy="#SignedUsername" /> 
        <fault policy="" /> 
      </defaultOperation
    </endpoint
  </mappings
  <policies xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
            xmlns:wssp="http://schemas.xmlsoap.org/ws/2002/12/secext" 
            xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"> 
    <!-- This policy ensures that the message is signed with a UsernameToken --
    <wsp:Policy wsu:Id="SignedUsername"> 
      <wssp:Integrity wsp:Usage="wsp:Required"> 
        <wssp:TokenInfo
          <wssp:SecurityToken
            <wssp:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</wssp:TokenType
          </wssp:SecurityToken
        </wssp:TokenInfo
        <wssp:MessageParts Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">wsp:Body()</wssp:MessageParts
      </wssp:Integrity
    </wsp:Policy
    <!-- This policy ensures that the message is signed with a X509 certificate --
    <wsp:Policy wsu:Id="SignedX509"> 
      <wssp:Integrity wsp:Usage="wsp:Required"> 
        <wssp:TokenInfo
          <wssp:SecurityToken
            <wssp:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3</wssp:TokenType
          </wssp:SecurityToken
        </wssp:TokenInfo
        <wssp:MessageParts Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">wsp:Body()</wssp:MessageParts
      </wssp:Integrity
    </wsp:Policy
  </policies>
</policyDocument>

You should recognise some familiar element names that were covered earlier in the article. Take some time to read through this policy document and make sense of what it’s trying to achieve. This is a lot to take in, so don’t worry if you don’t understand a lot of it. Once you see the demo working, you’ll be able to change things here and there to see how it all hangs together.

We’ve created two separate policies in this document, both related to security. The first policy, SignedUsername, requires that a message be signed with a UsernameToken; and the second policy, SignedX509, requires that a message be signed with an X509SecurityToken. Please note that the <policies> do not, and cannot, define the specific message to which they are applied. They are completely generic and document the policy conditions only. It is the <mappings> that associate a <policy> with an <endpoint>. If you look at the policyCache.config file, you’ll notice that we’ve enforced all request messages to be signed with an X509SecurityToken, and all response messages to be signed with a UsernameToken. We’ve left the policy empty for SOAP faults, as we don’t need this.

You’ve now created a policy file! At the moment there’s nothing linking this policy assertion to the web service itself, so we’ll create that link now. Although the <mappings> section maps endpoints to policies, the web service must have an entry in the web.config to know about the policy file at all. Without this, it will not process the policy assertions.

5.       Open the web.config file and find the <microsoft.web.services2> element.

6.       Add the following new sub-elements to this configuration section as follows:

    <policy>
      <cache name="policyCache.config" /> 
    </policy
    <security
      <x509 allowTestRoot="true" allowRevocationUrlRetrieval="false" verifyTrust="false" storeLocation="LocalMachine"/> 
    </security>

Note that the <policy> element links the policy file to the web service. The <security> element is necessary because we’re enforcing a digital signature with an X509SecurityToken. The WSE pipeline needs to know where to look to validate the signature, which is why we’ve defined the additional <x509> element in the web.config. Removing this element will cause a SOAP fault.

You’ll notice that if you try to run your client application now, it’ll return a SOAP Fault. As described earlier in the article, the WSE filters recognised that the request did not conform to the requirements laid out in the policy attached to the web service and rejected it with a SOAP Fault.

There’s one more thing that we haven’t yet done to our service. If you recall from the policy document, we required that the response message be signed with a UsernameToken. So change your web method to look like this:

          [WebMethod]
          public string HelloWorld() 
         
                   UsernameToken token = new UsernameToken(@"DOMAIN\Username", "Password", PasswordOption.SendPlainText); 
                   ResponseSoapContext.Current.Security.Tokens.Add(token); 
                   ResponseSoapContext.Current.Security.Elements.Add(new MessageSignature(token)); 
                   return "Hello World"; 
          }

Change the “DOMAIN\Username” and “Password” of the UsernameToken to be credentials of a valid user on your local computer or domain. The PasswordOption must be SendPlainText in order for this example to work. The security itself is not the focus of this article, so I’m not going into detail about it. If you want more information on this (or another article on WS-Security and/or WS-Secure Conversation) then please contact me.

Now we’ll update the client application to conform to the requirements of the policy.

Example: Updating the Client to use the Policy
Look back at the policy document we just created, and you find that all request messages require digital signing with an X509SecurityToken. So that’s all we need to implement to allow our client to communicate successfully with our service. Please note that this is not an explanation on X509 certificates, so I’m not going to labour on about how they work. I assume that you either already understand this or will research it later.

1.       First things first… right-click the Windows Forms Project and enable it for WSE 2.0. Notice that only the top checkbox is available for selection. This will add the necessary references for using the WSE. You’ll also need to add some “using” directives to your Form as follows:

using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;
using Microsoft.Web.Services2.Security.X509;

2.       Open the code for the Form you created earlier, and add the following method:

private X509SecurityToken GetX509Token()

                // This keyID shouldn’t need to change, provided you’re using the X509 certificates that come with WSE 2.0 
                string keyId = "gBfo0147lM6cKnTbbMSuMVvmFY4="; 

               X509SecurityToken token = null
                X509CertificateStore store = X509CertificateStore.CurrentUserStore(X509CertificateStore.MyStore);

                if (!store.OpenRead()) 
                           return null

                X509CertificateCollection certificates = store.FindCertificateByKeyIdentifier(Convert.FromBase64String(keyId)); 

                if (certificates.Count > 0) 
                         token = new X509SecurityToken(certificates[0]); 

                return token;
}

This method will retrieve the X509 certificate from the certificate store and return it to the caller as an X509SecurityToken.

3.       Now go back to the button click event handler. The code in the try-catch block is going to change to allow signing of the request message with the X509SecurityToken we just retrieved. Replace the code in the try {…} block with the following:

          PolicyService.PolicyService serviceProxy = new TestClient.PolicyService.PolicyService();
          X509SecurityToken token = GetX509Token(); 

          if (token == null
                   throw new Exception("Couldn’t find X509 Certificate."); 

          serviceProxy.RequestSoapContext.Security.Tokens.Add(token); 
          serviceProxy.RequestSoapContext.Security.Elements.Add(new MessageSignature(token)); 

          MessageBox.Show(serviceProxy.HelloWorld());

The process now includes additional steps to retrieve the security token and use it to sign the message. Signing the message is a very simple two-step process. Firstly, you must add the SecurityToken to the RequestSoapContext.Security.Tokens collection. Secondly, you need to attach a MessageSignature (based on the SecurityToken) to the RequestSoapContext.Security.Elements collection. You’ve now digitally signed the request message.

We’ve done all the necessary changes to both the client and the service, so go ahead and run the example to see it in action!

That’s all
This is a fairly simple example of how you can use WS-Policy to enforce usage requirements for your web services. I’m sure many of you can already see places where you’d like to be using it. The effective use of policies can cut down huge amounts of code that the developer would ordinarily need to write, and save a lot of testing time too! WS-Policy is only one of a number of relatively new web services specifications that are set to transform the way web services can be used. Hopefully this article has helped you understand the role that WS-Policy plays in gearing towards a service-oriented architecture.

References
If you’re interested in reading more on the actual specifications, you can get the full list of WS-* Specifications on the Microsoft site here. There’s also an excellent book on the WSE 2.0 with a small intro to Indigo, called Expert Service-Oriented Architecture in C#. If you develop web services, then this book is an absolute MUST!

Comments

 

TrackBack said:

November 11, 2004 8:27 AM
 

Arno Nel (Sharepoint Guy) said:

Nice stuff Stuart !!!
November 11, 2004 8:50 AM
 

TrackBack said:

November 11, 2004 10:49 AM
 

TrackBack said:

November 11, 2004 10:51 AM
 

John Lauser said:

This is a great article. I did run into one issue and I am wondering if it is a result of WSE 2.0 SP2.

In order to get the RequestSoapContext object I actually had to change

PolicyService.PolicyService serviceProxy = new TestClient.PolicyService.PolicyService();

to

PolicyService.PolicyServiceWse serviceProxy = new TestClient.PolicyService.PolicyServiceWse();

For some reason PolicyService.PolicyService always returned <undefined> when I looked at it in debug mode.


December 15, 2004 9:37 AM
 

Dave said:

Ack, i'm going out of my mind here, i set everything up to the point where you say that it should not work ,but it still does, any ideas? common faults? etc...
March 10, 2005 12:21 AM
 

Moosa said:

Hi,

I am trying the above and it doesn't seem to compile for me. I am having problems with the following lines:

serviceProxy.RequestSoapContext.Security.Tokens.Add(token);
serviceProxy.RequestSoapContext.Security.Elements.Add(new MessageSignature(token));

It gives compilation error as it can't find "RequestSoapContext" method. Your help will be greatly appreciated as I am currently working on my thesis on this topic.

Thanks.
May 14, 2005 2:39 AM
 

Stuart Gunter said:

Hi Moosa,

Have you made sure that your proxy class inherits from Microsoft.Web.Services2.WebServicesClientProtocol?

It's most likely that this is incorrect. When creating a proxy, it will often use the default System.Web.Services.Protocols.SoapHttpClientProtocol (which does not contain a definition for RequestSoapContext).

Check your proxy inheritance, and I'm sure that'll fix it.

BTW... If you set the proxy up correctly for WSE 2.0, it will usually create two proxy classes: xxxxx and xxxxxWse (where xxxxx is the name of the proxy class). The xxxxxWse proxy class will already have the correct inheritance chains set up.

Hope that helps.
May 14, 2005 10:06 AM
 

Moosa said:

Thanks Stuart. It did work. Your last para was helpful. My proxy class wasn't inheriting from System.Web.Services.Protocols.SoapHttpClientProtocol but xxxxxWse was. I just used that one instead and it compiled and worked. I had other issues after that but I managed to resolve them.

thanks a lot again. I am new to WSE and may ask you few other questions in the future. Is there a way I contact you through your email or something else?

Regards,
Moosa
May 17, 2005 1:10 AM
 

Gkals said:

Hi,

THis is a great article.However i was trying to create what was said in the article,and when i run the project is gives error at the lines
PolicyService.PolicyService serviceProxy = new TestClient.PolicyService.PolicyService();
I am beginner in webservices..so please dont mind my basic questions.
i read the comments which said i need to create a proxy and inherit it properly..can u explain what proxy you are talking about and please tell me how to create one.
Thanks in advance.
June 2, 2005 5:17 PM
 

Neelufur said:

Hi Stuart,
I am a beginer in WS.when i read ur article,it gave me a sigh of relief coz i was searching for an article to create web app since 2 weeks. But when i created app as per ur instructions i am getting the following exception:-

SOAP-Fault code:http://schemas.xmlsoap.org/soap/envelope/:Server
System.Web.Services.Protocols.SoapHeaderException:System.Web.Services.Protocols.SoapHeaderException:Server unavailable,please try later---->
system.ApplicationException:An error occures processing an outgoing fault response
-------End of inner exception stack trace---
at System.web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message,WebResponse response, stream responsestream,Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodname, Object[] parameters)
at TestClient.localhost.Service1Wse.HelloWorld() in C:..\visual studio projects\policyservice\testclient\web references\localhost\reference.cs
at TestClient.Form1.button1_Click(Object sender,EventArgs e) in C:..\testclient\form.cs.

I made my proxy class to inherit from Microsoft.Web.Services2.WebServicesClientProtocol even then i am getting the error.


I am going out my mind,Please help me out.Its very urgent.reply as soon as possible.

Thanks and Regards.

July 11, 2005 7:07 AM
 

Eran Nachum said:

Hello!

I did your project example with the exact steps that you have showed here, but I have encountered a problem (big one).

when I am trying to access the proxy via the client, I can't access the "serviceProxy.RequestSoapContext.Security.Tokens.Add(token)" method for an example. I tried to solve it by inherit from Microsoft.Web.Services2.WebServicesClientProtocol at the proxy, BUT now I have another problem: in the proxy code I cant user "ResponseSoapContext.Current.Security.Tokens.Add(token);" what can I do to resolse thgis problem and make my problem work???

With Greetings,
Eran Nachum - Web Developer - SQLink
September 19, 2005 3:29 PM
 

Reden said:

Hello,

the illustration on how the webservice can inherit the Microsoft.Web.Services2.WebServicesClientProtocol really helps, though I was not aware that you have to code this at the reference.?? located at the project directory, but overall it has paved way to solve my problem.

Thanks.

January 24, 2008 11:48 PM
 

Sitizen » WS-Policy + WS-PolicyAttachment said:

Pingback from  Sitizen &raquo; WS-Policy + WS-PolicyAttachment

June 3, 2008 5:39 AM
 

Pritam(ICRA Online) said:

It helped me alot..

October 30, 2008 4:07 AM
 

WS-Policy + WS-PolicyAttachment | North Vancouver I AM said:

Pingback from  WS-Policy + WS-PolicyAttachment | North Vancouver I AM

November 29, 2008 12:42 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Powered by Community Server (Commercial Edition), by Telligent Systems