艾偉:WCF安全之EndPointIdentity

狼人2007發表於2019-05-14

   最近在做一個專案,應用了WCF進行分散式開發,中間還涉及到訊息路由器等,好在有WCF提供了強大的基礎支援,當然,本身也作了不少的擴充套件,實際,我 最關心的是WCF的安全問題,網上不少朋友介紹的WCF的安全也是少得可憐,微軟釋出的WCF Security GUID好像講得也只是入門級別的教程,離真正應用到專案中還是有很大的距離,這也讓我萌發了分享的想法,今天先放出來佔個位置吧,有反對的朋友磚頭輕 點,呵~,可以告訴你,WCF的安全裡,有很多的小祕密,當然還是要告訴你,並且有此小祕密是要自己去體驗後才知道,在部落格排版方面,李會軍(軍哥)讓人 感覺最舒服,在解說方面,軍哥也是以簡潔著稱,我在這裡也學習一下,一起簡潔吧,我希望以後的WCF安全探討裡,一次只講一個小內容好了~

  概述

   Windows Communication Foundation (WCF) 是 Microsoft 為構建面向服務的應用程式而提供的統一程式設計模型(摘自MSDN),在分散式環境下的安全問題尤為重要,如果你覺得使用了WCF預設的安全措施可以讓你高枕 無憂,那明天你可就以回家種田了,當然,對於學習來說,足夠了~,但我們講的是真正的專案應用,WCF在各種協議下的安全提供和保證是不盡相同的。

   背景

 故事發生在一個陽光明媚的下午,一名女子為了混入某小區行竊,將上次偷到的管道維修工作牌別在胸前,當她走近管理員身邊時,被管理員一把抓個正著,原來這小區從上次失竊事件後,已經將維修隊解散,現在維修都是由管理員聯絡外部人員,自然也不用別什麼工作牌了。

  問題呈現

   1、許多朋友對這個EndPointIdentity相當的不屑顧,千萬不要小看它呀,有時候你被wcf弄生弄死的時候還不知道為什麼,這次你應該看清楚了。當你新建一個WCF服務類庫時,正確的EndPointIdentity宣告如下

<endpoint address =UserData binding=netTcpBinding contract=UserService.IUserData bindingConfiguration=EndpointBinding>
<identity>
     
<dns value=localhost/>
identity>
endpoint>

 說實現,EndPointIdentity這東西在革命初期(wcf初建立時),我覺得它就像是人一盲腸,多了它也沒啥用,少了它也不覺得礙事,你不信?刪了試試,你要真刪除了其實也沒什麼。

  2、客戶端如果引用了服務後設資料,生成的EndPointIdentity和伺服器端的一模一樣,不信你自己看,實際上,你也可以把它刪除了(革命初期),對服務呼叫沒啥影響。

  3、在你的繫結中,安全選項為None的時候,你想怎麼弄它就怎麼弄它,不要緊,隨便改。如下

<binding name=EndpointBinding>
   
<security mode=None>
      
<transport clientCredentialType=Windows protectionLevel=EncryptAndSign/>
      
<message clientCredentialType=Certificate/>
   
security>
binding>

 4、一旦你將安全策略調整為:Transport(傳輸安全)

<security mode=Transport>
   
<transport clientCredentialType=Windows protectionLevel=EncryptAndSign/>
   
<message clientCredentialType=Certificate/>
security>

 5、如果你的服務還是像初期一樣沒啥改動,僅是啟用了傳輸安全,clientCredentialType的憑據型別也只是windows,沒有使用證書,那可以肯定,你的服務沒啥問題出現。

6、不過這時候,你有一天不小心的將EndpointIdentity中的dns元素值誤刪除了1個字 ,我敢肯定,你的惡夢才剛開始,這時候,你再呼叫服務,將會收到一個異常。

================

未處理 System.ServiceModel.Security.SecurityNegotiationException
  Message=”伺服器已拒絕客戶端憑據。”

=================

非常限的提示,完全找不到任何線索,可能那時你還以為是不小心設定了某種安全策略哩或者是證書什麼的沒匹配呢。

  正題

IdentityElement類

表示一個配置元素,該配置元素使其他終結點在與該終結點交換訊息時可以對其進行身份驗證。此類不能被繼承。

EndpointIdentity類

一個 abstract 類,實現此類時可提供一個標識,與終結點交換訊息的客戶端可使用該標識對終結點進行身份驗證。

 

1、這兩兄弟其實是同一人,起到的作用都是一樣,只不過一個是作用於配置檔案,一個作用於託管程式碼中。利用EndpointIdentity來向伺服器標識自己是合法的呼叫,共有6種方式。

(1) 分別是dns、certificate、rsa、servicePrincipalName(spn)、userPrincipalName(upn)、certificateReference(x509證書標識),通常情況下,只採用一種,也是最常用的dns標識方式,當然,我不反對你6種方式一起使用,如何你有需要。

服務建立時預設的識別符號為:dns,並且其值為:localhost

(2)如果你的伺服器dns值為localhost,客戶端的dns與之不匹配,所丟擲的異常描述就是上面的紅色字型內容。

(3)如果伺服器dns值已被更改(這個更改當然不是你改改配置檔案中的值就行的),通常情況下,丟擲的異常都會告訴你明確的不匹配,不匹配在哪裡,預期是什麼,實際是什麼,那你改起來就費事了。

2、現在我們再把安全策略調整到訊息級別,試試。

Code
//客戶端配置
<binding name=EndpointBinding>
          
<security mode=Message>
            
<transport clientCredentialType=Windows protectionLevel=EncryptAndSign/>
            
<message clientCredentialType=Certificate/>
          
security>
        
binding>
      
netTcpBinding>
    
bindings>
    
<client>
        
<endpoint address =net.tcp://localhost:8799/UserService/UserData
                          binding
=netTcpBinding
                          contract
=Client.References.IUserData
                          bindingConfiguration
=EndpointBinding
                          behaviorConfiguration
=UserDataBehavior>
          
<identity>
            
<dns value=localhost/>           
          
identity>
        
endpoint>
client>  

//伺服器配置
<binding name=EndpointBinding>
          
<security mode=Message>
            
<transport clientCredentialType=Windows protectionLevel=EncryptAndSign/>
            
<message clientCredentialType=Certificate/>
          
security>
        
binding>
      
netTcpBinding>
    
bindings>
    
<services>
      
<service name=UserService.UserData behaviorConfiguration=UserDataBehavior>
        
<host>          
          
<baseAddresses>
            
<add baseAddress = net.tcp://localhost:8799/UserService />
          
baseAddresses>
        
host>
        
<endpoint address =UserData
                          binding
=netTcpBinding
                          contract
=UserService.IUserData
                          bindingConfiguration
=EndpointBinding>
        
endpoint>
service> 

看看配置檔案,你發現了什麼?是的,伺服器端的標識被刪除,客戶端的標識還是dns並且值為localhost,呼叫服務丟擲異常:

==========================

傳出訊息標識檢查失敗。所預期的遠端終結點的 DNS 標識為“localhost”,但是遠端終結點提供的 DNS 請求為“192168168151service”。如果此遠端終結點合法,您可以通過在建立通道代理時明確地將 DNS 標識“192168168151service”指定為 EndpointAddress 的“標識”屬性來解決此問題。

==========================

在這裡,我們忽略了一個事實,當你在服務中將安全策略調整了訊息級別安全時,服務必須配置x509證書,正所謂你叫天不應,叫地不靈啊,這時候EndpointIdentity跑出來搞亂了,明明伺服器預設是dns標識,值為:loclahost。為什麼突然跑出來個“192168168151service”呀?我也很想知道,原來,在服務配置證書後,預設的dns將被替換為證書主題,只要你把dns配置改回來,一切又沒問題了。

  新問題

這時候突然冒出來一個新的問題,如果有多個伺服器的時候怎麼辦呀?多個伺服器,多半會伴隨著路由器的出現(這只是一種假設,與業務有關),我也很想知道,有多個的時候的情況。

  解決它

 答案是通過程式碼動態建立一個EndpointIdentity,程式碼比較簡單,如下:

UserDataClient client = new UserDataClient();
EndpointIdentity identity 
= EndpointIdentity.CreateDnsIdentity(192168168151service);
AddressHeaderCollection headers 
= client.Endpoint.Address.Headers;//如果需要,還可以在這裡加入自定義的頭訊息
Uri uri = client.Endpoint.Address.Uri;
EndpointAddress remoteaddress 
= new EndpointAddress(uri, identity, headers.ToArray());
UserDataClient newClient 
= new UserDataClient(client.Endpoint.Binding, remoteaddress);
newClient.ClientCredentials.ClientCertificate.Certificate 
= client.ClientCredentials.ClientCertificate.Certificate;
client.Abort();
//關閉舊通道,當然,這裡你還可以用通道工廠建立物件,實際上也很簡單
string msg = newClient.GetData(50);

 

===========================================================

 後話

1、實際上,以上的這段程式碼這裡還包括動態的建立地址頭的相關資訊,宣告一個client的好處是可以使用原有的地址頭資訊(uri,binding等)

2、下篇更精彩,歡迎轉載,但請註明出處--樑規曉部落格(http://www.cnblogs.com/viter/)!

說得不對的地方,歡迎拍磚!

 


相關文章