这一篇我们利用上一篇制作的证书,来演示一个基于SSL的WCF服务,客户端需要验证服务器端的身份,服务器端不对客户端进行任何验证,即匿名客户端。
一、项目结构
为了演示方便,把项目分成了6层,首先说明一下项目的结构:
程序集名称 | 引用 | 简单说明 |
Client1 | 控制台客户端1,调用采用控制台自宿主的WCF | |
Client2 | 控制台客户端2,调用采用IIS宿主的WCF | |
Host_Server | System.ServiceModelLxContracts(项目中) LxServices (项目中) | 控制台服务端采用控制台宿主WCF |
HostWeb_Server | System.ServiceModelLxContracts(项目中) LxServices (项目中) | 空的ASP.NET 网站,只包含一个BookSrv.svc文件 |
LxContracts | System.ServiceModelSystem.Runtime.Serialization | 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] ListGetAllBooks(); }}
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 ListGetAllBooks() { List listBooks = new List (); 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 文件,代码如下:
和原来未基于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,并添加服务引用,输入: 之后,点击 “发现” 按钮(此时保证服务器端已经启动),会找我们发布的服务,命名为: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(); ListlistBook = new List (); 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 客户端,我们会发现服务调用成功了,没有任何的问题:
5、利用IIS宿主发布该服务:
我们在项目中添加一个 命名为 “HostWeb_Server” 的Asp.net 空网站,引用项目中的“LxContracts程序集”和“LxServices 程序集”,并添加一个“BookSrv.svc” 文件,删除“BookSrv.Svc.cs” 文件,右击“BookSrv.svc”文件,选择“查看标记”,将里面的内容修改为:
<%@ ServiceHost Language="C#" Service="LxServices.BookService" %>
并修改网站的webconfig 文件 为如下代码,之后,生成我们的网站。
接下来,打开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进行调用。添加服务引用,我们输入:,会弹出一个警告对话框;
我们先不去理他,点击是,并完成添加服务引用。
我们仍然用Client1 中的客户端代码进行 服务的调用,这时候会发生一个异常,如图:
这是为什么呢?这就是上一篇中 建立证书的时候,为什么要默认计算机名称的问题。我们把证书加入到 可信任的颁发机构的时候 颁发者 是Lx-PC,因为127.0.0.1 和 Lx-PC 不匹配,所以无法建立信任关系,因此会产生该异常。那意思是不是就得写计算机名称,而不能写 ip 了呢,其实我们这个是一个Win7 下的Demo,如果在局域网证书服务器发布的证书或者购买的第三方机构的证书,应该是不会有此问题的,一般来讲,证书 的CN 应该是我们的 网站的域名 比如:,两者保持一致就不会产生这个问题了,这块没有实验,只是个人的理解。
那怎么解决这个问题呢?有两种方法:
1) 引用服务的时候 更改为:,然后添加引用,运行我们的客户端,就会成功。
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; } ////// 验证服务器端证书的回调方法 /// 无论如何都相信该证书 /// 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(); ListlistBook = new List (); 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服务有所了解了,下一篇将在此代码基础上对客户端进行验证。