zoukankan      html  css  js  c++  java
  • 传说中的WCF:消息拦截与篡改

    我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过程中,期间发送和接收的SOAP是什么样子。当然,也有人是通过借助其他工具来抓取数据包来查看。那,有没有办法让程序自己输出相应的SOAP信息呢?

    当然有,这就是我们本文要说的,对消息的拦截与篡改,呵,我用了一个不太好听动词——篡改。

    由于WCF的模型相对复杂,对于如何拦截和修改消息会让许多刚接触的朋友有点抓狂。是的,虽然MSDN文档都有详细的说明,但估计你也和我有相同的感觉,看了MSDN的说明后依然一头雾水。确实如此,毕竟WCF不像窗口和控件那样可以看得见,理解起来比较直观,相反的,这些东西会相对抽象。

    说到消息拦截,这个你肯定可以理解,如果你不懂,你可以想一想电话窃听程序,我在你的手机上植入一种木马,可以截取你和MM的通话内容,其实这就是消息拦截。

    WCF相关的API比较难寻找,我当初也找了N久,现在,我直接把思路和方法告诉各位,也免得大家太辛苦。

    要对SOAP消息进行拦截和修改,我们需要实现两个接口,它们都位于System.ServiceModel.Dispatcher (程序集System.ServiceModel)。下面分别价绍。

    接口一:IClientMessageInspector

    从名字中我们可以猜测,它是用来拦截客户消息的,而看看它的方法,你就更加肯定当初的猜测了。

    • BeforeSendRequest:向服务器发送请求前拦截或修改消息(事前控制)
    • AfterReceiveReply:接收到服务器的回复消息后,在调用返回之前拦截或修改消息(事后诸葛亮)

    接口二:IDispatchMessageInspector

    刚才那个接口是针对客户端的,而这个是针对服务器端的。

    • AfterReceiveRequest:接收客户端请求后,在进入操作处理代码之前拦截或修改消息(欺上)
    • BeforeSendReply:服务器向客户端发送回复消息之前拦截和修改消息(瞒下)。

    虽然实现了这两个接口,但你会有新的疑问,怎么用?把它们放到哪儿才能拦截消息?因此,下一步就是要实现IEndpointBehavior按口(System.ServiceModel.Description命名空间,程序集System.ServiceModel),它有四个方法,而我们只需要处理两个就够了。

    下面是MSDN的翻译版本说明:

      • 使用 ApplyClientBehavior 方法可以在客户端应用程序中修改、检查或插入对终结点中的扩展。

      • 使用 ApplyDispatchBehavior 方法可以在服务应用程序中修改、检查或插入对终结点范围执行的扩展。

        我想不用额外解释了,说白了就是一个在客户拦截和修改消息,另一个在服务器端拦截和修改消息。

        在实现这两个方法时,和前面我们实现的IClientMessageInspector和IDispatchMessageInspector联系起来就OK了。

        做完了IEndpointBehavior的事情后,把它插入到服务终结点中就行了,无论是服务器端还是客户端,这一步都必须的,因为我们实现的拦截器是包括两个端的,因此,较好的做法是把这些类写到一个独立的类库(dll)中,这样一来,服务器端和客户端都可以引用它。详见后面的示例。

        理论课上完了,下面开始实验课,按照前面的指导思想,我们先要写一个类库。

        新建一个类库应用,然后添加System.ServiceModel程序集的引用,这个不用我教你了,你懂的。

        [csharp] view plain copy
         
         print?
        1. using System;  
        2. using System.Collections.Generic;  
        3. using System.Linq;  
        4. using System.Text;  
        5. using System.Threading.Tasks;  
        6.   
        7. using System.ServiceModel;  
        8. using System.ServiceModel.Dispatcher;  
        9. using System.ServiceModel.Description;  
        10. using System.ServiceModel.Channels;  
        11.   
        12. namespace MyLib  
        13. {  
        14.     /// <summary>  
        15.     ///  消息拦截器  
        16.     /// </summary>  
        17.     public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector  
        18.     {  
        19.         void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)  
        20.         {  
        21.             Console.WriteLine("客户端接收到的回复: {0}", reply.ToString());  
        22.         }  
        23.   
        24.         object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)  
        25.         {  
        26.             Console.WriteLine("客户端发送请求前的SOAP消息: {0}", request.ToString());  
        27.             return null;  
        28.         }  
        29.   
        30.         object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)  
        31.         {  
        32.             Console.WriteLine("服务器端:接收到的请求: {0}", request.ToString());  
        33.             return null;  
        34.         }  
        35.   
        36.         void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)  
        37.         {  
        38.             Console.WriteLine("服务器即将作出以下回复: {0}", reply.ToString());  
        39.         }  
        40.     }  
        41.   
        42.     /// <summary>  
        43.     /// 插入到终结点的Behavior  
        44.     /// </summary>  
        45.     public class MyEndPointBehavior : IEndpointBehavior  
        46.     {  
        47.         public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)  
        48.         {  
        49.             // 不需要  
        50.             return;  
        51.         }  
        52.   
        53.         public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)  
        54.         {  
        55.             // 植入“偷听器”客户端  
        56.             clientRuntime.ClientMessageInspectors.Add(new MyMessageInspector());  
        57.         }  
        58.   
        59.         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)  
        60.         {  
        61.             // 植入“偷听器” 服务器端  
        62.             endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());  
        63.         }  
        64.   
        65.         public void Validate(ServiceEndpoint endpoint)  
        66.         {  
        67.             // 不需要  
        68.             return;  
        69.         }  
        70.     }  
        71.   
        72. }  


        这一步,我们先建立服务器端。

        记得要引用我们刚才写的类库。

        [csharp] view plain copy
         
         print?
        1. using System;  
        2. using System.Collections.Generic;  
        3. using System.Linq;  
        4. using System.Text;  
        5. using System.Threading.Tasks;  
        6.   
        7. using System.Runtime;  
        8. using System.Runtime.Serialization;  
        9. using System.ServiceModel;  
        10. using System.ServiceModel.Description;  
        11.   
        12. namespace WCFServer  
        13. {  
        14.     class Program  
        15.     {  
        16.         static void Main(string[] args)  
        17.         {  
        18.             // 服务器基址  
        19.             Uri baseAddress = new Uri("http://localhost:1378/services");  
        20.             // 声明服务器主机  
        21.             using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))  
        22.             {  
        23.                 // 添加绑定和终结点  
        24.                 WSHttpBinding binding = new WSHttpBinding();  
        25.                 host.AddServiceEndpoint(typeof(IService), binding, "/test");  
        26.                 // 添加服务描述  
        27.                 host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });  
        28.                 // 把自定义的IEndPointBehavior插入到终结点中  
        29.                 foreach (var endpont in host.Description.Endpoints)  
        30.                 {  
        31.                     endpont.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());  
        32.                 }  
        33.                 try  
        34.                 {  
        35.                     // 打开服务  
        36.                     host.Open();  
        37.                     Console.WriteLine("服务已启动。");  
        38.                 }  
        39.                 catch (Exception ex)  
        40.                 {  
        41.                     Console.WriteLine(ex.Message);  
        42.                 }  
        43.                 Console.ReadKey();  
        44.             }  
        45.         }  
        46.     }  
        47.   
        48.     [ServiceContract(Namespace = "MyNamespace")]  
        49.     public interface IService  
        50.     {  
        51.         [OperationContract]  
        52.         int AddInt(int a, int b);  
        53.         [OperationContract]  
        54.         Student GetStudent();  
        55.         [OperationContract]  
        56.         CalResultResponse ComputingNumbers(CalcultRequest inMsg);  
        57.     }  
        58.   
        59.     [ServiceBehavior(IncludeExceptionDetailInFaults = true)]  
        60.     public class MyService : IService  
        61.     {  
        62.         public int AddInt(int a, int b)  
        63.         {  
        64.             return a + b;  
        65.         }  
        66.   
        67.         public Student GetStudent()  
        68.         {  
        69.             Student stu = new Student();  
        70.             stu.StudentName = "小明";  
        71.             stu.StudentAge = 22;  
        72.             return stu;  
        73.         }  
        74.   
        75.         public CalResultResponse ComputingNumbers(CalcultRequest inMsg)  
        76.         {  
        77.             CalResultResponse rmsg = new CalResultResponse();  
        78.             switch (inMsg.Operation)  
        79.             {  
        80.                 case "加":  
        81.                     rmsg.ComputedResult = inMsg.NumberA + inMsg.NumberB;  
        82.                     break;  
        83.                 case "减":  
        84.                     rmsg.ComputedResult = inMsg.NumberA - inMsg.NumberB;  
        85.                     break;  
        86.                 case "乘":  
        87.                     rmsg.ComputedResult = inMsg.NumberA * inMsg.NumberB;  
        88.                     break;  
        89.                 case "除":  
        90.                     rmsg.ComputedResult = inMsg.NumberA / inMsg.NumberB;  
        91.                     break;  
        92.                 default:  
        93.                     throw new ArgumentException("运算操作只允许加、减、乘、除。");  
        94.                     break;  
        95.             }  
        96.             return rmsg;  
        97.         }  
        98.     }  
        99.   
        100.     [DataContract]  
        101.     public class Student  
        102.     {  
        103.         [DataMember]  
        104.         public string StudentName;  
        105.         [DataMember]  
        106.         public int StudentAge;  
        107.     }  
        108.   
        109.     [MessageContract]  
        110.     public class CalcultRequest  
        111.     {  
        112.         [MessageHeader]  
        113.         public string Operation;  
        114.         [MessageBodyMember]  
        115.         public int NumberA;  
        116.         [MessageBodyMember]  
        117.         public int NumberB;  
        118.     }  
        119.   
        120.     [MessageContract]  
        121.     public class CalResultResponse  
        122.     {  
        123.         [MessageBodyMember]  
        124.         public int ComputedResult;  
        125.     }  
        126. }  


        接下来,实现客户端。

        a、引用刚才写的类库MyLib;

        b、引用WCF服务。

        [csharp] view plain copy
         
         print?
        1. using System;  
        2. using System.Collections.Generic;  
        3. using System.Linq;  
        4. using System.Text;  
        5. using System.Threading.Tasks;  
        6.   
        7. namespace WCFClient  
        8. {  
        9.     class Program  
        10.     {  
        11.         static void Main(string[] args)  
        12.         {  
        13.             WS.ServiceClient client = new WS.ServiceClient();  
        14.             // 记得在客户端也要插入IEndPointBehavior  
        15.             client.Endpoint.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());  
        16.             try  
        17.             {  
        18.                 // 1、调用带元数据参数和返回值的操作  
        19.                 Console.WriteLine(" 20和35相加的结果是:{0}", client.AddInt(20, 35));  
        20.                 // 2、调用带有数据协定的操作  
        21.                 WS.Student student = client.GetStudent();  
        22.                 Console.WriteLine(" 学生信息---------------------------");  
        23.                 Console.WriteLine("姓名:{0} 年龄:{1}", student.StudentName, student.StudentAge);  
        24.                 // 3、调用带消息协定的操作  
        25.                 Console.WriteLine(" 15乘以70的结果是:{0}", client.ComputingNumbers("乘", 15, 70));  
        26.             }  
        27.             catch (Exception ex)  
        28.             {  
        29.                 Console.WriteLine("异常:{0}", ex.Message);  
        30.             }  
        31.   
        32.             client.Close();  
        33.             Console.ReadKey();  
        34.         }  
        35.     }  
        36. }  


        现在你可以运行程序来观察了。


         

        知道了如何拦截消息,那么修改消息就不难了。

        现在我们把前面写的类库MyLib。

        将消息拦截器MyMessageInspector作如下修改:

        [csharp] view plain copy
         
         print?
        1. /// <summary>  
        2. ///  消息拦截器  
        3. /// </summary>  
        4. public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector  
        5. {  
        6.     void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)  
        7.     {  
        8.         //Console.WriteLine("客户端接收到的回复: {0}", reply.ToString());  
        9.         return;  
        10.     }  
        11.   
        12.     object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)  
        13.     {  
        14.         //Console.WriteLine("客户端发送请求前的SOAP消息: {0}", request.ToString());  
        15.         // 插入验证信息  
        16.         MessageHeader hdUserName = MessageHeader.CreateHeader("u", "fuck", "admin");  
        17.         MessageHeader hdPassWord = MessageHeader.CreateHeader("p", "fuck", "123");  
        18.         request.Headers.Add(hdUserName);  
        19.         request.Headers.Add(hdPassWord);  
        20.         return null;  
        21.     }  
        22.   
        23.     object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)  
        24.     {  
        25.         //Console.WriteLine("服务器端:接收到的请求: {0}", request.ToString());  
        26.         // 栓查验证信息  
        27.         string un = request.Headers.GetHeader<string>("u", "fuck");  
        28.         string ps = request.Headers.GetHeader<string>("p", "fuck");  
        29.         if (un == "admin" && ps == "abcd")  
        30.         {  
        31.             Console.WriteLine("用户名和密码正确。");  
        32.         }  
        33.         else  
        34.         {  
        35.             throw new Exception("验证失败,滚吧!");  
        36.         }  
        37.         return null;  
        38.     }  
        39.   
        40.     void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)  
        41.     {  
        42.         //Console.WriteLine("服务器即将作出以下回复: {0}", reply.ToString());  
        43.         return;  
        44.     }  
        45. }  

        注意:添加对System.Runtime.Serialization的引用。

        创建消息头时,第一个参数是名字,如上面的“u”,第二个参数是命名空间,这个可以自己来定义,比如上面的“fuck”,第三个参数就是消息头的内容。


        现在重新生成一下项目,再试试。

        前面我们说过,如果安装证书进行身份验证会相当TMD麻烦,而可以通过修改SOAP消息头来验证,但是,上次的做法会有一个麻烦,那就是每次调用操作协定都要手动修改一次,这一次,我们直接在终结点级别进行修改和验证,就省去了许多功夫。

  • 相关阅读:
    POJ3233]Matrix Power Series && [HDU1588]Gauss Fibonacci
    [codeforces 508E]Maximum Matching
    [SDOI2011]染色
    [CSU1806]Toll
    [HDU4969]Just a Joke
    [HDU1071]The area
    [HDU1724]Ellipse
    [VIJOS1889]天真的因数分解
    [BZOJ3379] Turning in Homework
    [BZOJ1572] WorkScheduling
  • 原文地址:https://www.cnblogs.com/yanglang/p/7068546.html
Copyright © 2011-2022 走看看