У меня есть некоторый пример кода, который я написал, пытаясь заставить клиента WCF общаться с сервером WCF (не размещенным в IIS) в сценарии запрос / ответ. Но мне нужно сделать это, используя сертификаты для аутентификации вместо имени пользователя / пароля. Мой тест работает нормально, когда компьютеры имеют одинаковые имя пользователя и пароль, но не проходит, когда я изменяю пароли на другие.
Я пробовал несколько разных идей, и ни одна из них не сработала. Может кто-нибудь, пожалуйста, скажите мне, что я делаю неправильно.
Вот мой пример кода:
Библиотека ответов на запросы WCF (Service1.cs):
using System;
namespace Test2ServLib
{
public class Service1 : IService1
{
public CompositeTypeRsp GetDataUsingDataContract(CompositeTypeReq composite)
{
CompositeTypeRsp Rsp = new CompositeTypeRsp();
if (composite == null)
{
Rsp.Value = "Hello {null} from " + Environment.MachineName;
return Rsp;
}
Rsp.Value = "Hello " + composite.Value + " from " + Environment.MachineName;
return Rsp;
}
}
}
Контракт интерфейса (IService1.cs):
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace Test2ServLib
{
[ServiceContract]
public interface IService1
{
[OperationContract]
CompositeTypeRsp GetDataUsingDataContract(CompositeTypeReq composite);
}
[DataContract]
public class CompositeTypeReq
{
string stringValue = "Hello ";
[DataMember]
public string Value
{
get { return stringValue; }
set { stringValue = value; }
}
}
[DataContract]
public class CompositeTypeRsp
{
string stringValue = "Hello ";
[DataMember]
public string Value
{
get { return stringValue; }
set { stringValue = value; }
}
}
}
Тестовое приложение (может быть как сервером, так и клиентом, Hoster.cs)
using System;
using System.ServiceModel;
using System.Threading;
using System.Windows.Forms;
using Test2ServLib;
namespace Test2HostApp
{
public partial class Hoster : Form
{
public Hoster()
{
InitializeComponent();
textBox1.Text = Environment.MachineName;
}
private void StartServer_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(runHost);
}
private static bool bRunning = false;
private static object locker = new object();
private static string proto = "https";// <-- this line changes between tests
private void runHost(object state)
{
lock (locker)
{
if (bRunning)
{
MessageBox.Show("Host already running");
return;
}
bRunning = true;
}
Uri baseAddress = new Uri($"{proto}://localhost:2202/Test2ServLib/Service1/");
WSHttpBinding binding = new WSHttpBinding();
// binding.Security.Mode = SecurityMode.Transport;
// binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
try
{
using (ServiceHost host = new ServiceHost(typeof(Service1), baseAddress))
{
host.Open();
MessageBox.Show("Host started");
while (bRunning == true)
{
Thread.Sleep(100);
}
host.Close();
MessageBox.Show("Host stopped");
}
}
catch (Exception ex)
{
MessageBox.Show($"{ex}");
}
}
private void StopServer_Click(object sender, EventArgs e)
{
lock (locker)
{
bRunning = false;
Thread.Sleep(100); // wait for the host to close
}
}
private void ClientReq_Click(object sender, EventArgs e)
{
try
{
Uri baseAddress = new Uri($"{proto}://{textBox1.Text}:2202/Test2ServLib/Service1/");
EndpointAddress address = new EndpointAddress(baseAddress);
ChannelFactory<IService1> factory =
new ChannelFactory<IService1>("Test2ServLib.IService1", address);
var patientSvc = factory.CreateChannel();
var rsp = patientSvc.GetDataUsingDataContract(new CompositeTypeReq { Value = "me" } );
MessageBox.Show(rsp.Value);
}
catch(Exception ex)
{
MessageBox.Show($"error-{ex}");
}
}
}
}
Код конструктора:
namespace Test2HostApp
{
partial class Hoster
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnStart = new System.Windows.Forms.Button();
this.btnStop = new System.Windows.Forms.Button();
this.btnSend = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.btnStart.Location = new System.Drawing.Point(12, 12);
this.btnStart.Name = "btn_Start";
this.btnStart.Size = new System.Drawing.Size(75, 23);
this.btnStart.TabIndex = 0;
this.btnStart.Text = "Start";
this.btnStart.UseVisualStyleBackColor = true;
this.btnStart.Click += new System.EventHandler(this.StartServer_Click);
//
// button2
//
this.btnStop.Location = new System.Drawing.Point(12, 41);
this.btnStop.Name = "btn_Stop";
this.btnStop.Size = new System.Drawing.Size(75, 23);
this.btnStop.TabIndex = 1;
this.btnStop.Text = "Stop";
this.btnStop.UseVisualStyleBackColor = true;
this.btnStop.Click += new System.EventHandler(this.StopServer_Click);
//
// button3
//
this.btnSend.Location = new System.Drawing.Point(12, 70);
this.btnSend.Name = "btn_Send";
this.btnSend.Size = new System.Drawing.Size(75, 23);
this.btnSend.TabIndex = 2;
this.btnSend.Text = "Send";
this.btnSend.UseVisualStyleBackColor = true;
this.btnSend.Click += new System.EventHandler(this.ClientReq_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(93, 70);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 22);
this.textBox1.TabIndex = 3;
//
// Hoster
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(282, 255);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.btnSend);
this.Controls.Add(this.btnStop);
this.Controls.Add(this.btnStart);
this.Name = "Hoster";
this.Text = "Hoster";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnStart;
private System.Windows.Forms.Button btnStop;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.TextBox textBox1;
}
}
Мой первый тест, мой протокол был установлен на "net.tcp" (отмеченная строка в hoster.cs)
Мой файл app.config был следующим:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<system.serviceModel>
<services>
<service behaviorConfiguration="announcementBehavior"
name="Test2ServLib.Service1">
<endpoint binding="netTcpBinding"
bindingConfiguration="RequestReplyNetTcpBinding"
contract="Test2ServLib.IService1" />
<host>
</host>
</service>
</services>
<client>
<endpoint binding="netTcpBinding"
bindingConfiguration="StandardNetTcpBinding"
contract="Test2ServLib.IService1"
name="Test2ServLib.IService1"
behaviorConfiguration="LargeEndpointBehavior">
</endpoint>
</client>
<behaviors>
<serviceBehaviors>
<behavior name="announcementBehavior">
<!--The following behavior attribute is required to enable WCF serialization of large data sets -->
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceDiscovery>
<announcementEndpoints>
<endpoint kind="announcementEndpoint"
address="net.tcp://localhost:8005/Announcement"
binding="netTcpBinding"
bindingConfiguration="RequestReplyNetTcpBinding"/>
</announcementEndpoints>
</serviceDiscovery>
<serviceThrottling maxConcurrentCalls="1500"
maxConcurrentSessions="1500"
maxConcurrentInstances="1500"/>
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="PeerTrust"/>
</clientCertificate>
<serviceCertificate findValue="WCfServer"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
<behavior name="discoveryBehavior">
<serviceDiscovery>
</serviceDiscovery>
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="PeerTrust"/>
</clientCertificate>
<serviceCertificate findValue="WCfServer"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="LargeEndpointBehavior">
<!--The behavior is required to enable WCF deserialization of large data sets -->
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<clientCredentials>
<clientCertificate findValue="WcfClient"
x509FindType="FindBySubjectName"
storeLocation="CurrentUser"
storeName="My" />
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="StandardNetTcpBinding"
receiveTimeout="05:00:00"
openTimeout="00:00:59"
closeTimeout="00:00:59"
maxBufferPoolSize="524288"
maxBufferSize="25000000"
maxConnections="50"
maxReceivedMessageSize="25000000"
listenBacklog="1500"
sendTimeout="00:05:00">
<security>
<message clientCredentialType="Certificate"/>
</security>
<reliableSession ordered="false"
inactivityTimeout="00:01:00"
enabled="true" />
<readerQuotas maxDepth="2147483647"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647" />
</binding>
<binding name="RequestReplyNetTcpBinding"
receiveTimeout="05:00:00"
openTimeout="00:00:59"
closeTimeout="00:00:59"
maxBufferPoolSize="524288"
maxBufferSize="25000000"
maxConnections="50"
maxReceivedMessageSize="25000000"
sendTimeout="00:05:00"
listenBacklog="1500">
<reliableSession ordered="false"
inactivityTimeout="00:01:00"
enabled="true" />
<readerQuotas maxDepth="2147483647"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647" />
<security >
<message clientCredentialType="Certificate"/>
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
При этом я могу общаться от одной системы к другой, но обе системы должны иметь одинаковые имя пользователя и пароли. Если я изменю пароль в одной системе, я получу:
error-System.ServiceModel.Security.SecurityNegotiationException: сервер отклонил учетные данные клиента. ---> System.Security.Authentication.InvalidCredentialException: сервер отклонил учетные данные клиента. ---> System.ComponentModel.Win32Exception: попытка входа не удалась
Похоже, он все еще использует аутентификацию по имени пользователя и паролю. Так что я попробовал это с https. Я изменил протокол на «https» и изменил конфигурацию моего приложения следующим образом:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="CertificateWithTransport" messageEncoding="Mtom">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="Certificate" />
</security>
</binding>
<binding name="CertificateWithTransportService" messageEncoding="Mtom" >
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="Test2ServLib.Service1"
behaviorConfiguration="credentialConfigService" >
<endpoint address=""
binding="wsHttpBinding"
bindingConfiguration="CertificateWithTransportService"
contract="Test2ServLib.IService1" />
</service>
</services>
<client>
<endpoint address=""
behaviorConfiguration="credentialConfigurationClient"
binding="wsHttpBinding"
bindingConfiguration="CertificateWithTransport"
contract="Test2ServLib.IService1"
name="Test2ServLib.IService1" />
</client>
<behaviors>
<serviceBehaviors>
<behavior name="credentialConfigService">
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" />
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="credentialConfigurationClient">
<clientCredentials>
<clientCertificate findValue="WCfServer"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Теперь, с этим изменением я даже не могу заставить клиента общаться с сервером, когда они работают на одном компьютере с той же учетной записью, я получаю:
error-System.ServiceModel.CommunicationException: Произошла ошибка при отправке HTTP-запроса на https://vm -pjh-135-2c: 2202 / Test2ServLib / Service1 / . Это может быть связано с тем, что сертификат сервера не настроен должным образом с HTTP.SYS в случае HTTPS. Это также может быть вызвано несоответствием привязки безопасности между клиентом и сервером. ---> System.Net.WebException: основное соединение было закрыто: при отправке произошла непредвиденная ошибка. ---> System.IO.IOException: невозможно прочитать данные из транспортного соединения: существующее соединение было принудительно закрыто удаленным хостом. ---> System.Net.Sockets.SocketException: существующее соединение было принудительно закрыто удаленным хостом
Что мне нужно сделать, чтобы одно из этих решений работало (или оба)?