[WCF]Secure a dynamically added message header via behavior extension(part 2)

 

This article is the continue of my previous one demonstrating how to secure a custom message header added dynamically in WCF message. In the previous part 1, I use a custom ContractBehavior to inject the security protectionRequirement that is necessary for securing our dynamically added message header.

 

In this one, I will provide the example that use a custom endpointBehavior to inject the protection requirement for securing dynamic message header. Things different from part1 include:

 

ü  Use endpointBehavior instead of contractBehavior to customize runtime

ü  Use an untyped messageheader instead of a user defined typed header

ü  Use code to initialize the WCF runtime instead of configuration file(both client and service)

ü  Use channelFactory(instead of auto-generated service proxy) to consume service

 

Here are complete code of the three projects(the same as part1):

 

 

l  Shared Library

This class library(referenced by both service and client app) include a service contract and a custom endpointbehavior.

 

namespace SharedLib

{

    [ServiceContract]

    public interface ITestService

    {

        [OperationContract]

        string GetData();

    }

/// <summary>

    /// my custom endpointBehavior class

    /// </summary>

    public class SecureHeaderEndpointBehavior : IEndpointBehavior

    {

        public string HeaderName { get; set; }

        public string HeaderNamespace { get; set; }

        public ProtectionLevel HeaderProtecionLevel { get; set; }

 

        public SecureHeaderEndpointBehavior() { }

 

        public SecureHeaderEndpointBehavior(string headerName, string headerNamespace, ProtectionLevel pLevel)

        { HeaderName = headerName; HeaderNamespace = headerNamespace; HeaderProtecionLevel = pLevel; }

 

        #region IEndpointBehavior Members

 

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

        {

            ChannelProtectionRequirements requirements = bindingParameters.Find<ChannelProtectionRequirements>();

 

//identifier for my custom message header

            XmlQualifiedName qnHeader = new XmlQualifiedName(HeaderName, HeaderNamespace); 

 

            //set protectionLevel

            if(HeaderProtecionLevel != ProtectionLevel.None)

            {

                requirements.IncomingSignatureParts.ChannelParts.HeaderTypes.Add(qnHeader);

 

                if (HeaderProtecionLevel == ProtectionLevel.EncryptAndSign)

                    requirements.IncomingEncryptionParts.ChannelParts.HeaderTypes.Add(qnHeader);

            }

        }

 

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

        {}

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

        {}

        public void Validate(ServiceEndpoint endpoint)

        {}

        #endregion

    }

}

 

 

l  WCF Service

The service provides a simple implementation of the shared service contract:

 

public class SimpleTestService :ITestService

    {

        #region ITestService Members

        public string GetData()

        {

            string data = "";

 

            OperationContext oc = OperationContext.Current;

            int i = oc.IncomingMessageHeaders.FindHeader("MyDynamicHeader", "urn:test");

            XmlDictionaryReader reader = oc.IncomingMessageHeaders.GetReaderAtHeader(i);

 

            data = "data from MyDynamicHeader: " + reader.ReadString();

 

            return data;

        }

 

        #endregion

    }

 

And here is the hosting code:

 

static void RunService()

        {

            string baseUrl = "http://localhost:11111/TestService";

 

            using (ServiceHost host = new ServiceHost(typeof(SimpleTestService), new Uri(baseUrl)))

            {

                //configure service

                ServiceMetadataBehavior smb = new ServiceMetadataBehavior(){ HttpGetEnabled = true};

                host.Description.Behaviors.Add(smb);

 

                WSHttpBinding binding = new WSHttpBinding( SecurityMode.Message);

                binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

 

                ServiceEndpoint sep = host.AddServiceEndpoint(typeof(ITestService), binding, "");

 

                host.Open();

 

 

                Console.WriteLine("service has started...........");

                Console.ReadLine();

 

            }

        }

 

l  WCF Client

The client application will use channelFactory to consume WCF service. It first inject our custom endpointBehavior and insert an untyped messageHeader before calling method.

 

static void CallService()

        {

            //init client service proxy

            string epAddress = "http://localhost:11111/TestService";

 

            WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);

            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

 

            ChannelFactory<ITestService> factory = new ChannelFactory<ITestService>(

                binding,

                epAddress);

 

            //here we inject our endpoint behavior into the client side proxy

            factory.Endpoint.Behaviors.Add(

                new SecureHeaderEndpointBehavior(

                    "MyDynamicHeader",

                    "urn:test",

                    ProtectionLevel.Sign

                    ));

      

            ITestService client = factory.CreateChannel();

 

 

            using (OperationContextScope scope = new OperationContextScope((IContextChannel)client))

            {

                OperationContext.Current.OutgoingMessageHeaders.Add(

                    MessageHeader.CreateHeader(

                        "MyDynamicHeader",

                        "urn:test",

                        "Some test data..."

                        ));

 

                string data = client.GetData();

 

                Console.WriteLine(data);

 

            }

        }