Сейчас активно дорабатывается государственная информационная система ЖКХ, а с 1 января 2017 года наступает ответственность для управляющих и ресурсоснабжающих организаций за непредоставление информации в системе.
Как показывает практика, разработчики, которые выполняют интеграцию своих информационных систем с ГИС ЖКХ, очень много времени тратят на установление защищенного соединения и подписание сообщений. Несмотря на то, что разработчиками ГИС ЖКХ предоставлено демонстрационное приложение по подписанию сообщений, я опишу как можно решить эту задачу с помощью WCF. Хочу отметить, что в этом случае дополнительное ПО (типа stunnel) для установки защищенного соединения не нужно.
Надеюсь, эта статья поможет разработчикам, которые в будущем будут интегрироваться с ГИС ЖКХ.
К сожалению, без покупки дополнительного программного обеспечения не обойтись. Нам нужен КриптоПро .NET. Есть трёхмесячный бесплатный срок использования. Эта библиотека будет обеспечивать https соединение и подпись сообщений по алгоритму XAdES-BES. Также не забываем, что нужен сертификат квалифицированной электронной подписи.
Для начала нужно установить сертификат квалифицированной электронной подписи в личное хранилище локального компьютера. Он должен быть установлен вместе с закрытым ключом.
Подробно описывать процесс тут не буду, информацию можно найти тут и тут.
На сайте ГИС ЖКХ в разделе Регламенты и инструкции находится файл «Регламент и форматы информационного взаимодействия внешних информационных систем с ГИС ЖКХ». Текущая версия 10.0.1.2. В этом файле находятся wsdl и xsd файлы, мы их будем использовать для создания прокси-классов WCF.
Скопируем все wsdl и xsd файлы в какую-нибудь папку, например «c:/gis». Это нужно для того, чтобы утилита генерации могла найти все базовые xsd файлы, на которые использует xsd файл сервиса.
Для генерации прокси-классов мы будем использовать стандартную утилиту SvcUtil.
Эта команда создаст нам прокси-класс сервиса для получения общих справочников (hcs-nsi-common). В этой команде указано, где найти wsdl и xsd файл описания сервиса, куда положить результирующий cs файл и как назвать namespace. Аналогично нужно запустить эту команду для остальных сервисов ГИС ЖКХ.
Добавим сгенерированный прокси-класс в проект
Добавим в конфиг приложения следующие разделы
В конфиге мы регистрируем MessageInspectorBehavior, который добавляет ClientMessageInspector:
Ещё приведу листинг класса SignatureMessageInspector, который и занимается подписанием сообщений:
Для выполнения непосредственного запроса к ГИС ЖКХ необходимо создать экземпляр клиента сгенерированного прокси-класса, указать настройки клиентской авторизации, сформировать объект запроса и вызвать необходимый метод:
» Исходный код проекта
Как показывает практика, разработчики, которые выполняют интеграцию своих информационных систем с ГИС ЖКХ, очень много времени тратят на установление защищенного соединения и подписание сообщений. Несмотря на то, что разработчиками ГИС ЖКХ предоставлено демонстрационное приложение по подписанию сообщений, я опишу как можно решить эту задачу с помощью WCF. Хочу отметить, что в этом случае дополнительное ПО (типа stunnel) для установки защищенного соединения не нужно.
Надеюсь, эта статья поможет разработчикам, которые в будущем будут интегрироваться с ГИС ЖКХ.
К сожалению, без покупки дополнительного программного обеспечения не обойтись. Нам нужен КриптоПро .NET. Есть трёхмесячный бесплатный срок использования. Эта библиотека будет обеспечивать https соединение и подпись сообщений по алгоритму XAdES-BES. Также не забываем, что нужен сертификат квалифицированной электронной подписи.
Подготовка
Для начала нужно установить сертификат квалифицированной электронной подписи в личное хранилище локального компьютера. Он должен быть установлен вместе с закрытым ключом.
Подробно описывать процесс тут не буду, информацию можно найти тут и тут.
Генерация прокси-классов
На сайте ГИС ЖКХ в разделе Регламенты и инструкции находится файл «Регламент и форматы информационного взаимодействия внешних информационных систем с ГИС ЖКХ». Текущая версия 10.0.1.2. В этом файле находятся wsdl и xsd файлы, мы их будем использовать для создания прокси-классов WCF.
Скопируем все wsdl и xsd файлы в какую-нибудь папку, например «c:/gis». Это нужно для того, чтобы утилита генерации могла найти все базовые xsd файлы, на которые использует xsd файл сервиса.
Для генерации прокси-классов мы будем использовать стандартную утилиту SvcUtil.
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\SvcUtil.exe" c:/gis/hcs-nsi-common-service.wsdl c:/gis/*.xsd /messageContract /enableDataBinding /syncOnly /directory:"c:/gis/proxies" /noConfig /noLogo /out:NsiCommonService.cs /namespace:*,Gis.Infrastructure.NsiCommonService
Эта команда создаст нам прокси-класс сервиса для получения общих справочников (hcs-nsi-common). В этой команде указано, где найти wsdl и xsd файл описания сервиса, куда положить результирующий cs файл и как назвать namespace. Аналогично нужно запустить эту команду для остальных сервисов ГИС ЖКХ.
Добавим сгенерированный прокси-класс в проект
Настройка конфига приложения
Добавим в конфиг приложения следующие разделы
<configuration> <system.serviceModel> <extensions> <behaviorExtensions> <add name="MessageInspectorBehavior" type="Gis.Crypto.MessageInspectorBehaviorElement, Gis" /> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="clientCertificateConf"> <clientCredentials> <!-- в блоке findValue указывается серийный ключ сертификата --> <clientCertificate findValue="" storeLocation="LocalMachine" x509FindType="FindBySerialNumber" /> <serviceCertificate> <authentication certificateValidationMode="None" revocationMode="NoCheck" /> </serviceCertificate> </clientCredentials> <MessageInspectorBehavior /> </behavior> </endpointBehaviors> </behaviors> <bindings> <customBinding> <binding> <textMessageEncoding messageVersion="Soap11"> <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16348" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> </textMessageEncoding> <httpsTransport authenticationScheme="Basic" useDefaultWebProxy="false" requireClientCertificate="true" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" /> </binding> </customBinding> </bindings> <client> <endpoint address="https://api.dom.gosuslugi.ru/ext-bus-nsi-common-service/services/NsiCommon" binding="customBinding" behaviorConfiguration="clientCertificateConf" contract="Gis.Infrastructure.NsiCommonService.NsiPortsType" name="NsiCommonPort" /> </client> </system.serviceModel> </configuration>
Описание классов
В конфиге мы регистрируем MessageInspectorBehavior, который добавляет ClientMessageInspector:
public class MessageInspectorBehavior : IEndpointBehavior { public void Validate(ServiceEndpoint endpoint) { } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { SignatureMessageInspector inspector = new SignatureMessageInspector(); clientRuntime.MessageInspectors.Add(inspector); } }
Ещё приведу листинг класса SignatureMessageInspector, который и занимается подписанием сообщений:
public class SignatureMessageInspector : IClientMessageInspector { public object BeforeSendRequest(ref Message request, IClientChannel channel) { string st = GetSignElement(MessageString(ref request)); //place for log request request = CreateMessageFromString(st, request.Version); return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { string st = MessageString(ref reply); //place for log response reply = CreateMessageFromString(st, reply.Version); } public static string GetSignElement(string messageString) { var originalDoc = new XmlDocument { PreserveWhitespace = true }; originalDoc.LoadXml(messageString); var nodes = originalDoc.SelectNodes($"//node()[@Id='{CryptoConsts.CONTAINER_ID}']"); if (nodes == null || nodes.Count == 0) { return originalDoc.OuterXml; } var gostXadesBesService = new GostXadesBesService(); string st = gostXadesBesService.Sign(messageString, CryptoConsts.CONTAINER_ID, CryptoConsts.CERTIFICATE_THUMBPRINT, string.Empty); return st; } Message CreateMessageFromString(String xml, MessageVersion ver) { return Message.CreateMessage(XmlReaderFromString(xml), int.MaxValue, ver); } XmlReader XmlReaderFromString(String xml) { var stream = new MemoryStream(); // NOTE: don't use using(var writer ...){...} // because the end of the StreamWriter's using closes the Stream itself. // var writer = new StreamWriter(stream); writer.Write(xml); writer.Flush(); stream.Position = 0; return XmlReader.Create(stream); } String MessageString(ref Message m) { // copy the message into a working buffer. MessageBuffer mb = m.CreateBufferedCopy(int.MaxValue); // re-create the original message, because "copy" changes its state. m = mb.CreateMessage(); Stream s = new MemoryStream(); XmlWriter xw = XmlWriter.Create(s); mb.CreateMessage().WriteMessage(xw); xw.Flush(); s.Position = 0; byte[] bXml = new byte[s.Length]; s.Read(bXml, 0, (int) s.Length); // sometimes bXML[] starts with a BOM if (bXml[0] != (byte) '<') { return Encoding.UTF8.GetString(bXml, 3, bXml.Length - 3); } return Encoding.UTF8.GetString(bXml, 0, bXml.Length); } }
Использование
Для выполнения непосредственного запроса к ГИС ЖКХ необходимо создать экземпляр клиента сгенерированного прокси-класса, указать настройки клиентской авторизации, сформировать объект запроса и вызвать необходимый метод:
class Program { static void Main(string[] args) { var service = new NsiPortsTypeClient(); service.ClientCredentials.UserName.UserName = "lanit"; service.ClientCredentials.UserName.Password = "tv,n8!Ya"; var request = new exportNsiListRequest1 { ISRequestHeader = new HeaderType { Date = DateTime.Now, MessageGUID = Guid.NewGuid().ToString() }, exportNsiListRequest = new exportNsiListRequest { version = "10.0.1.2", ListGroup = ListGroup.NSI, ListGroupSpecified = true, Id = CryptoConsts.CONTAINER_ID } }; var result = service.exportNsiList(request); } }
» Исходный код проекта
