WCF使用证书进行认证以跨越计算机边界

时间:2018-09-10 16:15:22

标签: c# wcf-security

我已经编写了一些示例代码,试图在请求/回复情况下使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:向https://vm-pjh-135-2c:2202/Test2ServLib/Service1/发出HTTP请求时发生错误。这可能是由于在HTTPS情况下未使用HTTP.SYS正确配置服务器证书。这也可能是由客户端和服务器之间的安全绑定不匹配引起的。 ---> System.Net.WebException:基础连接已关闭:发送时发生意外错误。 ---> System.IO.IOException:无法从传输连接中读取数据:现有连接被远程主机强行关闭。 ---> System.Net.Sockets.SocketException:现有的连接被远程主机强行关闭

要使这两个解决方案中的一个(或两个)都起作用,我需要做什么?

0 个答案:

没有答案