我们知道,在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 plaincopy print?
    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. }

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

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

    [csharp] view plaincopy print?
    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服务。

    [csharp] view plaincopy print?
    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作如下修改:

    [csharp] view plaincopy 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("客户端接收到的回复:\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消息拦截,利用消息拦截做身份验证服务

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

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

    替换 wcf 消息传输中的 命名空间,http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix 转载于:https://www.cnb ...

  3. 我写的几篇技术文章之一:Windows消息拦截技术的应用

    Windows消息拦截技术的应用<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office&quo ...

  4. Fiddler响应拦截数据篡改,实现特殊场景深度测试(一)

    一.日常测试的场景 1.页面文案太长,页面显示测试 2.钉钉已打卡,再次打卡测试 3.人在上海,App定位到北京测试 4.招聘岗位状态十几种,在招岗位页面才展示测试 5.各种图片.视频格式,页面显示测 ...

  5. HTTP代理实现请求报文的拦截与篡改2--功能介绍+源码下载

    返回目录 思路有了,下面就是来实现它了,在这里,我们采用我们熟悉的C#来实现,C#下有一个完整的WEB DEBUGGER(含代理服务器方式实现的HTTP请求拦截与篡改功能)的实现--Fiddler,F ...

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

    一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做适当的处理.处理完此次消息后又回到等待状态.从上面对Win ...

  7. Windows消息拦截技术的应用(Hook钩子)

    一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做适当的处理.处理完此次消息后又回到等待状态.从上面对Win ...

  8. WCF方法拦截及OperationInvoker传递参数到WCF方法的实现

    鉴于一些理由需要拦截WCF方法,比如参数的检测.一个实际的场景是CSRF防范中需要对CsrfToken的检测. 要实现拦截,在形式上要实现两点: 1:为WCF方法增加特性类: 2:捕获WCF方法: 第 ...

  9. 传说中的WCF(5):数据协定(a)

    在第4篇中,咱们了解了发送/接收SOAP头,从本篇开头,我们不妨更深入地去探求一下有关WCF中的消息到底是啥玩意儿.WCF庞大而复杂,而从 MSDN文档中,你会看到许多很专业很抽象的东西,你不禁会问, ...

最新文章

  1. linux 生成字母序列,讲解在Linux平台中的Bash序列表达式(Sequence Expression)
  2. kafka基础架构详解
  3. CF827F-Dirty Arkady‘s Kitchen【堆】
  4. 【Verilog HDL】命名的规则研究
  5. 深度学习之卷积神经网络 LeNet
  6. php byte stringbuffer,重拾java基础(十三):String姐妹StringBuffer、StringBuilder总结
  7. 安卓通过js与网页的H5页面进行交换
  8. 读书笔记——《程序员的思维修炼:开发认知潜能的九堂课》
  9. 基于微流域划分的洪水淹没分析
  10. ubuntu 安装 mujoco-py
  11. 中医证型关联规则----apriori算法挖掘及plotly数据可视化
  12. ls一1测距仪说明书_生产力小工具 篇一:激光测距靠谱吗?杜克LS-1激光测距仪开箱测评...
  13. Hive之配置和使用LZO压缩
  14. 自动化测试中的滑动验证码解决方案
  15. oppo--软件测试工程师岗位面试总结(二)
  16. 【imx6ull】视频监控项目(usb摄像头+ffmepeg)
  17. 爱奇艺 视频编码信息参考
  18. seo提交工具_基础seo教程「百度熊掌号」
  19. 使用FlameGraph生成火焰图
  20. 西瓜视频“万元月薪”计划

热门文章

  1. 记住这9点,SCI论文结果轻松写
  2. 安装rlwrap 的简单方法,亲测好用
  3. 图像处理--角点检测与匹配
  4. HashSet和HashMap的区别
  5. 计算机视觉与深度学习 | Matlab实现单目视觉里程计基于SURF特征(代码类)
  6. centos7 dns配置_Centos7.7 安装FreeIPA (三)
  7. VTLN(Vocal Tract Length Normalisation)
  8. 第四范式入选Forrester中国机器学习Now Tech™,成唯一AutoML专注类大型厂商
  9. c++17(20)-双向循环链表(不依赖具体数据)
  10. access开发精要(6)-计算