閱讀392 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[WCF安全係列]服務憑證(Service Credential)與服務身份(Service Identity)

采用TLS/SSL實現Transport安全的情況下,客戶端對服務證書實施認證。但是在默認情況下,這種認證僅僅是確保服務證書的合法性(通過數字簽名確保證書確實是由申明的CA頒發)和可信任性(證書或者CA證書存儲於相應的可信賴存儲區)。而WCF提供服務證書並不限於此,客戶端對服務認證的模式應該是這樣的:服務端預先知道了服務的身份,在進行服務調用之前,服務端需要提供相應的憑證用以輔助客戶端確認調用的服務具有預先確定的身份。對於這樣的服務認證模式,具有兩個重要的概念,即服務憑證和服務身份。

目錄:
一、服務憑證(Service Credential)
二、服務身份(Service Identity)
三、服務憑證協商(Service Credentials Negotiation)

認證就是通過對對方提供的憑證進行檢驗以確定對方身份的一個過程,從這個意義上講服務認證和客戶端認證並沒有本質的區別。但有服務認證確實有一點和客戶端認證不同:。而在真正進行服務調用的時候,客戶端要求服務提供相應的憑證。而客戶端根據這個憑證和實現確定的身份進行比較,從而確定當前正在調用的服務正是自己希望調用的那個。

通過上麵一節的介紹,我們已經知道了客戶端具有多種形式的憑證類型,但是服務憑證具有兩種典型的類型:。服務憑證的類型決定了認證方式,所以服務認證通過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]》)中已經有過介紹了。當通過以第一階段的認證之後,才會進入第二階段的認證,即通過比較服務證書和事先確立的服務身份信息進行對照進而確定服務是否是客戶端試圖訪問的服務,接下來討論關於服務身份的話題。

我們知道終結點時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就可以看作是這個標識符。在默認的情況下,。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有具有兩種表現形式,既可以直接采用作為服務身份標識,也可以采用為了存儲區中某個來表示。而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="https://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="https://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("https://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, "https://127.0.0.1:3721/calculatorservice");
   7:     host.Open();
   8:     ...
   9: }
  10: //客戶端代碼
  11: EndpointAddress address = new EndpointAddress("https://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
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 16:34:38

  上一篇:go  [WCF安全係列]實例演示:TLS/SSL在WCF中的應用[HTTPS]
  下一篇:go  [WCF安全係列]談談WCF的客戶端認證[Windows認證]