传说中的WCF:消息拦截与篡改
我們知道,在WCF中,客戶端對(duì)服務(wù)操作方法的每一次調(diào)用,都可以被看作是一條消息,而且,可能我們還會(huì)有一個(gè)疑問(wèn):如何知道客戶端與服務(wù)器通訊過(guò)程中,期間發(fā)送和接收的SOAP是什么樣子。當(dāng)然,也有人是通過(guò)借助其他工具來(lái)抓取數(shù)據(jù)包來(lái)查看。那,有沒有辦法讓程序自己輸出相應(yīng)的SOAP信息呢?
當(dāng)然有,這就是我們本文要說(shuō)的,對(duì)消息的攔截與篡改,呵,我用了一個(gè)不太好聽動(dòng)詞——篡改。
由于WCF的模型相對(duì)復(fù)雜,對(duì)于如何攔截和修改消息會(huì)讓許多剛接觸的朋友有點(diǎn)抓狂。是的,雖然MSDN文檔都有詳細(xì)的說(shuō)明,但估計(jì)你也和我有相同的感覺,看了MSDN的說(shuō)明后依然一頭霧水。確實(shí)如此,畢竟WCF不像窗口和控件那樣可以看得見,理解起來(lái)比較直觀,相反的,這些東西會(huì)相對(duì)抽象。
說(shuō)到消息攔截,這個(gè)你肯定可以理解,如果你不懂,你可以想一想電話竊聽程序,我在你的手機(jī)上植入一種木馬,可以截取你和MM的通話內(nèi)容,其實(shí)這就是消息攔截。
WCF相關(guān)的API比較難尋找,我當(dāng)初也找了N久,現(xiàn)在,我直接把思路和方法告訴各位,也免得大家太辛苦。
要對(duì)SOAP消息進(jìn)行攔截和修改,我們需要實(shí)現(xiàn)兩個(gè)接口,它們都位于System.ServiceModel.Dispatcher (程序集System.ServiceModel)。下面分別價(jià)紹。
接口一:IClientMessageInspector
從名字中我們可以猜測(cè),它是用來(lái)攔截客戶消息的,而看看它的方法,你就更加肯定當(dāng)初的猜測(cè)了。
- BeforeSendRequest:向服務(wù)器發(fā)送請(qǐng)求前攔截或修改消息(事前控制)
- AfterReceiveReply:接收到服務(wù)器的回復(fù)消息后,在調(diào)用返回之前攔截或修改消息(事后諸葛亮)
接口二:IDispatchMessageInspector
剛才那個(gè)接口是針對(duì)客戶端的,而這個(gè)是針對(duì)服務(wù)器端的。
- AfterReceiveRequest:接收客戶端請(qǐng)求后,在進(jìn)入操作處理代碼之前攔截或修改消息(欺上)
- BeforeSendReply:服務(wù)器向客戶端發(fā)送回復(fù)消息之前攔截和修改消息(瞞下)。
雖然實(shí)現(xiàn)了這兩個(gè)接口,但你會(huì)有新的疑問(wèn),怎么用?把它們放到哪兒才能攔截消息?因此,下一步就是要實(shí)現(xiàn)IEndpointBehavior按口(System.ServiceModel.Description命名空間,程序集System.ServiceModel),它有四個(gè)方法,而我們只需要處理兩個(gè)就夠了。
下面是MSDN的翻譯版本說(shuō)明:
-
使用?ApplyClientBehavior?方法可以在客戶端應(yīng)用程序中修改、檢查或插入對(duì)終結(jié)點(diǎn)中的擴(kuò)展。
-
使用?ApplyDispatchBehavior?方法可以在服務(wù)應(yīng)用程序中修改、檢查或插入對(duì)終結(jié)點(diǎn)范圍執(zhí)行的擴(kuò)展。
我想不用額外解釋了,說(shuō)白了就是一個(gè)在客戶攔截和修改消息,另一個(gè)在服務(wù)器端攔截和修改消息。
在實(shí)現(xiàn)這兩個(gè)方法時(shí),和前面我們實(shí)現(xiàn)的IClientMessageInspector和IDispatchMessageInspector聯(lián)系起來(lái)就OK了。
做完了IEndpointBehavior的事情后,把它插入到服務(wù)終結(jié)點(diǎn)中就行了,無(wú)論是服務(wù)器端還是客戶端,這一步都必須的,因?yàn)槲覀儗?shí)現(xiàn)的攔截器是包括兩個(gè)端的,因此,較好的做法是把這些類寫到一個(gè)獨(dú)立的類庫(kù)(dll)中,這樣一來(lái),服務(wù)器端和客戶端都可以引用它。詳見后面的示例。
?
理論課上完了,下面開始實(shí)驗(yàn)課,按照前面的指導(dǎo)思想,我們先要寫一個(gè)類庫(kù)。
新建一個(gè)類庫(kù)應(yīng)用,然后添加System.ServiceModel程序集的引用,這個(gè)不用我教你了,你懂的。
[csharp]?view plaincopy print? - using?System;??
- using?System.Collections.Generic;??
- using?System.Linq;??
- using?System.Text;??
- using?System.Threading.Tasks;??
- ??
- using?System.ServiceModel;??
- using?System.ServiceModel.Dispatcher;??
- using?System.ServiceModel.Description;??
- using?System.ServiceModel.Channels;??
- ??
- namespace?MyLib??
- {??
- ????///?<summary>??
- ????///??消息攔截器??
- ????///?</summary>??
- ????public?class?MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector??
- ????{??
- ????????void?IClientMessageInspector.AfterReceiveReply(ref?Message?reply,?object?correlationState)??
- ????????{??
- ????????????Console.WriteLine("客戶端接收到的回復(fù):\n{0}",?reply.ToString());??
- ????????}??
- ??
- ????????object?IClientMessageInspector.BeforeSendRequest(ref?Message?request,?IClientChannel?channel)??
- ????????{??
- ????????????Console.WriteLine("客戶端發(fā)送請(qǐng)求前的SOAP消息:\n{0}",?request.ToString());??
- ????????????return?null;??
- ????????}??
- ??
- ????????object?IDispatchMessageInspector.AfterReceiveRequest(ref?Message?request,?IClientChannel?channel,?InstanceContext?instanceContext)??
- ????????{??
- ????????????Console.WriteLine("服務(wù)器端:接收到的請(qǐng)求:\n{0}",?request.ToString());??
- ????????????return?null;??
- ????????}??
- ??
- ????????void?IDispatchMessageInspector.BeforeSendReply(ref?Message?reply,?object?correlationState)??
- ????????{??
- ????????????Console.WriteLine("服務(wù)器即將作出以下回復(fù):\n{0}",?reply.ToString());??
- ????????}??
- ????}??
- ??
- ????///?<summary>??
- ????///?插入到終結(jié)點(diǎn)的Behavior??
- ????///?</summary>??
- ????public?class?MyEndPointBehavior?:?IEndpointBehavior??
- ????{??
- ????????public?void?AddBindingParameters(ServiceEndpoint?endpoint,?BindingParameterCollection?bindingParameters)??
- ????????{??
- ????????????//?不需要??
- ????????????return;??
- ????????}??
- ??
- ????????public?void?ApplyClientBehavior(ServiceEndpoint?endpoint,?ClientRuntime?clientRuntime)??
- ????????{??
- ????????????//?植入“偷聽器”客戶端??
- ????????????clientRuntime.ClientMessageInspectors.Add(new?MyMessageInspector());??
- ????????}??
- ??
- ????????public?void?ApplyDispatchBehavior(ServiceEndpoint?endpoint,?EndpointDispatcher?endpointDispatcher)??
- ????????{??
- ????????????//?植入“偷聽器”?服務(wù)器端??
- ????????????endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new?MyMessageInspector());??
- ????????}??
- ??
- ????????public?void?Validate(ServiceEndpoint?endpoint)??
- ????????{??
- ????????????//?不需要??
- ????????????return;??
- ????????}??
- ????}??
- ??
- }??
- using?System;??
- using?System.Collections.Generic;??
- using?System.Linq;??
- using?System.Text;??
- using?System.Threading.Tasks;??
- ??
- using?System.Runtime;??
- using?System.Runtime.Serialization;??
- using?System.ServiceModel;??
- using?System.ServiceModel.Description;??
- ??
- namespace?WCFServer??
- {??
- ????class?Program??
- ????{??
- ????????static?void?Main(string[]?args)??
- ????????{??
- ????????????//?服務(wù)器基址??
- ????????????Uri?baseAddress?=?new?Uri("http://localhost:1378/services");??
- ????????????//?聲明服務(wù)器主機(jī)??
- ????????????using?(ServiceHost?host?=?new?ServiceHost(typeof(MyService),?baseAddress))??
- ????????????{??
- ????????????????//?添加綁定和終結(jié)點(diǎn)??
- ????????????????WSHttpBinding?binding?=?new?WSHttpBinding();??
- ????????????????host.AddServiceEndpoint(typeof(IService),?binding,?"/test");??
- ????????????????//?添加服務(wù)描述??
- ????????????????host.Description.Behaviors.Add(new?ServiceMetadataBehavior?{?HttpGetEnabled?=?true?});??
- ????????????????//?把自定義的IEndPointBehavior插入到終結(jié)點(diǎn)中??
- ????????????????foreach?(var?endpont?in?host.Description.Endpoints)??
- ????????????????{??
- ????????????????????endpont.EndpointBehaviors.Add(new?MyLib.MyEndPointBehavior());??
- ????????????????}??
- ????????????????try??
- ????????????????{??
- ????????????????????//?打開服務(wù)??
- ????????????????????host.Open();??
- ????????????????????Console.WriteLine("服務(wù)已啟動(dòng)。");??
- ????????????????}??
- ????????????????catch?(Exception?ex)??
- ????????????????{??
- ????????????????????Console.WriteLine(ex.Message);??
- ????????????????}??
- ????????????????Console.ReadKey();??
- ????????????}??
- ????????}??
- ????}??
- ??
- ????[ServiceContract(Namespace?=?"MyNamespace")]??
- ????public?interface?IService??
- ????{??
- ????????[OperationContract]??
- ????????int?AddInt(int?a,?int?b);??
- ????????[OperationContract]??
- ????????Student?GetStudent();??
- ????????[OperationContract]??
- ????????CalResultResponse?ComputingNumbers(CalcultRequest?inMsg);??
- ????}??
- ??
- ????[ServiceBehavior(IncludeExceptionDetailInFaults?=?true)]??
- ????public?class?MyService?:?IService??
- ????{??
- ????????public?int?AddInt(int?a,?int?b)??
- ????????{??
- ????????????return?a?+?b;??
- ????????}??
- ??
- ????????public?Student?GetStudent()??
- ????????{??
- ????????????Student?stu?=?new?Student();??
- ????????????stu.StudentName?=?"小明";??
- ????????????stu.StudentAge?=?22;??
- ????????????return?stu;??
- ????????}??
- ??
- ????????public?CalResultResponse?ComputingNumbers(CalcultRequest?inMsg)??
- ????????{??
- ????????????CalResultResponse?rmsg?=?new?CalResultResponse();??
- ????????????switch?(inMsg.Operation)??
- ????????????{??
- ????????????????case?"加":??
- ????????????????????rmsg.ComputedResult?=?inMsg.NumberA?+?inMsg.NumberB;??
- ????????????????????break;??
- ????????????????case?"減":??
- ????????????????????rmsg.ComputedResult?=?inMsg.NumberA?-?inMsg.NumberB;??
- ????????????????????break;??
- ????????????????case?"乘":??
- ????????????????????rmsg.ComputedResult?=?inMsg.NumberA?*?inMsg.NumberB;??
- ????????????????????break;??
- ????????????????case?"除":??
- ????????????????????rmsg.ComputedResult?=?inMsg.NumberA?/?inMsg.NumberB;??
- ????????????????????break;??
- ????????????????default:??
- ????????????????????throw?new?ArgumentException("運(yùn)算操作只允許加、減、乘、除。");??
- ????????????????????break;??
- ????????????}??
- ????????????return?rmsg;??
- ????????}??
- ????}??
- ??
- ????[DataContract]??
- ????public?class?Student??
- ????{??
- ????????[DataMember]??
- ????????public?string?StudentName;??
- ????????[DataMember]??
- ????????public?int?StudentAge;??
- ????}??
- ??
- ????[MessageContract]??
- ????public?class?CalcultRequest??
- ????{??
- ????????[MessageHeader]??
- ????????public?string?Operation;??
- ????????[MessageBodyMember]??
- ????????public?int?NumberA;??
- ????????[MessageBodyMember]??
- ????????public?int?NumberB;??
- ????}??
- ??
- ????[MessageContract]??
- ????public?class?CalResultResponse??
- ????{??
- ????????[MessageBodyMember]??
- ????????public?int?ComputedResult;??
- ????}??
- }??
- using?System;??
- using?System.Collections.Generic;??
- using?System.Linq;??
- using?System.Text;??
- using?System.Threading.Tasks;??
- ??
- namespace?WCFClient??
- {??
- ????class?Program??
- ????{??
- ????????static?void?Main(string[]?args)??
- ????????{??
- ????????????WS.ServiceClient?client?=?new?WS.ServiceClient();??
- ????????????//?記得在客戶端也要插入IEndPointBehavior??
- ????????????client.Endpoint.EndpointBehaviors.Add(new?MyLib.MyEndPointBehavior());??
- ????????????try??
- ????????????{??
- ????????????????//?1、調(diào)用帶元數(shù)據(jù)參數(shù)和返回值的操作??
- ????????????????Console.WriteLine("\n20和35相加的結(jié)果是:{0}",?client.AddInt(20,?35));??
- ????????????????//?2、調(diào)用帶有數(shù)據(jù)協(xié)定的操作??
- ????????????????WS.Student?student?=?client.GetStudent();??
- ????????????????Console.WriteLine("\n學(xué)生信息---------------------------");??
- ????????????????Console.WriteLine("姓名:{0}\n年齡:{1}",?student.StudentName,?student.StudentAge);??
- ????????????????//?3、調(diào)用帶消息協(xié)定的操作??
- ????????????????Console.WriteLine("\n15乘以70的結(jié)果是:{0}",?client.ComputingNumbers("乘",?15,?70));??
- ????????????}??
- ????????????catch?(Exception?ex)??
- ????????????{??
- ????????????????Console.WriteLine("異常:{0}",?ex.Message);??
- ????????????}??
- ??
- ????????????client.Close();??
- ????????????Console.ReadKey();??
- ????????}??
- ????}??
- }??
- ///?<summary>??
- ///??消息攔截器??
- ///?</summary>??
- public?class?MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector??
- {??
- ????void?IClientMessageInspector.AfterReceiveReply(ref?Message?reply,?object?correlationState)??
- ????{??
- ????????//Console.WriteLine("客戶端接收到的回復(fù):\n{0}",?reply.ToString());??
- ????????return;??
- ????}??
- ??
- ????object?IClientMessageInspector.BeforeSendRequest(ref?Message?request,?IClientChannel?channel)??
- ????{??
- ????????//Console.WriteLine("客戶端發(fā)送請(qǐng)求前的SOAP消息:\n{0}",?request.ToString());??
- ????????//?插入驗(yàn)證信息??
- ????????MessageHeader?hdUserName?=?MessageHeader.CreateHeader("u",?"fuck",?"admin");??
- ????????MessageHeader?hdPassWord?=?MessageHeader.CreateHeader("p",?"fuck",?"123");??
- ????????request.Headers.Add(hdUserName);??
- ????????request.Headers.Add(hdPassWord);??
- ????????return?null;??
- ????}??
- ??
- ????object?IDispatchMessageInspector.AfterReceiveRequest(ref?Message?request,?IClientChannel?channel,?InstanceContext?instanceContext)??
- ????{??
- ????????//Console.WriteLine("服務(wù)器端:接收到的請(qǐng)求:\n{0}",?request.ToString());??
- ????????//?栓查驗(yàn)證信息??
- ????????string?un?=?request.Headers.GetHeader<string>("u",?"fuck");??
- ????????string?ps?=?request.Headers.GetHeader<string>("p",?"fuck");??
- ????????if?(un?==?"admin"?&&?ps?==?"abcd")??
- ????????{??
- ????????????Console.WriteLine("用戶名和密碼正確。");??
- ????????}??
- ????????else??
- ????????{??
- ????????????throw?new?Exception("驗(yàn)證失敗,滾吧!");??
- ????????}??
- ????????return?null;??
- ????}??
- ??
- ????void?IDispatchMessageInspector.BeforeSendReply(ref?Message?reply,?object?correlationState)??
- ????{??
- ????????//Console.WriteLine("服務(wù)器即將作出以下回復(fù):\n{0}",?reply.ToString());??
- ????????return;??
- ????}??
- }??
這一步,我們先建立服務(wù)器端。記得要引用我們剛才寫的類庫(kù)。
[csharp]?view plaincopy print?
接下來(lái),實(shí)現(xiàn)客戶端。a、引用剛才寫的類庫(kù)MyLib;
b、引用WCF服務(wù)。
[csharp]?view plaincopy print?
現(xiàn)在你可以運(yùn)行程序來(lái)觀察了。?
?知道了如何攔截消息,那么修改消息就不難了。
現(xiàn)在我們把前面寫的類庫(kù)MyLib。
將消息攔截器MyMessageInspector作如下修改:
[csharp]?view plaincopy print?注意:添加對(duì)System.Runtime.Serialization的引用。
創(chuàng)建消息頭時(shí),第一個(gè)參數(shù)是名字,如上面的“u”,第二個(gè)參數(shù)是命名空間,這個(gè)可以自己來(lái)定義,比如上面的“fuck”,第三個(gè)參數(shù)就是消息頭的內(nèi)容。
現(xiàn)在重新生成一下項(xiàng)目,再試試。?
前面我們說(shuō)過(guò),如果安裝證書進(jìn)行身份驗(yàn)證會(huì)相當(dāng)TMD麻煩,而可以通過(guò)修改SOAP消息頭來(lái)驗(yàn)證,但是,上次的做法會(huì)有一個(gè)麻煩,那就是每次調(diào)用操作協(xié)定都要手動(dòng)修改一次,這一次,我們直接在終結(jié)點(diǎn)級(jí)別進(jìn)行修改和驗(yàn)證,就省去了許多功夫。
-
總結(jié)
以上是生活随笔為你收集整理的传说中的WCF:消息拦截与篡改的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 《vSphere性能设计:性能密集场景下
- 下一篇: odoo权限