WCF安全之EndPointIdentity
最近在做一個項目,應用了WCF進行分布式開發,中間還涉及到消息路由器等,好在有WCF提供了強大的基礎支持,當然,本身也作了不少的擴展,實際,我 最關心的是WCF的安全問題,網上不少朋友介紹的WCF的安全也是少得可憐,微軟發布的WCF Security GUID好像講得也只是入門級別的教程,離真正應用到項目中還是有很大的距離,這也讓我萌發了分享的想法,今天先放出來占個位置吧,有反對的朋友磚頭輕 點,呵~,可以告訴你,WCF的安全里,有很多的小秘密,當然還是要告訴你,并且有此小秘密是要自己去體驗后才知道,在博客排版方面,李會軍(軍哥)讓人 感覺最舒服,在解說方面,軍哥也是以簡潔著稱,我在這里也學習一下,一起簡潔吧,我希望以后的WCF安全探討里,一次只講一個小內容好了~
概述
Windows Communication Foundation (WCF) 是 Microsoft 為構建面向服務的應用程序而提供的統一編程模型(摘自MSDN),在分布式環境下的安全問題尤為重要,如果你覺得使用了WCF默認的安全措施可以讓你高枕 無憂,那明天你可就以回家種田了,當然,對于學習來說,足夠了~,但我們講的是真正的項目應用,WCF在各種協議下的安全提供和保證是不盡相同的。
背景
故事發生在一個陽光明媚的下午,一名女子為了混入某小區行竊,將上次偷到的管道維修工作牌別在胸前,當她走近管理員身邊時,被管理員一把抓個正著,原來這小區從上次失竊事件后,已經將維修隊解散,現在維修都是由管理員聯系外部人員,自然也不用別什么工作牌了。
問題呈現
1、許多朋友對這個EndPointIdentity相當的不屑顧,千萬不要小看它呀,有時候你被wcf弄生弄死的時候還不知道為什么,這次你應該看清楚了。當你新建一個WCF服務類庫時,正確的EndPointIdentity聲明如下
<identity>
<dns value="localhost"/>
identity>
endpoint>
說實現,EndPointIdentity這東西在革命初期(wcf初建立時),我覺得它就像是人一盲腸,多了它也沒啥用,少了它也不覺得礙事,你不信?刪了試試,你要真刪除了其實也沒什么。
2、客戶端如果引用了服務元數據,生成的EndPointIdentity和服務器端的一模一樣,不信你自己看,實際上,你也可以把它刪除了(革命初期),對服務調用沒啥影響。
3、在你的綁定中,安全選項為None的時候,你想怎么弄它就怎么弄它,不要緊,隨便改。如下
<security mode="None">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
<message clientCredentialType="Certificate"/>
security>
binding>
4、一旦你將安全策略調整為: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、現在我們再把安全策略調整到消息級別,試試。

看看配置文件,你發現了什么?是的,服務器端的標識被刪除,客戶端的標識還是dns并且值為localhost,調用服務拋出異常:
==========================
傳出消息標識檢查失敗。所預期的遠程終結點的 DNS 標識為“localhost”,但是遠程終結點提供的 DNS 請求為“192168168151service”。如果此遠程終結點合法,您可以通過在創建通道代理時明確地將 DNS 標識“192168168151service”指定為 EndpointAddress 的“標識”屬性來解決此問題。
==========================
在這里,我們忽略了一個事實,當你在服務中將安全策略調整了消息級別安全時,服務必須配置x509證書,正所謂你叫天不應,叫地不靈啊,這時候EndpointIdentity跑出來搞亂了,明明服務器默認是dns標識,值為:loclahost。為什么突然跑出來個“192168168151service”呀?我也很想知道,原來,在服務配置證書后,默認的dns將被替換為證書主題,只要你把dns配置改回來,一切又沒問題了。
新問題
這時候突然冒出來一個新的問題,如果有多個服務器的時候怎么辦呀?多個服務器,多半會伴隨著路由器的出現(這只是一種假設,與業務有關),我也很想知道,有多個的時候的情況。
解決它
答案是通過代碼動態創建一個EndpointIdentity,代碼比較簡單,如下:
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/)!
說得不對的地方,歡迎拍磚!