WCF: Multiple Secure Ports

By AdamJune 25, 2012 at 5:09 AM

The Problem

We were faced with an interesting issue on a project recently.  We had WCF (Windows Communication Framework) running on a custom port (as IIS does not allow more than one HTTPS site per port, per IP) and once we purchased a second IP address, we moved the web services site to the regular HTTPS port (443) on that new URL.  But because we already had clients in the field running the application that called the web services, we needed to maintain both ports on the same DNS name (443 and the custom port).   The problem?  WCF does not support this scenario without modification, especially if you have a custom ServiceHost (which we do as we wanted to introduce StructureMap into the WCF bootstrap process).  Let’s look at the solution.

The Solution

The first step in the solution is to tell WCF that you actually want to support this scenario.  You simply add the following line (line #2) to your web.config file in your WCF web services web site:

<system.serviceModel>
		<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
		<bindings>
			<basicHttpBinding>
				<binding name="BasicHttpBinding_INotify" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="524288" maxBufferPoolSize="524288" maxReceivedMessageSize="524288"
                    messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                    useDefaultWebProxy="true">
					<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
					<security mode="TransportWithMessageCredential">
						<transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
						<message clientCredentialType="UserName" algorithmSuite="Default" />
					</security>
				</binding>
			</basicHttpBinding>
		</bindings>
</system.serviceModel>

 

The next step is to make sure that all your end points are using relative Uri’s and not absolute.  In our custom ServiceHostwe had the following code (note line #27):

public ServicesServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
            serviceBehaviourType = typeof(ServicesServiceBehavior<>).MakeGenericType(serviceType);
            Initialize(serviceType, baseAddresses);
        }

protected void Initialize(Type serviceType, params Uri[] baseAddresses)
        {
            var httpsAddresses = from ba in baseAddresses
                                 where ba.Scheme == Uri.UriSchemeHttps
                                 select ba;

            ConfigureAuthentication();

            foreach (var httpsAddress in httpsAddresses)
            {
                var serviceContract = serviceType.GetInterfaceWithAttribute(typeof(ServiceContractAttribute));
                if (serviceContract == null)
                {
                    serviceContract = serviceType;
                }

                var binding = ConfigureBinding();
                try
                {
                    this.AddServiceEndpoint(serviceContract, binding, httpsAddress);
                }
                catch (Exception e)
                {
                }

                if (!this.Description.Behaviors.Contains(typeof(ServiceMetadataBehavior)))
                {
                    this.Description.Behaviors.Add(new ServiceMetadataBehavior()
                    {
                        HttpGetEnabled = true
                    });
                }
                var mex = MetadataExchangeBindings.CreateMexHttpBinding();
                this.AddServiceEndpoint(typeof(IMetadataExchange), mex, "mex");

                ((ServiceDebugBehavior)this.Description.Behaviors[typeof(ServiceDebugBehavior)])
	.IncludeExceptionDetailInFaults = true;
                ServiceSecurityAuditBehavior ssab = new ServiceSecurityAuditBehavior()
                {
                    AuditLogLocation = AuditLogLocation.Application,
                    MessageAuthenticationAuditLevel = AuditLevel.SuccessOrFailure,
                    ServiceAuthorizationAuditLevel = AuditLevel.SuccessOrFailure,
                    SuppressAuditFailure = false
                };
                this.Description.Behaviors.Add(ssab);
            }
        }

The simple solution was to change line #27 to:

this.AddServiceEndpoint(serviceContract, binding, "");

and everything worked.  To achieve the same results in XML, you simply would add an endpoint description to the XML like this (note that the address attributes are empty):

<service 
    name="Microsoft.ServiceModel.Samples.CalculatorService"
    behaviorConfiguration="CalculatorServiceBehavior">
  <!-- This endpoint is exposed at the base address provided by host: http://localhost/servicemodelsamples/service.svc  -->
  <endpoint address=""
            binding="basicHttpBinding"
            contract="Microsoft.ServiceModel.Samples.ICalculator" />
  <!-- secure endpoint exposed at {base address}/secure: http://localhost/servicemodelsamples/service.svc/secure -->
  <endpoint address="secure"
            binding="wsHttpBinding"
            contract="Microsoft.ServiceModel.Samples.ICalculator" />
  ...
</service>

Posted in: Programming

Tags:



blog comments powered by Disqus