• [No0000125]WCF安全体系


    WCF的安全体系主要包括三个方面:传输安全(Transfer Security)、授权或者访问控制(Authorization OR Access Control)以及审核(Auditing)。而传输安全又包括两个方面:认证(Authentication)和消息保护(Message Protection)。认证帮助客户端或者服务确认对方的真实身份,而消息保护则通过签名和加密实现消息的一致性和机密性。WCF采用两种不同的机制来解决这三个涉及到传输安全的问题,我们一般将它们称为不同的安全模式,即Transport安全模式和Message安全模式。

    目录 
    一、Transport安全模式 
                 1.1 TLS、SSL和HTTPS 
                 1.2 Transport安全模型的优缺点 
    二、Message安全模式 
              2.1 WS-Security 
              2.2 WS-Trust 
              2.3 WS-SecurreConversation 
              2.4 WS-SecurityPolicy 
              2.5 Message安全模式的优缺点 
    三、Mixed安全模式

    注:由于英文中的Transfer Security和Transport Security都可以翻译成传输安全,以示区别,我用中文的“传输安全”表示涉及到认证、消息一致性和机密性的Transfer Security,而采用“Transport安全(模式)”表示基于某种网络协议的传输层安全(模式),与“Transport安全(模式)”相对的是“Message安全(模式)”。

    一、Transport安全模式

    顾名思义,Transport安全模式就是利用基于传输层协议的安全机制解决传输安全涉及的三个问题,即认证、消息一致性和进行性。而TLS/SSL是实现Transport安全最常用(并非唯一)的方式。

    TLS、SSL和HTTPS

    任何一本介绍WCF的书,在介绍Transport安全模式的时候,必然会提到SSL或者HTTPS,有时还会提到TLS。有一些人不太明白这三者到底有什么区别,尤其是不能很好的区分TLS和SSL的差别。在这里,我们就先来简单介绍一下这三个相关的概念。

    SSL(Secure Sockets Layer)最初是由Netscape公司开发的一种安全协议,应用于Netscape浏览器以解决与Web服务器之间的安全传输问题。SSL先后经历了三个主要的版本,即1.0、2.0和3.0。之后SSL被IETF (Internet Engineering Task Force)接管,正是根名为TLS(Transport Layer Security)。可以这么说,SSL是TLS的前身,TLS 1.0相当于SSL 3.1。

    TLS/SSL本身是和具体的网络传输协议无关的,既可以用于HTTP,也可以用于TCP。而HTTPS(Hypertext Transfer Protocol Secure)则是将HTTP和TLS/SSL两者结合起来。在一般情况下,HTTPS通常采用443端口进行通信。对于WCF来说,所以基于HTTP协议的绑定的Transport安全都是通过HTTPS来实现的。而NetTcpBinding和NetNamedPipeBinding也提供了对TLS/SSL的支持,一般我们将TLS/SSL在TCP上的应用称为SSL Over TCP。

    TLS/SSL帮助我们解决两个问题:客户端对服务端的验证,以及通过对传输层传输的数据段(Segment)进行加密确保消息的机密性。出于性能的考虑,这里采用的是对称加密而不是非对称加密。接下来,我从消息交换的角度来说明上述的两个问题是如何通过TLS/SSL解决的。

    我们以访问一个HTTPS站点为例。当客户端和这个HTTPS站点所在的Web服务器进行正式的访问请求之前,在它们之间必须建立了安全的HTTP连接。而这样一个安全的连接的创建通过客户端和Web站点之间的多次握手或者协商(Negotiation)来完成。如下图示,整个协商过程主要包括三个步骤。image

    步骤一:客户端向HTTPS站点发送协商请求,该请求中包括客户端所能够支持的加密算法列表;

    步骤二:HTTPS站点从加密算法列表中选择自己支持的并且安全级别最高的算法(有时候站点也可能综合考虑性能和安全两者之间的平衡,从中选择一个“最佳”的加密算法),连同绑定到该站点的数字证书(所有HTTPS站点在部署的时候都会绑定一个X.509证书)一并发送给客户端;

    步骤三:客户端接收到服务端发回的数字证书之后,通过验证证书进而确定服务身份。在验证成功的情况下,客户端会生成一个随机随机数,作为会话密钥(Session Key),缓存在客户端。客户端随后并采用服务端发回的加密算法,利用从证书中提取的公钥进行加密。加密后的会话密钥被发送给服务端,服务端使用自己的私钥采用相对应的算法进行机密得到该会话密钥。至此,客户端和服务端具有一个只有它们彼此知晓的会话密钥,所有的请求和回复消息均通过该会化密钥进行加密和解密。

    有人可能会说,客户端为何不直接用从数字证书提取的公钥对所有的请求消息进行加密,服务端采用私钥进行解密。之所以选择对称加密而不是非对称加密,主要有两方面的原因:

    • 对称加密/解密比非对称加密/解密需要更少的计算,所以具有更好的性能;
    • 上述的加密方式只能确保客户端向服务端请求消息的机密性,而不能保证服务端向客户端回复消息的机密性。

    Transport安全模型的优缺点

    较之我们后续介绍的Message安全模式,Transport安全模式具有一个最到的优点,那就是高性能。虽然TLS/SSL在正式进行消息交换之前需要通过协商建立一个安全的连接,但是这个协商过程完全通过传输层协议来完成。而且这种安全模式还可以充分利用网络适配器的硬件加速,这样就可以介绍CPU时间,进而提供性能。但是,受限于传输层安全协议的特点,Transport安全模式也具有一些不可避免的局限性:

    • Transport安全模式依赖于具体的传输协议;
    • 它只能提供基于点对点(Point-to-Point)的安全传输保障,即客户端之间连接服务的场景。如果在客户端的服务端之间的网络需要一些用于消息路由的中间结点,Transport安全模式则没有了用武之地。
    • 在Transport安全模式下,意味着我们不得不在传输层而不能在应用层解决对客户端的认证,这就决定了可供选择的认证方式不如Message模式多。

    也正是由于上述的这些局限(主要还是只能提供点对点的安全传输保障),决定了Intranet是Transport安全模式主要的应用环境。为了克服这些局限,我们需要一种与传输协议无关的、能够提供端到端(End-to-End)安全传输保障的、具有多种认证解决方案的安全模式,那就是Message安全模式。

    二、Message安全模式

    Transport安全模式将安全传输策略应用到传输层的数据段,进而间接地实现基于消息的安全传输。而Message模式则直接将安全策略的目标对象对准消息本身,通过对消息进行签名、加密实现消息安全传输。所以Message安全模式不会因底层是HTTP或者TCP传输协议而采用不同的安全机制,并且能够提供从消息最初发送端到最终接收端之间的安全传输,即端到端(End-To-End)安全传输。Message模式下的安全协议是一种应用层协议,我们可以在应用层上实现对客户端的验证,因而具有更多的认证解决方案的选择。

    此外, WCF的Message安全模式并不是微软在Windows平台下的闭门造车,而是遵循一系列开放的标准或者规范,那就是围绕着WS-Security的四个WS-*规范,即WS-Trust、WS-Secure Conversation和WS-Security Policy。这就意味着WCF的Message安全具有很好的互操作性或平台无关性。

    WS-Security

    WS-Security由是由结构化信息标准促进组织(OASIS:Organization for the Advancement of Structured Information Standards)制定。对于本书的读者来说,我相信你应该不会对OASIS这个组织感到陌生了吧。不仅仅是WS-Security,包括我们接下来要介绍的WS-Trust、WS-Secure Conversation和WS-Security Policy,它们的制定者也是这个标准组织。

    WS-Security,有时候又被简称为WSS,制定了一整套标准的基于SOAP(包括SOAP 1.1和SOAP 1.2)的扩展以帮助创建一个安全的Web服务。到目前为止,OASIS推出了两个版本,第一个正式的版本于2004年3月发布,即WS-Security 1.0,有时候被称为WS-Security 2004。2006年2月,OASIS发布了WS-Security 1.1。

    定义在WS-Security中的SOAP的安全机制可以广泛地应用于现有的多种安全模式下,比如PKI、Kerberos和SSL等。WS-Security支持多种安全令牌(Security Token)格式(比如用户名/密码、SAML、X509证书和Kerberos票据等)、多种签名格式和加密技术。针对具体的安全令牌,WS-Security通过对应的Profile文档进行单独定义。

    WS-Security提供了关于SOAP安全交换的三个主要机制:如何将安全令牌作为消息的一部分进行传输,如何检测接收到的消息是否和原始发送的一致,以及如何确保消息的真实内容仅对真正的接收者可见。安全令牌的传输主要解决身份认证的问题,所以这三个方面就是传输安全面对的三个问题:身份认证、消息一致性和消息机密性。WS-Security提供了一个抽象的消息安全模型。在这个安全模型中,通过安全令牌,结合数字签名和加密技术实现对消息交换实体的认证和对消息本身的保护。

    WS-Trust

    WS-Trust通过定义了一系列SOAP扩展,旨在消息交换相关方之间建立一个信任的关系(Trust Relationship)。在绝大部分场景下,这里所指的信任关系是双向而非单向的。站在消息交换的角度来讲,信任关系不仅仅包括消息接收者对请求者的信任,也包括请求者对接收者的信任。要建立起彼此之间的信任关系,一个前提能够互相验证对方的真实身份,所以这里也就涉及到一个双向验证的问题。

    在Web服务的世界中,消息交换为通信的唯一手段,那么相关方之间的信任关系的建立也只能围绕着消息交换来实现。定义在WS-Trust中的Web服务的信任模型基于这样的处理机制:Web服务要求接收的消息中包含有能够证明所需申明(包括身份、权限或者能力等)。如果Web服务接收到的消息不具有这些申明证明信息,它可以选择忽略或者拒绝该消息。

    在这里,这些证明信息通过安全令牌(Security Token)的方式存在。实际上WS-Trust为我们提供了一种大体上的消息交换机制以实现安全令牌的颁发(Issuance)、续订(Renewal)和终止(Cancel)等。至于具体采用的安全消息交换形式,则借助于WS-Security,所以WS-Trust实际上是建立在WS-Trust之上的。WS-Trust具有两个主要的版本,即WS-Trust 1.3和WS-Trust 1.4,它们发布的时间分别是2005年和2008年。这两个版本的WS-Trust对应的命名空间URI分别为http://docs.oasis-open.org/ws-sx/ws-trust/200512和http://docs.oasis-open.org/ws-sx/ws-trust/200802。以下的内容主要基于WS-Trust 1.4。

    WS-SecurreConversation

    通过上面的介绍,我们知道安全传输旨在解决两个方面的问题,即身份认证和消息保护(消息的一致性和机密性)。我们假设这样一个应用场景:客户端和服务分别采用用户名/密码和X.509证书作为各自的用户凭证,那么针对于每一个单一的消息交换,可以通过下面的方式解决上述两个问题:

    • 客户端采用服务端证书的公钥对消息进行加密,服务端在接收到消息的时候通过自己的私钥进行解密;
    • 客户端每次服务调用均附加一个基于用户名/密码的安全令牌,服务端提取它对用以验证访问者的身份。

    这好像是一个“完美”的解决方案,但是不知道你是否考虑过这样一个问题:如果客户端和服务端在一段时间内需要进行频繁的通信,那么性能问题就产生了。影响性能的因素主要包括:服务端需要对客户端进行频繁的认证;频繁地进行非对称加密/解密。

    我们更加希望的是实现这样的安全传输机制:客户端和服务端在进行正式的消息交换之前,在它们之间通过彼此的认证,并建立起一个安全的上下文环境,或者说一个安全的会话。在这个上下文中,服务端无需对客户端进行重复的认证。此外,一个仅在当前上下文中被双方共享的密钥被创建出来,采用对称加密技术对消息进行签名和加密。

    这个机制基本类似于TLS/SSL,不过TLS/SSL只是在传输层针对于数据段提供安全传输保障,而我们现在介绍的则是针对于SOAP消息的安全传输。由于这个机制主要为交互双方在同一个上下文环境中的多次消息交换提供安全传输的保障,我们将其称为Secure Conversation,OASIS为此制定了相应的规范,也就是我们本节要介绍的WS-SecureConversation。而WCF通过Secure Session机制提供对WS-SecureConversation的实现。

    WS-SecureConversation具有两个主要的版本,即1.3和1.4,分别在2007和2009年发布,以下的内容主要针对WS-SecureConversation 1.4。WS-SecureConversation建立在WS-Security和WS-Trust基础之上,旨在提供一种机制对实现对安全上下文的创建和整个生命周期的控制。

    我们目前提到的安全上下文(Security Context)仅仅是一种概念上的描述,而在真正的消息交换中,它通过一个具体的安全上下文令牌(SCT:Security Context Token)来表示。安全上下文令牌属于一种特殊的安全令牌,而WS-Trust为我们定义了一套完整的安全令牌传播机制。所以WS-SecureConversation完全借助于定义在WS-Trust的消息交换方式来完成针对安全上下文令牌的颁发(Issurance)、修复(Amending)、续订(Renewal)和注销(Cancel)。

    WS-SecurityPolicy

    一个Web服务(这里指广义的、与技术平台无关的Web服务)除了实现通过服务契约定义的业务功能之外,为了实现一些额外的功能(比如安全、事务和可靠传输等),还需要具有一些与业务无关的行为(Behavior)和能力(Capability),我们可以将这些统称为Web服务的策略(Policy)。WS-Policy提供了一个基于XML的框架模型和语法用于描述Web服务的能力、要求和行为属性。关于WS-Policy,在本书第二章有相应的介绍。

    而这里介绍的WS-SecurityPolicy则建立在WS-Policy基础上,定义了一系列关于安全传输的策略断言(Policy Assertion)。而这些策略断言最终被应用在WS-Security、WS-Trust和WS-SecureConversation中。WS-SecurityPolicy具有两个主要的版本,即WS-SecurityPolicy1.2和WS-SecurityPolicy1.3,发布时间分别为2007年和2009年。

    到此为止,我们已经介绍了WS-*体系中关于安全的4个重要的规范,WS-Security、WS-Trust、WS-SecureConversation和WS-SecurityPolicy。而WCF的消息安全模式是这四个WS-*规范的实现者。如果你想深刻地理解WCF的安全体系,对这四个安全规范的了解是必须的,这也是我为何要花这么的篇幅来介绍它们的原因。

    但是处于篇幅的限制,这里对仅仅提供对WS-Security、WS-Trust、WS-Secure Conversation和WS-Security Policy大体上的介绍。如果读者对此有兴趣,可以通过下面的提供的地址下载下载官方文档。

    WS-Security 1.0:http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0.pdf 
    WS-Security 1.1(核心规范):http://www.oasis-open.org/committees/download.php/16790/wss-v1.1-spec-os-SOAPMessageSecurity.pdf 
    WS-Trust 1.3:http://docs.oasis-open.org/ws-sx/ws-trust/200512/ws-trust-1.3-os.pdf 
    WS-Trust 1.4:http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/os/ws-trust-1.4-spec-os.pdf 
    WS-SecureConversation 1.3:http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/ws-secureconversation-1.3-os.pdf 
    WS-SecureConversation 1.4:http://docs.oasis-open.org/ws-sx/ws-secureconversation/v1.4/os/ws-secureconversation-1.4-spec-os.pdf 
    WS-SecurityPolicy 1.2:http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/ws-securitypolicy-1.2-spec-os.pdf 
    WS-SecurityPolicy 1.3:http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/ws-securitypolicy-1.2-spec-os.pdf

    Message安全模式的优缺点

    关于WCF的Message安全模式的优点,实际上在前面已经有所提及,在这里做一个总结。总的来说,较之Transport安全模式,Message安全模式具有如下的优点:

    • 由于Message安全模式是通过在应用层通过对消息实施加密、签名等安全机制实现的,所以这是一种于具体传输协议无关的安全机制,不会因底层采用的是TCP或者HTTP而有所不同。较之Transport安全,这种基于应用层实现的安全机制在认证方式上具有更多的选择;
    • 由于Message安全模式下各种安全机制都是直接应用在消息(SOAP)级别的,无论消息路由的路径有多复杂,都能够保证消息的安全传输。所以,不同于Transport安全模式只能提供点对点(Point-to-Point)的安全,Message安全模式能够提供端到端(End-to-End)安全;
    • 由于Message安全模式是对WS-Security、WS-Trust、WS-SecureConversation和WS-SecurityPolicy这四个WS-*规范的实现,所有具有很好的互操作性,能够提供跨平台的支持。

    但是Transport安全模式有一点是Message安全模式不能比的,那就是性能。Transport安全模式能够借助于网络适配器硬件加速,降低CPU计算时间,从而提供高效的传输安全。Message模式在性能上稍逊一筹。

    三、复合安全模式(Mixed Security Mode)

    由于WCF的两种安全模式,即Transport和Message安全模式,都具有各自的优缺点,我们通过两者的结合构成一种混合的安全传输解决方案。我们称之为混合(Mixed)的安全模式。那么,这种新的安全模式是如何对Transport和Message安全模式进行“混合”呢?

    我们知道,安全传输旨在解决三个问题:认证、消息一致性和机密性,而认证既包括服务端对客户端的认证,也包括客户端对服务端的认证。对于混合安全模式,消息的一致性、机密性和客户端对服务端的认证通过Transport安全模式来实现,而采用Message安全模式实现服务端对客户端的认证。

    混合(以下简称Mixed)安全模式充分利用了Transport安全模式硬件加速优势,以提供高性能和具有高吞吐量的服务。由于服务端对客户端的验证是通过Message安全模式来实现的,所以我们具有更多关于客户端安全凭证和认证方式的选择。此外,由于Transport安全模式不可回避的极限性,混合安全模式也只能提供点到点的安全。

    在接下来的系列文章中我们正是讨论关于身份认证的主题。在前面我们已经谈到了,WCF中的认证属于“双向认证”,既包括服务对客户端的认证(以下简称客户端认证),也包括客户端对服务的认证(以下简称服务认证)。客户端认证和服务认证从本质上并没有什么不同,无非都是被认证一方提供相应的用户凭证供对方对自己的身份进行验证。我们先来讨论服务认证,客户端认证放在后续的文章中。

    我们对TLS/SSL进行了简单的介绍。我们知道,客户端和服务在为建立安全上下文而进行的协商过程中会验证服务端的X.509证书如否值得信任。对于服务证书的验证实际上可以看成是一种服务认证,或者说TLS/SSL对证书的验证可以看成是WCF服务认证的一个环节。

    目录 
    TLS/SSL与X.509证书 
    创建基于TLS/SSL的WCF服务 
        创建X.509证书 
        服务寄宿  
        服务调用 
        改变证书认证模式

    一、TLS/SSL与X.509证书

    TLS/SSL是实现Transport安全模式的一种主要的方式,但不是唯一方式。对于所有基于HTTP的绑定(主要指BasicHttpBinding、WSHttpBinding和WS2007HttpBinding,而WSDualHttpBinding不支持Transport安全模式),如果选择了Transport或者Mixed安全模式,不论采用怎样的认证方式,底层的实现总是基于TLS/SSL(HTTPS)。

    而对于NetTcpBinding来说,如果采用Transport安全模式,并且采用非Windows认证(客户端凭证类型选择None或者Certificate),最终的传输安全的实现也是基于TLS/SSL(SSL Over TCP)。如果选择Mixed安全模式,不论选择怎样的客户端凭证类型,WCF最终都会采用TLS/SSL来提供对传输安全的实现。也正是因为如此,在这两种情况下,你总是需要选择一个X.509证书作为服务的凭证。举个例子,对于如下的配置,终结点采用NetTcpBinding绑定,并且选择Transport安全模式,但是却采用匿名的认证方式(客户端凭证类型为None)。

    <system.serviceModel>
       <bindings>
         <netTcpBinding>
           <binding name="transportTcpBinding">
             <security mode="Transport">
               <transport clientCredentialType="None"/>
             </security>
           </binding>
         </netTcpBinding>      
       </bindings>
       <services>
         <service name="Artech.WcfServices.Services.CalculatorService">
           <endpoint address="net.tcp://127.0.0.1/calculatorservice" 
     binding="netTcpBinding" bindingConfiguration="transportTcpBinding" 
     contract="Artech.WcfServices.Contracts.ICalculator" />
         </service>
       </services>
     </system.serviceModel>

    在对服务进行寄宿时,会抛出如下图所示的InvalidOperationException异常,提示“未提供服务证书。请在 ServiceCredentials 中指定服务证书”。

    image

    作为服务凭证的证书通过服务行为ServiceCredentials来指定,对于WCF的安全体系来说,ServiceCredentials是个非常重要的对象,在本章后续文章中我们将反复地使用到它。对于服务凭证的指定,需要使用到ServiceCredentials的只读属性ServiceCertificate,该属性对应的类型为X509CertificateRecipientServiceCredential。X509CertificateRecipientServiceCredential对象实际上是对一个X509Certificate2对象的封装,它定义了若干SetCertificate方法重载用以指定一个X.509证书作为服务的凭证。ServiceCredentials和X509CertificateRecipientServiceCredential的相关定义反映在如下所示的代码片断中。

    public class ServiceCredentials : SecurityCredentialsManager, IServiceBehavior
     {
         //其他成员
         public X509CertificateRecipientServiceCredential ServiceCertificate   { get; }
     }
     public sealed class X509CertificateRecipientServiceCredential
     {    
         //其他成员
         public void SetCertificate(string subjectName);
         public void SetCertificate(string subjectName, StoreLocation storeLocation, StoreName storeName);
         public void SetCertificate(StoreLocation storeLocation, StoreName storeName, X509FindType findType, object findValue);
      
         public X509Certificate2 Certificate { get; set; }
     }

    如果采用自我寄宿的方式,我们可以通过编程的方式来为寄宿的服务设置一个代表服务凭证的X.509证书。在下面给出的代码片断中,我们为服务设置一个主体名称为Jinnan-PC(我的机器名)的X.509证书,该证书是一个基于个人存储(Personal Store,通过StoreName.My表示)的本机(StoreLocation.LocalMachine)证书。

    using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
    {
        ServiceCredentials serviceCredentials = host.Description.Behaviors.Find<ServiceCredentials>();
        if (null == serviceCredentials)
        {
            serviceCredentials = new ServiceCredentials();
            host.Description.Behaviors.Add(serviceCredentials);
        }
        serviceCredentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "Jinnan-PC");
        host.Open();
        ...
    }

    当然,我们依旧推荐采用配置的方式进行服务凭证的设置。对于上面一段设置服务证书的代码,我们可以通过下面的一段配置来代替。

    <system.serviceModel>
       ...
       <services>
         <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="serviceCertificateBehavior">
           <endpoint address="net.tcp://127.0.0.1/calculatorservice" 
     binding="netTcpBinding" bindingConfiguration="transportTcpBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
         </service>
       </services>
       <behaviors>
         <serviceBehaviors>
           <behavior name="serviceCertificateBehavior">
             <serviceCredentials>
               <serviceCertificate storeLocation="LocalMachine" storeName="My"   
     x509FindType="FindBySubjectName" findValue="Jinnan-PC" />
             </serviceCredentials>
           </behavior>
         </serviceBehaviors>
       </behaviors>
     </system.serviceModel>

    对于采用基于TLS/SSL的Transport安全模式,对服务证书的验证方式会因为绑定类型的不同而具有小小的差异。

    二、创建基于TLS/SSL的WCF服务

    image接下来我们会通过一个简单的例子来演示如何在WCF服务中使用基于TLS/SSL的Transport安全。该实例会涉及两种不同的绑定类型(WS2007HttpBinding和NetTcpBinding)和寄宿方式(自我寄宿和IIS寄宿)。

    我们还是采用惯用的计算服务的例子,演示实例的解决方式具有右图所示的结构。Contract和Services为两个类库项目,分别用于定义服务契约和实现契约的服务类型。而Hosting和Client为两个控制台应用,前者用于进行服务寄宿(自我寄宿),后者用于模拟客户端程序。下面的代码片断代码了分别定义在Contracts和Services项目中的服务契约接口ICalculator和具体的服务类型CalculatorService。

    using System.ServiceModel;
    namespace Artech.WcfServices.Contracts
    {
        [ServiceContract(Namespace = "http://www.artech.com/")]
        public interface ICalculator
        {
            [OperationContract]
            double Add(double x, double y);
        }
    }
     
    using Artech.WcfServices.Contracts;
    namespace Artech.WcfServices.Services
    {
        public class CalculatorService : ICalculator
        {
            public double Add(double x, double y)
            {
                return x + y;
            }
        }
    } 

    创建X.509证书

    由于TLS/SSL需要通过协商的方式生成一个用于消息签名和加密的会话密钥,而会话密钥的交换依赖一个X.509证书以确保安全。所以我们首要的任务是需要得到一个X.509证书,这样一个证书可以直接借助于MakeCert工具,通过命令行的方式创建一个主体名称为Jinnan-PC(我个人的机器名,你需要替换成你本机的名称)的证书。为了方便,我们在测试的时候倾向于创建自签名证书,即证书授予者和颁发者身份合二为一。不过为了演示证书正常的信任链,我们不采用这种方式。所以我们需要通过运行如下的命令行先创建一个CA证书。该CA证书本身是自签名的(对应于-r命令行开关)

       1: Makecert -n "CN=RootCA" -r -sv C:RootCA.pvk C:RootCA.cer

    上面的命令行在执行的过程中,会弹出两个用于输入密码的对话框。你需要输入相应的密码用以包括生成的两个文件,一个是包含私钥的文件RootCA.pvk,另一个是证书文件RootCA.cer,它们都保存在C盘根目录下。

    然后通过如下的命令行创建一个主题名称为Jinnan-PC(我的机器名,你需要换成你的机器名或者本机影射的Host Name)的证书,并以上面创建证书对应的CA(RootCA)作为该证书的颁发者(-ic C:RootCA.cer -iv C:RootCA.pvk)。该证书最终自动保存到本机(-sr LocalMachine)的个人存储区(-ss My)。而-pe表示证书的私钥可以被导出。-sky表示密钥的类型或者作用,具有两个选项signature和exchange,前者用于数字签名,后者用于加密和密钥交换,这里选用exchange。

       1: Makecert -n "CN=Jinnan-PC" -ic C:RootCA.cer -iv C:RootCA.pvk -sr LocalMachine -ss My -pe -sky exchange

    服务寄宿

    我们先使用NetTcpBinding作为绑定,在Hosting项目中定义如下的配置。从配置中我们可以看到:寄宿的CalculatorService服务唯一的终结点使用了Transport模式的NetTcpBinding绑定。该绑定的客户端凭证类型为None,意味着接受匿名客户端。通过命令行生成和存储的X.509证书通过服务行为的方式被设置成寄宿服务的凭证。

    <?xml version="1.0" encoding="utf-8" ?>
     <configuration>
     <system.serviceModel>
       <bindings>
         <netTcpBinding>
           <binding name="transportTcpBinding">
             <security mode="Transport">
               <transport clientCredentialType="None"/>
             </security>
           </binding>
         </netTcpBinding>      
       </bindings>
       <services>
         <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="serviceCertificateBehavior">
           <endpoint address="net.tcp://Jinnan-PC/calculatorservice" 
     binding="netTcpBinding" bindingConfiguration="transportTcpBinding" 
     contract="Artech.WcfServices.Contracts.ICalculator" />
         </service>
       </services>
       <behaviors>
         <serviceBehaviors>
           <behavior name="serviceCertificateBehavior">
             <serviceCredentials>
               <serviceCertificate storeLocation="LocalMachine" storeName="My"  
     x509FindType="FindBySubjectName" findValue="Jinnan-PC" />
             </serviceCredentials>
           </behavior>
         </serviceBehaviors>
       </behaviors>
     </system.serviceModel>
     </configuration>

    通过上面的配置,我们创建的X.509证书通过ServiceCredentials服务行为被指定为服务的凭证。此外还有一点值得注意的是:终结点地址采用了没有使用localhost和127.0.0.1,而是直接使用了机器名(Jinnan-PC),至于为什么需要这么做,在后续的内容中你会找到答案。而对于寄宿服务的程序,我们力求简洁,在Main方法中仅仅包括如下的代码。

       using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
       {
           host.Open();
           Console.Read();
       }

    服务调用

    然后我们在Client项目中定义如下的客户端配置,用于进行服务调用的终结点的NetTcpBinding具有与服务端相同的配置。客户端通过如下一段简单的代码进行服务的调用。

    <?xml version="1.0" encoding="utf-8" ?>
     <configuration>
       <system.serviceModel>
         <bindings>
           <netTcpBinding>
             <binding name="transportTcpBinding">
               <security mode="Transport">
                 <transport clientCredentialType="None"/>
               </security>
             </binding>
           </netTcpBinding>
         </bindings>
         <client>
           <endpoint name="calculatorService" address="net.tcp://jinnan-PC/calculatorservice" 
     binding="netTcpBinding" bindingConfiguration="transportTcpBinding" 
     contract="Artech.WcfServices.Contracts.ICalculator" />
         </client>    
       </system.serviceModel>
     </configuration>

    服务调用程序:

     using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
      {
          ICalculator calculator = channelFactory.CreateChannel();
          Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2)); ;
      }
      Console.Read();

    完成所有编程和配置工作之后,我们先后启动Hosting和Client这两个控制台程序,你会发现服务并不能正常地调用,而是抛出如下图所示的SecurityNegotiationException异常,提示服务证书不受信任。

    image

    改变证书认证模式

    之所以会抛出这样的异常,原因在于:WCF默认采用ChainTrust的模式对服务证书进行验证。该认证模式要求服务证书的颁发机构链必须在客户端的“受信任根证书颁发机构(Trusted Root Certification Authorities)”。为了解决这个问题,我们具有如下两种“解决方案”:

    • 将服务证书的颁发机构纳入到受信任根证书颁发机构中。你可以通过MMC的证书管理单元的导出/入功能将颁发机构的证书(C:RootCA.cer)导入到受信任根证书颁发机构存储区中。但是不幸的是,由于CA证书是通过MakeCert.exe创建的,即使导入到受信任根证书颁发机构存储区,它也不能作为受信任的CA;
    • 通过System.ServiceModel.Description.ClientCredentials这个终结点行为改变默认的认证模式。

    ClientCredentials和之前提到的ServiceCredentials是两个相对的“行为”类型,前者是使用在客户端的终结点行为,后者则是使用在服务端的服务行为。在本章后续的内容中,我们还将不断的使用到它们。现在我们先看讨论一下如何通过ClientCredentials来改变客户端对服务证书的认证模式。

    首选你可以通过通过ChannelFactory的Credentials属性得到ClientCredentials对象。ClientCredential具有一个类型为X509CertificateRecipientClientCredential的ServiceCertificate只读属性表示服证书。证书的认证行为定义在X509CertificateRecipientClientCredential的Authentication只读属性中,该属性的类型为X509ServiceCertificateAuthentication。我们通过X509ServiceCertificateAuthentication的CertificateValidationMode属性设置相应的证书认证模式。关于服务证书认证模式涉及到的应用编程接口反映在如下所示的代码片断中。

    public abstract class ChannelFactory 
     {    
         //其他成员
         public ClientCredentials Credentials { get; }    
     }
     public class ClientCredentials : SecurityCredentialsManager, IEndpointBehavior
     {
         //其他成员
         public X509CertificateRecipientClientCredential ServiceCertificate { get; }
     }
      public sealed class X509CertificateRecipientClientCredential
     {    
         //其他成员
         public X509ServiceCertificateAuthentication Authentication { get; }
      }
      public class X509ServiceCertificateAuthentication
      {
          //其他成员
          public X509CertificateValidator CustomCertificateValidator { get; set; }
          public X509CertificateValidationMode CertificateValidationMode { get; set; }
      }

    证书认证模式通过枚举X509CertificateValidationMode表示,它具有如下五个选项:None、PeerTrust、ChainTrust、PeerOrChainTrust和Custom。选择None意味着无需认证,而ChainTrust则要求证书的颁发机构必须是“受信任根证书颁发机构”存储区,而PerTrust要求证书本身(不是CA证书)存在于“受信任的个人(Trusted People)”存储区。如果这些认证模式不能满足你的需求,你还可以选择Custom。在这种情况下,你需要通过继承抽象类X509CertificateValidator自定义验证规则,并将验证逻辑定义在抽象方法Validate中。最终将自定义的X509CertificateValidator赋给X509ServiceCertificateAuthentication的CustomCertificateValidator属性。X509CertificateValidationMode和X509CertificateValidator的定义如下。

     public enum X509CertificateValidationMode
      {
          None,
          PeerTrust,
          ChainTrust,
          PeerOrChainTrust,
          Custom
      }
     public abstract class X509CertificateValidator
     {
         //其他成员
         public abstract void Validate(X509Certificate2 certificate);    
     }

    对于本例来说,我们创建的证书既不再受信任根证书颁发机构存储区,也不在受信任的个人存储区。如果我们不愿意自定义X509CertificateValidator,可以通过如下的代码选择None模式以避免异常的发生。

     using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
     {
         channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
         ICalculator calculator = channelFactory.CreateChannel();
         Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2)); ;
     }
     Console.Read();
     

    我们也可以通过配置的方式来对ClientCredentials这个终结点行为进行相应的设置,通过上面这段程序对服务证书验证模式的设置与下面的这段配置在功能上是等效的。

     <system.serviceModel>
     ...
      <client>
        <endpoint name="calculatorService" behaviorConfiguration="IgoreSvcCertValidation" 
    address="net.tcp://jinnan-PC/calculatorservice" binding="netTcpBinding" bindingConfiguration="transportTcpBinding" 
    contract="Artech.WcfServices.Contracts.ICalculator" />
      </client>
      <behaviors>
        <endpointBehaviors>
          <behavior name="IgoreSvcCertValidation">
            <clientCredentials>
              <serviceCertificate>
                <authentication certificateValidationMode="None"/>
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>
    </system.serviceModel>

    通过终结点行为ClientCredentials改变服务证书认证不仅仅可以适用于非HTTPS下的Transport安全模式,同时适用于Message安全模式。但是当我们采用HTTPS的时候,我们需要采用另外一种改变证书认证模式的方式

  • 相关阅读:
    shell编程系列5--数学运算
    qperf测量网络带宽和延迟
    使用gprof对应用程序做性能评测
    [转]极不和谐的 fork 多线程程序
    Emacs显示光标在哪个函数
    Iterm2的一些好用法
    [转]最佳日志实践
    Deep Introduction to Go Interfaces.
    CGo中传递多维数组给C函数
    seaweedfs 源码笔记(一)
  • 原文地址:https://www.cnblogs.com/Chary/p/No0000125.html
Copyright © 2020-2023  润新知