Программно настроить конечные точки ServiceHost с помощью HTTPS?

Я использую безфайловую активацию, вот мой полный файл web.config на стороне сервера, который имеет две конечные точки:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" 
             type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
             requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="RedStripe"
         connectionString="Data Source=S964;Initial Catalog=MyDatabase;Persist Security Info=True;User ID=sa;Password=***;MultipleActiveResultSets=True"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.web>
    <customErrors mode="Off"/>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment>
      <!-- where virtual .svc files are defined -->
      <serviceActivations>     
        <add service="Company.Project.Business.Services.AccountClassService" 
             relativeAddress="Account/AccountClassService.svc" 
             factory="Company.Project.WebHost.CustomServiceHostFactory"/>

        <add service="Company.Project.Business.Services.AccountService"
             relativeAddress="Account/AccountService.svc"
             factory="Company.Project.WebHost.CustomServiceHostFactory"/>

      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>
</configuration>

Вот мой CustomServiceHostFactory:

public class CustomServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new CustomServiceHost(serviceType, baseAddresses);
    }
}

А вот мой CustomServiceHost:

public class CustomServiceHost : ServiceHost
{        
    public CustomServiceHost(Type serviceType, Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
    }

    protected override void InitializeRuntime()
    {
        AddServiceDebugBehavior();
        AddWcfMessageLoggingBehavior();
        AddGlobalErrorHandlingBehavior();
        AddServiceCredentialBehavior();
        AddEndpoints();
        ConfigureThrottling();
        base.InitializeRuntime();
    }

    private void AddEndpoints()
    {
        var wsHttpBinding = WcfHelpers.ConfigureWsHttpBinding();

        foreach (Uri address in BaseAddresses)
        {
            var endpoint = new ServiceEndpoint(
                ContractDescription.GetContract(Description.ServiceType),
                wsHttpBinding, new EndpointAddress(address));

            AddServiceEndpoint(endpoint);

            //adding mex
            AddServiceMetadataBehavior();
            AddServiceEndpoint(
                ServiceMetadataBehavior.MexContractName,
                MetadataExchangeBindings.CreateMexHttpBinding(),
                address.AbsoluteUri + "/mex");

            break;
        }
    }
    private void AddGlobalErrorHandlingBehavior()
    {
        var errorHanlderBehavior = Description.Behaviors.Find<GlobalErrorBehaviorAttribute>();

        if (errorHanlderBehavior == null)
        {
            Description.Behaviors.Add(new GlobalErrorBehaviorAttribute(typeof(GlobalErrorHandler)));
        }
    }

    private void AddServiceCredentialBehavior()
    {
        var credentialBehavior = Description.Behaviors.Find<ServiceCredentials>();

        if (credentialBehavior == null)
        {
            var customAuthenticationBehavior = new ServiceCredentials();
            customAuthenticationBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
            customAuthenticationBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();
            Description.Behaviors.Add(customAuthenticationBehavior);
        }
    }
    private void AddServiceDebugBehavior()
    {
        var debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior>();

        if (debugBehavior == null)
        {
            Description.Behaviors.Add(
                new ServiceDebugBehavior() {IncludeExceptionDetailInFaults = true});
        }
        else
        {
            if (!debugBehavior.IncludeExceptionDetailInFaults)
                debugBehavior.IncludeExceptionDetailInFaults = true;
        }
    }
    private void AddServiceMetadataBehavior()
    {
        var metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>();

        if (metadataBehavior == null)
        {
            ServiceMetadataBehavior serviceMetadataBehavior = new ServiceMetadataBehavior();
            serviceMetadataBehavior.HttpsGetEnabled = true;
            Description.Behaviors.Add(serviceMetadataBehavior);
        }
    }
    private void AddWcfMessageLoggingBehavior()
    {
        var messageInspectorBehavior = Description.Behaviors.Find<WcfMessageInspector>();

        if (messageInspectorBehavior == null)
        {
            Description.Behaviors.Add(new WcfMessageInspector());
        }
    }
    private void ConfigureThrottling()
    {
        var throttleBehavior = Description.Behaviors.Find<ServiceThrottlingBehavior>();

        if (throttleBehavior != null) return;

        throttleBehavior = new ServiceThrottlingBehavior
        {
            MaxConcurrentCalls = 100,
            MaxConcurrentInstances = 100,
            MaxConcurrentSessions = 100
        };

        Description.Behaviors.Add(throttleBehavior);
    }
}

Наконец, вот WcfHelper, в котором определена привязка. Это находится в общем месте, поэтому я могу программно настроить привязку на стороне клиента, используя то же самое:

public class WcfHelpers
{
    public static WSHttpBinding ConfigureWsHttpBinding()
    {
        return new WSHttpBinding
        {
            Name = "myWSHttpBinding",                
            OpenTimeout = new TimeSpan(0, 10, 0),
            CloseTimeout = new TimeSpan(0, 10, 0),
            SendTimeout = new TimeSpan(0, 10, 0),
            MaxBufferPoolSize = 104857600,
            MaxReceivedMessageSize = 104857600,
            Namespace = Constants.RedStripeNamespace,
            ReaderQuotas = new XmlDictionaryReaderQuotas()
            {
                MaxDepth = 104857600,
                MaxStringContentLength = 104857600,
                MaxArrayLength = 104857600,
                MaxBytesPerRead = 104857600,
                MaxNameTableCharCount = 104857600
            },
            Security =
            {
                Mode = SecurityMode.TransportWithMessageCredential,
                Message = { ClientCredentialType = MessageCredentialType.UserName }
            }
        };

    }
}

Когда я публикую этот проект WebHost и пытаюсь перейти к одному из двух адресов, например: https://myserver/Project/Account/AccountService.svc я получаю следующую ошибку:

Предоставленная схема URI «http» недействительна; ожидаемый «https». Имя параметра: context.ListenUriBaseAddress

Я заметил, что в методе CustomServiceHost AddEndpoints() при переборе BaseAddresses, если я жестко прописываю там адрес, например: https://myserver/Project/Account/AccountService.svc Затем я могу успешно перейти к нему. Как строятся BaseAddresses при использовании безфайловой активации и относительной адресации? Где я могу указать, что они используют https (там, где сейчас они используют http)?

Заранее спасибо.


Редактировать 1: Это решит проблему, но похоже на полный взлом. Где мне указать https с помощью безфайловой активации, чтобы относительный адрес строился с https?

var endpoint = new ServiceEndpoint(ContractDescription.GetContract(Description.ServiceType),
wsHttpBinding, new EndpointAddress(address.OriginalString.Replace("http:", "https:")));

Изменить 2: кажется, я начинаю понимать, что здесь происходит. Спасибо @Andreas K за то, что указали мне правильное направление. Если я зайду в IIS и посмотрю привязки для сайта, их будет несколько, как показано на изображении:введите описание изображения  здесь

Я поместил некоторый код для записи в базу данных внутри моего метода AddEndpoints() при переборе BaseAddresses. Когда я пытаюсь использовать браузер для доступа к службе, например: https://my.server.local/Project/Account/AccountService.svc, в базе данных создаются ДВЕ записи. http://my.server.local/Project/Account/AccountService.svc https://my.server.local/Project/Account/AccountService.svc

Таким образом, кажется, что IIS SITE BINDING подхватывается. Однако теперь я не уверен, почему в базе данных нет больше записей для BaseAddresses. Где находятся net.pipe, net.tcp и т. д.?


person BBauer42    schedule 01.05.2015    source источник
comment
Вы можете опубликовать свой полный app.config на стороне сервера?   -  person tchrikch    schedule 04.05.2015
comment
вы имеете в виду web.config? Я обновил сообщение, включив в него полный файл web.config на стороне сервера.   -  person BBauer42    schedule 04.05.2015
comment
спасибо, этого должно хватить   -  person tchrikch    schedule 04.05.2015


Ответы (3)


Оказывается, BaseAddresses исходят из привязки IIS, как упоминалось в моем обновлении 2, и снова спасибо @Andreas K за то, что он указал мне правильное направление. В IIS у меня есть один веб-сайт с несколькими приложениями под ним. В этих привязках у меня включены как http, так и https. Я обновил свой метод AddEndpoings() в CustomServiceHost, чтобы он выглядел следующим образом:

private void AddEndpoints()
{
    var wsHttpBinding = WcfHelpers.ConfigureWsHttpBinding();

    foreach (var address in BaseAddresses.Where(a => a.Scheme == Uri.UriSchemeHttps))
    {
        var endpoint = new ServiceEndpoint(
            ContractDescription.GetContract(Description.ServiceType),
            wsHttpBinding, 
            new EndpointAddress(address));

        AddServiceEndpoint(endpoint);
        AddServiceMetadataBehavior();
    }
}

Так как другим приложениям под сайтом нужен http, мой BaseAddresses всегда содержит два (http и https). Мне нужно было вручную отфильтровать http, так как я не хочу раскрывать их для этого конкретного сайта. Теперь, когда я знаю, КАК они заселяются, я доволен. Спасибо всем.

person BBauer42    schedule 05.05.2015

Попробуй это:

WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; // no password

// If you are not using IIS, you need to bind cert to port
Process proc = new Process();
proc.StartInfo.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "netsh.exe");
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.Arguments =
    string.Format("http add sslcert ipport={0}:{1} certhash={2} appid={{{3}}}", ip, port, cert.Thumbprint, Guid.NewGuid());
proc.Start();
proc.WaitForExit();

Чтобы получить сертификат, выполните следующие действия (примечание: сертификат должен находиться в хранилище сертификатов):

X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
cert = store.Certificates.Find(X509FindType.FindBySubjectName, certSubject, false)[0];

Это будет работать без IIS. Если вы используете IIS, вам не нужно привязывать сертификат к порту (я думаю)

person Zeus82    schedule 04.05.2015
comment
Попробуйте где? Я ДОЛЖЕН использовать WsHttpBinding и TransportWithMessageCredential, поэтому не уверен, как это применимо? И да, я использую IIS. - person BBauer42; 04.05.2015
comment
Можете ли вы заменить свою WsHttpBinding на WebHttpBinding? И настроить его как в примере, который я привожу? - person Zeus82; 04.05.2015
comment
Хорошо, я изменил свой WsHttpBinding, чтобы использовать ваш WebHttpBinding, повторно опубликовать в IIS и получить ту же ошибку. Предоставленная схема URI «http» недействительна; ожидаемый «https». - person BBauer42; 04.05.2015
comment
Откуда он получает URI? Это из вашего web.config? Убедитесь, что ваш URI начинается с HTTPS - person Zeus82; 04.05.2015
comment
Это мой вопрос. Я использую безфайловую активацию, как указано в посте. Я просто указываю относительный адрес в теге ‹serviceActivations›. Я не знаю, как заполняются BaseAddresses и как изменить их для использования https. - person BBauer42; 04.05.2015

Я нашел это в msdn:

Активация без файлов Хотя файлы .svc упрощают доступ к службам WCF, еще проще было бы определить виртуальные конечные точки активации в файле Web.config, что полностью устраняет необходимость в файлах .svc. В WCF 4 вы можете определить конечные точки активации виртуальных служб, которые сопоставляются с вашими типами служб в Web.config. Это позволяет активировать службы WCF без необходимости поддерживать физические файлы .svc (так называемая «бесфайловая активация»). В следующем примере показано, как настроить конечную точку активации:

<configuration>
  <system.serviceModel>
    <serviceHostingEnvironment>
      <serviceActivations>
        <add relativeAddress="Greeting.svc" service="GreetingService"/>
      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>
</configuration>

После этого теперь можно активировать GreetingService, используя относительный путь «Greeting.svc» (относительно базового адреса веб-приложения). Чтобы проиллюстрировать это, я создал на своем компьютере приложение IIS под названием «GreetingSite», которое я назначил пулу приложений «ASP.NET v4.0» и сопоставил его с каталогом проекта GreetingService, который содержит Интернет. показанный выше конфиг. Теперь я могу просто перейти к http://localhost/GreetingSite/Greeting.svc, фактически не имея физического файла . svc-файл на диске. На рис. 9 показано, как это выглядит в браузере.

Я надеюсь это тебе поможет

person Andreas K    schedule 05.05.2015
comment
Да, у меня все это работает, и я публикую в IIS. В моем сообщении вы увидите, что у меня есть раздел serviceActivations с двумя определенными службами, каждая из которых имеет относительный адрес, службу и заводское значение. Моя фабрика определяет wsHttpBinding с Security.Mode = TransportWithMessageCredential и Security.Message.ClientCredentialType = MessageCredentialType.Username. Это должно привести к тому, что схема будет https, но в моем CustomServiceHost BaseAddresses всегда http. Как мне установить их на https? - person BBauer42; 05.05.2015
comment
Кроме того, см. редактирование 1 в сообщении, если вы еще этого не сделали. Я могу исправить проблему, но это похоже на взлом. - person BBauer42; 05.05.2015
comment
вы размещаете свой сервис wcf на IIS. Таким образом, IIS обеспечивает привязку к внешней стороне. Пожалуйста, проверьте эту привязку. И да, редактирование похоже на взлом: P - person Andreas K; 05.05.2015
comment
В IIS у меня установлен флажок «Требовать SSL», а для ClientCertificates установлено значение «Принять». Вы можете увидеть привязку, которую я использую в OP, если только нет какой-либо другой привязки, о которой я не знаю? - person BBauer42; 05.05.2015
comment
Да, у IIS есть своя привязка. Я думаю, что если вы используете относительный адрес для службы WCF, URL-адрес просто устанавливается вместе из службы привязки IIS + webapp +. Не могли бы вы взглянуть на это меню в консоли IIS: sslshopper.com/assets/images/host-headers-iis7/ - person Andreas K; 05.05.2015
comment
В этом окне я вижу тип: https, IP-адрес заполнен, порт 443, сертификат SSL заполнен моим сертификатом, все выглядит правильно для меня. - person BBauer42; 05.05.2015
comment
хм, да, это кажется правильным. Дай мне подумать об этом. Вместе мы во всем разберемся. - person Andreas K; 05.05.2015
comment
В этой статье: msdn.microsoft.com/en-us/magazine/cc163412. aspx см. рис. 2. Использование относительных URI и главу «Соображения по адресации IIS». Надеюсь, там вы найдете решение... - person Andreas K; 05.05.2015
comment
пожалуйста, смотрите мой обновленный пост Edit 2, спасибо за помощь до сих пор. - person BBauer42; 05.05.2015
comment
Привет, так что я думаю, что вы только что поняли это. Как я уже сказал, это привязка IIS. На ваш вопрос: есть только адреса http и https, сопоставленные с вашим хостом IIS. Буду признателен, если вы примете мой ответ как правильный. - person Andreas K; 05.05.2015
comment
Да, спасибо! Я сам опубликовал и отметил более подробный ответ, но наградил вас наградой за то, что вы указали мне правильное направление. Спасибо. - person BBauer42; 05.05.2015