【實踐】WCF傳輸安全2:基於SSL的WCF匿名客戶端


這一篇我們利用上一篇制作的證書,來演示一個基於SSL的WCF服務,客戶端需要驗證服務器端的身份,服務器端不對客戶端進行任何驗證,即匿名客戶端。

一、項目結構

為了演示方便,把項目分成了6層,首先說明一下項目的結構:

程序集名稱 引用 簡單說明
Client1   控制台客戶端1,調用采用控制台自宿主的WCF
Client2   控制台客戶端2,調用采用IIS宿主的WCF
Host_Server

System.ServiceModel
LxContracts(項目中)
LxServices (項目中)

控制台服務端采用控制台宿主WCF
HostWeb_Server

System.ServiceModel
LxContracts(項目中)
LxServices (項目中)

空的ASP.NET 網站,只包含一個BookSrv.svc文件
LxContracts

System.ServiceModel
System.Runtime.Serialization

WCF 的數據契約和操作契約
LxServices LxContracts(項目中) WCF 服務實現
















二、代碼說明:

1、類庫LxContracts(包括數據契約Books.cs和操作契約IBookContract.cs 文件) 

Books.cs 代碼
using System.Runtime.Serialization;

namespace LxContracts
{
    [DataContract]
    public class Books
    {
        [DataMember]
        public int BookId { get; set; }

        [DataMember]
        public string BookName { get; set; }

        [DataMember]
        public float Price { get; set; }
    }
}
IBookContract.cs 代碼
using System.ServiceModel;
using System.Collections.Generic;

namespace LxContracts
{
    [ServiceContract]
    public interface IBookContract
    {
        [OperationContract]
        List<Books> GetAllBooks();
    }
}

 2、類庫LxServices(包括服務類BookService.cs 文件)

BookService.cs 代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LxContracts;

namespace LxServices
{
    public class BookService:IBookContract
    {
        public List<Books> GetAllBooks()
        {
            List<Books> listBooks = new List<Books>();
            listBooks.Add(new Books() { BookId = 1, BookName = "讀者", Price = 4.8f });
            listBooks.Add(new Books() { BookId = 2, BookName = "青年文摘", Price = 4.5f });
            return listBooks;
        }
    }
}

3、Host_Server (控制台宿主)

我們的服務代碼已經寫好了,現在我們對該服務進行一下宿主,首先采用控制台宿主,為解決方案添加一個Host_Server 的控制台程序,我們服務的綁定方式采用wsHttpBinding 方式。並修改其App.config 文件,代碼如下:

宿主程序 App.config 代碼
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="LxBehavior">
          <!--不提供通過瀏覽器輸入https訪問元數據的方式-->
          <serviceMetadata httpsGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="false" />
          <!--服務器端提供證書-->
          <serviceCredentials>
            <serviceCertificate storeName="My" x509FindType="FindBySubjectName" findValue="Lx-PC" storeLocation="LocalMachine"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <wsHttpBinding>
        <binding name="LxWsHttpBinding">
          <security mode="Transport">
            <!--采用傳輸安全,客戶端憑證=none(匿名客戶端)-->
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <services>
      <service name="LxServices.BookService" behaviorConfiguration="LxBehavior">
        <endpoint address="BookService" binding="wsHttpBinding" bindingConfiguration="LxWsHttpBinding" contract="LxContracts.IBookContract" />
        <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <!--基地址是https-->
            <add baseAddress="https://Lx-PC:9000/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

和原來未基於SSL的WCF服務配置http 變為了https,至於基地址為什么寫 計算機名這個原因,我們下面會進行解釋。

宿主程序 Main函數代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using LxServices;

namespace Host_Server
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(BookService)))
            {
                host.Opened += delegate
                {
                    Console.WriteLine("服務已經啟動,按任意鍵終止服務!");
                };
                host.Open();
                Console.ReadKey();
                host.Close();
            }
        }
    }
}

我們生成一下Host_Server,右擊該項目選擇 “啟動新實例”,控制台宿主程序可以進行啟動,如下圖所示:

4、Client1:(控制台客戶端1 對 控制台宿主的WCF 進行調用)

我們來調用一下新建控制台應用程序Client1,並添加服務引用,輸入: https://lx-pc:9000/mex 之后,點擊 “發現” 按鈕(此時保證服務器端已經啟動),會找我們發布的服務,命名為:WCF.BookSrv, 選擇“高級“,集合類型選擇: System.Collections.Generic;點擊"確定",成功添加該服務引用。

這里需要解答一下上一篇做證書的時候為什么要將我們的證書 導入到 受信任人 或者 受信任的根證書頒發機構中,如果不這樣做的話,我們的證書就是不可信任的,在客戶端添加引用的時候,會有這樣一個提示 “頒發此安全證書的公司不是您信任的公司”

 

由於我們在制作證書的時候,進行了此步驟的操作,所以不會產生該問題。

我們編寫一下Client1客戶端代碼:

Client1 Main 函數代碼
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Security;
using Client1.WCF.BookSrv;

namespace Client1
{
    class Program
    {
        static void Main(string[] args)
        {
            WCF.BookSrv.BookContractClient proxyClient = new WCF.BookSrv.BookContractClient();

            List<Books> listBook = new List<Books>();
            listBook = proxyClient.GetAllBooks();
            Console.WriteLine("客戶端1 調用自宿主服務器端WCF");
            listBook.ForEach(b =>
            {
                Console.WriteLine("---------------");
                Console.WriteLine("圖書編號:{0}", b.BookId);
                Console.WriteLine("圖書名稱:《{0}》", b.BookName);
                Console.WriteLine("圖書價格:{0}¥", b.Price);
                Console.WriteLine("---------------");
            });
            Console.ReadKey();
        }
    }
}

客戶端 Client1 添加引用后,自動產生的App.config 代碼:

Client1 App.config 代碼
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IBookContract" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
                    allowCookies="false">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <reliableSession ordered="true" inactivityTimeout="00:10:00"
                        enabled="false" />
                    <security mode="Transport">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="Windows" negotiateServiceCredential="true" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://lx-pc:9000/BookService" binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_IBookContract" contract="WCF.BookSrv.IBookContract"
                name="WSHttpBinding_IBookContract" />
        </client>
    </system.serviceModel>
</configuration>

  好了,我們在啟動 服務器端的情況下,運行一下 我們的Client1 客戶端,我們會發現服務調用成功了,沒有任何的問題: 

5、利用IIS宿主發布該服務:

我們在項目中添加一個 命名為 “HostWeb_Server” 的Asp.net 空網站,引用項目中的“LxContracts程序集”和“LxServices 程序集”,並添加一個“BookSrv.svc” 文件,刪除“BookSrv.Svc.cs” 文件,右擊“BookSrv.svc”文件,選擇“查看標記”,將里面的內容修改為:

<%@ ServiceHost Language="C#" Service="LxServices.BookService" %>

並修改網站的webconfig 文件 為如下代碼,之后,生成我們的網站。

Web.Config 文件代碼
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="LxBehavior">
          <!--提供通過瀏覽器輸入https訪問元數據的方式-->
          <serviceMetadata httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
          <serviceCredentials>
            <serviceCertificate storeName="My" x509FindType="FindBySubjectName" findValue="Lx-PC" storeLocation="LocalMachine" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <wsHttpBinding>
        <binding name="LxWsHttpBinding">
          <security mode="Transport">
            <transport clientCredentialType="None" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <services>
      <service name="LxServices.BookService" behaviorConfiguration="LxBehavior">
        <endpoint address="" binding="wsHttpBinding" bindingConfiguration="LxWsHttpBinding" contract="LxContracts.IBookContract" />
      </service>
    </services>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <defaultDocument>
      <files>
        <remove value="index.html" />
        <remove value="default.aspx" />
        <remove value="iisstart.htm" />
        <remove value="index.htm" />
        <remove value="Default.asp" />
        <remove value="Default.htm" />
        <add value="BookSrv.svc" />
      </files>
    </defaultDocument>
  </system.webServer>
</configuration>

接下來,打開IIS,新建網站,命名為 LxWCFSSL,程序池選擇“ASP.NET v4.0”,物理路徑選擇我們解決方案中 HostWeb_Server 的目錄,綁定類型選擇 https,端口默認 433,證書選擇 Lx-PC,如下圖:

點擊“確定” 按鈕之后,點擊我們新建的網站,在SSL 設置中,選擇“要求SSL”,客戶端證書選擇 忽略,

這時候,我們就在IIS中部署好了我們的這個基於SSL的WCF站點。細心的人這時候可以發現,如果再次運行netsh 命令,結果如下:

是不是發現我們建立的證書又跟443端口進行了綁定了呢。

6、Client2 (控制台客戶端2 對 IIS宿主的WCF 進行調用)

我們在解決方案中添加 控制台程序Client2 對剛剛在IIS中部署的WCF進行調用。添加服務引用,我們輸入:https://127.0.0.1/BookSrv.svc,會彈出一個警告對話框;

我們先不去理他,點擊是,並完成添加服務引用。

我們仍然用Client1 中的客戶端代碼進行 服務的調用,這時候會發生一個異常,如圖:

這是為什么呢?這就是上一篇中 建立證書的時候,為什么要默認計算機名稱的問題。我們把證書加入到 可信任的頒發機構的時候 頒發者 是Lx-PC,因為127.0.0.1 和 Lx-PC 不匹配,所以無法建立信任關系,因此會產生該異常。那意思是不是就得寫計算機名稱,而不能寫 ip 了呢,其實我們這個是一個Win7 下的Demo,如果在局域網證書服務器發布的證書或者購買的第三方機構的證書,應該是不會有此問題的,一般來講,證書 的CN 應該是我們的 網站的域名 比如:http://wwww.xxx.com,兩者保持一致就不會產生這個問題了,這塊沒有實驗,只是個人的理解。

那怎么解決這個問題呢?有兩種方法:

1) 引用服務的時候 更改為:https://Lx-Pc/BookSrv.svc,然后添加引用,運行我們的客戶端,就會成功。

2) 這是網上找到的一個方法,就是 在驗證服務器證書回調事件增加一方法,讓它始終返回True,始終信任該證書。

我們在Client2 中增加一個ServerTrust的類,代碼如下:

ServerTrust.cs 代碼
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;

namespace Client2
{
    public static class ServerTrust
    {
        public static void TrustSrv()
        {
            System.Net.ServicePointManager.ServerCertificateValidationCallback -= ValidateServerCertificate;
            System.Net.ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
        }
        /// <summary>  
        /// 驗證服務器端證書的回調方法
        /// 無論如何都相信該證書
        /// </summary>        
        private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }
    }
}

 修改一下Client2 代碼: 

Cilent2 Main 函數代碼
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Security;
using Client2.WCF.BookSrv;

namespace Client2
{
    class Program
    {
        static void Main(string[] args)
        {

            WCF.BookSrv.BookContractClient proxyClient = new WCF.BookSrv.BookContractClient();
            //強制信任服務器端憑證
            ServerTrust.TrustSrv();
            List<Books> listBook = new List<Books>();
            listBook = proxyClient.GetAllBooks();
            Console.WriteLine("客戶端2 調用IIS宿主WCF");
            listBook.ForEach(b =>
            {
                Console.WriteLine("---------------");
                Console.WriteLine("圖書編號:{0}", b.BookId);
                Console.WriteLine("圖書名稱:《{0}》", b.BookName);
                Console.WriteLine("圖書價格:{0}¥", b.Price);
                Console.WriteLine("---------------");
            });
            Console.ReadKey();
        }
    }
}

  Client2 編譯后運行也會成功調用我們部署的服務。 

個人認為第二種方法不可取,如果這樣的話,客戶端對服務器端的認證就失去了意義,這僅僅是解決該DEMO 的一個方式。

至此,我們的這個Demo 結束,我們也應該對證書的配置和基於SSL的WCF服務有所了解了,下一篇將在此代碼基礎上對客戶端進行驗證。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM