WCF Interoperability and Extensibility

摘要:WCF Interoperability and Extensibility

 

WCF Interoperability and Extensibility
 
Developing a Custom TextMessageEncoder
Out of the box, Windows Communication Foundation is perfectly suited for simple WCF-to-WCF exchanges.  However, the diverse communication requirements of most real world applications aren’t simple. For interactions with a high-availability client application, for instance, performance is paramount.  Interoperability is straightforward, if the client and service are built on the .NET Framework. For communication with an existing Java EE-based application or with diverse partner applications, however, interoperability becomes the highest goal. Security requirements are also quite different, varying across connections with local Windows-based applications, a Java EE-based application running on another operating system, and a variety of partner applications coming in across the Internet. Even transactional requirements might vary, with only the internal applications being allowed to make transactional requests. How can these diverse business and technical requirements be met without exposing the creators of the new application to unmanageable complexity?
 
The answer to this question is WCF. Designed for exactly this kind of diverse but realistic scenario, WCF is the default technology for Windows applications that expose and access services. This three segment article introduces WCF, examining what it provides and showing how it’s used. The goal is to make clear what WCF is, show what problems it solves, and illustrate how WCF’s extensibility can help solve real world interoperability problems.
 
WCF is implemented primarily as a set of classes on top of the .NET Framework’s Common Language Runtime (CL R). Because it extends their familiar environment, WCF allows .NET developers to build service-oriented applications in a familiar way. As the figure below shows, WCF allows creating clients that access services. Both the client and the service can run in pretty much any Windows process—WCF doesn’t define a required host. Wherever they run, clients and services can interact via SOAP, via a WCF-specific binary protocol, or in some other way.
 
 
The secnario I’ll describe comes from a real-world WCF interoperability development challenge.   I had to write a WCF client for a non-WCF SOAP11 service (Java).  I had no control over the service which at lease had a minimal WSDL but no exposed metadata.  The initial login request occurs over HTTPS with authenticationMode="MutualCertificate" using X509 certificates for both the client and the service endpoint.
 
The client is configured with a custom binding:
 
<bindingname="Login">
 <textMessageEncodingmessageVersion="Soap11"writeEncoding="utf-8" />
 <securitydefaultAlgorithmSuite="Basic128Rsa15
allowSerializedSigningTokenOnReply="false"

       enableUnsecuredRespons e="true"

authenticationMode="MutualCertificate"
       securityHeaderLayout="Strict"
includeTimestamp="false"
messageProtectionOrder="SignBeforeEncrypt"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005…"
       requireSignatureConfirmation="false">
    <localClientSettingsdetectReplays="false"reconnectTransportOnFailure="true" />
    <secureConversationBootstrap />
 </security>
 <httpsTransportkeepAliveEnabled="false"requireClientCertificate="false" />
</binding>
 
 

The service also requires that both the header and the body be signed.  This requires modifying the message contract that was auto-generated from the WSDL Service Reference.  Signing is accomplished by adding a ProtectionLe vel statement to the MessageHeaderAttribute in the contract:

 
ProtectionLevel = System.Net.Security.ProtectionLevel.Sign
 
The service establishes a session for follow-on requests from the client and sends session related security tokens to the client in the login response to be used in subsequent requests (similar to a Security Token Service [STS] in a WS-Federation).  The login response is valid; however, because the client isn’t expecting security tokens, the response causes a MessageSecurityException "Cannot find a token authenticator for the 'System.IdentityModel.Tokens.UserNameSecurityToken' token type. Tokens of that type cannot be accepted according to current security settings."
 
 The response Security header element contains four tokens:
  1. Timestamp
  2. UsernameToken
  3. SAML Assertion
  4. Binary Security Token
Now, we have the first requirement for WCF extensibility that enables interoperability – develop a custom text message handler to extract the unexpected security tokens.  This MSDN sample - http://msdn.microsoft.com/en-us/library/ms751486  - fully describes how to develop a custom text message encoder.  I used this sample and modified the ReadMessage method to extract and persist the security tokens:
 
 
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
    byte[] msgContents = new byte[buffer.Count];
    Array.Copy(buffer.Array, buffer.Offset, msgContents, 0, buffer.Count);
    bufferManager.ReturnBuffer(buffer.Array);
 
    MemoryStream stream = new MemoryStream(msgContents);
    stream = removeTokensFromStream(stream);
    return ReadMessage(stream, int.MaxValue);
}
 
// used to persist & remove unexpected security tokens from the message
private MemoryStream removeTokensFromStream(MemoryStream message)
{
    MemoryStream outputStream = new MemoryStream();
 
    // remove the unexpected security header
    message.Position = 0;
    XElement xmlMessage = XElement.Load(message);
    XNamespace sec = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    XNamespace saml = "urn:oasis:names:tc:SAML:1.0:assertion";
 
    // find and persist the SAML Assertion
    XElement samlAssertion = xmlMessage.Descendants(saml + "Assertion").First();
    samlAssertion.Save("saml.xml");
 
    xmlMessage.Descendants(sec + "Security").Remove();
 
    // save the modified message
    MemoryStream outputMessage = new MemoryStream();
    xmlMessage.Save(outputStream);
 
    outputStream.Position = 0;
    return outputStream;
}
 
 
The response has now been modified to conform to what WCF expects from a message exchange that uses a custom binding wit h authenticationMode="MutualCertificate" for X509 certificates and is process without an exception.  The service’s SAML Assertion is also saved to be used in a strong authentication scenario for follow-on requests.  In the next part of this segment, I will discuss how we use the WriteMessage method in another custom TextMessageEncoder to add the SAML Assertion to follow-on service requests.

Now we examine how to use another custom text message encoder to modify the security header of a WCF message to be interoperable with a non-WCF service.

 
This code is developed using the .NET Framework 4.0, Windows Communication Foundation (WCF).  Following the service login described in Part One, the client is configured with a custom binding. Note below that UserNameOverTransport is used as the security authentication mode. This allows WCF to create a security section containing a Username token. 

 
<bindingname="InSession">
 <customTextMessageEncodingmessageType="InSession"messageVersion="Soap11"
    encoding="utf-8" />            
 <securityallowSerializedSigningTokenOnReply="false"enableUnsecuredResponse="true"
    authenticationMode="UserNameOverTransport"securityHeaderLayout="Strict"
    includeTimestamp="true"messageSecurityVersion="WSSecurity10WSTrustFebruary2005..."
    requireSignatureConfirmation="false">
    <localClientSettingsblue;"> detectReplays="false"reconnectTransportOnFailure="true" />
    <secureConversationBootstrap />
 </security>
  <httpsTransportkeepAliveEnabled="false"requireClientCertificate="false" />
</binding>
 

The request  Security header elem ent needs to contain three tokens:

1.       Timestamp
2.       UsernameToken
3.       SAML Assertion
Since the authentication mode is UserNameOverTransport, the Username token is created when WCF creates the message. The security section also specifies includeTimestamp="true", so the timestamp token is included in the message security section. All that is left is for the custom message encoder, customTextMessageEncodingmessageType="InSession", to add the SAML assertion to the message. Again, an custom text message encoder is created as described in the MSDN sample http://msdn.microsoft.com/en-us/library/ms751486(v=VS.90).aspx. The WriteMessage method is modified to insert the SAML token:
 
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
    MemoryStream stream = new MemoryStream();
    XmlWriter writer = XmlWriter.Create(stream, this.writerSettings);
    message.WriteMessage(writer);
    writer.Close();
 
    // get the persisted SAML Assertion and add to the stream representation of the message
    stream.Position = 0;
    XElement xmlMessage = XElement.Load(stream);
 
    XElement samlXml = XElement.Load("saml.xml");
    XNamespace sec = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    xmlMessage.Descendants(sec + "Security").First().Add(samlXml);
 
    stream.Position = 0;
    xmlMessage.Save(stream);
    xmlMessage.Save("request.xml");
 
    byte[] messageBytes = stream.GetBuffer();
    int messageLength = (int)stream.Position;
    stream.Close();
 
    int totalLength = messageLength + messageOffset;
    byte[] totalBytes = bufferManager.TakeBuffer(totalLength);

    Array.Copy (messageBytes, 0, totalBytes, messageOffset, messageLength);

 
    ArraySegment<byte> byteArray = new ArraySegment<byte>(totalBytes, messageOffset, messageLength);
    return byteArray;
}
 
The WCF request has now been modified to deliver what the non-WCF service is expecting in the request.
 
we looked at how to modify a response from a non-WCF service to conform to what WCF expects using a custom text message encoder. In this part, we examined how to use another custom text message encoder to modify the security header of a WCF message to be interoperable with a non-WCF service. In the final section, Part 3, we’ll look at how to use a custom message inspector to further modify WCF messages for non-WCF service interoperability.