最近在做一個項目,應用了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
Messag e="服務器已拒絕客戶端憑據。"
============ =====
非常限的提示,完全找不到任何線索,可能那時你還以為是 不小心設置了某種安全策略哩或者是證書什麼的沒匹配呢。
正題
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、現在我們再把安全策略調整到消息級別,試試。
//客 戶端配置
<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);