Programming with Web Services Enhancements 2.0

 

Matt Powell
MSDN Web Services

May 2004

Applies to:
   Web Services Enhancements 2.0 for Microsoft® .NET
   Microsoft® .NET Framework version 1.1
   Microsoft® Visual Studio® .NET 2003
   Microsoft® Windows Server® 2003 or Windows® XP with Service Pack 1
   Kerberos Security Tokens

Summary: Learn about the support for policy, trust, content token, and new programming model capabilities in Web Services Enhancements 2.0. (20 printed pages)

Download the source code for the Rock Paper Scissors application from the Microsoft Download Center.


Contents

Basic Security with WSE 2.0
Installing Web Service Enhancements 2.0
Configuring a Web Service Project to Use WSE 2.0
Adding WSE Authentication to the Client Application
The WSE Pipeline
Using Security Policy
Rock Paper Scissors: A Secure, Distributed-Messaging Sample Application
More on Kerberos Token Support
WSE 2.0 Addressing
TCP Messaging
Other Features of WSE 2.0
Conclusion
Related Books

The .NET Framework provides a first-class foundation for building and consuming Web services based off of the fundamental protocols of XML, SOAP, and WSDL. But despite the interoperable and loosely coupled beauty of these traditional Web services, before long one finds one's self wanting for more. For instance, in the area of security, it seems contradictory to depend on HTTP security mechanisms when SOAP messages were designed to hold metadata-like security information. Similarly, the request/response nature of HTTP, which fits perfectly for many message exchange patterns, seems like an overly restrictive one-size-fits-all solution when other message exchange patterns would fit your business needs better. This is a particularly frustrating restriction because the SOAP specification goes out of its way to avoid such limitations in Web service message exchange.

Web Services Enhancements (WSE) is the Microsoft extension to the Web service support in the .NET Framework that builds on the foundation of XML, SOAP, and WSDL with higher-level protocols that support such things as message-based security, policy-based administration, and the flexibility to move message-exchange out of the HTTP-only world. The result is a Web service platform that can save developers from the tedious, time-consuming, and fragile world of developing higher-level requirements themselves. Instead they can rely on the supported and interoperable solution that is provided by WSE.

Web Services Enhancements 2.0 builds on the 1.0 version of WSE that provided basic support for security, message routing, and binary data transfer. For instance, in the area of security, WSE 1.0 provided support for message-based authentication, digital signatures, and encryption. With WSE 2.0, this ability has been extended to include support for Kerberos tokens that integrate with your Windows users and groups. WSE 2.0 also supports the WS-SecureConversation specification that means a security token can be used for multiple messages between two entities and greater processing efficiency is achieved. Support for WS-SecurityPolicy means that you don't even have to write extra code to implement these higher-level security capabilities. With WSE 2.0, by simply associating your Web service with a policy document, security restrictions can be left as an administrative task. And WSE 2.0 security takes advantage of the advancements that have been made in the area of higher-level Web service protocols by implementing the recently approved Oasis standard version of WS-Security. This means that applications built with WSE 2.0 security features will interoperate with other Web service platforms as their WS-Security-compliant implementations become available.

WSE 2.0 provides other capabilities as well. Support for message exchange over HTTP is extended to include support for transferring messages over TCP connections or within process boundaries. Policy support extends to not only include security requirements, but also Time-To-Live (TTL) restrictions. Security restrictions can also work within the Windows role-based security model so that access or functionality can be controlled based off of membership in Windows groups. Finally, WSE 2.0 provides a plethora of extension interfaces so that you can plug your own code into the WSE 2.0 platform so that you can build things like your own policy assertion handler, custom transport, or security token support.

We will look into how to program with WSE 2.0 by seeing how you can secure basic Web services, and how policy can be used, and then we will look at a sample application that takes advantage of the addressing, policy, TCP messaging, and security capabilities of WSE 2.0.

Basic Security with WSE 2.0

We will start digging into WSE 2.0 by looking at a simple Web service that is created with Visual Studio. We expose a Web method that simply takes a name as input and then returns a personalized hello message. The code for our initial WebMethod is shown below.

[WebMethod]
public string Hello(string name)
{
    return "Hello, " + name;
}

The code to call this Web service is shown below:

static void Main(string[] args)
{
    string name = args[0];
    BasicWSEService.Service1 proxy = new BasicWSEService.Service1();
    Console.WriteLine(proxy.Hello(name));
}

Now that we have a basic Web service and a client application calling it, we want to use WSE 2.0 to enable security. But before we can do this, we first need to install WSE 2.0.

Installing Web Service Enhancements 2.0

Web Services Enhancements 2.0 is a free download that you can get from the Microsoft Download Center. WSE 2.0 can be installed on the same machine and can be used in the same applications as WSE 1.0. This allows applications to communicate with both Web services built with WSE 1.0 and Web services built with WSE 2.0. Because WSE 1.0 supports different protocol versions than WSE 2.0 does, you cannot use WSE 2.0 code to communicate with a WSE 1.0 Web service; however, WSE 2.0 and WSE 1.0 can run side-by-side; i.e., a single application can use both assemblies to communicate with the new protocols and the old protocols simultaneously.

If you installed the WSE 2.0 Technical Preview, then you will need to uninstall it before installing the released version of WSE 2.0. For the sake of this article, we will assume that you will install all the features of WSE 2.0, including the Visual Studio® tools that simplify the configuration options for building WSE 2.0 applications.

One of the common questions asked by new developers using WSE is, "Where is the documentation?" This is understandable since many expect the WSE docs to be embedded in the .NET Framework documentation within Visual Studio. However, this is not the case. You can find a link to the complete set of docs on your Start > All Programs menu under the Microsoft WSE 2.0 folder.

Once WSE 2.0 is installed, we can get to the business of adding WSE security to our sample application.

Configuring a Web Service Project to Use WSE 2.0

The .NET assemblies that provide the WSE functionality are contained in the Micorosoft.Web.Services2 namespace. Before using the classes from this namespace, we first have to add a reference to this assembly. We can do this by hand and then make the necessary configuration changes that are outlined in the WSE documentation, or we can make the job a lot easier by using the WSE Visual Studio add-in. The add-in is accessible by right-clicking the Visual Studio project, and then selecting the WSE Settings 2.0... option on the context menu. Figure 1 shows the General tab of the WSE settings dialog when we open the settings tool for our simple Visual Studio Web service project.

Figure 1. The WSE Settings 2.0 dialog box

There are two check boxes shown on the General tab. The first check box enables use of the current project with WSE. This means that the Microsoft.Web.Services2.dll will be added to the project references and that changes will be made to the Web.config file to add support for the WSE configuration handler. In addition any Web References that are created from this point on will include WSE 2.0 support in the proxy classes generated.

The second check box is only enabled if this is an ASP.NET Web Service project. By selecting it, the WSE SoapExtension is added to the project, which will enable the additional protocol support to work within the ASP.NET Web service HTTP handler (for .ASMX files). This is accomplished by modifying the Web.config file and adding the WSE SoapExtension to the list of .asmx SOAP Extensions for the virtual directory.

In the sample code supplied with this article, we have enabled the Visual Studio Web service project and the console application project for WSE support by checking the appropriate boxes on this tab.

Adding WSE Authentication to the Client Application

For our Web service calling console applications, the first thing to do is add credentials using a WS-Security UsernameToken. A UsernameToken is akin to using basic HTTP authentication, but within the SOAP message headers. Because we added the Web reference to our Web service to this project before we enabled it for WSE, we need to refresh our Web reference. We can do this by right-clicking the Web reference and selecting the Update Web Reference option on the context menu.

Once we have a WSE-enabled Web reference, we can modify the code to send credentials with a UsernameToken.

WARNING: The following code would send the user credentials in clear text on the network, except for the fact that we are sending it over an SSL connection (note the endpoint URL begins with "https").

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

namespace BasicWSEClient
{
    class BasicWSE2ConsoleApp
    {
        static void Main()
        {
            Console.WriteLine("Enter name: ");
            string name = Console.ReadLine();
            Console.WriteLine("Enter password: ");
            string password = Console.ReadLine();
            BasicWSEService.Service1Wse proxy 
                = new BasicWSEService.Service1Wse();
            // Only send over secure channel (note the 'S' in HTTPS)
            proxy.Url = "https://myserver/basicWSE/Service1.asmx";
            proxy.RequestSoapContext.Security.Tokens.Add(
                new UsernameToken(name, 
                                  password, 
                                  PasswordOption.SendPlainText));
            Console.WriteLine(proxy.Hello(name));
            Console.WriteLine("Hit Enter to end the program.");
            Console.ReadLine();
        }
    }
}

The first thing to notice about this code is that the proxy class we use is now called BasicWSEService.Service1WSE instead of BasicWSEService.Service1. The class with the appended WSE is created in addition to the standard Web reference class. However, the Service1WSE class derives from the WSE Microsoft.Web.Services2.WebServicesClientProtocol class that includes support for adding WS-Security tokens. To add the UsernameToken to the request, we simply create an instance of the UsernameToken class and add it to the Tokens collection of the RequestSoapContext. Let's take a look at how WSE works when you add a security object, or other object, to its context.

The WSE Pipeline

The RequestSoapContext and the corresponding ResponseSoapContext properties on the proxy class are functionally the WSE extensions to the underlying Web service code. They provide all the access to the enhanced Web service features and includes addressing, security, attachment information, information on the underlying channel, and direct access to the SOAP envelope. Both the RequestSoapContext and ResponseSoapContext derive from the SoapContext class. The information from the SoapContext class is what is used to build the SOAP headers for the serialized SOAP message. The SoapContext is the input into the WSE pipeline that is comprised of of a series of filters that each takes its particular information from the SoapContext. The filters use that information to generate the corresponding SOAP headers for the message being created.

Figure 2 shows the how the SoapContext is used by the various output filters in the WSE pipeline to create the headers for the SOAP message.

Figure 2. The WSE output pipeline

The idea behind the filters is that, like the higher-level Web service protocols, they can be plugged into the application infrastructure strictly when needed. For instance, if you do not require support for referral headers, then you can choose to leave the referral filter out of your pipeline processing via a configuration option.

Although the SoapContext provides much of the input for the output filters, it is worth noting that there are potentially other inputs. For instance, we will be looking at the WSE support for policy shortly, and the policy filter takes input from the policy file to determine the headers and message processing that it performs.

One of the extensibility features of WSE is that you can create your own filters for input or output processing and plug it into the pipeline. This provides a mechanism for providing support for Web service protocols that are not implemented by the WSE libraries themselves. One could imagine creating a filter to implement WS-ReliableMessaging or WS-Eventing, or a similar protocol.

Another extensibility feature is that the filter that implements security interacts with a series of Security Token Managers that provide support for the different kinds of security tokens supported by WSE. One such Security Token Manager handles the UsernameToken that we created with our code above. You can create your own Security Token Manager to support other types of security tokens.

The default token manager for UsernameTokens tries to validate usernames and passwords against the Windows user database. This can only be done if the username and clear-text password are provided. Therefore, in order for this to work, it is important that, when we created the UsernameToken in our sample code above, we used the SendPlainText password option in the constructor of our UsernameToken. If you use this option and do not take further precautions, then your passwords will be discoverable on the network. Therefore, you should not use this option in production environments where security is a real concern. Other precautions that you can take include sending requests over an SSL connection or using an X.509 token for the server to encrypt the UsernameToken.

On the server where our Web service lives, a similar processing model to the outgoing pipeline described above is used to handle the incoming SOAP message. However, the processing is simply reversed. Instead of output filters that use SoapContext information to build headers, input filters that process SOAP headers to modify the SoapContext are used. This reversed processing model is shown in Figure 3.

Figure 3. The WSE input pipeline

The following code shows how we can access the SoapContext from our Web service and verify that the name submitted matches the name in the UsernameToken:

[WebMethod]
public string Hello(string name)
{
    SoapContext ctxt = RequestSoapContext.Current;
    foreach (SecurityToken tok in ctxt.Security.Tokens)
        if (tok is UsernameToken)
        {
            UsernameToken user = (UsernameToken)tok;
            if (user.Username == name)
            {
                if (user.Principal.IsInRole(
                    System.Net.Dns.GetHostName() 
                    + @"\Kings"))
                    return "Hello, King " + name;
                return "Hello, " + name;
            }
        }
            
    return "Hello, Liar";
}

If WSE successfully authenticates the credentials associated with an incoming request against the Windows accounts, it will create a Principal object associated with the token that contains information on the user account and the groups it belongs to. The code accesses the Principal object and uses the IsInRole method to check if the authenticated user is a member of the "Kings" user group on the local machine. If the user is a member, then the response method adds a royal twist to our response message like so:

  Hello, King Eric

After the Web method is called and the results are to be returned, the response message is built much like the outgoing message was created on the client shown in Figure 2.

If there are any problems during the processing of a SOAP message in the pipeline, then a SOAP fault will be generated that will indicate a failure. If you are trying to debug problems with your message processing, you can turn on diagnostic capabilities through WSE configuration options. If you open up the WSE Settings dialog box, there is a Diagnostics tab where you can enable message and policy tracing. This will provide additional information that can be used to determine what messages have been sent and received, and traces policy successes and failures.

Using Security Policy

Writing code to check if a user is in a group, or to do any similar checking of the principal object, is tedious at best and prone to failure at worst. It also makes sense that accessing a resource like a Web service is probably something that an administrator should configure and not something to leave up to programmers writing code at design time. This is where policy comes in. Support for WS-Policy allows all the code we have written so far to functionally become a configuration option.

Instead of creating a UsernameToken, our next sample will use the current user's logged-on credentials to access the Web service with a Kerberos token, and the Web service will use those credentials to build the Web service response. Now the code for our Web method looks like this:

[WebMethod]
public string PolicyHello()
{
    SoapContext ctxt = RequestSoapContext.Current;
    foreach (SecurityToken tok in ctxt.Security.Tokens)
        return "Hello, " + tok.Principal.Identity.Name;
    return ""; // You should never get here.
}

The code for the WebMethod is straightforward. It is pulling information out of the first security token it finds associated with the message. But how can we make sure we have a security token in the first place? Of course, the answer is that our policy file guarantees that all requests will have a security token. In fact, it requires quite a bit more: specified portions of all incoming messages are digitally signed with a Kerberos token. But signing with a Kerberos token requires that a security token is present.

So let's take a look at the policy file:

<?xml version="1.0" encoding="utf-8"?>
<policyDocument xmlns="http://schemas.microsoft.com/wse/2003/06/Policy">
  <mappings xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy">
    <endpoint uri="http://localhost/BasicWSE2/Service1.asmx">
      <operation requestAction="http://msdn.microsoft.com/BasicWSE2/PolicyHello">
        <request policy="#Sign-Kerberos" />
        <response policy="" />
        <fault policy="" />
      </operation>
    </endpoint>
  </mappings>
  <policies 
xmlns:wsu=
  "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
  xmlns:wssp="http://schemas.xmlsoap.org/ws/2002/12/secext"
  xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy"
  xmlns:wsse=
   "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
    <wsp:Policy wsu:Id="Sign-Kerberos">
      <wsp:MessagePredicate wsp:Usage="wsp:Required"
        Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
          wsp:Body() wsp:Header(wsa:To)
          wsp:Header(wsa:Action) wsp:Header(wsa:MessageID)
          wse:Timestamp()</wsp:MessagePredicate>
      <wssp:Integrity wsp:Usage="wsp:Required">
        <wssp:TokenInfo>
          <wssp:SecurityToken>
      <wssp:TokenType>http://schemas.xmlsoap.org/ws/2003/12/kerberos/Kerberosv5ST</wssp:TokenType>
          </wssp:SecurityToken>
        </wssp:TokenInfo>
        <wssp:MessageParts Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
          wsp:Body() wsp:Header(wsa:Action)
          wsp:Header(wsa:FaultTo) wsp:Header(wsa:From)
          wsp:Header(wsa:MessageID) wsp:Header(wsa:RelatesTo)
          wsp:Header(wsa:ReplyTo) wsp:Header(wsa:To) wse:Timestamp()
        </wssp:MessageParts>
      </wssp:Integrity>
    </wsp:Policy>
  </policies>
</policyDocument>

The policy file is simply an XML file that has two major sections: the mappings element and the policies element. The mappings element maps policies to endpoints. Each endpoint can have a policy associated with the request, response, or fault. If no policy applies, you can set the corresponding policy attribute to a zero-length string as is done for the response and fault messages.

The request message has the Sign-Kerberos policy associated with it. The policy name simply maps to the wsu:Id attribute of the corresponding wsp:Policy element in the policies section. Within the specific policy, there are two elements: wsp:MessagePredicate and wssp:Integrity. The wsp:MessagePredicate element indicates which parts of the SOAP message must exist. The wssp:Integrity element indicates that the incoming message must be digitally signed. Listed are the details of the type of token to be used (a Kerberos 5 token), and the parts of the message to be included when calculating the signature.

The policy file is associated with the Web service by a configuration setting in the Web.config. The configuration settings can be achieved by checking the Enable Policy checkbox on the Policy tab of the WSE 2.0 Settings dialog.

Although policy of incoming messages is verified at the Web service, the client must also be cognizant of the message requirements so that it can properly build a compliant message. Therefore, there is also a configuration setting that must be made in the application config of the client application and a copy of the corresponding profile is required.

If a Kerberos token is required by the policy on the client and one has not been programmatically added, WSE will automatically create a Kerberos token based on the current Windows user credentials. No code is required on the client to send credentials as long as the policy has properly been configured. However, if you are sending multiple messages with your Kerberos token, you can avoid regenerating a bunch of new tokens by adding a token to the local PolicyEnforcementSecurityTokenCache. This can be done with the following lines of code:

PolicyEnforcementSecurityTokenCache.GlobalCache.Add(
    new KerberosToken("host/" + 
    System.Net.Dns.GetHostName()));

The PolicyEnforcementSecurityTokenCache is a cache that holds tokens that can be used for meeting policy requirements. This code adds a KerberosToken to the cache. On my machine named "mattpo" it added a token from the current user context to the "host/mattpo" location. Other kinds of tokens could also be added to the PolicyEnforcementSecurityTokenCache. When a connection is attempted, WSE will look first in the collection of tokens (if there are any) in the SoapContext token cache, then it will check the PolicyEnforcementSecurityTokenCache. In this case, the requirement of the policy is that this token can perform digital signatures and be a Kerberos token. Luckily that is exactly the kind of token created and added to the global cache. If WSE did not find an appropriate token in either the context or the policy enforcement cache, it will attempt to create one using the current user credentials.

Again, our Web service is completed by getting the name of the individual connecting through the Principal associated with a given token.

Rock Paper Scissors: A Secure, Distributed-Messaging Sample Application

In order to demonstrate the many facets of WSE, I wanted to create a slightly more complicated application that demonstrates some of the messaging functionality in WSE 2.0, along with the security capabilities we have been looking at so far. In particular, I was interested in using the TCP messaging support to show peer-to-peer communication, and I wanted it to work within the realm of Windows security. What I came up with was a simple distributed version of the old Rock Paper Scissors game, now made secure and distributed with WSE 2.0.

Rock Paper Scissors is a two-person children's game, and traditionally involves each child slapping his hands together 3 times, and on the third slap making the shape of a rock, a pair of scissors, or a piece of paper. The winner is determined by the reasoning shown in the following table:

  Rock Paper Scissors
Rock Tie Paper wraps rock.
Paper wins!
Rock smashes scissors.
Rock wins!
Paper Paper wraps rock.
Paper wins!
Tie Scissors cut paper.
Scissors win!
Scissors Rock smashes scissors.
Rock wins!
Scissors cut paper.
Scissors win!
Tie

This game is commonly used to determine who gets to choose players first for a sandlot baseball game, who gets to eat the last of the ice cream, or even who gets to punch the opposing player in the arm.

We have taken Rock Paper Scissors out of the sandlot and placed it directly in the midst of secure, collaborative, corporate enterprise technology with WSE 2.0. The messaging architecture for the game is shown in Figure 1 below.

Figure 4. The Rock Paper Scissors messaging and security model

There are two main components of Rock Paper Scissors, an ASP.NET Web service called RPSService, and a peer-to-peer application that will be run by multiple users. The role of the RPSService is for users to register to play or to find a player to play with. The peer-to-peer application initially communicates with the RPSService to find a peer to communicate with, and then communicates directly with its peer once the opposing player is determined.

Peer-to-Peer: WSE 2.0 takes messaging based on the traditional HTTP request/response, RPC world to a world where peer-to-peer messaging, asynchronous messaging, and message queuing are all possibilities.

Kerberos: This entire application is built using the integrated Windows Kerberos support. WSE 2.0 allows you to connect across your network in a secure fashion. We will be authenticating, digitally signing, and encrypting messages to insure the highest level of security and it will all be based off our Windows user accounts.

Easy Administration: Instead of controlling access by writing code, we can configure the security of our messaging via policy configuration files. Our policy files allow us to control what kind of security we want to enforce, how and what portions of our messages should be digitally signed or encrypted, as well as criteria for acceptance based on the age of the message. The appropriate people can now control security: your administrators.

Rock Paper Scissors: The important part of this application is not the application itself, but how it uses secure messaging, policy, and addressing to communicate between the different entities in this example. But you never know—I can envision my boss saying, "To determine employee bonuses this year, I would like each of you to run a little application called RockPaperScissors..."

More on Kerberos Token Support

WSE 1.0 provided support for username tokens and X.509 security tokens. These tokens could be added to a messages collection of security tokens and used to create digital signatures or perform encryption. With WSE 2.0, Kerberos token support has been added if you are running on Windows Server 2003 or Windows XP with Service Pack 1, and are running in a Windows Active Directory® network environment that supports Kerberos.

The Kerberos tickets that are used to create tokens allow the current user to communicate with the host indicated. In particular, the token will be used to encrypt the messages being sent so that only the remote host can read them.

Unlike other types of tokens, the Kerberos token uses the current Windows user's security context to create the token. If you interrogate the token created, you will find a Principal member property that indicates the user who created this token. We pull the username from the principal in the code that receives the message with the Kerberos token. The opponent variable below is a KerberosToken object pulled from the Tokens collection on the incoming message. You can grab information about the creator of the token like we do with the name below, but you can also call the IsInRole() method to programmatically determine Active Directory group membership.

this.opposingNameLabel.Text 
    = "Playing: " + opponent.Principal.Identity.Name;

WSE 2.0 Addressing

One of the changes between WSE 1.0 and WSE 2.0 is the support for WS-Addressing. WS-Addressing largely replaces the capabilities of the WS-Routing specification that was supported in WSE 1.0. Instead of focusing on routing paths, WS-Addressing functionally provides a mechanism for adding To and From headers to a SOAP envelope. There is also support in WS-Addressing for an Action header, a ReplyTo header, a FaultTo header, and others. The Action header is similar to the SOAPAction HTTP header that is often used for SOAP messages sent with HTTP. In the case of .asmx Web services, the HTTP SOAPAction header is used to determine which Web method of a service should be invoked for an incoming message. Similarly, the Action SOAP header will be used for determining functions to call for messages received over non-HTTP transports.

The message destination WS-Addressing headers like To and From were originally URIs. WSE 2.0 now supports a newer version of WS-Addressing, and has replaced many headers that took URIs with headers that take Endpoint References instead. You can think of Endpoint References as extensible URI elements. An Endpoint Reference includes an Address element, which is still a URI, and serves the same purpose that the earlier version's URI served, but an Endpoint Reference allows you to include other metadata besides the URI. For instance, there is a Policy element that could be used to include policy information, or there is a PortType property that gives you information on the kind of port that a Web service's endpoint exposes.

We will be taking advantage of the ReplyTo header in our Rock Paper Scissors application in order to determine where to send the next message. When one of the peer applications sends the one-way RegisterPlayer message to the RPSService, the peer application will specify a ReplyTo endpoint reference that will indicate where an opposing player should send messages when peer-to-peer communications start. Similarly, when a second peer application sends a FindPlayer message to the RPSServer, the RPSServer returns a message with a ReplyTo header indicating the first peer application's endpoint. This tells the second peer application that it should send its next message to the first peer application's endpoint. We continue specifying the ReplyTo header in the remaining peer-to-peer messages in order to always indicate where the next message should be addressed.

The code below is the code used by the peer application to begin listening for peer-to-peer communications, and then to indicate the URI in the ReplyTo header immediately before calling the RPSServer RegisterPlayer Web method.

myPeerUri = new Uri("soap.tcp://" 
    + System.Net.Dns.GetHostName() 
    + ":3131/RPSPeer1");
SoapReceivers.Add(new EndpointReference(myPeerUri), 
    typeof(PeerService));
RPSService.RPSServerWse proxy = new RPSService.RPSServerWse();
proxy.RequestSoapContext.Addressing.ReplyTo = myPeerUri;

try
{
   proxy.RegisterPlayer();
}

TCP Messaging

Messaging in WSE 2.0 is a key part of the new enhanced capabilities. WSE 2.0 provides support for communicating within a process, over TCP asynchronously, or over TCP in a request/response manner. In the case of our Rock Paper Scissors application, we will be sending messages to indicate whether a player chose rock, paper, or scissors for a particular instance of the game. Because we are relying on user interaction to determine the contents of the messages we are sending, we cannot wait an indeterminate amount of time for a request to have a response sent. Therefore, we are using asynchronous TCP messages to communicate a particular play. One person sends a play, and then the application waits patiently for the opposing player to send a play. You can think of this as being similar to a shipping order sent to a warehouse that must wait until someone manually packages up the material before a response can be sent indicating that the request is complete.

You have already seen part of the code needed to establish a TCP endpoint in the ReplyTo code shown above. There is a SoapReceiver class with an Add method that allows us to register our listening code. In our case, we created a class called PeerService that inherits from the SoapReceiver class that listens for incoming Rock Paper Scissors plays. Our PeerService class simply overrides a method called Receive that handles the incoming messages. The code for the PeerService class is shown below:

public class PeerService : SoapReceiver
{
    public static Form1 Form;
    protected override void Receive(SoapEnvelope envelope)
    {
        Form.opponentPlay 
           = (char)envelope.GetBodyObject(typeof(char));
        foreach (SecurityToken tok 
                    in envelope.Context.Security.Tokens)
        {
            if (tok is KerberosToken)
            {
                Form.opponent = (KerberosToken)tok;
                break;
            }
        }
        Form.OpponentUri 
            = envelope.Context.Addressing.ReplyTo.Address.Value;
        if (Form.peerToken == null)
            Form.peerToken 
                = new KerberosToken(
                    "host/" + Form.OpponentUri.Host);
        Form.opposingNameLabel.Invoke(
            new Form1.ReceivePlayDelegate(Form.ReceivePlay));
    }
}

The Receive method takes as a parameter a SoapEnvelope object. The SoapEnvelope class is derived from the XmlDocument class, so you have access to the SOAP body and headers through a normal XML DOM interface. Instead of using the DOM interface, we take advantage of the GetBodyObject method that functionally uses the XmlSerializer to create an instance of a class based on the XML within the body of the SOAP message. We save the player's selection in a public property of our Form class.

We also pull the Kerberos token from the request and save it. It will later be used to get the name of the user we are playing against. Next, we save the opponent's ReplyTo URI so that we know where to send the response. We also build a Kerberos token based off the host indicated in the URI that will be used for encrypting the message returned.

The asynchronous TCP support using the SoapReceivers.Add method works by listening for incoming connections and will call the Receive method on the indicated class when a message is received. The Receive method will be called on one of the threads in the process threadpool that will most likely not be the same thread that handles the message pump for you application's main window. That is why at the end of our Receive method I call the Invoke method on one of the controls in our Windows form. This launches the indicated delegate function on the main window's thread so that normal interactions with the various controls on my form will work correctly. In our case, the delegate does things like determines the winner based off the two plays and updates the user interface appropriately.

When we call the SoapReceivers.Add method (shown again below), we pass it two parameters: an Endpoint Reference and the class type for the listening class. The URI indicates a couple things. First, the URI type is soap.tcp. This indicates that this is a URI for SOAP messages being sent across TCP. The host name of the URI indicates the machine where we are listening, and the TCP port number that we listen to for incoming connections follows it. In our example, we use port 3131. You should refrain from using port numbers below 1000 because they are reserved for particular types of applications (for example, port 80 is reserved for HTTP servers).

myPeerUri = new Uri("soap.tcp://" 
    + System.Net.Dns.GetHostName() 
    + ":3131/RPSPeer1");
SoapReceivers.Add(new EndpointReference(myPeerUri), 
    typeof(PeerService)); 

Sending a message to an asynchronous TCP listener is easy as well. We simply create a SoapEnvelope object, and use the SetBodyObject method to serialize our object into the XML within the body. The SoapEnvelope includes a Context property that is used just like the SoapRequestContext and SoapResponseContext properties that we are accustomed to using for normal HTTP bound SOAP interactions. We use it to set the required Action SOAP header, and the optional ReplyTo header. We also add the Kerberos token we created earlier and use it to encrypt the message. To send our message, we use the SoapSender class that corresponds to the SoapReceiver class we used to listen for incoming messages. It takes the endpoint URI in its constructor and then sends the SoapEnvelope passed to its Send method. The code for sending one of our peer-to-peer messages is shown below.

// Send Message            
SoapEnvelope envelope = new SoapEnvelope();
envelope.SetBodyObject(myPlay);
envelope.Context.Addressing.Action 
    = "http://msdn.microsoft.com/RPSService";
envelope.Context.Security.Tokens.Add(peerToken);
envelope.Context.Security.Elements.Add(
    new EncryptedData(peerToken));
envelope.Context.Addressing.ReplyTo = myPeerUri;
SoapSender peerProxy = new SoapSender(this.OpponentUri);
peerProxy.Send(envelope);

You can get the full source code for the Rock Paper Scissors application from the Microsoft Download Center.

Other Features of WSE 2.0

Using the Rock Paper Scissors application we have explored many of the features of WSE 2.0, but there are a number of other features available as well. There is support for security context tokens that allows you to establish an efficient symmetric key for encrypting multiple messages between two endpoints instead of generating a new key for each message. Along with security context tokens, there is support for creating a Security Token Service that will issue context tokens for communicating between two endpoints.

In the messaging world, we only looked at the support for sending asynchronous TCP messages, but the synchronous request/response support is pretty remarkable as well. It uses a SoapMethod attribute similar to the WebMethod attribute used for .asmx Web services and generally works quite similarly. There is also support for calling services within a single application space that may seem a bit silly, but then again, Windows message pumps—the basis upon which windowing applications are created—is also based on sending messages within a single application space.

Conclusion

WSE 2.0 provides an introductory look into how higher-level Web service capabilities can save developers a lot of time building service-oriented applications. With its support for Oasis-standardized WS-Security, it is the first of its kind to step into the world of interoperable message-based security. Implementations on other platforms will surely follow, which bodes well for the future of other higher-level services in the interoperable Web services architecture. WSE provides not just WS-Security, but a plethora of capabilities that provide a hint of the Web services world to come.

Related Books

.NET Web Services: Architecture and Implementation

Microsoft .NET and J2EE Interoperability Toolkit

Real World XML Web Services: For VB and VB .Net Developers

Show: