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

[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 = "https://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 = "https://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);

            }

        }