[WCF安全系列]服务凭证(Service Credential)与服务身份(Service Identity)

采用TLS/SSL实现Transport安全的情况下,客户端对服务证书实施认证。但是在默认情况下,这种认证仅仅是确保服务证书的合法性(通过数字签名确保证书确实是由申明的CA颁发)和可信任性(证书或者CA证书存储于相应的可信赖存储区)。而WCF提供服务证书并不限于此,客户端对服务认证的模式应该是这样的:服务端预先知道了服务的身份,在进行服务调用之前,服务端需要提供相应的凭证用以辅助客户端确认调用的服务具有预先确定的身份。对于这样的服务认证模式,具有两个重要的概念,即服务凭证和服务身份。

目录:
一、服务凭证(Service Credential)
二、服务身份(Service Identity)
三、服务凭证协商(Service Credentials Negotiation)

一、服务凭证(Service Credential)

认证就是通过对对方提供的凭证进行检验以确定对方身份的一个过程,从这个意义上讲服务认证和客户端认证并没有本质的区别。但有服务认证确实有一点和客户端认证不同:客户端在对服务进行认证之前就预先确定了服务应当具有的身份。而在真正进行服务调用的时候,客户端要求服务提供相应的凭证。而客户端根据这个凭证和实现确定的身份进行比较,从而确定当前正在调用的服务正是自己希望调用的那个。

通过上面一节的介绍,我们已经知道了客户端具有多种形式的凭证类型,但是服务凭证具有两种典型的类型:Windows凭证和X.509证书。服务凭证的类型决定了认证方式,所以服务认证通过Windows认证或者对X.509证书的检验来实现。

而Windows认证具有两种具体的实现,即KerberosNTLM。通过前面对Kerberos和NTLM的介绍,你应该知道只有Kerberos支持双向认证,而NTLM则不能。因此,只有在基于域(Domain)的网络环境中,基于Windows认证的服务认证才是可行的。而在工作组(Work
Group)环境中,我们推荐使用基于证书的服务认证。

服务认证方式的选择决定于客户端认证采用的方式,基本的策略是这样的:如果采用Windows认证的方式对客户端实施认证,服务认证同样采用Windows认证。基于X.509证书的认证在非Windows客户端认证下被采用。进一步地,如果客户端凭证类型为Windows,那么WCF采用执行服务寄宿进程的Windows帐号对应的Windows凭证作为服务凭证。如果其他非Windows凭证作为客户端凭证,你必须为服务显式地指定一个X.509证书作为服务凭证。这也是为何在前面演示的实例中,当NetTcpBinding采用Transport安全模式,客户端凭证被设置成None时,为何需要为服务指定一个X.509证书作为服务凭证的原因。

在WCF的应用编程接口中,具有一个重要的服务行为ServiceCredentials。这个类并不简单象它的名称所表示的那样用于进行服务凭证的设置,实际上需要在服务端执行的很多认证、授权行为都是通过ServiceCredentials(或者ServicePointManager的RemoteCertificateValidationCallback回调)来实现的。而在这里,我们暂时只关心如何通过ServiceCredentials为服务指定一个X.509证书作为服务凭证。关于这一点,已经在前面作过介绍了。

如果服务采用基于X.509证书作为服务凭证,客户端对服务的认证过程实际上分为两个阶段。第一个阶段是验证证书的合法性,在默认的情况下会采用ChainTrust认证模式,不过可以通过终结点行为ClientCredentials(或者ServicePointManager的RemoteCertificateValidationCallback回调)来设置不同的认证模式。关于具体对服务证书认证模式的设置在前面的实例演示(《TLS/SSL在WCF中的应用[SSL over TCP]》和《TLS/SSL在WCF中的应用[HTTPS]》)中已经有过介绍了。当通过以第一阶段的认证之后,才会进入第二阶段的认证,即通过比较服务证书和事先确立的服务身份信息进行对照进而确定服务是否是客户端试图访问的服务,接下来讨论关于服务身份的话题。

二、服务身份(Service Identity)

我们知道终结点时WCF最为核心的概念,终结点通过类型ServiceEndpoint表示。终结点具有ABC三要素分别表示地址、绑定和契约,其中地址通过EndpointAddress表示。如果你对EndpointAddress有一定的了解,你应该清楚该类具有一个只读的Identity的属性,对应的类型为EndpointIdentity,相关定义如下面的代码片断所示。

   1: public class ServiceEndpoint
   2: {
   3:     //其他成员
   4:     public EndpointAddress Address {  get; set; }
   5: }
   6: public class EndpointAddress
   7: {
   8:     //其他成员
   9:     public EndpointIdentity Identity { get; }
  10: }

我们通常所说的“调用某个服务”实际上应该是“调用服务的某个终结点”,而服务身份实际上也应该是“终结点身份”。与此对应的,通过ServiceEndpoint对象表示的终结点的身份通过Address的Identity属性来表示,而该属性的类型就是本节着重介绍的EndpointIdentity。在深入介绍EndpointIdentity之前,我们不妨先来看看它的定义。

   1: public abstract class EndpointIdentity
   2: {
   3:     //其他成员
   4:     public static EndpointIdentity CreateSpnIdentity(string spnName);
   5:     public static EndpointIdentity CreateUpnIdentity(string upnName);
   6:     public static EndpointIdentity CreateRsaIdentity(X509Certificate2 certificate);
   7:     public static EndpointIdentity CreateRsaIdentity(string publicKey);
   8:     public static EndpointIdentity CreateX509CertificateIdentity(X509Certificate2 certificate);
   9:     public static EndpointIdentity CreateDnsIdentity(string dnsName);
  10:    
  11:     public Claim IdentityClaim { get; }
  12: }
  13: public class SpnEndpointIdentity : EndpointIdentity
  14: {
  15:     //省略成员
  16: }
  17: public class UpnEndpointIdentity : EndpointIdentity
  18: {
  19:     //省略成员
  20: }
  21: public class DnsEndpointIdentity : EndpointIdentity
  22: {
  23:     //省略成员
  24: }
  25: public class RsaEndpointIdentity : EndpointIdentity
  26: {
  27:     //省略成员
  28: }
  29: public class X509CertificateEndpointIdentity : EndpointIdentity
  30: {
  31:     //省略成员
  32: }

服务身份声明通过属性IdentityClaim表示,这些信息是为最终的认证服务服务的。从上面的代码我们可能看出,EndpointIdentity实际上是一个抽象类,它具有如下几个常用的子类:SpnEndpointIdentityUpnEndpointIdentityX509CertificateEndpointIdentityRsaEndpointIdentityDnsEndpointIdentity,分别表示不同的服务身份类型。这些个具体的EndpointIdentity可以通过对应的静态方法CreateXxxIdentity创建。

我们先来介绍一下SpnEndpointIdentity和UpnEndpointIdentity。这两个EndpointIdentity是Windows认证下服务身份的两种表现形式。前者被称为服务主体名(SPN:Service Principal Name,以下简称SPN),另一种被称为用户主体名(UPN:User Principal Name,以下简称UPN)。

如果你对Kerberos有一定的了解,相信一定对SPN不会感到陌生。对于一个运行在域环境中某台机器上的服务,它能被访问它的客户端认证的先决条件是:客户端能够唯一标识该服务,而SPN就可以看作是这个标识符。在默认的情况下,如果服务寄宿进程在机器帐号(或者系统帐号,比如LocalService, LocalSystem, or NetworkService等)下,服务身份通过SPN表示;如果执行服务寄宿进程的是一个域用户帐户,则采用UPN表示服务身份。WCF中的SPN和UPN的格式如下。如果客户端预先指定SPN/UPN表示服务身份,它通过执行服务寄宿进程帐号对应的Windows凭证和SPN/UPN进行比较,从未确定服务运行在预先设定的机器或者某个域用户帐号下。

   1: SPN:Host/<<HostName>> (Host/artech-win7-x64)
   2:  
   3: UPN:<<DomainName>>/<<UserName>>(Microsoft/BillGates)或者
   4: <<UserName>>@<<DomainName>> (BillGates@Microsoft)

如果采用X.509证书作为服务凭证,服务身份可以通过X509CertificateEndpointIdentity和RsaEndpointIdentity表示。而X509CertificateEndpointIdentity有具有两种表现形式,既可以直接采用X.509证书中的指纹作为服务身份标识,也可以采用为了存储区中某个证书的引用来表示。而RsaEndpointIdentity则将X.509证书的RSA密钥作为服务身份标识。如果客户端预先制定了相应的X509CertificateEndpointIdentity/RsaEndpointIdentity作为服务身份,它会通过将作为服务凭证的X.509证书与此进行比较进而确定服务是相应证书的真正拥有者。

而对于DnsEndpointIdentity,故名思义就是基于域名系统(DNS: Domain Name
System)的服务身份表现形式。如果采用X.509证书作为服务凭证,并且这个证书的主题名称是一个DNS,客户端可以采用DnsEndpointIdentity来对服务证书进行认证。在基于SPN的Windows认证下,并且SPN是基于一个DNS,客户端也可以采用DnsEndpointIdentity认证服务。换句话会说,对于如下如下表示的DnsEndpointIdentity和SpnEndpointIdentity,在Windows认证下具有相同的认证效果。

   1: DnsEndpointIdentity:artech.com
   2: SpnEndpointIdentity: host/artech.com

服务端和客户端的终结点都可以设置这个表示服务身份标识的EndpointIdentity,不过对于整个服务认证机制,EndpointIdentity之于服务端和客户端终结点具有不同的作用。服务端终结点设置的EndpointIdentity用于元数据发布,客户端终结点设置EndpointIdentity最终用于对服务的认证。

一般情况下,在进行服务寄宿的时候,终结点的EndpointIdentity无需指定,因为WCF会根据绑定采用的客户端凭证类型和寄宿进程运行的Windows帐号为你生成相应的EndpointIdentity。终结点的EndpointIdentity最终会成员元数据的一部分被写入服务的WSDL中。比如说,我们采用IIS的方式寄宿服务,终结点采用Transport模式的WS2007HttpBinding,EndpointIdentity对应在WSDL部分的内容将会如下面的XML片断所示。由于IIS(IIS
6或之后版本)在Network
Servier帐号下执行,所以默认会使用SPN作为服务身份标识(SPN中的Jinnan-Win7-X64为机器名称)。

   1: <wsdl:definitions name="CalculatorService" targetNamespace="http://tempuri.org/">
   2:   ...
   3:   <wsdl:service name="CalculatorService">
   4:     <wsdl:port name="WS2007HttpBinding_ICalculator" binding="tns:WS2007HttpBinding_ICalculator">
   5:       <soap12:address location="https://jinnan-win7-x86/WcfServices/CalculatorService.svc"/>
   6:       <wsa10:EndpointReference>
   7:         <wsa10:Address>
   8:           https://jinnan-win7-x64/WcfServices/CalculatorService.svc
   9:         </wsa10:Address>
  10:         <Identity>
  11:           <Spn>host/Jinnan-Win7-X64</Spn>
  12:         </Identity>
  13:       </wsa10:EndpointReference>
  14:     </wsdl:port>
  15:   </wsdl:service>
  16: </wsdl:definitions>

客户端通过添加服务引用或者直接使用SvcUtil.exe导入元数据生成客户端代码和配置的时候,WSDL中的服务身份标识会自动被写入配置中。上述六种不同形式的EndpointIdentity在配置中的表示如下面的XML片断所示。

   1: <system.serviceModel>
   2:   <client>
   3:     <endpoint address="http://jinnan-win7-x86/calculatorservice1" binding="ws2007HttpBinding" contract="Artech.WcfServices.Contracts.ICalculator">
   4:       <identity>
   5:         <userPrincipalName value="jinnan@contoso.com"/>
   6:         <servicePrincipalName value="host/jinnan-win7-x86"/>
   7:         <certificate encodedValue="f332bf17db3abb8f9a9a2694ba2c75da701bef0f"/>
   8:         <certificateReference storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" findValue="jinnan-win7-x86"/>
   9:         <rsa value="sdhjgr...djakjhg"/>
  10:         <dns value="jinnan-win7-x86"/>
  11:       </identity>
  12:     </endpoint>
  13:   </client>
  14: </system.serviceModel>

如果你是通过单纯编程的方式来创建用于进行服务调用的终结点,你可以按照如下的方式手工创建相应的EndpointIdentity对象。在创建作为终结点地址的EndpointAddress对象时,作为构造函数的参数传入。一旦成功创建EndpointAddress对象,你就可以通过它的只读属性Identity获得你指定的EndpointIdentity。

   1: EndpointIdentity identity = EndpointIdentity.CreateSpnIdentity(@"host\Jinnan-Win7-X86");
   2: EndpointAddress address = new EndpointAddress(new Uri("http://jinnan-win7-x64/calculatorservice"),identity);
   3: ServiceEndpoint endpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(ICalculator)),new WS2007HttpBinding(),address);
   4: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(endpoint))
   5: {
   6:     ICalculator calculator = channelFactory.CreateChannel();
   7:     double result = calculator.Add(1, 2);
   8:     ...
   9: }

三、服务凭证协商

被用于调用服务的客户端终结点最终都关联到一个EndpointIdentity对象上,而该EndpointIdentity对象代表了客户端希望调服务的真实身份。客户端在正式向服务发送功能性消息之前,会根据服务端提供的服务凭证和这个EndpointIdentity对服务实施认证。如果服务凭证与客户端持有的服务身份相一致,则认证成功,并开始后续的消息交换,否则双方之间的交互到此为止。

在默认的情况下,正进行服务认证中客户端和服务端有一个“协商(Negotiation)”的过程。客户端通过此协商过程从服务端获取服务凭证,所以我们将这个协商机制成为“服务凭证协商(Service
Credentials
Negotiation)”。对于Transport安全模式,服务凭证协商过程总是会发生,但是对于Message安全模式,你可以通过编程或者配置避免服务凭证协商。

如果服务凭证不能通过协商的方式即时地传递给客户端,那么必然要通过另外的方式递交给它。对于Windows认证,需要客户端和服务端必须出于同一域中。而对基于X.509证书的服务凭证,需要实现安装到客户端。抑制服务凭证协商会因避免证书的传递而对安全性有所增强,但是也会因为需要额外的证书递交机制而带来额外的负担。如果你只需要拥有相应证书的客户端才能调用你的服务,不妨采用这种方式。

对于所有支持Message模式的绑定来说,只有基于WS的绑定(WSHttpBinding、WS2007HttpBinding和WSDualHttpBinding)支持服务凭证协商。而开启和关闭服务凭证协商可以通过设置MessageSecurityOverHttp类型的NegotiateServiceCredential属性来实现。

   1: public class MessageSecurityOverHttp
   2: {
   3:     //其他成员
   4:    public bool NegotiateServiceCredential {get;  set; }
   5: }

不论是在进行服务寄宿还是服务调用的时候,你都可以通过编程的方式来关闭服务凭证协商机制。具体的编程方式,可以参考如下的代码。

   1: //服务寄宿代码
   2: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
   3: {
   4:     WS2007HttpBinding binding = new WS2007HttpBinding( SecurityMode.Message);
   5:     binding.Security.Message.NegotiateServiceCredential = false;
   6:     host.AddServiceEndpoint(typeof(ICalculator), binding, "http://127.0.0.1:3721/calculatorservice");
   7:     host.Open();
   8:     ...
   9: }
  10: //客户端代码
  11: EndpointAddress address = new EndpointAddress("http://jinnan-win7-x64/calculatorservice");
  12: WS2007HttpBinding binding = new WS2007HttpBinding(SecurityMode.Message);
  13: binding.Security.Message.NegotiateServiceCredential = false;
  14: ServiceEndpoint endpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(ICalculator)), binding, address);
  15: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(endpoint))
  16: {
  17:     ICalculator calculator = channelFactory.CreateChannel();
  18:     double result = calculator.Add(1, 2);
  19:     ...
  20: }

我们当然还是推荐采用配置的方式来控制服务凭证协商,在WSHttpBinding、WS2007HttpBinding和WSDualHttpBinding的<security>/<message>配置节点中,你可以找到negotiateServiceCredential配置属性,这是开启和关闭服务凭证协商的开关,相应的配置如下所示。

   1: <system.serviceModel>
   2:   <bindings>
   3:     <ws2007HttpBinding>
   4:       <binding name="transportWS2007HttpBinding">
   5:         <security mode="Message">
   6:           <message clientCredentialType="UserName" negotiateServiceCredential="false"/>
   7:         </security>
   8:       </binding>
   9:     </ws2007HttpBinding>      
  10:   </bindings>
  11: ...
  12: </system.serviceModel>

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-10-16 09:10:20

[WCF安全系列]服务凭证(Service Credential)与服务身份(Service Identity)的相关文章

[WCF安全系列]实例演示:TLS/SSL在WCF中的应用[SSL over TCP]

在接下来的系列文章中我们正是讨论关于身份认证的主题.在前面我们已经谈到了,WCF中的认证属于"双向认证",既包括服务对客户端的认证(以下简称客户端认证),也包括客户端对服务的认证(以下简称服务认证).客户端认证和服务认证从本质上并没有什么不同,无非都是被认证一方提供相应的用户凭证供对方对自己的身份进行验证.我们先来讨论服务认证,客户端认证放在后续的文章中. 在<从两种安全模式谈起>中,我们对TLS/SSL进行了简单的介绍.我们知道,客户端和服务在为建立安全上下文而进行的协商

[WCF安全系列]绑定、安全模式与客户端凭证类型:BasicHttpBinding

整个安全传输是在WCF的信道层进行的,而绑定是信道层的缔造者,所以终结点采用哪种类型的绑定以及对绑定的属性进行怎样的设置决定了信道层最终采用何种机制实现消息的安全传输.具体来说,我们可以通过绑定设置最终采用的安全模式,以及基于相应安全模式下进行认证和消息保护的行为. 一.Binding安全相关的应用编程接口 不同的绑定类型由于其采用的传输协议不同,应用的场景也各有侧重,很难提供一种统一的应用编程接口完成基于不同绑定的安全设置,所以每一种绑定都具有各自用于安全设置相关的类型.但是基于对安全的设置,

我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承

当今的IT领域,SOA已经成为了一个非常时髦的词,对SOA风靡的程度已经让很多人对SOA,对面向服务产生误解.其中很大一部分人甚至认为面向服务将是面向对象的终结,现在的面向对象将会被面向服务完全代替.在开始本Blog之前,我先来谈谈我对SOA和OO的区别,首先申明,这只是一家之言,欢迎大家批评指正,并且关于SO的谈论不是本Blog的主题,只是主题的引子,在这里只是简单讨论而已 . OO和SO之间具有共同的部分,在运用的领域上存在交集,只有在基于他们交集层面上谈论谁是谁非才有意义,下面是我对SO和

[WCF安全系列]认证与凭证:X.509证书

在<上篇>中,我们谈到了常用的认证方式:用户名/密码认证和Windows认证.在下篇中,我们着重来介绍另外一种重要的凭证类型:X.509证书,以及针对X.509证书的认证方式.不过为了让读者能够真正地全面地了解X.509证书,我们需要先了解一些关于非对称密码学的背景知识. 目录 一.非对称密码学(Asymmetric Cryptography)     消息加密(Encryption)     数字签名(Digital Signature) 二.数字证书     数字证书的颁发机制     创

[WCF安全系列]认证与凭证:用户名/密码认证与Windows认证

如果要给认证下一个定义,我个人的倾向这样的定义:认证是确定被认证方的真实身份和他或她申明(Claim)的身份是否相符的行为.认证方需要被认证方提供相应的身份证明材料,以鉴定本身的身份是否与声称的身份相符.在计算机的语言中,这里的身份证明有一个专有的名称,即"凭证(Credential)",或者用户凭证(User Credential).认证凭证(Authentication Credential). 一.凭证的属性 最好的设计就是能够尽可能的模拟现实的设计.对于安全认证来说,在现实生活

[WCF安全系列]谈谈WCF的客户端认证[用户名/密码认证]

对于基于Internet的应用,基于用户名和密码的认证方式是最为常用的,而WCF为你提供了不同模式的用户名认证方式.首先还是从用户凭证的表示说起. 一.用户名/密码认证的三种模式 基于用户名/密码的用户凭证通过类型UserNamePasswordClientCredential表示.而在ClientCredentials中,只读属性UserName表示这样一个用户凭证.你可以按照Windows凭证的方式为ChannelFactory<TChannel>或者ClientBase<TChan

WCF专题系列(2):深入WCF寻址Part 2—自定义寻址报头

在WCF专题系列(1):深入WCF寻址Part1一文中,我们对Web服务寻址规范做 了一些认识,了解了终结点引用和消息信息报头两种结构,该规范在Web服务中 的地位举足轻重,后续我们会经常提到该规范.在本文中,我们将继续深入WCF 寻址的内容,包括元数据中的终结点地址,自定义寻址标头等相关信息. 终结点地址定义 了解了Web服务寻址规范,再回到WCF,在WCF中,终 结点地址是由EndpointAddress类来表示的,它其中很重要的几个部分是:一个 表示服务地址的统一资源定位符 (URI),一

WCF服务编程设计规范(6):队列服务、安全和服务总线

WCF服务编程设计规范(6):队列服务.安全和服务总线.本节整理队列服务(Queue Servuce).服务安全(Service Security)和服务总线(Service Bus)的设计规范. Queued Services 队列服务 1. On the client, always verify that the queue (and a dead-letter queue, when applicable) is available before calling the queued s

WCF专题系列(7):消息如何传递之绑定Part 2

概述 每个服务终结点都包含一个地址Address.一个绑定Binding 和一个 契约Contract.契约指定可用的操作,绑定指定如何与服务进行通信,而地址指 定查找服务的位置,在WCF专题系列前5篇中,深入了解了WCF中寻址的细节.本 文为消息如何传递之绑定第二部分,将详细介绍WCF内置的各种绑定元素,绑定 元素之间的顺序以及如何创建一个自定义的绑定元素. 在WCF专题系列( 6):消息如何传递之绑定Part 1一文中,我提到绑定由绑定元素组成,每个绑 定元素用来描述终结点与客户端通信方式中