這一篇我們利用上一篇制作的證書,來演示一個基於SSL的WCF服務,客戶端需要驗證服務器端的身份,服務器端不對客戶端進行任何驗證,即匿名客戶端。
一、項目結構
為了演示方便,把項目分成了6層,首先說明一下項目的結構:
程序集名稱 | 引用 | 簡單說明 |
Client1 | 控制台客戶端1,調用采用控制台自宿主的WCF | |
Client2 | 控制台客戶端2,調用采用IIS宿主的WCF | |
Host_Server | System.ServiceModel |
控制台服務端采用控制台宿主WCF |
HostWeb_Server | System.ServiceModel |
空的ASP.NET 網站,只包含一個BookSrv.svc文件 |
LxContracts | System.ServiceModel |
WCF 的數據契約和操作契約 |
LxServices | LxContracts(項目中) | WCF 服務實現 |
二、代碼說明:
1、類庫LxContracts(包括數據契約Books.cs和操作契約IBookContract.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; } } }

using System.ServiceModel; using System.Collections.Generic; namespace LxContracts { [ServiceContract] public interface IBookContract { [OperationContract] List<Books> GetAllBooks(); } }
2、類庫LxServices(包括服務類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 文件,代碼如下:

<?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,至於基地址為什么寫 計算機名這個原因,我們下面會進行解釋。

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客戶端代碼:

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 代碼:

<?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 文件 為如下代碼,之后,生成我們的網站。

<?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的類,代碼如下:

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 代碼:

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服務有所了解了,下一篇將在此代碼基礎上對客戶端進行驗證。