zoukankan      html  css  js  c++  java
  • 【实践】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服务有所了解了,下一篇将在此代码基础上对客户端进行验证。

  • 相关阅读:
    Autofac小例子
    Spring自带mock测试Controller
    [转载]转Java 几个memcached 连接客户端对比 选择
    danga的MemcachedClient的几个缺陷
    linux查看memcached状态
    Linux下配置安装PHP环境(Apache2)
    使用memcache.php监控memcached
    memcached运行情况监测
    memcached监控的几种方法(nagios等)
    xmemcached使用的几点优化建议
  • 原文地址:https://www.cnblogs.com/lxblog/p/2683514.html
Copyright © 2011-2022 走看看