Category Archives: wcf

PHP клиент для WCF сервиса

В предыдущей статье я подробно рассматривал вопросы создания клиент-серверного .NET приложения поддерживающего обмен данными через WCF сервисы и реализующего защиту передаваемых данных на транспортном уровне. Обе части приложения (клиентская и серверная) были реализованы при помощи .NET. Однако, бывают ситуации, когда существуют уже готовые WCF сервисы и необходимо реализовать их поддержку сторонним клиентом. В данной статье будут рассмотрен вопрос создания PHP клиента для уже существующего WCF сервиса. В качестве окружения будут использованы Windows 7, Apache 2.2.17 с поддержкой PHP 5.2.9-2 и созданный ранее WCF сервис.

Конфигурирование PHP

Для начала надо проверить, что PHP сконфигурирован правильно и включена поддержка всех необходимых библиотек. Для этого достаточно написать простейший скрипт вида:

<?php phpinfo(); ?>

и посмотреть на результат его исполнения. Если все сконфигурировано верно, то в результирующей таблице будет следующая информация:
soap

Soap Client enabled
Soap Server enabled

openssl

OpenSSL support enabled
OpenSSL Version OpenSSL 0.9.8k 25 Mar 2009

Секция soap отвечает за возможность создания класса SoapClient. Если ее нет, то при запуске клиентского кода вы увидите вот эту ошибку:

 Fatal error: Class 'SoapClient' not found in D:\wcfclient.php on line 10

В этом случае в файле php.ini необходимо найти строку:

;extension=php_soap.dll

и раскомментировать ее путем стирания символа “;” стоящего в начале строки.
Секция openssl отвечает за использование SSL при обмене данными. Если ее нет, то при попытке создать клиента к сервису, url которого начинается с “https://…”, вы увидите вот такую ошибку:

Notice: SoapClient::SoapClient() [soapclient.soapclient]: Unable to find the wrapper "https" - did you forget to enable it when you configured PHP? in D:\wcfclient.php on line 10

В этом случае в файле php.ini необходимо найти строку:

;extension=php_openssl.dll

и раскомментировать ее путем стирания символа “;” стоящего в начале строки. После внесения изменений в php.ini необходимо перезагрузить Apache.

Создание PHP клиента

При создании php клиента к WCF сервису следует обратить особое внимание на тип привязки, используемой в сервисе. В данной статье я рассмотрю два типа привязок: basicHttpBinding и wsHttpBinding. Основные различия между этими двумя типами привязок хорошо описаны здесь. Особенно важным пунктом является разница в версиях SOAP.

WCF сервис

Как я уже указывал выше, в качестве сервера будет выступать созданный ранее WCF сервис. Из соображений экономии места здесь я приведу только его конфигурацию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="SecurityBehavior">
          <serviceMetadata httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Hosting.Service1"
               behaviorConfiguration="SecurityBehavior">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="TransportSecurityBinding" contract="Hosting.IService1">
        </endpoint>
        <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="https://localhost:7586/Service1/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="TransportSecurityBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

По сути, важными здесь являются только 18-я и 23-я строки: в первой задается тип привязки, а во второй указывается url сервиса. Кроме того, перед началом работ по созданию клиента надо убедиться, что серверная часть скомпилирована и запущенна. Если все готово, то поместив в адресную строку интернет-браузера следующий ulr “https://localhost:7586/Service1/?wsdl” (url сервера + параметр “wsdl”) мы должны увидеть wsdl-описание сервиса, сгенерированное сервером.

PHP клиент для basicHttpBinding

basicHttpBinding использует в своей работе SOAP 1.1 который поддерживается классом SoapClient по умолчанию. В данном случае создание клиента не займет у нас много времени:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "PHP client for WCF service using 'basicHttpBinding'\r\n\r\n";
$accountService = new SoapClient('https://localhost:7586/Service1/?wsdl', array("trace" => 1, "exceptions" => 0));
try { 
	$obj->name = "qwerty";
	$result = $accountService->SayHello($obj);
	echo $result->SayHelloResult;
}
catch (SoapFault $fault) { 
	var_dump($fault->faultcode);
	var_dump($fault->faultstring);
	die;
}
?>

Все самое интересное происходит в строках 4 и 6-8. Фактически, всю техническую работу берет на себя класс SoapClient, который мы создаем в 4-ой строке. Нам необходимо только указать url сервиса (см. 23-ю строку конфигурации сервера) и несколько дополнительных параметров. Вызов сервиса происходит в строке 7, печать результата в строке 8.

PHP клиент для wsHttpBinding

В силу того, что wsHttpBinding основывается на WS*-спецификациях и использует в своей работе SOAP 1.2 – клиентская часть будет чуть обширнее. Для начала изменим конфигурацию сервера:

1
2
3
4
5
6
7
8
9
10
11
12
13
    ...
    <endpoint address="" binding="wsHttpBinding" bindingConfiguration="TransportSecurityBinding" contract="Hosting.IService1"/>
    ...
    <bindings>
      <wsHttpBinding>
        <binding name="TransportSecurityBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    ...

скомпилируем и перезапустим серверную часть.
Теперь, на стороне клиента, надо принудительно указать классу SoapClient использовать SOAP 1.2. Кроме того, в силу особенностей SOAP 1.2 потребуется указать несколько дополнительных заголовков:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "PHP client for WCF service using 'wsHttpBinding'\r\n\r\n";
$connect = array("soap_version" => SOAP_1_2, "trace" => 1, "exceptions" => 0);
$accountService = new SoapClient('https://localhost:7586/Service1/?wsdl', $connect);
$actionHeaders[] = new SoapHeader("http://www.w3.org/2005/08/addressing", "Action", "http://tempuri.org/IService1/SayHello", true);
$actionHeaders[] = new SoapHeader("http://www.w3.org/2005/08/addressing", "To", "https://localhost:7586/Service1/",  true);
$accountService->__setSoapHeaders($actionHeaders);
try { 
	$obj->name = "qwerty";
	$result = $accountService->SayHello($obj);
	echo $result->SayHelloResult;
} catch (SoapFault $fault) { 
	var_dump($fault->faultcode);
	var_dump($fault->faultstring);
	die;
}
?>

Обратите внимание на 4-ю строку. В ней указывается версия SOAP, которую необходимо использовать. Дополнительные заголовки задаются в 6-8 строках. Если эти строки опустить, то вы увидите вот эту:

1
The SOAP action specified on the message, '', does not match the HTTP SOAP Action, 'http://tempuri.org/IService1/SayHello'.

или вот эту ошибку:

1
The message with To '' cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher.  Check that the sender and receiver's EndpointAddresses agree.

URL “http://tempuri.org/IService1/SayHello” в 6-ой строке берется из wsdl, предоставляемого сервером.
Все, клиентская часть готова, можно запускать 🙂 Отдельное спасибо разработчикам php, все работает практически из коробки и без использования сторонних библиотек.

Безопасная передача данных через WCF с использованием SSL

В современном мире одним из основных требований при разработке сетевых приложений является безопасность передачи данных по открытым каналам между узлами системы, например, между клиентом и сервером. Одним из стандартизированных и самых надежных способов является использование SSL-протокола. SSL (Secure Sockets Layer — уровень защищённых сокетов) — это криптографический протокол, обеспечивающий безопасность обмена данными по сети. Подробную информацию о SSL можно найти здесь. В данной статье будет показано, как создать простейшее приложение, поддерживающее передачу данных посредством WCF, работающее в автономном (standalone application) режиме без использования IIS и поддерживающее безопасный обмен данными посредством SSL на уровне транспорта.

Создание SSL-сертификатов

Для начала нам потребуется создать два SSL-сертификата. Создавать их будем при помощи утилиты makecert. Подробную документацию к ней можно найти здесь

  • Создадим самозаверенный (самоподписанный) сертификат. Он будет заменять общеизвестный (общедоступный) сертификат авторизационного центра:
1
2
3
4
5
makecert -r -n "CN=имя_самоподписанного_сертификата" -sv имя_файла_ключа.pvk имя_файла_сертификата.cer
 
-r   - Создает самозаверенный сертификат.
-n   - Задает имя сертификата субъекта. Имя должно соответствовать стандарту X.500. Проще всего заключить имя в двойные кавычки и поставить перед ним префикс CN=, например: -n "CN=myName".
-sv  - Задает PVK-файл закрытого ключа субъекта. Если файл не существует, он будет создан.

Результатом исполнения команды будет самоподписанный сертификат, размещенный в текущей папке. В той же папке будет создан файл с ключом. Созданный сертификат необходимо импортировать в корневое хранилище доверенных сертификатов при помощи mmc-консоли (Start->Run->mmc, File->Add/Remove Snap-in) и оснастки “Сертификаты”.

  • Создадим SSL-сертификат для серверной части нашего WCF-приложения. Он будет заменять сертификат, выданный нашей компании сертификационным центром. Подпишем его созданным на предыдущем шаге самоподписанным сертификатом, имитируя таким образом выдачу данного сертификата центром авторизации:
1
2
3
4
5
6
7
8
9
10
11
12
makecert -pe -n "CN=имя_SSL_сертификата" -iv имя_файла_ключа_самоподписанного_сертификата.pvk -ic имя_файла_самоподписанного_сертификата.cer -sr localMachine -ss My -sky exchange имя_файла_SSL_сертификата.cer
 
-pe  - Помечает созданный закрытый ключ как экспортируемый. Это позволит включить закрытый ключ в сертификат.
-n   - Задает имя сертификата субъекта. Имя должно соответствовать стандарту X.500. Проще всего заключить имя в двойные кавычки и поставить перед ним префикс CN=, например: -n "CN=myName".
-iv  - Задает PVK-файл закрытого ключа поставщика. Закрытый ключ созданного ранее самоподписанного сертификата.
-ic  - Задает файл сертификата поставщика. Созданный ранее сертификат, которым будет подписан создаваемый сертификат.
-sr  - Задает местонахождение хранилища сертификатов субъекта. Расположение может быть либо currentuser (значение по умолчанию), либо localmachine.
-ss  - Задает имя хранилища сертификатов субъекта, в котором будет храниться созданный сертификат. Например (CA, MY и т.д.)
-sky - Определяет тип ключа субъекта, который должен быть одним из следующих:
         signature (что означает, что этот ключ используется для цифровой подписи),
         exchange (что означает, что этот ключ используется для шифрования и обмена ключами,)
         или целое число, представляющее тип поставщика. По умолчанию можно указать 1 для ключа обмена или 2 для ключа сигнатуры.

Созданный SSL-сертификат будет автоматически помещен в хранилище сертификатов “LocalMachine”->”Personal”. Это можно проверить при помощи mmc-консоли и оснастки “Сертификаты”. Если в хранилище нет созданного SSL-сертификата, его необходимо туда импортировать. Я назвал созданный сертификат localhost (это имя понадобится нам в будущем)

1
...-n "CN=localhost"...

Серверная часть приложения

Для создания серверной части приложения воспользуемся средой разработки Miscosoft Visual Studio 2010 в которой создадим новое консольное приложение (File->New->Project-> Console Application) и сохраним его как Hosting. Теперь надо открыть свойства проекта и изменить имя сборки и пространство имен по умолчанию на Hosting (в принципе можно этого не делать, но тогда Ваше пространство имен будет отличаться от используемого в приведенных примерах кода).
SimpleWCFApplication
Теперь добавим к нему WCF-сервис. Кликнем правой кнопкой на нашем проекте в дереве проектов, выберем пункт меню Add->New Item, выберем в списке в открывшемся окне элемент WCF Service и нажмем кнопку Add. Среда автоматически добавит в наш проект два новых файла: Service1.cs и IService1.cs. Первый будет содержать код класса, реализующего наш WCF-сервис:

1
2
3
4
5
6
7
8
9
10
11
using System;
 
namespace Hosting
{
  public class Service1 : IService1
  {
    public void DoWork()
    {
    }
  }
}

Второй будет содержать контракт нашего WCF-сервиса:

1
2
3
4
5
6
7
8
9
10
11
using System.ServiceModel;
 
namespace Hosting
{
  [ServiceContract]
  public interface IService1
  {
    [OperationContract]
    void DoWork();
  }
}

Также некоторые изменения появятся в файле конфигурации приложения app.config, но о них чуть позже.
Изменим контракт нашего сервиса:

1
2
3
4
5
6
7
8
9
10
11
12
//IService1.cs
using System.ServiceModel;
 
namespace Hosting
{
  [ServiceContract]
  public interface IService1
  {
    [OperationContract]
    string SayHello(string name);
  }
}

и сам сервис таким образом, чтобы он мог принимать и отдавать данные клиенту:

1
2
3
4
5
6
7
8
9
10
11
12
13
//Service1.cs
using System;
 
namespace Hosting
{
  public class Service1 : IService1
  {
    public string SayHello(string name)
    {
      return String.Format("Hello, {0}!", name);
    }
  }
}

Теперь наш сервис умеет принимать от клиента его имя и возвращать клиенту строку приветствия. Самое время заняться конфигурацией. Для экономии места я не буду приводить здесь конфигурацию по умолчанию, автоматически созданную средой при добавлении в проект wcf-сервиса, а приведу уже готовую конфигурацию app.config, которую мы подробно рассмотрим.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="SecurityBehavior">
          <serviceMetadata httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <serviceCertificate findValue="localhost"
                                storeLocation="LocalMachine"
                                storeName="My"
                                x509FindType="FindBySubjectName"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Hosting.Service1"
               behaviorConfiguration="SecurityBehavior">
        <endpoint address=""
                  binding="basicHttpBinding"
                  bindingConfiguration="TransportSecurityBinding"
                  contract="Hosting.IService1">
        </endpoint>
        <endpoint address="mex"
                  binding="mexHttpsBinding"
                  contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="https://localhost:7586/Service1/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="TransportSecurityBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

Посмотрим, что же изменилось:
SimpleWCFApplication_hosting_app_config_diff

  • строчка 6: У секции, описывающей поведение сервиса, появилось имя “SecurityBehavior”. Это сделано для того, чтобы можно было ссылаться на эту секцию из секции, описывающей сам сервис
  • строчка 7: httpsGetEnabled=”true” вместо httpGetEnabled=”true”. Теперь для получения информации о сервисе будет использоваться https протокол
  • строки 9-14: Добавилось описание serviceCredentials. Именно здесь размещена информация об SSL-сертификате, который будет использовать наше серверное приложение для аутентификации себя как авторизованного приложения. Здесь указано имя используемого сертификата, его месторасположение и способ его поиска в хранилище сертификатов
  • строчка 20: Добавилась ссылка на описание поведения сервиса
  • строчка 22: Изменен биндинг (привязка). По умолчанию система предлагала использовать wsHttpBinding, но мы его заменим на basicHttpBinding как более простой и быстрый
  • строчка 23: Добавилась ссылка на конфигурацию биндинга
  • строчка 27: Изменен биндинг (привязка), отвечающий за получение метаинформации о нашем wcf-сервисе. Теперь будет использован mexHttpsBinding и соответственно https протокол
  • строчка 31: Изменен url сервиса и указано, что для доступа к нему надо обязательно использовать защищенный https протокол. Обратите внимание на номер порта: 7586. Для корректной работы нашего приложения необходимо будет произвести дополнительную настройку операционной системы, но об этом чуть ниже. 7586 – это произвольно выбранный порт из диапазона свободных портов. Вы можете выбрать себе любой другой свободный порт.
  • строчки 36-44: Описание конфигурации нашего биндинга (привязки). В первую очередь здесь надо обратить внимание на строчки 39-41. Они содержат информацию о том, что для данного биндинга необходимо включить поддержку безопасности на уровне транспорта, но не использовать при этом механизм аутентификации пользователей. Другими словами, будет использован SSL-протокол, обеспечивающий безопасную передачу данных, но проверка пользователей производиться не будет, т.е. наш сервис может быть использован любым клиентом, а не только авторизованными пользователями.

Теперь остается только активировать сервис и обработать возможные исключения при запуске приложения. Сделаем это в функции Main() файла Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Program.cs
using System;
using System.ServiceModel;
 
namespace Hosting
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        var service = new ServiceHost(typeof(Service1));
        service.Open();
        Console.WriteLine("Hosting started. Press any key to exit...");
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
      finally
      {
        Console.ReadKey();
      }
    }
  }
}

На этом серверная часть нашего приложения закончена.

Конфигурирование портов операционной системы при использовании SSL

Подробную информацию о конфигурировании портов при использовании SSL-сертификатов можно найти здесь. Здесь я кратко резюмирую что необходимо сделать:

  • Для просмотра текущей конфигурации портов в Windows Server 2003 или Windows XP необходимо использовать утилиту HttpCfg.exe:
1
httpcfg query ssl
  • Для просмотра текущей конфигурации портов в Windows Vista или Windows 7 необходимо использовать утилиту Netsh.exe:
1
netsh http show sslcert
  • Для выполнения привязки сертификата к номеру порта в Windows Server 2003 или Windows XP необходимо использовать утилиту HttpCfg.exe в режиме “set”:
1
2
3
4
5
6
7
8
9
10
11
httpcfg set ssl -i IP:port -c MY -m 65536 -h Thumbprint
 
-i IP:port    - 0.0.0.0 или реальный IP сервера : номер используемого порта
-h Thumbprint - отпечаток сертификата. Можно просмотреть при при помощи mmc с установленной оснасткой 'cсертификаты'
                Найти требуемый сертификат в общем списке -> Свойства -> Детали -> Thumbprint, скопировать в блокнот и удалить все пробелы
-c StoreName. String that specifies the name of the store where the certificate being added resides. If no string is specified, the name "MY" is used by default.
-m CheckMode. string containing one or more numbers representing flags that determine the default mode for checking the certificate. The numbers may consist of one or more of the following flag values:
    1     - Client certificate will not be verified for revocation.
    2     - Use cached client certificate revocation.
    4     - Enable revocation freshness time.
    65536 - No usage check.

Пример использования:

1
httpcfg set ssl -i 0.0.0.0:7586 -c MY -m 65536 -h a44ddc00cc645e6227b5606c6c351b62d4f7c8c1

Внимательно проверьте отпечаток сертификата и номер порта, к которому нужно осуществлять привязку. Номер порта следует взять из файла конфигурации вашего сервиса, отпечаток сертификата – см. выше.

  • Для выполнения привязки сертификата к номеру порта в Windows Vista или Windows 7 необходимо использовать утилиту Netsh.exe:
1
2
3
4
5
6
netsh http add sslcert ipport=0.0.0.0:7586 certhash=a44ddc00cc645e6227b5606c6c351b62d4f7c8c1 appid={00112233-4455-6677-8899-AABBCCDDEEFF}
 
-ipport IP:port    - 0.0.0.0 или реальный IP сервера : номер используемого порта
-certhash          - отпечаток сертификата. Можно просмотреть при при помощи mmc с установленной оснасткой 'cсертификаты'
                     Найти требуемый сертификат в общем списке -> Свойства -> Детали -> Thumbprint, скопировать в блокнот и удалить все пробелы
-appid:            - представляет собой идентификатор GUID, который можно использовать для определения приложения-владельца.

Теперь, после настройки портов, можно скомпилировать и запустить серверную часть нашего приложения, чтобы убедиться, что все в порядке.

Клиентская часть приложения

Возвращаемся в Visual Studio и добавляем в наше решение еще один консольный проект. Назовем его Client. Изменим в его свойствах наименование сборки и пространство имен на Client.
Теперь добавим в него ссылку на созданный ранее wcf-сервис. ВНИМАНИЕ: чтобы добавление ссылки прошло успешно, серверная часть должна быть запущена! Проще всего запустить серверную часть через командную строку: Start->Run->cmd.exe, перейти в папку где лежит собранный hosting.exe и запустить его. Тоже самое можно сделать используя стандартный проводник файлов (windows file explorer).
Кликаем правой кнопкой мыши по нашему проекту в дереве проектов и выбираем Add Service Reference. В открывшемся окне в поле адрес вбиваем “https://localhost:7586/Service1/” и нажимаем кнопку “Go”
SimpleWCFApplication_add_service_reference
Нажимаем “Ok”. Все, ссылка добавлена. Теперь остается только вызвать сервис и обработать возможные исключения при запуске приложения. Сделаем это в функции Main() файла Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Program.cs
using System;
 
namespace Client
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        Console.Write("Please, enter you name: ");
        var name = Console.ReadLine();
        var service = new ServiceReference1.Service1Client();
        var result = service.SayHello(name);
        Console.WriteLine(result);
      }
      catch(Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
      finally
      {
        Console.Write("Press any key to exit...");
        Console.ReadKey();
      }
    }
  }
}

Обратите внимание на строки 14-16. Это и есть вызов сервиса и печать результатов его работы. Все готово, можно компилировать и запускать приложение.
SimpleWCFApplication_client_start
Вводим имя, например Vasia, отправляем запрос на сервер и получаем в ответ строку приветствия, которую и выводим на экран:
SimpleWCFApplication_client_get_response
На этом все: мы создали готовое клиент-серверное приложение, работающее через https-протокол с использованием SSL-сертификата и отвечающее требованию к безопасности передачи данных через открытые каналы данных. Удачи 🙂