[WCF]Secure a dynamically added message header via behavior extension (part 1)
[WCF]Secure a dynamically added message header via behavior extension (part 1)
Recently I receive some questions about how to sign/encrypt a custom message header which is dynamically added when calling WCF service.
Sure, sign/encrypt means that we’re using message layer security and in most cases, we’ll specify how the WCF message will be secured via MessageContract and ProtectionLevel. E.g.
[MessageContract] public class PatientRecord { [MessageHeader(ProtectionLevel = None)] public int recordID; [MessageHeader(ProtectionLevel = Sign)] public string patientName; [MessageHeader(ProtectionLevel = EncryptAndSign)] public string SSN; [MessageBodyMember(ProtectionLevel = None)] public string comments; [MessageBodyMember(ProtectionLevel = Sign)] public string diagnosis; [MessageBodyMember(ProtectionLevel = EncryptAndSign)] public string medicalHistory; } |
And when you generate client-side service proxy, it will also get the message protection info from the service metadata(wsdl).
However, for those message headers that are added dynamically at runtime, there is no predefined protection information for them. Then, how can we secure them?
Well, Nicholas and Zulfiqar have provided two good articles describing how to achieve this task.
#Securing Custom Headers, Version 1
https://blogs.msdn.com/drnick/archive/2007/01/18/securing-custom-headers-version-1.aspx
#Setting ProtectionLevel from config file
https://zamd.net/2008/02/10/SettingProtectionLevelFromConfigFile.aspx
Since the two ones haven’t included all the detailed code, I’ll provide some detailed examples here(two examples in two parts). The example in this one will use a custom ContractBehavior to add the security protection info for our custom message header.
The entire example solution contains three projects, the WCF service, the WCF client and a shared class library contains the custom message header and custom ContractBehavior.
Shared class library
A very simple custom message header type
namespace SharedLib { public class SimpleHeader : MessageHeader { public const string HEADER_NAME = "SimpleHeader"; public const string HEADER_NAMESPACE = "urn:test"; public string Content { get; set; } protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion) { writer.WriteString(Content); } public override string Name { get { return "SimpleHeader"; } } public override string Namespace { get { return "urn:test"; } } } |
The custom contract behavior which plays the key role here:
namespace SharedLib { public class SimpleContractBehavior : IContractBehavior { #region IContractBehavior Members public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { ChannelProtectionRequirements requirements = bindingParameters.Find<ChannelProtectionRequirements>(); //add signature requirement for the custom header requirements.IncomingSignatureParts.ChannelParts.HeaderTypes.Add( new XmlQualifiedName(SimpleHeader.HEADER_NAME, SimpleHeader.HEADER_NAMESPACE) ); //add encryption requirement ... requirements.IncomingEncryptionParts.ChannelParts.HeaderTypes.Add( new XmlQualifiedName(SimpleHeader.HEADER_NAME, SimpleHeader.HEADER_NAMESPACE) );
} public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) {
} public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime) {
} public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) {
} #endregion } } |
As highlighted above, we use “AddBindingParameters” method of the ContractBehavior to inject the secured protection level info for our custom message header(we’ll add the header at client application code).
WCF Service
The service is nothing particular, as long as you use a binding and configure to use message layer security(windows or username or cert token):
namespace ServiceApp { [ServiceContract] public interface ITestService { [OperationContract] string GetDescription(); } public class TestService : ITestService { #region ITestService Members public string GetDescription() { OperationContext op = OperationContext.Current; int i = op.IncomingMessageHeaders.FindHeader(SimpleHeader.HEADER_NAME, SimpleHeader.HEADER_NAMESPACE); XmlDictionaryReader reader = op.IncomingMessageHeaders.GetReaderAtHeader(i); return "This is a simple test WCF service!\r\nHeaderContent: " + reader.ReadString(); } #endregion } class Program { static ServiceHost _host = null; static void Main(string[] args) { Config_Service(); Run_Service(); } static void Config_Service() { _host = new ServiceHost(typeof(TestService), new Uri("https://localhost:11111/TestService"));
//binding, use windows authentication for client WSHttpBinding binding = new WSHttpBinding( SecurityMode.Message); binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows; //create service endpoint ServiceEndpoint sep = _host.AddServiceEndpoint(typeof(ITestService),binding,"");
ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); smb.HttpGetEnabled = true; _host.Description.Behaviors.Add(smb); Console.WriteLine("service endpoint configured...."); } static void Run_Service() { _host.Open(); Console.WriteLine("service started............"); Console.ReadLine(); } } } |
WCF Client
Finally, our client application. Just simply generate the proxy via “Add ServiceReference” without anything particular change.
As I highlighted, two things important here:
1. Inject our custom contractBehavior into the endpoint’s contract
2. Add the message header into outgoing message
namespace ClientApp { class Program { static void Main(string[] args) { CallService(); } static void CallService() { TestSVC.TestServiceClient client = new TestSVC.TestServiceClient(); client.Endpoint.Contract.Behaviors.Add( new SimpleContractBehavior() ); using (OperationContextScope scope = new OperationContextScope((IContextChannel)client.InnerChannel)) { OperationContext op = OperationContext.Current; op.OutgoingMessageHeaders.Add( new SimpleHeader() { Content = "My test header content." } ); string ret = client.GetDescription(); Console.WriteLine(ret); } } } } |
You can use the Message Logging to view the WCF message over transport layer so as to verify the output.
Well, this is the way using custom ContractBehavior, in the next part, I’ll demonstrate how to use an endpoint behavior to add the message header protection info.
Comments
Anonymous
February 18, 2009
PingBack from http://www.anith.com/?p=11208Anonymous
July 18, 2010
blogs.msdn.com/.../wcf-secure-a-dynamically-added-message-header-via-behavior-extension-part-1.aspxAnonymous
July 18, 2010
I implemented your solution and the custom header is added but not signed. I'm using custom binding and authentication mode is CertificateOverTransport. Can you help? Thanks.Anonymous
February 14, 2015
I hawe the same problem like YY. I implemented your solution and the custom header is added but not signed. I'm using custom binding and authentication mode is CertificateOverTransport. Can you help? Thanks.