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

    传说中的WCF(1):这东西难学吗?
    传说中的WCF(2):服务协定的那些事儿
    传说中的WCF(3):多个协定
    传说中的WCF(4):发送和接收SOAP头
    传说中的WCF(5):数据协定(a)
    传说中的WCF(6):数据协定(b)
    传说中的WCF(7):“单向”&“双向”
    传说中的WCF(8):玩转消息协定
    传说中的WCF(9):流与文件传输
    传说中的WCF(10):消息拦截与篡改
    传说中的WCF(11):会话(Session)
    传说中的WCF(12):服务器回调有啥用
    传说中的WCF(13):群聊天程序
    传说中的WCF(14):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程序集的引用,这个不用我教你了,你懂的。

      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("客户端接收到的回复:\n{0}", reply.ToString());
              }

              object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
              {
                  Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString());
                  return null;
              }

              object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
              {
                  Console.WriteLine("服务器端:接收到的请求:\n{0}", request.ToString());
                  return null;
              }

              void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
              {
                  Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString());
              }
          }

          /// <summary>
          /// 插入到终结点的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)
              {
                  // 植入“偷听器” 服务器端
                  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)
              {
                  // 服务器基址
                  Uri baseAddress = new Uri("http://localhost:1378/services");
                  // 声明服务器主机
                  using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))
                  {
                      // 添加绑定和终结点
                      WSHttpBinding binding = new WSHttpBinding();
                      host.AddServiceEndpoint(typeof(IService), binding, "/test");
                      // 添加服务描述
                      host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
                      // 把自定义的IEndPointBehavior插入到终结点中
                      foreach (var endpont in host.Description.Endpoints)
                      {
                          endpont.EndpointBehaviors.Add(new MyLib.MyEndPointBehavior());
                      }
                      try
                      {
                          // 打开服务
                          host.Open();
                          Console.WriteLine("服务已启动。");
                      }
                      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("运算操作只允许加、减、乘、除。");
                          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;
          }
      }


      接下来,实现客户端。

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

      b、引用WCF服务。

      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、调用带元数据参数和返回值的操作
                      Console.WriteLine("\n20和35相加的结果是:{0}", client.AddInt(20, 35));
                      // 2、调用带有数据协定的操作
                      WS.Student student = client.GetStudent();
                      Console.WriteLine("\n学生信息---------------------------");
                      Console.WriteLine("姓名:{0}\n年龄:{1}", student.StudentName, student.StudentAge);
                      // 3、调用带消息协定的操作
                      Console.WriteLine("\n15乘以70的结果是:{0}", client.ComputingNumbers("乘", 15, 70));
                  }
                  catch (Exception ex)
                  {
                      Console.WriteLine("异常:{0}", ex.Message);
                  }

                  client.Close();
                  Console.ReadKey();
              }
          }
      }


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

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

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

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

          /// <summary>
          ///  消息拦截器
          /// </summary>
          public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector
          {
              void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
              {
                  //Console.WriteLine("客户端接收到的回复:\n{0}", reply.ToString());
                  return;
              }

              object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
              {
                  //Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString());
                  // 插入验证信息
                  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("服务器端:接收到的请求:\n{0}", request.ToString());
                  // 栓查验证信息
                  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("验证失败,滚吧!");
                  }
                  return null;
              }

              void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
              {
                  //Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString());
                  return;
              }
          }

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

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


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

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

    转IT黄老邪

  • 相关阅读:
    P1342 请柬
    P1186 玛丽卡
    Scala 中下划线的用法
    IDEA2017 maven Spark HelloWorld项目(本地断点调试)
    Spark内存管理详解
    Spark基础知识
    scala基本语法
    分布式锁的一点理解
    Redis并发问题
    redis集群原理
  • 原文地址:https://www.cnblogs.com/jcomet/p/3058551.html
Copyright © 2011-2022 走看看