我们知道,在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程序集的引用,这个不用我教你了,你懂的。

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

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

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

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

      接下来,实现客户端。

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

      b、引用WCF服务。

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

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

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

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

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

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

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

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

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

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

传说中的WCF:消息拦截与篡改的更多相关文章

  1. 传说中的WCF(10):消息拦截与篡改

    我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过 程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过 ...

  2. 重温WCF之消息拦截与篡改(八)

    我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过借 ...

  3. WCF消息拦截,利用消息拦截做身份验证服务

    本文参考  http://blog.csdn.net/tcjiaan/article/details/8274493  博客而写 添加对信息处理的类 /// <summary> /// 消 ...

  4. Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务、WCF消息头添加安全验证Token

    原文:Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务.WCF消息头添加安全验证Token 为什么选择wcf?   因为好像wcf和wpf就是哥俩,,, 为什么选择异步 ...

  5. 自定义HTTP消息拦截

    /// <summary> /// HTTP消息拦截器 /// </summary> public class RequestHandler : DelegatingHandl ...

  6. WCF初探-4:WCF消息交换模式之请求与答复模式

    请求与答复模式( Request/Reply) 这种交换模式是使用最多的一中,它有如下特征: 调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型 相比Duplex来讲,这种模式强调的 ...

  7. WCF初探-19:WCF消息协定

    WCF消息协定概述 在生成 WCF应用程序时,开发人员通常会密切关注数据结构和序列化问题,而不必关心携带数据的消息结构. 对于这些应用程序,为参数或返回值创建数据协定的过程很简单.但是,有时完全控制 ...

  8. 替换 wcf 消息传输中的 命名空间

    替换 wcf 消息传输中的 命名空间,http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix

  9. Windows消息拦截技术的应用

    Windows消息拦截技术的应用 民航合肥空管中心 周毅 一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做 ...

随机推荐

  1. Flutter - 下载别人的Flutter项目,本地编译不过

    如果直接下载了别人的Flutter项目,点击运行基本会不通过的,这是gradle版本差异造成的. 你需要修改android/gradle/wrapper/gradle-wrapper.properti ...

  2. AJAX 过程总结

    AJAX 工作过程:(1) 创建对象(需要处理兼容性问题) 创建XMLHttpRequest对象(创建一个异步调用对象) <!-- ie6以上 --> var xhr = new XMLH ...

  3. linux 修改文件最大数

    ulimit -a 查看所有 open files (-n) 1024 是linux操作系统对一个进程打开的文件句柄数量的限制(也包含打开的套接字数量) ulimit -SHn 10000 ##临时修 ...

  4. AndroidStudio更改包名

    最近开发一个项目 和以前开发的某一个功能类似 不想再重新搭建界面 从零开始去写... 就想把原来的项目copy一份 但是这样的话安装在手机中会把原来的项目覆盖掉 这是因为它们的applicationI ...

  5. JUC——线程池

    线程池本质的概念就是一堆线程一起完成一件事情. Executor package java.util.concurrent; public interface Executor { void exec ...

  6. 402. Remove K Digits/738.Monotone Increasing Digits/321. Create Maximum Number

    Given a non-negative integer num represented as a string, remove k digits from the number so that th ...

  7. openstack horizon开发第一天

    horizon插件构造 创建一个dashboardmkdir opesntack_dashboard/dashboards/mydashboardpython manage.py startdash ...

  8. centos6.9 安装完xampp 7.2.0后,执行/opt/lampp/lampp报错

    # /opt/lampp/lampp egrep: error while loading shared libraries: libc.so.6: cannot open shared object ...

  9. string类型的常用方法

    1. 在尾部插入/删除元素 string s("hello"); // 插入/删除一个字符 s.push_back('!'); s.pop_back(); // 插入多个字符 s. ...

  10. 5337朱荟潼Java实验报告一

    一.实验内容 1.内容一输出“Hello 名”. import java.util.Scanner;public class Hello{public static void main(String[ ...